/**
    system firewall bypassing for Windows and Mac OS

    Copyright (c) 2020-2022 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#define INITGUID /* windows.h */

#include "config.h"
#include "spth.h"

#include "logger.h"
#include "table.h"
#include "error.h"
#include "utils.h"
#include "system.h"
#include "firewall.h"
#include "crypto.h"
#include "file.h"
#include "socket.h"
#include "contact.h"
#include "param.h"

#ifndef _WIN32
#include <sys/stat.h>
#include <unistd.h>
#else
#include <shlwapi.h>
#endif

#include <string.h>
#include <stdio.h>
#include <errno.h>

#define SIM_MODULE SIM_MODULE_SYSTEM

#ifdef _WIN32

#include "netfw.h"

#ifndef __IEnumVARIANT_INTERFACE_DEFINED__
#define __IEnumVARIANT_INTERFACE_DEFINED__
DEFINE_GUID (IID_IEnumVARIANT, 0x00020404, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
#endif

#define FIREWALL_PREFIX "SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\"
#define FIREWALL_PROFILE1 "DomainProfile"
#define FIREWALL_PROFILE2 "StandardProfile"
#define FIREWALL_POSTFIX "\\AuthorizedApplications\\List"

#define FIREWALL_GROUP L"Secure Instant Messenger and Phone"
#define FIREWALL_ENABLED L":*:Enabled:simphone"
#define FIREWALL_ELEVATE L"---INST"

static void log_ucs_ (int level, const wchar_t *ucs, const char *utf, HRESULT hr, const char *newline) {
  unsigned len = wcslen (ucs);
  simtype str = sim_convert_ucs_to_utf (ucs, &len);
  if (hr == S_OK) {
    LOG_ANY_ (level, "%s %s%s", utf, str.str, newline);
  } else
    LOG_ANY_ (level, "%s %s error 0x%X%s", utf, str.str, hr, newline);
  string_free (str);
}

static simbool sim_firewall_check_skipped (INetFwRule *rule, unsigned *skipped) {
  simbool ok = true;
  VARIANT_BOOL enabled = VARIANT_FALSE;
  NET_FW_ACTION action = NET_FW_ACTION_BLOCK;
  NET_FW_RULE_DIRECTION direction = 0;
  long profiles = NET_FW_PROFILE2_PRIVATE, protocol = 0;
  if (rule->lpVtbl->get_Enabled (rule, &enabled) != S_OK || enabled != VARIANT_TRUE)
    return false;
  if (rule->lpVtbl->get_Action (rule, &action) != S_OK || action != NET_FW_ACTION_ALLOW)
    return false;
  if (rule->lpVtbl->get_Profiles (rule, &profiles) != S_OK || profiles != NET_FW_PROFILE2_ALL)
    ok = false;
  if (rule->lpVtbl->get_Protocol (rule, &protocol) != S_OK || rule->lpVtbl->get_Direction (rule, &direction) != S_OK)
    return false;
  if (protocol != NET_FW_IP_PROTOCOL_TCP && protocol != NET_FW_IP_PROTOCOL_UDP)
    ok = false;
  if (direction != NET_FW_RULE_DIR_IN && direction != NET_FW_RULE_DIR_OUT)
    ok = false;
  if (profiles & NET_FW_PROFILE2_PUBLIC && (protocol == NET_FW_IP_PROTOCOL_TCP || protocol == NET_FW_IP_PROTOCOL_UDP))
    *skipped |= 16 << ((direction != NET_FW_RULE_DIR_IN) * 2 + (protocol == NET_FW_IP_PROTOCOL_UDP));
  if (ok)
    *skipped |= 1 << ((direction != NET_FW_RULE_DIR_IN) * 2 + (protocol == NET_FW_IP_PROTOCOL_UDP));
  return ok;
}

static simtype sim_firewall_list (INetFwRules *rules, unsigned *skipped, const simtype id) {
  IUnknown *enumerator = NULL;
  IEnumVARIANT *variant = NULL;
  HRESULT hr = rules->lpVtbl->get__NewEnum (rules, &enumerator);
  simtype list = array_new_strings (8);
  list.len = 0;
  if (SUCCEEDED (hr)) {
    hr = enumerator->lpVtbl->QueryInterface (enumerator, &IID_IEnumVARIANT, (void **) &variant);
    if (SUCCEEDED (hr)) {
      ULONG fetched = 0;
      VARIANT var;
      while ((hr = variant->lpVtbl->Next (variant, 1, &var, &fetched)) != S_FALSE) {
        BSTR name, appname;
        IDispatch *dispatch;
        INetFwRule *rule = NULL;
        if (FAILED (hr)) {
          LOG_ERROR_ ("firewall Next error 0x%X\n", hr);
          break;
        }
        var.vt = VT_DISPATCH;
        dispatch = V_DISPATCH (&var);
        hr = dispatch->lpVtbl->QueryInterface (dispatch, &IID_INetFwRule, (void **) &rule);
        if (FAILED (hr)) {
          LOG_ERROR_ ("QueryInterface INetFwRule error 0x%X\n", hr);
          break;
        }
        hr = rule->lpVtbl->get_ApplicationName (rule, &appname);
        if (! FAILED (hr)) {
          simtype newid = sim_system_dir_get_id (appname);
          if (! newid.len && newid.typ != SIMNIL) {
            LOG_CODE_ERROR_ (log_ucs_ (SIM_LOG_ERROR, appname, "GetFileInformationByHandle",
                                       HRESULT_FROM_WIN32 (GetLastError ()), "\n"));
          } else if (! string_check_diff_len (newid, id.str, id.len)) {
            hr = rule->lpVtbl->get_Name (rule, &name);
            if (FAILED (hr)) {
              LOG_ERROR_ ("get_Name error 0x%X\n", hr);
            } else if (name && ! (skipped && sim_firewall_check_skipped (rule, skipped))) {
              array_append (&list, string_copy_len (name, SysStringByteLen (name) + sizeof (wchar_t)));
              array_append (&list, string_copy_len (appname, SysStringByteLen (appname) + sizeof (wchar_t)));
            }
          }
          string_buffer_free (newid);
        } else
          LOG_ERROR_ ("get_ApplicationName error 0x%X\n", hr);
        rule->lpVtbl->Release (rule);
      }
      variant->lpVtbl->Release (variant);
    } else
      LOG_ERROR_ ("QueryInterface IEnumVARIANT error 0x%X\n", hr);
    enumerator->lpVtbl->Release (enumerator);
  } else
    LOG_WARN_ ("get__NewEnum error 0x%X\n", hr);
  return list;
}

static HRESULT sim_firewall_remove (INetFwRules *rules, wchar_t *name, const wchar_t *appname) {
  HRESULT hr = rules->lpVtbl->Remove (rules, name);
  if (LOG_PROTECT_ (SIM_MODULE, SUCCEEDED (hr) ? SIM_LOG_DEBUG : SIM_LOG_WARN)) {
    int level = SUCCEEDED (hr) ? SIM_LOG_DEBUG : SIM_LOG_WARN;
    log_ucs_ (level, name, SUCCEEDED (hr) ? "firewall removed rule" : "firewall Remove", hr, " ");
    log_ucs_ (level, appname, "EXE", S_OK, "\n");
    LOG_UNPROTECT_ ();
  }
  return hr;
}

static HRESULT sim_firewall_add (INetFwRules *rules, const simtype appname, int direction) {
  INetFwRule *rule = NULL;
  HRESULT hr = CoCreateInstance (&CLSID_NetFwRule, NULL, CLSCTX_INPROC_SERVER, &IID_INetFwRule, (void **) &rule);
  void *md = sim_crypt_md_new (CRYPT_MD_RIPEMD);
  simtype hash = sim_crypt_md_hash (md, appname, pointer_new_len (&direction, sizeof (direction)), nil ());
  if (SUCCEEDED (hr)) {
    simtype addr = sim_contact_convert_to_address (hash, 'F');
    unsigned len = addr.len;
    simtype ucs = sim_convert_utf_to_ucs (addr.ptr, &len);
    rules->lpVtbl->Remove (rules, ucs.ptr); /* just in case sim_system_dir_get_id on the same exe file had not worked */
    rule->lpVtbl->put_Name (rule, ucs.ptr);
    rule->lpVtbl->put_ApplicationName (rule, appname.ptr);
    rule->lpVtbl->put_Description (rule, FIREWALL_GROUP);
    rule->lpVtbl->put_Grouping (rule, FIREWALL_GROUP);
    rule->lpVtbl->put_Enabled (rule, VARIANT_TRUE);
    rule->lpVtbl->put_Protocol (rule, direction & 65535);
    rule->lpVtbl->put_Direction (rule, direction >> 16);
    rule->lpVtbl->put_Action (rule, NET_FW_ACTION_ALLOW);
    rule->lpVtbl->put_Profiles (rule, NET_FW_PROFILE2_ALL);
    hr = rules->lpVtbl->Add (rules, rule);
    if (SUCCEEDED (hr)) {
      LOG_DEBUG_ ("firewall added rule %s\n", addr.str);
    } else
      LOG_WARN_ ("firewall Add %s error 0x%X\n", addr.str, hr);
    string_free (ucs);
    string_free (addr);
    rule->lpVtbl->Release (rule);
  } else {
    LOG_WARN_ ("CoCreateInstance INetFwRule error 0x%X\n", hr);
    hr = S_OK;
  }
  string_free (hash);
  return hr;
}

static int sim_firewall_enable_com (const wchar_t *pathname) {
  static const int firewall_protocol_direction[] = {
    (NET_FW_RULE_DIR_IN << 16) + NET_FW_IP_PROTOCOL_TCP, (NET_FW_RULE_DIR_IN << 16) + NET_FW_IP_PROTOCOL_UDP,
    (NET_FW_RULE_DIR_OUT << 16) + NET_FW_IP_PROTOCOL_TCP, (NET_FW_RULE_DIR_OUT << 16) + NET_FW_IP_PROTOCOL_UDP
  };
  INetFwPolicy2 *policy = NULL;
  HRESULT hrinit = CoInitializeEx (0, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE), hrexit = S_OK, hr;
  if (SUCCEEDED (hrinit) || hrinit == RPC_E_CHANGED_MODE) {
    hr = CoCreateInstance (&CLSID_NetFwPolicy2, NULL, CLSCTX_INPROC_SERVER, &IID_INetFwPolicy2, (void **) &policy);
    if (SUCCEEDED (hr)) {
      unsigned idx, skipped = 0;
      simtype id = sim_system_dir_get_id (pathname);
      if (id.len) {
        INetFwRules *rules = NULL;
        hr = policy->lpVtbl->get_Rules (policy, &rules);
        if (SUCCEEDED (hr)) {
          simtype name = pointer_new_len (pathname, wcslen (pathname) * sizeof (*pathname));
          simtype list = sim_firewall_list (rules, &skipped, id);
          for (idx = 1; idx <= list.len; idx += 2) {
            hr = sim_firewall_remove (rules, list.arr[idx].ptr, list.arr[idx + 1].ptr);
            if (! SUCCEEDED (hr))
              hrexit = hr;
          }
          array_free (list);
          for (idx = 0; idx < SIM_ARRAY_SIZE (firewall_protocol_direction); idx++)
            if (! (skipped & (1 << idx))) {
              hr = sim_firewall_add (rules, name, firewall_protocol_direction[idx]);
              if (! SUCCEEDED (hr))
                hrexit = hr;
            }
          rules->lpVtbl->Release (rules);
          if ((skipped & 48) == 48)
            hrexit = S_OK;
          LOG_DEBUG_ ("firewall map 0x%02X\n", skipped);
        } else
          LOG_ERROR_ ("get_Rules error 0x%X\n", hr);
      } else if (id.typ != SIMNIL)
        LOG_CODE_ERROR_ (log_ucs_ (SIM_LOG_ERROR, pathname, "GetFileInformationByHandle",
                                   HRESULT_FROM_WIN32 (GetLastError ()), "\n"));
      string_buffer_free (id);
      policy->lpVtbl->Release (policy);
    } else
      LOG_WARN_ ("CoCreateInstance INetFwPolicy2 error 0x%X\n", hr);
    if (SUCCEEDED (hrinit))
      CoUninitialize ();
  } else
    LOG_ERROR_ ("CoInitializeEx error 0x%X\n", hrinit);
  return hrexit;
}

static int sim_firewall_enable_xp (const wchar_t *pathname, const char *key) {
  DWORD err = ERROR_INVALID_DATA, len, size, type = REG_NONE;
  HKEY hkey;
  wchar_t *rule = sim_new (len = wcslen (pathname) * sizeof (*pathname) + sizeof (FIREWALL_ENABLED));
  wcscat (wcscpy (rule, pathname), FIREWALL_ENABLED);
  if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) {
    if (RegQueryValueExW (hkey, pathname, 0, &type, NULL, &size) == ERROR_SUCCESS && type == REG_SZ) {
      wchar_t *old = sim_new (type = size);
      if (RegQueryValueExW (hkey, pathname, 0, NULL, (simbyte *) old, &type) == ERROR_SUCCESS && ! StrCmpIW (rule, old))
        err = ERROR_SUCCESS;
      sim_free (old, size);
    }
    RegCloseKey (hkey);
  }
  if (err == ERROR_INVALID_DATA) {
    err = RegCreateKeyEx (HKEY_LOCAL_MACHINE, key, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hkey, NULL);
    if (err == ERROR_SUCCESS) {
      err = RegSetValueExW (hkey, pathname, 0, REG_SZ, (simbyte *) rule, (wcslen (rule) + 1) * sizeof (*rule));
      if (err != ERROR_SUCCESS)
        LOG_ERROR_ ("RegSetValueEx error %d\n", err);
      RegCloseKey (hkey);
    } else
      LOG_WARN_ ("RegCreateKeyEx error %d\n", err);
  }
  sim_free (rule, len);
  return err;
}

