/* * Entry point for register-dns thread. */ static DWORD WINAPI RegisterDNS (LPVOID unused) { DWORD err; DWORD i; WCHAR sys_path[MAX_PATH]; DWORD timeout = RDNS_TIMEOUT * 1000; /* in milliseconds */ /* default paths of net and ipconfig commands */ WCHAR net[MAX_PATH] = L"C:\\Windows\\system32\\net.exe"; WCHAR ipcfg[MAX_PATH] = L"C:\\Windows\\system32\\ipconfig.exe"; struct { WCHAR *argv0; WCHAR *cmdline; DWORD timeout; } cmds [] = { { net, L"net stop dnscache", timeout }, { net, L"net start dnscache", timeout }, { ipcfg, L"ipconfig /flushdns", timeout }, { ipcfg, L"ipconfig /registerdns", timeout }, }; int ncmds = sizeof (cmds) / sizeof (cmds[0]); HANDLE wait_handles[2] = {rdns_semaphore, exit_event}; if(GetSystemDirectory(sys_path, MAX_PATH)) { _snwprintf (net, MAX_PATH, L"%s\\%s", sys_path, L"net.exe"); net[MAX_PATH-1] = L'\0'; _snwprintf (ipcfg, MAX_PATH, L"%s\\%s", sys_path, L"ipconfig.exe"); ipcfg[MAX_PATH-1] = L'\0'; } if (WaitForMultipleObjects (2, wait_handles, FALSE, timeout) == WAIT_OBJECT_0) { /* Semaphore locked */ for (i = 0; i < ncmds; ++i) { ExecCommand (cmds[i].argv0, cmds[i].cmdline, cmds[i].timeout); } err = 0; if ( !ReleaseSemaphore (rdns_semaphore, 1, NULL) ) err = MsgToEventLog (M_SYSERR, TEXT("RegisterDNS: Failed to release regsiter-dns semaphore:")); } else { MsgToEventLog (M_ERR, TEXT("RegisterDNS: Failed to lock register-dns semaphore")); err = ERROR_SEM_TIMEOUT; /* Windows error code 0x79 */ } return err; }
/* * Execute a command and return its exit code. If timeout > 0, terminate * the process if still running after timeout milliseconds. In that case * the return value is the windows error code WAIT_TIMEOUT = 0x102 */ static DWORD ExecCommand (const WCHAR *argv0, const WCHAR *cmdline, DWORD timeout) { DWORD exit_code; STARTUPINFOW si; PROCESS_INFORMATION pi; DWORD proc_flags = CREATE_NO_WINDOW|CREATE_UNICODE_ENVIRONMENT; WCHAR *cmdline_dup = NULL; ZeroMemory (&si, sizeof(si)); ZeroMemory (&pi, sizeof(pi)); si.cb = sizeof(si); /* CreateProcess needs a modifiable cmdline: make a copy */ cmdline_dup = wcsdup (cmdline); if ( cmdline_dup && CreateProcessW (argv0, cmdline_dup, NULL, NULL, FALSE, proc_flags, NULL, NULL, &si, &pi) ) { WaitForSingleObject (pi.hProcess, timeout ? timeout : INFINITE); if (!GetExitCodeProcess (pi.hProcess, &exit_code)) { MsgToEventLog (M_SYSERR, TEXT("ExecCommand: Error getting exit_code:")); exit_code = GetLastError(); } else if (exit_code == STILL_ACTIVE) { exit_code = WAIT_TIMEOUT; /* Windows error code 0x102 */ /* kill without impunity */ TerminateProcess (pi.hProcess, exit_code); MsgToEventLog (M_ERR, TEXT("ExecCommand: \"%s %s\" killed after timeout"), argv0, cmdline); } else if (exit_code) MsgToEventLog (M_ERR, TEXT("ExecCommand: \"%s %s\" exited with status = %lu"), argv0, cmdline, exit_code); else MsgToEventLog (M_INFO, TEXT("ExecCommand: \"%s %s\" completed"), argv0, cmdline); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { exit_code = GetLastError(); MsgToEventLog (M_SYSERR, TEXT("ExecCommand: could not run \"%s %s\" :"), argv0, cmdline); } free (cmdline_dup); return exit_code; }
BOOL ReportStatusToSCMgr(SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status) { static DWORD dwCheckPoint = 1; BOOL res = TRUE; if (status->dwCurrentState == SERVICE_START_PENDING) { status->dwControlsAccepted = 0; } else { status->dwControlsAccepted = SERVICE_ACCEPT_STOP; } if (status->dwCurrentState == SERVICE_RUNNING || status->dwCurrentState == SERVICE_STOPPED) { status->dwCheckPoint = 0; } else { status->dwCheckPoint = dwCheckPoint++; } /* Report the status of the service to the service control manager. */ res = SetServiceStatus(service, status); if (!res) { MsgToEventLog(MSG_FLAGS_ERROR, TEXT("SetServiceStatus")); } return res; }
int _tmain(int argc, TCHAR *argv[]) { SERVICE_TABLE_ENTRY dispatchTable[] = { { automatic_service.name, ServiceStartAutomatic }, { interactive_service.name, ServiceStartInteractive }, { NULL, NULL } }; openvpn_service[0] = automatic_service; openvpn_service[1] = interactive_service; if (argc > 1 && (*argv[1] == TEXT('-') || *argv[1] == TEXT('/'))) { if (_tcsicmp(TEXT("install"), argv[1] + 1) == 0) { return CmdInstallServices(); } else if (_tcsicmp(TEXT("remove"), argv[1] + 1) == 0) { return CmdRemoveServices(); } else if (_tcsicmp(TEXT("start"), argv[1] + 1) == 0) { BOOL is_auto = argc < 3 || _tcsicmp(TEXT("interactive"), argv[2]) != 0; return CmdStartService(is_auto ? automatic : interactive); } else { goto dispatch; } return 0; } /* If it doesn't match any of the above parameters * the service control manager may be starting the service * so we must call StartServiceCtrlDispatcher */ dispatch: _tprintf(TEXT("%s -install to install the services\n"), APPNAME); _tprintf(TEXT("%s -start <name> to start a service (\"automatic\" or \"interactive\")\n"), APPNAME); _tprintf(TEXT("%s -remove to remove the services\n"), APPNAME); _tprintf(TEXT("\nStartServiceCtrlDispatcher being called.\n")); _tprintf(TEXT("This may take several seconds. Please wait.\n")); if (!StartServiceCtrlDispatcher(dispatchTable)) { MsgToEventLog(MSG_FLAGS_ERROR, TEXT("StartServiceCtrlDispatcher failed.")); } return 0; }
static VOID ReturnError (HANDLE pipe, DWORD error, LPCWSTR func, DWORD count, LPHANDLE events) { DWORD result_len; LPWSTR result = L"0xffffffff\nFormatMessage failed\nCould not return result"; DWORD_PTR args[] = { (DWORD_PTR) error, (DWORD_PTR) func, (DWORD_PTR) "" }; if (error != ERROR_OPENVPN_STARTUP) { FormatMessageW (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, 0, (LPWSTR) &args[2], 0, NULL); } result_len = FormatMessageW (FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY, L"0x%1!08x!\n%2!s!\n%3!s!", 0, 0, (LPWSTR) &result, 0, (va_list*) args); WritePipeAsync (pipe, result, wcslen (result) * 2, count, events); #ifdef UNICODE MsgToEventLog (MSG_FLAGS_ERROR, result); #else MsgToEventLog (MSG_FLAGS_ERROR, "%S", result); #endif if (error != ERROR_OPENVPN_STARTUP) LocalFree ((LPVOID) args[2]); if (result_len) LocalFree (result); }
static void BlockDNSErrHandler (DWORD err, const char *msg) { TCHAR buf[256]; LPCTSTR err_str; if (!err) return; err_str = TEXT("Unknown Win32 Error"); if (FormatMessage (FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, NULL, err, 0, buf, sizeof (buf), NULL)) { err_str = buf; } #ifdef UNICODE MsgToEventLog (M_ERR, L"%S (status = %lu): %s", msg, err, err_str); #else MsgToEventLog (M_ERR, "%s (status = %lu): %s", msg, err, err_str); #endif }
static DWORD GetRegString (HKEY key, LPCTSTR value, LPTSTR data, DWORD size) { DWORD type; LONG status = RegQueryValueEx (key, value, NULL, &type, (LPBYTE) data, &size); if (status == ERROR_SUCCESS && type != REG_SZ) status = ERROR_DATATYPE_MISMATCH; if (status != ERROR_SUCCESS) { SetLastError (status); return MsgToEventLog (M_SYSERR, TEXT("Error querying registry value: HKLM\\%s\\%s"), REG_KEY, value); } return ERROR_SUCCESS; }
static DWORD HandleBlockDNSMessage (const block_dns_message_t *msg, undo_lists_t *lists) { DWORD err = 0; HANDLE engine = NULL; LPCWSTR exe_path; #ifdef UNICODE exe_path = settings.exe_path; #else WCHAR wide_path[MAX_PATH]; MultiByteToWideChar (CP_UTF8, 0, settings.exe_path, MAX_PATH, wide_path, MAX_PATH); exe_path = wide_path; #endif if (msg->header.type == msg_add_block_dns) { err = add_block_dns_filters (&engine, msg->iface.index, exe_path, BlockDNSErrHandler); if (!err) err = AddListItem (&(*lists)[block_dns], engine); } else { engine = RemoveListItem (&(*lists)[block_dns], CmpEngine, NULL); if (engine) { err = delete_block_dns_filters (engine); engine = NULL; } else MsgToEventLog (M_ERR, TEXT("No previous block DNS filters to delete")); } if (err && engine) { delete_block_dns_filters (engine); } return err; }
/* * Check workdir\fname is inside config_dir * The logic here is simple: we may reject some valid paths if ..\ is in any of the strings */ static BOOL CheckConfigPath(const WCHAR *workdir, const WCHAR *fname, const settings_t *s) { WCHAR tmp[MAX_PATH]; const WCHAR *config_file = NULL; const WCHAR *config_dir = NULL; /* convert fname to full path */ if (PathIsRelativeW(fname) ) { snwprintf(tmp, _countof(tmp), L"%s\\%s", workdir, fname); tmp[_countof(tmp)-1] = L'\0'; config_file = tmp; } else { config_file = fname; } #ifdef UNICODE config_dir = s->config_dir; #else if (MultiByteToWideChar(CP_UTF8, 0, s->config_dir, -1, widepath, MAX_PATH) == 0) { MsgToEventLog(M_SYSERR, TEXT("Failed to convert config_dir name to WideChar")); return FALSE; } config_dir = widepath; #endif if (wcsncmp(config_dir, config_file, wcslen(config_dir)) == 0 && wcsstr(config_file + wcslen(config_dir), L"..") == NULL) { return TRUE; } return FALSE; }
VOID WINAPI ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv) { DWORD error = NO_ERROR; settings_t settings; service = RegisterServiceCtrlHandlerEx(automatic_service.name, ServiceCtrlAutomatic, &status); if (!service) { return; } status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; status.dwCurrentState = SERVICE_START_PENDING; status.dwServiceSpecificExitCode = NO_ERROR; status.dwWin32ExitCode = NO_ERROR; status.dwWaitHint = 3000; if (!ReportStatusToSCMgr(service, &status)) { MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr #1 failed")); goto finish; } /* * Create our exit event */ exit_event = create_event(EXIT_EVENT_NAME, false, false, true); if (!exit_event) { MsgToEventLog(M_ERR, TEXT("CreateEvent failed")); goto finish; } /* * If exit event is already signaled, it means we were not * shut down properly. */ if (WaitForSingleObject(exit_event, 0) != WAIT_TIMEOUT) { MsgToEventLog(M_ERR, TEXT("Exit event is already signaled -- we were not shut down properly")); goto finish; } if (!ReportStatusToSCMgr(service, &status)) { MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr #2 failed")); goto finish; } /* * Read info from registry in key HKLM\SOFTWARE\OpenVPN */ error = GetOpenvpnSettings(&settings); if (error != ERROR_SUCCESS) { goto finish; } /* * Instantiate an OpenVPN process for each configuration * file found. */ { WIN32_FIND_DATA find_obj; HANDLE find_handle; BOOL more_files; TCHAR find_string[MAX_PATH]; openvpn_sntprintf(find_string, MAX_PATH, TEXT("%s\\*"), settings.config_dir); find_handle = FindFirstFile(find_string, &find_obj); if (find_handle == INVALID_HANDLE_VALUE) { MsgToEventLog(M_ERR, TEXT("Cannot get configuration file list using: %s"), find_string); goto finish; } /* * Loop over each config file */ do { HANDLE log_handle = NULL; STARTUPINFO start_info; PROCESS_INFORMATION proc_info; struct security_attributes sa; TCHAR log_file[MAX_PATH]; TCHAR log_path[MAX_PATH]; TCHAR command_line[256]; CLEAR(start_info); CLEAR(proc_info); CLEAR(sa); if (!ReportStatusToSCMgr(service, &status)) { MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr #3 failed")); FindClose(find_handle); goto finish; } /* does file have the correct type and extension? */ if (match(&find_obj, settings.ext_string)) { /* get log file pathname */ if (!modext(log_file, _countof(log_file), find_obj.cFileName, TEXT("log"))) { MsgToEventLog(M_ERR, TEXT("Cannot construct logfile name based on: %s"), find_obj.cFileName); FindClose(find_handle); goto finish; } openvpn_sntprintf(log_path, _countof(log_path), TEXT("%s\\%s"), settings.log_dir, log_file); /* construct command line */ openvpn_sntprintf(command_line, _countof(command_line), TEXT(PACKAGE " --service %s 1 --config \"%s\""), EXIT_EVENT_NAME, find_obj.cFileName); /* Make security attributes struct for logfile handle so it can * be inherited. */ if (!init_security_attributes_allow_all(&sa)) { error = MsgToEventLog(M_SYSERR, TEXT("InitializeSecurityDescriptor start_" PACKAGE " failed")); goto finish; } /* open logfile as stdout/stderr for soon-to-be-spawned subprocess */ log_handle = CreateFile(log_path, GENERIC_WRITE, FILE_SHARE_READ, &sa.sa, settings.append ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (log_handle == INVALID_HANDLE_VALUE) { error = MsgToEventLog(M_SYSERR, TEXT("Cannot open logfile: %s"), log_path); FindClose(find_handle); goto finish; } /* append to logfile? */ if (settings.append) { if (SetFilePointer(log_handle, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) { error = MsgToEventLog(M_SYSERR, TEXT("Cannot seek to end of logfile: %s"), log_path); FindClose(find_handle); goto finish; } } /* fill in STARTUPINFO struct */ GetStartupInfo(&start_info); start_info.cb = sizeof(start_info); start_info.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; start_info.wShowWindow = SW_HIDE; start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); start_info.hStdOutput = start_info.hStdError = log_handle; /* create an OpenVPN process for one config file */ if (!CreateProcess(settings.exe_path, command_line, NULL, NULL, TRUE, settings.priority | CREATE_NEW_CONSOLE, NULL, settings.config_dir, &start_info, &proc_info)) { error = MsgToEventLog(M_SYSERR, TEXT("CreateProcess failed, exe='%s' cmdline='%s' dir='%s'"), settings.exe_path, command_line, settings.config_dir); FindClose(find_handle); CloseHandle(log_handle); goto finish; } /* close unneeded handles */ Sleep(1000); /* try to prevent race if we close logfile * handle before child process DUPs it */ if (!CloseHandle(proc_info.hProcess) || !CloseHandle(proc_info.hThread) || !CloseHandle(log_handle)) { error = MsgToEventLog(M_SYSERR, TEXT("CloseHandle failed")); goto finish; } } /* more files to process? */ more_files = FindNextFile(find_handle, &find_obj); } while (more_files); FindClose(find_handle); } /* we are now fully started */ status.dwCurrentState = SERVICE_RUNNING; status.dwWaitHint = 0; if (!ReportStatusToSCMgr(service, &status)) { MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr SERVICE_RUNNING failed")); goto finish; } /* wait for our shutdown signal */ if (WaitForSingleObject(exit_event, INFINITE) != WAIT_OBJECT_0) { MsgToEventLog(M_ERR, TEXT("wait for shutdown signal failed")); } finish: if (exit_event) { CloseHandle(exit_event); } status.dwCurrentState = SERVICE_STOPPED; status.dwWin32ExitCode = error; ReportStatusToSCMgr(service, &status); }
DWORD GetOpenvpnSettings (settings_t *s) { TCHAR priority[64]; TCHAR append[2]; DWORD error; HKEY key; LONG status = RegOpenKeyEx (HKEY_LOCAL_MACHINE, REG_KEY, 0, KEY_READ, &key); if (status != ERROR_SUCCESS) { SetLastError (status); return MsgToEventLog (M_SYSERR, TEXT("Could not open Registry key HKLM\\%s not found"), REG_KEY); } error = GetRegString (key, TEXT("exe_path"), s->exe_path, sizeof (s->exe_path)); if (error != ERROR_SUCCESS) goto out; error = GetRegString (key, TEXT("config_dir"), s->config_dir, sizeof (s->config_dir)); if (error != ERROR_SUCCESS) goto out; error = GetRegString (key, TEXT("config_ext"), s->ext_string, sizeof (s->ext_string)); if (error != ERROR_SUCCESS) goto out; error = GetRegString (key, TEXT("log_dir"), s->log_dir, sizeof (s->log_dir)); if (error != ERROR_SUCCESS) goto out; error = GetRegString (key, TEXT("priority"), priority, sizeof (priority)); if (error != ERROR_SUCCESS) goto out; error = GetRegString (key, TEXT("log_append"), append, sizeof (append)); if (error != ERROR_SUCCESS) goto out; /* read if present, else use default */ error = GetRegString (key, TEXT("ovpn_admin_group"), s->ovpn_admin_group, sizeof (s->ovpn_admin_group)); if (error != ERROR_SUCCESS) { openvpn_sntprintf(s->ovpn_admin_group, _countof(s->ovpn_admin_group), OVPN_ADMIN_GROUP); error = 0; /* this error is not fatal */ } /* set process priority */ if (!_tcsicmp (priority, TEXT("IDLE_PRIORITY_CLASS"))) s->priority = IDLE_PRIORITY_CLASS; else if (!_tcsicmp (priority, TEXT("BELOW_NORMAL_PRIORITY_CLASS"))) s->priority = BELOW_NORMAL_PRIORITY_CLASS; else if (!_tcsicmp (priority, TEXT("NORMAL_PRIORITY_CLASS"))) s->priority = NORMAL_PRIORITY_CLASS; else if (!_tcsicmp (priority, TEXT("ABOVE_NORMAL_PRIORITY_CLASS"))) s->priority = ABOVE_NORMAL_PRIORITY_CLASS; else if (!_tcsicmp (priority, TEXT("HIGH_PRIORITY_CLASS"))) s->priority = HIGH_PRIORITY_CLASS; else { SetLastError (ERROR_INVALID_DATA); error = MsgToEventLog (M_SYSERR, TEXT("Unknown priority name: %s"), priority); goto out; } /* set log file append/truncate flag */ if (append[0] == TEXT('0')) s->append = FALSE; else if (append[0] == TEXT('1')) s->append = TRUE; else { SetLastError (ERROR_INVALID_DATA); error = MsgToEventLog (M_ERR, TEXT("Log file append flag (given as '%s') must be '0' or '1'"), append); goto out; } out: RegCloseKey (key); return error; }
static BOOL GetStartupData (HANDLE pipe, STARTUP_DATA *sud) { size_t len; BOOL ret = FALSE; WCHAR *data = NULL; DWORD size, bytes, read; bytes = PeekNamedPipeAsync (pipe, 1, &exit_event); if (bytes == 0) { MsgToEventLog (M_SYSERR, TEXT("PeekNamedPipeAsync failed")); ReturnLastError (pipe, L"PeekNamedPipeAsync"); goto out; } size = bytes / sizeof (*data); data = malloc (bytes); if (data == NULL) { MsgToEventLog (M_SYSERR, TEXT("malloc failed")); ReturnLastError (pipe, L"malloc"); goto out; } read = ReadPipeAsync (pipe, data, bytes, 1, &exit_event); if (bytes != read) { MsgToEventLog (M_SYSERR, TEXT("ReadPipeAsync failed")); ReturnLastError (pipe, L"ReadPipeAsync"); goto out; } if (data[size - 1] != 0) { MsgToEventLog (M_ERR, TEXT("Startup data is not NULL terminated")); ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); goto out; } sud->directory = data; len = wcslen (sud->directory) + 1; size -= len; if (size <= 0) { MsgToEventLog (M_ERR, TEXT("Startup data ends at working directory")); ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); goto out; } sud->options = sud->directory + len; len = wcslen (sud->options) + 1; size -= len; if (size <= 0) { MsgToEventLog (M_ERR, TEXT("Startup data ends at command line options")); ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); goto out; } sud->std_input = sud->options + len; data = NULL; /* don't free data */ ret = TRUE; out: free (data); return ret; }
/* * Check whether user is a member of Administrators group or * the group specified in s->ovpn_admin_group */ BOOL IsAuthorizedUser(SID *sid, settings_t *s) { LOCALGROUP_USERS_INFO_0 *groups = NULL; DWORD nread; DWORD nmax; WCHAR *tmp = NULL; const WCHAR *admin_group[2]; WCHAR username[MAX_NAME]; WCHAR domain[MAX_NAME]; WCHAR sysadmin_group[MAX_NAME]; DWORD err, len = MAX_NAME; int i; BOOL ret = FALSE; SID_NAME_USE sid_type; /* Get username */ if (!LookupAccountSidW(NULL, sid, username, &len, domain, &len, &sid_type)) { MsgToEventLog(M_SYSERR, TEXT("LookupAccountSid")); goto out; } /* Get an array of groups the user is member of */ err = NetUserGetLocalGroups(NULL, username, 0, LG_INCLUDE_INDIRECT, (LPBYTE *) &groups, MAX_PREFERRED_LENGTH, &nread, &nmax); if (err && err != ERROR_MORE_DATA) { SetLastError(err); MsgToEventLog(M_SYSERR, TEXT("NetUserGetLocalGroups")); goto out; } if (GetBuiltinAdminGroupName(sysadmin_group, _countof(sysadmin_group))) { admin_group[0] = sysadmin_group; } else { MsgToEventLog(M_SYSERR, TEXT("Failed to get the name of Administrators group. Using the default.")); /* use the default value */ admin_group[0] = SYSTEM_ADMIN_GROUP; } #ifdef UNICODE admin_group[1] = s->ovpn_admin_group; #else tmp = NULL; len = MultiByteToWideChar(CP_UTF8, 0, s->ovpn_admin_group, -1, NULL, 0); if (len == 0 || (tmp = malloc(len*sizeof(WCHAR))) == NULL) { MsgToEventLog(M_SYSERR, TEXT("Failed to convert admin group name to WideChar")); goto out; } MultiByteToWideChar(CP_UTF8, 0, s->ovpn_admin_group, -1, tmp, len); admin_group[1] = tmp; #endif /* Check if user's groups include any of the admin groups */ for (i = 0; i < nread; i++) { if (wcscmp(groups[i].lgrui0_name, admin_group[0]) == 0 || wcscmp(groups[i].lgrui0_name, admin_group[1]) == 0 ) { MsgToEventLog(M_INFO, TEXT("Authorizing user %s by virtue of membership in group %s"), username, groups[i].lgrui0_name); ret = TRUE; break; } } out: if (groups) { NetApiBufferFree(groups); } free(tmp); return ret; }