static int sim_firewall_enable_exe (const wchar_t *pathname) {
  int err, err2;
  LOG_CODE_DEBUG_ (log_ucs_ (SIM_LOG_DEBUG, pathname, "GetModuleFileName", S_OK, "\n"));
  if (system_version_major > 5)
    return sim_firewall_enable_com (pathname);
  err = sim_firewall_enable_xp (pathname, FIREWALL_PREFIX FIREWALL_PROFILE1 FIREWALL_POSTFIX);
  err2 = sim_firewall_enable_xp (pathname, FIREWALL_PREFIX FIREWALL_PROFILE2 FIREWALL_POSTFIX);
  return err == SIM_OK ? err2 : err;
}

int firewall_init_ (simbool install, int *error) {
  int len = 0, err = SIM_OK;
  wchar_t exe[MAX_PATH];
  if (! error) {
    wchar_t **args = CommandLineToArgvW (GetCommandLineW (), &len);
    sim_firewall_enable_exe (args[0]);
    if (! args || len != 2 || wcscmp (args[1], FIREWALL_ELEVATE))
      err = SIM_API_INSTALL_CANCELLED;
    LocalFree (args);
    return err;
  }
  *error = SIM_OK;
  sim_error_reset_text ();
  len = GetModuleFileNameW (NULL, SIM_ARRAY (exe));
  if (len <= 0 || len >= SIM_ARRAY_SIZE (exe)) {
    *error = len ? ENOMEM : GetLastError ();
    LOG_ERROR_ ("GetModuleFileName error %d\n", *error);
  } else if (sim_firewall_enable_exe (exe) != SIM_OK && install) {
    SHELLEXECUTEINFOW info;
    memset (&info, 0, sizeof (info));
    info.cbSize = sizeof (info);
    info.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
    info.lpVerb = L"runas";
    info.lpFile = exe;
    info.lpParameters = FIREWALL_ELEVATE;
    info.nShow = SW_HIDE;
    if ((err = param_uninit ()) == SIM_OK && ! ShellExecuteExW (&info)) {
      *error = GetLastError ();
      if (LOG_PROTECT_ (SIM_MODULE, SIM_LOG_WARN)) {
        log_ucs_ (SIM_LOG_WARN, info.lpFile, "ShellExecuteEx", S_OK, "\n");
        log_ucs_ (SIM_LOG_WARN, info.lpParameters, "ShellExecuteEx", HRESULT_FROM_WIN32 (*error), "\n");
        LOG_UNPROTECT_ ();
      }
      if (*error == ERROR_CANCELLED)
        *error = SIM_API_INSTALL_CANCELLED;
    }
    if (err != SIM_OK || (err = param_init (true)) != SIM_OK) {
      *error = err;
    } else if (*error == SIM_OK) {
      *error = WaitForSingleObject (info.hProcess, 2000);
      if (sim_firewall_enable_exe (exe) == SIM_OK) {
        *error = SIM_OK;
      } else if (*error == WAIT_OBJECT_0 || *error == SIM_OK)
        *error = SIM_API_INSTALL_FAILED;
    } else if (*error != SIM_API_INSTALL_CANCELLED) {
      simtype str = sim_convert_ucs_to_utf (exe, (unsigned *) &len);
      sim_error_set_text (" (", str.ptr, ")", *error);
      string_free (str);
    }
    CloseHandle (info.hProcess);
  }
  return err;
}

#endif /* _WIN32 */

#ifdef __APPLE__

#include <Security/Security.h>

#define FIREWALL_PLIST "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<"                                            \
                       "!"                                                                                        \
                       "DOCTYPE plist PUBLIC "                                                                    \
                       "\"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"    \
                       "<plist version=\"1.0\">\n<dict>\n        <key>Label</key>\n        <string>%s</string>\n" \
                       "        <key>ProgramArguments</key>\n        <array>\n"                                   \
                       "                <string>/usr/libexec/ApplicationFirewall/socketfilterfw</string>\n"       \
                       "                <string>%s</string>\n                <string>%s</string>\n"               \
                       "        </array>\n        <key>RunAtLoad</key>\n        <true/>\n"                        \
                       "        <key>StartInterval</key>\n        <integer>666</integer>\n</dict>\n</plist>\n"
#define FIREWALL_DAEMONS "/Library/LaunchDaemons"
#define FIREWALL_APP "Info.plist"
#define FIREWALL_SIZE_NAME 2048

static simbool sim_firewall_check_plist (FILE *file, const char *pathname) {
  struct stat buf;
  if (! lstat (pathname, &buf) || ! fstat (fileno (file), &buf))
    return S_ISREG (buf.st_mode) && ! buf.st_uid && ! (buf.st_mode & (S_IWGRP | S_IWOTH));
  LOG_WARN_ ("firewall fstat %s error %d\n", pathname, errno);
  return false;
}

static simtype sim_firewall_enable (const char *pathname, const char *command, simtype *plist) {
  simbool ok = false;
  void *md = sim_crypt_md_new (CRYPT_MD_RIPEMD);
  simtype hash = sim_crypt_md_hash (md, pointer_new (pathname), pointer_new (command), nil ());
  simtype addr = sim_contact_convert_to_address (hash, 'F'), buf = string_buffer_new (strlen (pathname) + 1000);
  simtype name = string_concat (FIREWALL_DAEMONS "/", addr.ptr, ".plist", NULL);
  FILE *f = fopen (name.ptr, "rt");
  *plist = string_buffer_new (buf.len);
  sprintf (plist->ptr, FIREWALL_PLIST, addr.str, command, pathname);
  if (f) {
    unsigned len = strlen (plist->ptr);
    ok = fread (buf.str, 1, buf.len, f) == len && ! memcmp (buf.str, plist->str, len);
    ok = ok && sim_firewall_check_plist (f, name.ptr);
    fclose (f);
  }
  if (! ok) {
    mkdir (FIREWALL_DAEMONS, 0755);
    remove (name.ptr);
    if ((f = fopen (name.ptr, "wt")) != NULL) {
      if ((ok = ! fputs (plist->ptr, f) && ! fflush (f) && sim_firewall_check_plist (f, name.ptr)) == false)
        LOG_WARN_ ("firewall fwrite %s error %d\n", pathname, errno);
      ok &= ! fclose (f);
      if (! ok)
        LOG_WARN_ ("firewall fclose %s error %d\n", pathname, errno);
    } else
      LOG_NOTE_ ("firewall fopen %s error %d\n", name.str, errno);
  }
  string_buffer_free (buf);
  string_free (addr);
  string_free (hash);
  if (! ok)
    return name;
  string_free (name);
  string_buffer_free (*plist);
  return *plist = nil ();
}

static int firewall_exec_ (AuthorizationRef auth, const char *command, char **args, const char *input) {
  FILE *p = NULL;
  OSStatus err = AuthorizationExecuteWithPrivileges (auth, command, kAuthorizationFlagDefaults, args, &p);
  if (err == errAuthorizationSuccess) {
    LOG_DEBUG_ ("firewall exec %s %s %s\n", command, args[0], args[1]);
    if (input && *input) {
      if ((err = fputs (input, p)) != 0) {
        err = err >= 0 && ! fflush (p) ? SIM_OK : errno ? errno : SIM_FILE_NO_SPACE;
      } else
        err = SIM_FILE_END;
    }
    if (err != SIM_OK) {
      LOG_WARN_ ("firewall pipe %s write error %d\n", command, err);
    } else if ((err = socket_select_readable_ (fileno (p), 2, 0)) <= 0) {
      LOG_WARN_ ("firewall exec %s timeout %d\n", command, err ? errno : SIM_SERVER_TIMEOUT);
      err = FILE_CASE_ERROR (err, SIM_SERVER_TIMEOUT);
    } else
      err = SIM_OK;
    if (fclose (p)) {
      err = errno ? errno : SIM_FILE_ERROR;
      LOG_WARN_ ("firewall close %s error %d\n", command, errno);
    }
  } else
    LOG_WARN_ ("firewall exec %s %s %s error %d\n", command, args[0], args[1], err);
  if (err != SIM_OK)
    sim_error_set_text (" (", command, ")", err);
  return err;
}

static char *sim_firewall_get_app (const char *pathname, simtype output) {
  char *b = output.ptr;
  int count;
  for (count = 0; count < 100; count++) {
    simbool done = true;
    const char *s = pathname, *e;
    char *d = b, *o = d + output.len, *t, tmp[FIREWALL_SIZE_NAME + sizeof (FIREWALL_APP)];
    *d++ = '/';
    while (*s) {
      for (; *s == '/'; s++) {}
      for (e = s; *e && *e != '/'; e++) {}
      if (e == s)
        break;
      if (s[0] == '.' && s[1] == '.' && e == s + 2) {
        if (d > b + 1)
          d--;
        for (s = e; d != b && d[-1] != '/'; d--) {}
      } else if (s[0] != '.' || e != s + 1) {
        for (; s != e && d != o; *d++ = *s++) {}
        if (d >= o - 1) {
          LOG_ERROR_ ("readlink %s name too long\n", pathname);
          sim_error_set_text (" (", pathname, ")", SIM_FILE_BAD_NAME);
          return NULL;
        }
        *d = 0;
        if ((int) (output.len = readlink (b, d + 1, o - d - 1)) < 0 && errno != EINVAL)
          LOG_ERROR_ ("readlink %s error %d\n", b, errno);
        if (! output.len || (int) output.len >= o - d - 1) {
          LOG_ERROR_ ("readlink %s buffer overflow\n", pathname);
          sim_error_set_text (" (", pathname, ")", SIM_FILE_BAD_NAME);
          return NULL;
        }
        if ((int) output.len > 0) {
          done = false;
          e = ++d;
          if (*d != '/') {
            for (d--; d != b && d[-1] != '/'; d--) {}
          } else
            d = b;
          memmove (d, e, output.len);
          d += output.len;
        }
        *d++ = '/';
      } else
        s = e;
    }
    if (d > b + 1)
      d--;
    *d = 0;
    if (done) {
      if ((t = strrchr (b, '/')) != NULL) {
        *t = 0;
        if ((o = strrchr (b, '/')) != NULL) {
          struct stat buf;
          *o = 0;
          if (! stat (strcat (strcpy (tmp, b), "/" FIREWALL_APP), &buf) && (d = strrchr (b, '/')) != NULL)
            *d = 0;
          *o = '/';
        }
        *t = '/';
      }
      LOG_DEBUG_ ("readlink %s\n", b);
      return b;
    }
    pathname = strcpy (tmp, b);
    output.len = sizeof (tmp) - sizeof (FIREWALL_APP);
  }
  LOG_ERROR_ ("readlink %s loop\n", pathname);
  sim_error_set_text (" (", pathname, ")", SIM_FILE_BAD_NAME);
  return NULL;
}

int firewall_init_ (simbool install, int *error) {
  int err = SIM_OK;
  simtype tmp, name[3], plist[3];
  char exe[FIREWALL_SIZE_NAME], *info;
  if (! error)
    return SIM_OK;
  *error = SIM_OK;
  sim_error_reset_text ();
  sim_system_get_exe (&tmp);
  if (tmp.len && tmp.str[0] == '/' && sim_firewall_get_app (tmp.ptr, pointer_new_len (exe, sizeof (exe)))) {
    int mask = umask (022), i, j;
    name[0] = sim_firewall_enable (exe, "--add", &plist[0]);
    name[1] = sim_firewall_enable (exe, "--unblockapp", &plist[1]);
    if (install && (name[0].typ != SIMNIL || name[1].typ != SIMNIL)) {
      if ((err = param_uninit ()) == SIM_OK) {
        AuthorizationRef a;
        AuthorizationItem right = { kAuthorizationRightExecute, 0, NULL, 0 };
        AuthorizationRights rights = { 1, &right };
        const AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed |
                                         kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
        *error = AuthorizationCreate (&rights, kAuthorizationEmptyEnvironment, flags, &a);
        if (*error == errAuthorizationSuccess) {
          static char *firewall_args_mkdir[] = { "-p", FIREWALL_DAEMONS, NULL };
          static char *firewall_args_rm[] = { "-fd", NULL, NULL };
          static char *firewall_args_tee[] = { NULL, NULL };
          static char *firewall_args_add[] = { "--add", NULL, NULL };
          static char *firewall_args_remove[] = { "--remove", NULL, NULL };
          firewall_args_add[1] = firewall_args_remove[1] = exe;
          for (i = 0; i <= 1; i++) {
            firewall_args_rm[1] = firewall_args_tee[0] = name[i].ptr;
            firewall_exec_ (a, "/bin/mkdir", firewall_args_mkdir, NULL);
            firewall_exec_ (a, "/bin/rm", firewall_args_rm, NULL);
            err = firewall_exec_ (a, "/usr/bin/tee", firewall_args_tee, plist[i].ptr);
            for (j = 0; j <= (err == SIM_OK) * 4; j++) {
              if ((name[2] = sim_firewall_enable (exe, i ? "--unblockapp" : "--add", &plist[2])).typ == SIMNIL)
                break;
              string_buffer_free (plist[2]);
              string_free (name[2]);
              if (err == SIM_OK)
                pth_usleep_ (200000);
            }
            if (name[2].typ != SIMNIL)
              *error = err == SIM_OK ? SIM_API_INSTALL_FAILED : err;
          }
          firewall_exec_ (a, "/usr/libexec/ApplicationFirewall/socketfilterfw", firewall_args_remove, NULL);
          info = sim_error_get_text (*error);
          err = firewall_exec_ (a, "/usr/libexec/ApplicationFirewall/socketfilterfw", firewall_args_add, NULL);
          if (*error != SIM_OK) {
            sim_error_set_text (info, NULL, NULL, *error);
          } else
            *error = err;
          sim_free_string (info);
          if ((err = AuthorizationFree (a, kAuthorizationFlagDestroyRights)) != errAuthorizationSuccess)
            LOG_ERROR_ ("AuthorizationFree error %d\n", err);
        } else if (*error == errAuthorizationCanceled) {
          *error = SIM_API_INSTALL_CANCELLED;
        } else
          LOG_WARN_ ("AuthorizationCreate error %d\n", *error);
        info = sim_error_get_text (*error);
        if ((err = param_init (true)) == SIM_OK && *error != SIM_OK)
          if (*error != SIM_API_INSTALL_CANCELLED && *error != SIM_API_INSTALL_FAILED)
            sim_error_set_text (info, NULL, NULL, *error);
        sim_free_string (info);
      }
      if (err != SIM_OK)
        *error = err;
    }
    string_buffer_free (plist[1]);
    string_buffer_free (plist[0]);
    string_free (name[1]);
    string_free (name[0]);
    umask (mask);
  } else
    *error = tmp.typ == SIMNIL ? SIM_NO_ERROR : SIM_FILE_BAD_NAME;
  string_free (tmp);
  return err;
}

#endif /* __APPLE__ */
