#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif

#ifndef CONFIG
#define CONFIG "config.h"
#endif // CONFIG
#include CONFIG

#if defined(USE_MSRPC) && !defined(_WIN32) && !defined(__CYGWIN__)
#error Microsoft RPC is only available on Windows and Cygwin
#endif

#if defined(USE_MSRPC) && defined(SIMPLE_SOCKETS)
#error You can only define either USE_MSRPC or SIMPLE_SOCKETS but not both
#endif

#if defined(NO_SOCKETS) && defined(USE_MSRPC)
#error Cannot use inetd mode with Microsoft RPC
#endif

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

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

#if _MSC_VER
#include "wingetopt.h"
#endif

#ifndef _WIN32
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>

#if !defined(NO_LIMIT) && !__minix__
#include <sys/ipc.h>
#if !__ANDROID__
#include <sys/shm.h>
#endif // !__ANDROID__
#endif // !defined(NO_LIMIT) && !__minix__

#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifndef NO_LIMIT
#include <semaphore.h>
#endif // NO_LIMIT
#endif // !_WIN32

#if __APPLE__
#include <mach-o/dyld.h>
#endif // __APPLE__

#if __linux__ && defined(USE_AUXV)
#include <sys/auxv.h>
#endif

#if __FreeBSD__
#include <sys/sysctl.h>
#endif

#include "vlmcsd.h"
// ReSharper disable CppUnusedIncludeDirective
#include "endian.h"
// ReSharper restore CppUnusedIncludeDirective
#include "shared_globals.h"
#include "output.h"
#ifndef USE_MSRPC
#include "network.h"
#else // USE_MSRPC
#include "msrpc-server.h"
#endif // USE_MSRPC
#include "ntservice.h"
#include "helpers.h"

#ifndef NO_TAP
#include "wintap.h"
#endif

static const char* const optstring = "N:B:m:t:w:0:3:6:H:A:R:u:g:L:p:i:P:l:r:U:W:C:c:F:O:o:x:T:K:E:M:j:SseDdVvqkZ";

#if !defined(NO_SOCKETS) && !defined(USE_MSRPC) && !defined(SIMPLE_SOCKETS)
static uint_fast8_t maxsockets = 0;

#endif // !defined(NO_SOCKETS) && !defined(USE_MSRPC) && !defined(SIMPLE_SOCKETS)

#ifdef _NTSERVICE
static int_fast8_t installService = 0;
static const char *restrict ServiceUser = NULL;
static const char *restrict ServicePassword = "";
#endif

#ifndef NO_PID_FILE
static const char *fn_pid = NULL;
#endif

#ifndef NO_INI_FILE

#ifdef INI_FILE
static const char *fn_ini = INI_FILE;
#else // !INI_FILE
static const char *fn_ini = NULL;
#endif // !INI_FILE

#ifndef NO_TAP
char* tapArgument = NULL;
#endif // NO_TAP

static const char* IniFileErrorMessage = "";
char* IniFileErrorBuffer = NULL;
#define INIFILE_ERROR_BUFFERSIZE 256

static IniFileParameter_t IniFileParameterList[] =
{
		{ "Windows", INI_PARAM_WINDOWS },
		{ "Office2010", INI_PARAM_OFFICE2010 },
		{ "Office2013", INI_PARAM_OFFICE2013 },
		{ "Office2016", INI_PARAM_OFFICE2016 },
#	ifndef NO_SOCKETS
		{ "ExitLevel", INI_PARAM_EXIT_LEVEL },
#	endif // NO_SOCKETS
#	ifndef NO_TAP
		{ "VPN", INI_PARAM_VPN },
#   endif // NO_TAP
#	ifndef NO_EXTERNAL_DATA
		{ "KmsData", INI_PARAM_DATA_FILE },
#	endif // NO_EXTERNAL_DATA
#	ifndef NO_STRICT_MODES
		{ "WhiteListingLevel", INI_PARAM_WHITELISTING_LEVEL },
		{ "CheckClientTime", INI_PARAM_CHECK_CLIENT_TIME },
#		ifndef NO_CLIENT_LIST
		{ "StartEmpty", INI_PARAM_START_EMPTY },
		{ "MaintainClients", INI_PARAM_MAINTAIN_CLIENTS },
#		endif // NO_CLIENT_LIST
#	endif // NO_STRICT_MODES
#	ifndef NO_RANDOM_EPID
		{ "RandomizationLevel", INI_PARAM_RANDOMIZATION_LEVEL },
		{ "LCID", INI_PARAM_LCID },
#	endif // NO_RANDOM_EPID
#	if !defined(NO_SOCKETS) && (defined(USE_MSRPC) || defined(SIMPLE_SOCKETS) || defined(HAVE_GETIFADDR))
		{ "Port", INI_PARAM_PORT },
#	endif // defined(USE_MSRPC) || defined(SIMPLE_SOCKETS)
#	if !defined(NO_SOCKETS) && !defined(USE_MSRPC)
#	ifndef SIMPLE_SOCKETS
		{ "Listen", INI_PARAM_LISTEN },
#	endif // SIMPLE_SOCKETS
#	if HAVE_FREEBIND
		{ "FreeBind", INI_PARAM_FREEBIND },
#	endif // HAVE_FREEBIND
#	if !defined(NO_LIMIT) && !__minix__
		{ "MaxWorkers", INI_PARAM_MAX_WORKERS },
#	endif // !defined(NO_LIMIT) && !__minix__
#	endif // !defined(NO_SOCKETS) && !defined(USE_MSRPC)
#	if !defined(NO_TIMEOUT) && !__minix__ && !defined(USE_MSRPC) & !defined(USE_MSRPC)
		{ "ConnectionTimeout", INI_PARAM_CONNECTION_TIMEOUT },
#	endif // !defined(NO_TIMEOUT) && !__minix__ && !defined(USE_MSRPC) & !defined(USE_MSRPC)
#	ifndef USE_MSRPC
		{ "DisconnectClientsImmediately", INI_PARAM_DISCONNECT_IMMEDIATELY },
#	ifndef SIMPLE_RPC
		{ "UseNDR64", INI_PARAM_RPC_NDR64 },
		{ "UseBTFN", INI_PARAM_RPC_BTFN },
#	endif // !SIMPLE_RPC
#	endif // USE_MSRPC
#	ifndef NO_PID_FILE
		{ "PIDFile", INI_PARAM_PID_FILE },
#	endif // NO_PID_FILE
#	ifndef NO_LOG
		{ "LogDateAndTime", INI_PARAM_LOG_DATE_AND_TIME },
		{ "LogFile", INI_PARAM_LOG_FILE },
#	ifndef NO_VERBOSE_LOG
		{ "LogVerbose", INI_PARAM_LOG_VERBOSE },
#	endif // NO_VERBOSE_LOG
#	endif // NO_LOG
#	ifndef NO_CUSTOM_INTERVALS
		{"ActivationInterval", INI_PARAM_ACTIVATION_INTERVAL },
		{"RenewalInterval", INI_PARAM_RENEWAL_INTERVAL },
#	endif // NO_CUSTOM_INTERVALS
#	if !defined(NO_USER_SWITCH) && !defined(_WIN32)
		{ "user", INI_PARAM_UID },
		{ "group", INI_PARAM_GID},
#	endif // !defined(NO_USER_SWITCH) && !defined(_WIN32)
#	if !defined(NO_PRIVATE_IP_DETECT)
		{"PublicIPProtectionLevel", INI_PARAM_PUBLIC_IP_PROTECTION_LEVEL },
#	endif
};

#endif // NO_INI_FILE


#if !defined(NO_LIMIT) && !defined (NO_SOCKETS) && !__minix__

#if !defined(USE_THREADS) && !defined(CYGWIN) && !defined(USE_MSRPC)
static int shmid = -1;
#endif


#endif // !defined(NO_LIMIT) && !defined (NO_SOCKETS) && !__minix__

#ifndef NO_USER_SWITCH
#ifndef _WIN32

static const char *uname = NULL, *gname = NULL;
static gid_t gid = INVALID_GID;
static uid_t uid = INVALID_UID;

// Get Numeric id of user/group
static char GetNumericId(gid_t *restrict id, const char *const c)
{
	char* endptr;
	gid_t temp;

	temp = (gid_t)strtoll(c, &endptr, 10);
	if (!*endptr) *id = temp;
	if (*endptr || temp == (gid_t)-1) errno = EINVAL;

	return *endptr || *id == (gid_t)-1;
}


// Get group id from option argument
static char GetGid()
{
	struct group *g;

	if ((g = getgrnam(optarg)))
		gid = g->gr_gid;
	else
		return GetNumericId(&gid, optarg);

	return 0;
}


// Get user id from option argument
static char GetUid()
{
	struct passwd *u;

	////PORTABILITY: Assumes uid_t and gid_t are of same size (shouldn't be a problem)
	if ((u = getpwnam(optarg)))
		uid = u->pw_uid;
	else
		return GetNumericId((gid_t*)&uid, optarg);

	return 0;
}
#endif // _WIN32
#endif //NO_USER_SWITCH

#ifdef NO_HELP
static __noreturn void usage()
{
	printerrorf("Incorrect parameters\n\n");
	exit(VLMCSD_EINVAL);
}
#else // HELP


static __noreturn void usage()
{
	printerrorf("vlmcsd %s\n"
		"\nUsage:\n"
		"   %s [ options ]\n\n"
		"Where:\n"
#		ifndef NO_CL_PIDS
		"  -w <ePID>\t\talways use <ePID> for Windows\n"
		"  -0 <ePID>\t\talways use <ePID> for Office2010\n"
		"  -3 <ePID>\t\talways use <ePID> for Office2013\n"
		"  -6 <ePID>\t\talways use <ePID> for Office2016\n"
		"  -H <HwId>\t\talways use hardware Id <HwId>\n"
#		endif // NO_CL_PIDS
#		if !defined(_WIN32) && !defined(NO_USER_SWITCH)
		"  -u <user>\t\tset uid to <user>\n"
		"  -g <group>\t\tset gid to <group>\n"
#		endif // !defined(_WIN32) && !defined(NO_USER_SWITCH)
#		ifndef NO_RANDOM_EPID
		"  -r 0|1|2\t\tset ePID randomization level (default 1)\n"
		"  -C <LCID>\t\tuse fixed <LCID> in random ePIDs\n"
#		endif // NO_RANDOM_EPID
#		if !defined(NO_PRIVATE_IP_DETECT)
#		if HAVE_GETIFADDR
		"  -o 0|1|2|3\t\tset protection level against clients with public IP addresses (default 0)\n"
#		else // !HAVE_GETIFADDR
#		ifndef USE_MSRPC
		"  -o 0|2\t\tset protection level against clients with public IP addresses (default 0)\n"
#		else // USE_MSRPC
		"  -o 0|2\t\tset protection level against clients with public IP addresses (default 0). Limited use with MS RPC\n"
#		endif // USE_MSRPC
#		endif // !HAVE_GETIFADDR
#		endif // !defined(NO_PRIVATE_IP_DETECT)
#		ifndef NO_TAP
		"  -O <v>[=<a>][/<c>]\tuse VPN adapter <v> with IPv4 address <a> and CIDR <c>\n"
#		endif
#		ifndef NO_SOCKETS
		"  -x <level>\t\texit if warning <level> reached (default 0)\n"
#		if !defined(USE_MSRPC) && !defined(SIMPLE_SOCKETS)
		"  -L <address>[:<port>]\tlisten on IP address <address> with optional <port>\n"
		"  -P <port>\t\tset TCP port <port> for subsequent -L statements (default 1688)\n"
#		if HAVE_FREEBIND
		"  -F0, -F1\t\tdisable/enable binding to foreign IP addresses\n"
#		endif // HAVE_FREEBIND
#		else // defined(USE_MSRPC) || defined(SIMPLE_SOCKETS)
		"  -P <port>\t\tuse TCP port <port> (default 1688)\n"
#		endif // defined(USE_MSRPC) || defined(SIMPLE_SOCKETS)
#		if !defined(NO_LIMIT) && !__minix__
		"  -m <clients>\t\tHandle max. <clients> simultaneously (default no limit)\n"
#		endif // !defined(NO_LIMIT) && !__minix__
#		ifdef _NTSERVICE
		"  -s\t\t\tinstall vlmcsd as an NT service. Ignores -e"
#		ifndef _WIN32
		", -f and -D"
#		endif // _WIN32
		"\n"
		"  -S\t\t\tremove vlmcsd service. Ignores all other options\n"
		"  -U <username>\t\trun NT service as <username>. Must be used with -s\n"
		"  -W <password>\t\toptional <password> for -U. Must be used with -s\n"
#		endif // _NTSERVICE
#		ifndef NO_LOG
		"  -e\t\t\tlog to stdout\n"
#		endif // NO_LOG
#		ifndef _WIN32 //
		"  -D\t\t\trun in foreground\n"
#		else // _WIN32
		"  -D\t\t\tdoes nothing. Provided for compatibility with POSIX versions only\n"
#		endif // _WIN32
#		endif // NO_SOCKETS
#		ifndef NO_STRICT_MODES
		"  -K 0|1|2|3\t\tset whitelisting level for KMS IDs (default -K0)\n"
		"  -c0, -c1\t\tdisable/enable client time checking (default -c0)\n"
#		ifndef NO_CLIENT_LIST
		"  -M0, -M1\t\tdisable/enable maintaining clients (default -M0)\n"
		"  -E0, -E1\t\tdisable/enable start with empty client list (default -E0, ignored if -M0)\n"
#		endif // !NO_CLIENT_LIST
#		endif // !NO_STRICT_MODES
#		ifndef USE_MSRPC
#		if !defined(NO_TIMEOUT) && !__minix__
		"  -t <seconds>\t\tdisconnect clients after <seconds> of inactivity (default 30)\n"
#		endif // !defined(NO_TIMEOUT) && !__minix__
		"  -d\t\t\tdisconnect clients after each request\n"
		"  -k\t\t\tdon't disconnect clients after each request (default)\n"
#		ifndef SIMPLE_RPC
		"  -N0, -N1\t\tdisable/enable NDR64\n"
		"  -B0, -B1\t\tdisable/enable bind time feature negotiation\n"
#		endif // !SIMPLE_RPC
#		endif // USE_MSRPC
#		ifndef NO_PID_FILE
		"  -p <file>\t\twrite pid to <file>\n"
#		endif // NO_PID_FILE
#		ifndef NO_INI_FILE
		"  -i <file>\t\tuse config file <file>\n"
#		endif // NO_INI_FILE
#		ifndef NO_EXTERNAL_DATA
		"  -j <file>\t\tuse KMS data file <file>\n"
#		endif // !NO_EXTERNAL_DATA
#		ifndef NO_CUSTOM_INTERVALS
		"  -R <interval>\t\trenew activation every <interval> (default 1w)\n"
		"  -A <interval>\t\tretry activation every <interval> (default 2h)\n"
#		endif // NO_CUSTOM_INTERVALS
#		ifndef NO_LOG
#		ifndef _WIN32
		"  -l syslog		log to syslog\n"
#		endif // _WIN32
		"  -l <file>\t\tlog to <file>\n"
		"  -T0, -T1\t\tdisable/enable logging with time and date (default -T1)\n"
#		ifndef NO_VERBOSE_LOG
		"  -v\t\t\tlog verbose\n"
		"  -q\t\t\tdon't log verbose (default)\n"
#		endif // NO_VERBOSE_LOG
#		endif // NO_LOG
#		ifndef NO_VERSION_INFORMATION
		"  -V\t\t\tdisplay version information and exit\n"
#		endif // NO_VERSION_INFORMATION
		,
		Version, global_argv[0]);

	exit(VLMCSD_EINVAL);
}
#endif // HELP


#ifndef NO_CUSTOM_INTERVALS
#ifndef NO_INI_FILE

__pure static BOOL getTimeSpanFromIniFile(DWORD* result, const char *const restrict argument)
{
	DWORD val = timeSpanString2Minutes(argument);
	if (!val)
	{
		IniFileErrorMessage = "Incorrect time span.";
		return FALSE;
	}

	*result = val;
	return TRUE;
}

#endif // NO_INI_FILE


__pure static DWORD getTimeSpanFromCommandLine(const char *const restrict optarg, const char optchar)
{
	DWORD val = timeSpanString2Minutes(optarg);

	if (!val)
	{
		printerrorf("Fatal: No valid time span specified in option -%c.\n", optchar);
		exit(VLMCSD_EINVAL);
	}

	return val;
}

#endif // NO_CUSTOM_INTERVALS


#ifndef NO_INI_FILE
static void ignoreIniFileParameter(uint_fast8_t iniFileParameterId)
{
	uint_fast8_t i;

	for (i = 0; i < vlmcsd_countof(IniFileParameterList); i++)
	{
		if (IniFileParameterList[i].Id != iniFileParameterId) continue;
		IniFileParameterList[i].Id = 0;
		break;
	}
}
#else // NO_INI_FILE
#define ignoreIniFileParameter(x)
#endif // NO_INI_FILE


#ifndef NO_INI_FILE
static BOOL getIniFileArgumentBool(int_fast8_t *result, const char *const argument)
{
	IniFileErrorMessage = "Argument must be true/on/yes/1 or false/off/no/0";
	return getArgumentBool(result, argument);
}


static BOOL getIniFileArgumentInt(unsigned int *result, const char *const argument, const unsigned int min, const unsigned int max)
{
	unsigned int tempResult;

	if (!stringToInt(argument, min, max, &tempResult))
	{
		vlmcsd_snprintf(IniFileErrorBuffer, INIFILE_ERROR_BUFFERSIZE, "Must be integer between %u and %u", min, max);
		IniFileErrorMessage = IniFileErrorBuffer;
		return FALSE;
	}

	*result = tempResult;
	return TRUE;
}


static __pure int isControlCharOrSlash(const char c)
{
	if ((unsigned char)c < '!') return TRUE;
	if (c == '/') return TRUE;
	return FALSE;
}


static void iniFileLineNextWord(const char **s)
{
	while (**s && isspace((int)**s)) (*s)++;
}


static BOOL setHwIdFromIniFileLine(const char **s, const uint32_t index)
{
	iniFileLineNextWord(s);

	if (**s == '/')
	{
		if (KmsResponseParameters[index].HwId) return TRUE;

		BYTE* HwId = (BYTE*)vlmcsd_malloc(sizeof(((RESPONSE_V6 *)0)->HwId));
		hex2bin(HwId, *s + 1, sizeof(((RESPONSE_V6 *)0)->HwId));
		KmsResponseParameters[index].HwId = HwId;
	}

	return TRUE;
}


static BOOL setEpidFromIniFileLine(const char **s, const uint32_t index)
{
	iniFileLineNextWord(s);
	const char *savedPosition = *s;
	uint_fast16_t i;

	for (i = 0; !isControlCharOrSlash(**s); i++)
	{
		if (utf8_to_ucs2_char((const unsigned char*)*s, (const unsigned char**)s) == (WCHAR)~0)
		{
			return FALSE;
		}
	}

	if (i < 1 || i >= PID_BUFFER_SIZE) return FALSE;
	if (KmsResponseParameters[index].Epid) return TRUE;

	size_t size = *s - savedPosition + 1;

	char* epidbuffer = (char*)vlmcsd_malloc(size);
	memcpy(epidbuffer, savedPosition, size - 1);
	epidbuffer[size - 1] = 0;

	KmsResponseParameters[index].Epid = epidbuffer;

#ifndef NO_LOG
	KmsResponseParameters[index].EpidSource = fn_ini;
#endif //NO_LOG

	return TRUE;
}


static BOOL setIniFileParameter(uint_fast8_t id, const char *const iniarg)
{
	unsigned int result;
	BOOL success = TRUE;
	const char *s = (const char*)iniarg;

	switch (id)
	{
	case INI_PARAM_WINDOWS:
		setEpidFromIniFileLine(&s, EPID_INDEX_WINDOWS);
		setHwIdFromIniFileLine(&s, EPID_INDEX_WINDOWS);
		break;

	case INI_PARAM_OFFICE2010:
		setEpidFromIniFileLine(&s, EPID_INDEX_OFFICE2010);
		setHwIdFromIniFileLine(&s, EPID_INDEX_OFFICE2010);
		break;

	case INI_PARAM_OFFICE2013:
		setEpidFromIniFileLine(&s, EPID_INDEX_OFFICE2013);
		setHwIdFromIniFileLine(&s, EPID_INDEX_OFFICE2013);
		break;

	case INI_PARAM_OFFICE2016:
		setEpidFromIniFileLine(&s, EPID_INDEX_OFFICE2016);
		setHwIdFromIniFileLine(&s, EPID_INDEX_OFFICE2016);
		break;

#	ifndef NO_TAP

	case INI_PARAM_VPN:
		tapArgument = (char*)vlmcsd_strdup(iniarg);
		break;

#	endif // NO_TAP

#	if !defined(NO_USER_SWITCH) && !_WIN32

	case INI_PARAM_GID:
	{
		struct group *g;
		IniFileErrorMessage = "Invalid group id or name";
		if (!(gname = vlmcsd_strdup(iniarg))) return FALSE;

		if ((g = getgrnam(iniarg)))
			gid = g->gr_gid;
		else
			success = !GetNumericId(&gid, iniarg);
		break;
	}

	case INI_PARAM_UID:
	{
		struct passwd *p;
		IniFileErrorMessage = "Invalid user id or name";
		if (!(uname = vlmcsd_strdup(iniarg))) return FALSE;

		if ((p = getpwnam(iniarg)))
			uid = p->pw_uid;
		else
			success = !GetNumericId(&uid, iniarg);
		break;
	}

#	endif // !defined(NO_USER_SWITCH) && !defined(_WIN32)

#	ifndef NO_RANDOM_EPID

	case INI_PARAM_LCID:
		success = getIniFileArgumentInt(&result, iniarg, 0, 32767);
		if (success) Lcid = (uint16_t)result;
		break;

	case INI_PARAM_RANDOMIZATION_LEVEL:
		success = getIniFileArgumentInt(&result, iniarg, 0, 2);
		if (success) RandomizationLevel = (int_fast8_t)result;
		break;

#	endif // NO_RANDOM_EPID

#	if (defined(USE_MSRPC) || defined(SIMPLE_SOCKETS) || defined(HAVE_GETIFADDR)) && !defined(NO_SOCKETS)

	case INI_PARAM_PORT:
		defaultport = vlmcsd_strdup(iniarg);
		break;

#	endif // (defined(USE_MSRPC) || defined(SIMPLE_SOCKETS) || defined(HAVE_GETIFADDR)) && !defined(NO_SOCKETS)

#	if !defined(NO_SOCKETS) && !defined(USE_MSRPC) && !defined(SIMPLE_SOCKETS)

	case INI_PARAM_LISTEN:
		maxsockets++;
		return TRUE;

#	endif // !defined(NO_SOCKETS) && !defined(USE_MSRPC) && !defined(SIMPLE_SOCKETS)
#	if !defined(NO_LIMIT) && !defined(NO_SOCKETS) && !__minix__

	case INI_PARAM_MAX_WORKERS:
#		ifdef USE_MSRPC
		success = getIniFileArgumentInt(&MaxTasks, iniarg, 1, RPC_C_LISTEN_MAX_CALLS_DEFAULT);
#		else // !USE_MSRPC
		success = getIniFileArgumentInt(&MaxTasks, iniarg, 1, SEM_VALUE_MAX);
#		endif // !USE_MSRPC
		break;

#	endif // !defined(NO_LIMIT) && !defined(NO_SOCKETS) && !__minix__

#	ifndef NO_PID_FILE

	case INI_PARAM_PID_FILE:
		fn_pid = vlmcsd_strdup(iniarg);
		break;

#	endif // NO_PID_FILE

#	ifndef NO_EXTERNAL_DATA

	case INI_PARAM_DATA_FILE:
		fn_data = vlmcsd_strdup(iniarg);
#		ifndef NO_INTERNAL_DATA
		ExplicitDataLoad = TRUE;
#		endif // NO_INTERNAL_DATA
		break;

#	endif // NO_EXTERNAL_DATA

#	ifndef NO_STRICT_MODES

	case INI_PARAM_WHITELISTING_LEVEL:
		success = getIniFileArgumentInt(&WhitelistingLevel, iniarg, 0, 3);
		break;

	case INI_PARAM_CHECK_CLIENT_TIME:
		success = getIniFileArgumentBool(&CheckClientTime, iniarg);
		break;

#	ifndef NO_CLIENT_LIST
	case INI_PARAM_MAINTAIN_CLIENTS:
		success = getIniFileArgumentBool(&MaintainClients, iniarg);
		break;

	case INI_PARAM_START_EMPTY:
		success = getIniFileArgumentBool(&StartEmpty, iniarg);
		break;

#	endif // NO_CLIENT_LIST
#	endif // !NO_STRICT_MODES


#	ifndef  NO_LOG

	case INI_PARAM_LOG_FILE:
		fn_log = vlmcsd_strdup(iniarg);
		break;

	case INI_PARAM_LOG_DATE_AND_TIME:
		success = getIniFileArgumentBool(&LogDateAndTime, iniarg);
		break;

#	ifndef NO_VERBOSE_LOG
	case INI_PARAM_LOG_VERBOSE:
		success = getIniFileArgumentBool(&logverbose, iniarg);
		break;

#	endif // NO_VERBOSE_LOG
#	endif // NO_LOG

#	ifndef NO_CUSTOM_INTERVALS

	case INI_PARAM_ACTIVATION_INTERVAL:
		success = getTimeSpanFromIniFile(&VLActivationInterval, iniarg);
		break;

	case INI_PARAM_RENEWAL_INTERVAL:
		success = getTimeSpanFromIniFile(&VLRenewalInterval, iniarg);
		break;

#	endif // NO_CUSTOM_INTERVALS

#	ifndef USE_MSRPC

#	if !defined(NO_TIMEOUT) && !__minix__

	case INI_PARAM_CONNECTION_TIMEOUT:
		success = getIniFileArgumentInt(&result, iniarg, 1, 600);
		if (success) ServerTimeout = (DWORD)result;
		break;

#	endif // !defined(NO_TIMEOUT) && !__minix__

	case INI_PARAM_DISCONNECT_IMMEDIATELY:
		success = getIniFileArgumentBool(&DisconnectImmediately, iniarg);
		break;

	case INI_PARAM_RPC_NDR64:
		success = getIniFileArgumentBool(&UseServerRpcNDR64, iniarg);
		break;

	case INI_PARAM_RPC_BTFN:
		success = getIniFileArgumentBool(&UseServerRpcBTFN, iniarg);
		break;

#	endif // USE_MSRPC

#	ifndef NO_SOCKETS

	case INI_PARAM_EXIT_LEVEL:
		success = getIniFileArgumentInt(&result, iniarg, 0, 1);
		if (success) ExitLevel = (int_fast8_t)result;
		break;

#	endif // NO_SOCKETS

#	if HAVE_FREEBIND

	case INI_PARAM_FREEBIND:
		success = getIniFileArgumentBool(&freebind, iniarg);
		break;

#	endif // HAVE_FREEBIND

#	if !defined(NO_PRIVATE_IP_DETECT)

	case INI_PARAM_PUBLIC_IP_PROTECTION_LEVEL:
		success = getIniFileArgumentInt(&PublicIPProtectionLevel, iniarg, 0, 3);

#		if !HAVE_GETIFADDR
		if (PublicIPProtectionLevel & 1)
		{
			IniFileErrorMessage = "Must be 0 or 2";
			success = FALSE;
		}
#		endif // !HAVE_GETIFADDR

		break;

#	endif // !defined(NO_PRIVATE_IP_DETECT)

	default:
		return FALSE;
	}

	return success;
}


static BOOL getIniFileArgument(const char **s)
{
	while (!isspace((int)**s) && **s != '=' && **s) (*s)++;
	iniFileLineNextWord(s);

	if (*((*s)++) != '=')
	{
		IniFileErrorMessage = "'=' required after keyword.";
		return FALSE;
	}

	iniFileLineNextWord(s);

	if (!**s)
	{
		IniFileErrorMessage = "missing argument after '='.";
		return FALSE;
	}

	return TRUE;
}


static BOOL handleIniFileParameter(const char *s)
{
	uint_fast8_t i;

	for (i = 0; i < vlmcsd_countof(IniFileParameterList); i++)
	{
		if (strncasecmp(IniFileParameterList[i].Name, s, strlen(IniFileParameterList[i].Name))) continue;
		if (!IniFileParameterList[i].Id) return TRUE;

		if (!getIniFileArgument(&s)) return FALSE;

		return setIniFileParameter(IniFileParameterList[i].Id, s);
	}

	IniFileErrorMessage = "Unknown keyword.";
	return FALSE;
}


#if !defined(NO_SOCKETS) && !defined(SIMPLE_SOCKETS) && !defined(USE_MSRPC)
static BOOL setupListeningSocketsFromIniFile(const char *s)
{
	if (!maxsockets) return TRUE;
	if (strncasecmp("Listen", s, 6)) return TRUE;
	if (!getIniFileArgument(&s)) return TRUE;

	vlmcsd_snprintf(IniFileErrorBuffer, INIFILE_ERROR_BUFFERSIZE, "Cannot listen on %s.", s);
	IniFileErrorMessage = IniFileErrorBuffer;
	return addListeningSocket(s);
}
#endif // !defined(NO_SOCKETS) && !defined(SIMPLE_SOCKETS) && !defined(USE_MSRPC)


static BOOL readIniFile(const uint_fast8_t pass)
{
	char  line[256];
	const char *s;
	unsigned int lineNumber;
	uint_fast8_t lineParseError;

	FILE *restrict f;
	BOOL result = TRUE;

	IniFileErrorBuffer = (char*)vlmcsd_malloc(INIFILE_ERROR_BUFFERSIZE);

	if (!((f = fopen(fn_ini, "r")))) return FALSE;

	for (lineNumber = 1; (s = fgets(line, sizeof(line), f)); lineNumber++)
	{
		line[strlen(line) - 1] = 0;

		iniFileLineNextWord(&s);
		if (*s == ';' || *s == '#' || !*s) continue;

#		ifndef NO_SOCKETS
		if (pass == INI_FILE_PASS_1)
#		endif // NO_SOCKETS
		{
			if (handleIniFileParameter(s)) continue;

			lineParseError = TRUE;/*!checkGuidInIniFileLine(&s, &appIndex) ||
					!setEpidFromIniFileLine(&s, appIndex) ||
					!setHwIdFromIniFileLine(&s, appIndex);*/
		}
#		if !defined(NO_SOCKETS) && !defined(SIMPLE_SOCKETS) && !defined(USE_MSRPC)
		else if (pass == INI_FILE_PASS_2)
		{
			lineParseError = !setupListeningSocketsFromIniFile(s);
		}
		else
		{
			return FALSE;
		}
#		endif // !defined(NO_SOCKETS) &&  && !defined(SIMPLE_SOCKETS) && !defined(USE_MSRPC)

		if (lineParseError)
		{
			printerrorf("Warning: %s line %u: \"%s\". %s\n", fn_ini, lineNumber, line, IniFileErrorMessage);
			continue;
		}
	}

	if (ferror(f)) result = FALSE;

	free(IniFileErrorBuffer);
	fclose(f);

#	if !defined(NO_SOCKETS) && !defined(NO_LOG)

	if (pass == INI_FILE_PASS_1 && !InetdMode && result)
	{
#		ifdef _NTSERVICE
		if (!installService)
#		endif // _NTSERVICE
			logger("Read ini file %s\n", fn_ini);
	}

#	endif // !defined(NO_SOCKETS) && !defined(NO_LOG)

	return result;
}
#endif // NO_INI_FILE


#if !defined(NO_SOCKETS)
#if !defined(_WIN32)
#if !defined(NO_SIGHUP)
static void exec_self(char** argv)
{
	getExeName();

	if (fn_exe != NULL)
	{
		execv(fn_exe, argv);
	}
	else
	{
		execvp(argv[0], argv);
	}
}


__noreturn static void HangupHandler(const int signal_unused)
{
	int i;
	int_fast8_t daemonize_protection = TRUE;
	CARGV argv_in = multi_argv == NULL ? global_argv : multi_argv;
	int argc_in = multi_argc == 0 ? global_argc : multi_argc;
	const char** argv_out = (const char**)vlmcsd_malloc((argc_in + 2) * sizeof(char**));

	for (i = 0; i < argc_in; i++)
	{
		if (!strcmp(argv_in[i], "-Z")) daemonize_protection = FALSE;
		argv_out[i] = argv_in[i];
	}

	argv_out[argc_in] = argv_out[argc_in + 1] = NULL;
	if (daemonize_protection) argv_out[argc_in] = (char*) "-Z";

	exec_self((char**)argv_out);
	int error = errno;

#	ifndef NO_LOG
	logger("Fatal: Unable to restart on SIGHUP: %s\n", strerror(error));
#	endif

#	ifndef NO_PID_FILE
	if (fn_pid) unlink(fn_pid);
#	endif // NO_PID_FILE
	exit(error);
}
#endif // NO_SIGHUP


__noreturn static void terminationHandler(const int signal_unused)
{
	cleanup();
	exit(0);
}


#if defined(CHILD_HANDLER) || __minix__
static void childHandler(const int signal)
{
	waitpid(-1, NULL, WNOHANG);
}
#endif // defined(CHILD_HANDLER) || __minix__


static int daemonizeAndSetSignalAction()
{
	struct sigaction sa;
	sigemptyset(&sa.sa_mask);

#	ifndef NO_LOG
	if (!nodaemon) if (daemon(!0, logstdout))
#	else // NO_LOG
	if (!nodaemon) if (daemon(!0, 0))
#	endif // NO_LOG
	{
		printerrorf("Fatal: Could not daemonize to background.\n");
		return(errno);
	}

	if (!InetdMode)
	{
#		ifndef USE_THREADS

#		if defined(CHILD_HANDLER) || __minix__
		sa.sa_handler = childHandler;
#		else // !(defined(CHILD_HANDLER) || __minix__)
		sa.sa_handler = SIG_IGN;
#		endif // !(defined(CHILD_HANDLER) || __minix__)
		sa.sa_flags = SA_NOCLDWAIT;

		if (sigaction(SIGCHLD, &sa, NULL))
			return(errno);

#		endif // !USE_THREADS

		sa.sa_handler = terminationHandler;
		sa.sa_flags = 0;

		sigaction(SIGINT, &sa, NULL);
		sigaction(SIGTERM, &sa, NULL);

#		ifndef NO_SIGHUP
		sa.sa_handler = HangupHandler;
		sa.sa_flags = SA_NODEFER;
		sigaction(SIGHUP, &sa, NULL);
#		endif // NO_SIGHUP
	}

	return 0;
}


#else // _WIN32

static BOOL terminationHandler(const DWORD fdwCtrlType)
{
	// What a lame substitute for Unix signal handling
	switch (fdwCtrlType)
	{
	case CTRL_C_EVENT:
	case CTRL_CLOSE_EVENT:
	case CTRL_BREAK_EVENT:
	case CTRL_LOGOFF_EVENT:
	case CTRL_SHUTDOWN_EVENT:
		cleanup();
		exit(0);
	default:
		return FALSE;
	}
}


static DWORD daemonizeAndSetSignalAction()
{
	if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)terminationHandler, TRUE))
	{
#ifndef NO_LOG
		DWORD rc = GetLastError();
		logger("Warning: Could not register Windows signal handler: Error %u\n", rc);
#endif // NO_LOG
	}

	return ERROR_SUCCESS;
}
#endif // _WIN32
#endif // !defined(NO_SOCKETS)


// Workaround for Cygwin fork problem (only affects cygwin processes that are Windows services)
// Best is to compile for Cygwin with threads. fork() is slow and unreliable on Cygwin
#if !defined(NO_INI_FILE) || !defined(NO_LOG) || !defined(NO_CL_PIDS) || !defined(NO_EXTERNAL_DATA)
__pure static char* getCommandLineArg(char *const restrict optarg)
{
#	if !__CYGWIN__ || defined(USE_THREADS) || defined(NO_SOCKETS)
	return optarg;
#	else
	if (!IsNTService) return optarg;

	return vlmcsd_strdup(optarg);
#	endif
}
#endif // !defined(NO_INI_FILE) || !defined(NO_LOG) || !defined(NO_CL_PIDS) || !defined(NO_EXTERNAL_DATA)


static void parseGeneralArguments() {
	int o;

#ifndef NO_CL_PIDS
	BYTE* HwId;
#endif // NO_CL_PIDS

	for (opterr = 0; (o = getopt(global_argc, (char* const*)global_argv, (const char*)optstring)) > 0; ) switch (o)
	{
#	if !defined(NO_SOCKETS) && !defined(NO_SIGHUP) && !defined(_WIN32)
	case 'Z':
		IsRestarted = TRUE;
		nodaemon = TRUE;
		break;
#	endif // !defined(NO_SOCKETS) && !defined(NO_SIGHUP) && !defined(_WIN32)

#	ifndef NO_TAP

	case 'O':
		ignoreIniFileParameter(INI_PARAM_VPN);
		tapArgument = getCommandLineArg(optarg);
		break;

#	endif // NO_TAP

#	ifndef NO_CL_PIDS

	case 'w':
		KmsResponseParameters[EPID_INDEX_WINDOWS].Epid = getCommandLineArg(optarg);
#		ifndef NO_LOG
		KmsResponseParameters[EPID_INDEX_WINDOWS].EpidSource = "command line";
#		endif // NO_LOG
		break;

	case '0':
		KmsResponseParameters[EPID_INDEX_OFFICE2010].Epid = getCommandLineArg(optarg);
#		ifndef NO_LOG
		KmsResponseParameters[EPID_INDEX_OFFICE2010].EpidSource = "command line";
#		endif // NO_LOG
		break;

	case '3':
		KmsResponseParameters[EPID_INDEX_OFFICE2013].Epid = getCommandLineArg(optarg);
#		ifndef NO_LOG
		KmsResponseParameters[EPID_INDEX_OFFICE2013].EpidSource = "command line";
#		endif // NO_LOG
		break;

	case '6':
		KmsResponseParameters[EPID_INDEX_OFFICE2016].Epid = getCommandLineArg(optarg);
#		ifndef NO_LOG
		KmsResponseParameters[EPID_INDEX_OFFICE2016].EpidSource = "command line";
#		endif // NO_LOG
		break;

	case 'H':
		HwId = (BYTE*)vlmcsd_malloc(sizeof(((RESPONSE_V6 *)0)->HwId));
		hex2bin(HwId, optarg, sizeof(((RESPONSE_V6 *)0)->HwId));

		KmsResponseParameters[EPID_INDEX_WINDOWS].HwId =
			KmsResponseParameters[EPID_INDEX_OFFICE2010].HwId =
			KmsResponseParameters[EPID_INDEX_OFFICE2013].HwId =
			KmsResponseParameters[EPID_INDEX_OFFICE2016].HwId = HwId;
		break;

#	endif // NO_CL_PIDS

#	ifndef NO_EXTERNAL_DATA

	case 'j':
		ignoreIniFileParameter(INI_PARAM_DATA_FILE);
		fn_data = getCommandLineArg(optarg);
#		ifndef NO_INTERNAL_DATA
		ExplicitDataLoad = TRUE;
#		endif // NO_INTERNAL_DATA
		break;

#	endif // NO_EXTERNAL_DATA

#	ifndef NO_SOCKETS

	case 'x':
		ignoreIniFileParameter(INI_PARAM_EXIT_LEVEL);
		ExitLevel = getOptionArgumentInt((char)o, 0, 1);
		break;

	case 'P':
		ignoreIniFileParameter(INI_PARAM_PORT);
#		if !defined(SIMPLE_SOCKETS) && !defined(USE_MSRPC)
		ignoreIniFileParameter(INI_PARAM_LISTEN);
#		else
		defaultport = optarg;
#		endif // !SIMPLE_SOCKETS
		break;

#	if !defined(NO_LIMIT) && !__minix__

	case 'm':
#		ifdef USE_MSRPC
		MaxTasks = getOptionArgumentInt(o, 1, RPC_C_LISTEN_MAX_CALLS_DEFAULT);
#		else // !USE_MSRPC
		MaxTasks = getOptionArgumentInt((char)o, 1, SEM_VALUE_MAX);
#		endif // !USE_MSRPC
		ignoreIniFileParameter(INI_PARAM_MAX_WORKERS);
		break;

#		endif // !defined(NO_LIMIT) && !__minix__
#		endif // NO_SOCKETS

#	if !defined(NO_TIMEOUT) && !__minix__ && !defined(USE_MSRPC)
	case 't':
		ServerTimeout = getOptionArgumentInt((char)o, 1, 600);
		ignoreIniFileParameter(INI_PARAM_CONNECTION_TIMEOUT);
		break;
#	endif // !defined(NO_TIMEOUT) && !__minix__ && !defined(USE_MSRPC)

#	ifndef NO_PID_FILE
	case 'p':
		fn_pid = getCommandLineArg(optarg);
		ignoreIniFileParameter(INI_PARAM_PID_FILE);
		break;
#	endif

#	ifndef NO_INI_FILE
	case 'i':
		fn_ini = getCommandLineArg(optarg);
		if (!strcmp(fn_ini, "-")) fn_ini = NULL;
		break;
#	endif

#	ifndef NO_LOG

	case 'T':
		if (!getArgumentBool(&LogDateAndTime, optarg)) usage();
		ignoreIniFileParameter(INI_PARAM_LOG_DATE_AND_TIME);
		break;

	case 'l':
		fn_log = getCommandLineArg(optarg);
		ignoreIniFileParameter(INI_PARAM_LOG_FILE);
		break;

#	ifndef NO_VERBOSE_LOG
	case 'v':
	case 'q':
		logverbose = o == 'v';
		ignoreIniFileParameter(INI_PARAM_LOG_VERBOSE);
		break;

#	endif // NO_VERBOSE_LOG
#	endif // NO_LOG

#	if !defined(NO_PRIVATE_IP_DETECT)
	case 'o':
		ignoreIniFileParameter(INI_PARAM_PUBLIC_IP_PROTECTION_LEVEL);
		PublicIPProtectionLevel = getOptionArgumentInt((char)o, 0, 3);

#		if !HAVE_GETIFADDR
		if (PublicIPProtectionLevel & 1) usage();
#		endif // !HAVE_GETIFADDR

		break;
#	endif // !defined(NO_PRIVATE_IP_DETECT)

#	ifndef NO_SOCKETS
#	if !defined(USE_MSRPC) && !defined(SIMPLE_SOCKETS)
	case 'L':
		maxsockets++;
		ignoreIniFileParameter(INI_PARAM_LISTEN);
		break;
#	if HAVE_FREEBIND
	case 'F':
		if (!getArgumentBool(&freebind, optarg)) usage();
		ignoreIniFileParameter(INI_PARAM_FREEBIND);
		break;
#	endif // HAVE_FREEBIND
#	endif // !defined(USE_MSRPC) && !defined(SIMPLE_SOCKETS)

#	ifdef _NTSERVICE
	case 'U':
		ServiceUser = optarg;
		break;

	case 'W':
		ServicePassword = optarg;
		break;

	case 's':
#		ifndef USE_MSRPC
		if (InetdMode) usage();
#		endif // USE_MSRPC
		if (!IsNTService) installService = 1; // Install
		break;

	case 'S':
		if (!IsNTService) installService = 2; // Remove
		break;
#	endif // _NTSERVICE

#	ifndef NO_STRICT_MODES

	case 'K':
		WhitelistingLevel = (int_fast8_t)getOptionArgumentInt((char)o, 0, 3);
		ignoreIniFileParameter(INI_PARAM_WHITELISTING_LEVEL);
		break;

	case 'c':
		if (!getArgumentBool(&CheckClientTime, optarg)) usage();
		ignoreIniFileParameter(INI_PARAM_CHECK_CLIENT_TIME);
		break;

#	ifndef NO_CLIENT_LIST
	case 'E':
		if (!getArgumentBool(&StartEmpty, optarg)) usage();
		ignoreIniFileParameter(INI_PARAM_START_EMPTY);
		break;

	case 'M':
		if (!getArgumentBool(&MaintainClients, optarg)) usage();
		ignoreIniFileParameter(INI_PARAM_MAINTAIN_CLIENTS);
		break;

#	endif // !NO_CLIENT_LIST
#	endif // !NO_STRICT_MODES

	case 'D':
#		ifndef _WIN32
		nodaemon = 1;
#		else // _WIN32
#		ifdef _PEDANTIC
		printerrorf("Warning: Option -D has no effect in the Windows version of vlmcsd.\n");
#		endif // _PEDANTIC
#		endif // _WIN32
		break;

#	ifndef NO_LOG

	case 'e':
		logstdout = 1;
		break;

#	endif // NO_LOG
#	endif // NO_SOCKETS

#	ifndef NO_RANDOM_EPID
	case 'r':
		RandomizationLevel = (int_fast8_t)getOptionArgumentInt((char)o, 0, 2);
		ignoreIniFileParameter(INI_PARAM_RANDOMIZATION_LEVEL);
		break;

	case 'C':
		Lcid = (uint16_t)getOptionArgumentInt((char)o, 0, 32767);

		ignoreIniFileParameter(INI_PARAM_LCID);

#		ifdef _PEDANTIC
		if (!IsValidLcid(Lcid))
		{
			printerrorf("Warning: %s is not a valid LCID.\n", optarg);
		}
#		endif // _PEDANTIC

		break;
#	endif // NO_RANDOM_PID

#	if !defined(NO_USER_SWITCH) && !defined(_WIN32)
	case 'g':
		gname = optarg;
		ignoreIniFileParameter(INI_PARAM_GID);
#		ifndef NO_SIGHUP
		if (!IsRestarted)
#		endif // NO_SIGHUP
			if (GetGid())
			{
				printerrorf("Fatal: %s for %s failed: %s\n", "setgid", gname, strerror(errno));
				exit(errno);
			}
		break;

	case 'u':
		uname = optarg;
		ignoreIniFileParameter(INI_PARAM_UID);
#		ifndef NO_SIGHUP
		if (!IsRestarted)
#		endif // NO_SIGHUP
			if (GetUid())
			{
				printerrorf("Fatal: %s for %s failed: %s\n", "setuid", uname, strerror(errno));
				exit(errno);
			}
		break;
#	endif // NO_USER_SWITCH && !_WIN32

#	ifndef NO_CUSTOM_INTERVALS
	case 'R':
		VLRenewalInterval = getTimeSpanFromCommandLine(optarg, (char)o);
		ignoreIniFileParameter(INI_PARAM_RENEWAL_INTERVAL);
		break;

	case 'A':
		VLActivationInterval = getTimeSpanFromCommandLine(optarg, (char)o);
		ignoreIniFileParameter(INI_PARAM_ACTIVATION_INTERVAL);
		break;
#	endif // NO_CUSTOM_INTERVALS

#	ifndef USE_MSRPC
	case 'd':
	case 'k':
		DisconnectImmediately = o == 'd';
		ignoreIniFileParameter(INI_PARAM_DISCONNECT_IMMEDIATELY);
		break;

#	ifndef SIMPLE_RPC
	case 'N':
		if (!getArgumentBool(&UseServerRpcNDR64, optarg)) usage();
		ignoreIniFileParameter(INI_PARAM_RPC_NDR64);
		break;

	case 'B':
		if (!getArgumentBool(&UseServerRpcBTFN, optarg)) usage();
		ignoreIniFileParameter(INI_PARAM_RPC_BTFN);
		break;
#	endif // !SIMPLE_RPC
#	endif // !USE_MSRPC

#	ifndef NO_VERSION_INFORMATION
	case 'V':
#		ifdef _NTSERVICE
		if (IsNTService) break;
#		endif
#		if defined(__s390__) && !defined(__zarch__) && !defined(__s390x__)
		printf("vlmcsd %s %i-bit\n", Version, sizeof(void*) == 4 ? 31 : (int)sizeof(void*) << 3);
#		else
		printf("vlmcsd %s %i-bit\n", Version, (int)sizeof(void*) << 3);
#		endif // defined(__s390__) && !defined(__zarch__) && !defined(__s390x__)
		printPlatform();
		printCommonFlags();
		printServerFlags();
		exit(0);
#	endif // NO_VERSION_INFORMATION

	default:
		usage();
	}

	// Do not allow non-option arguments
	if (optind != global_argc)
		usage();

#	ifdef _NTSERVICE
	// -U and -W must be used with -s
	if ((ServiceUser || *ServicePassword) && installService != 1) usage();
#	endif // _NTSERVICE
}


#if !defined(NO_PID_FILE)
static void writePidFile()
{
#	ifndef NO_SIGHUP
	if (IsRestarted) return;
#	endif // NO_SIGHUP

	if (fn_pid && !InetdMode)
	{
		FILE *file = fopen(fn_pid, "w");

		if (file)
		{
#			if _MSC_VER
			fprintf(file, "%u", (unsigned int)GetCurrentProcessId());
#			else
			fprintf(file, "%u", (unsigned int)getpid());
#			endif
			fclose(file);
		}

#		ifndef NO_LOG
		else
		{
			logger("Warning: Cannot write pid file '%s'. %s.\n", fn_pid, strerror(errno));
		}
#		endif // NO_LOG
	}
}
#else
#define writePidFile()
#endif // !defined(NO_PID_FILE)

#if !defined(NO_SOCKETS) && !defined(USE_MSRPC)

void cleanup()
{
	if (!InetdMode)
	{
#		ifndef NO_CLIENT_LIST
		if (MaintainClients) CleanUpClientLists();
#		endif // !NO_CLIENT_LIST

#		ifndef NO_PID_FILE
		if (fn_pid) vlmcsd_unlink(fn_pid);
#		endif // NO_PID_FILE
		closeAllListeningSockets();

#		if !defined(NO_LIMIT) && !defined(NO_SOCKETS) && !defined(_WIN32) && !__minix__
		sem_unlink("/vlmcsd");
#		if !defined(USE_THREADS) && !defined(CYGWIN)
		if (shmid >= 0)
		{
			if (MaxTaskSemaphore != (sem_t*)-1) shmdt(MaxTaskSemaphore);
			shmctl(shmid, IPC_RMID, NULL);
		}
#		endif // !defined(USE_THREADS) && !defined(CYGWIN)
#		endif // !defined(NO_LIMIT) && !defined(NO_SOCKETS) && !defined(_WIN32) && !__minix__

#		ifndef NO_LOG
		logger("vlmcsd %s was shutdown\n", Version);
#		endif // NO_LOG
	}
}

#elif defined(USE_MSRPC)

void cleanup()
{
#	ifndef NO_PID_FILE
	if (fn_pid) unlink(fn_pid);
#	endif // NO_PID_FILE

#	ifndef NO_LOG
	logger("vlmcsd %s was shutdown\n", Version);
#	endif // NO_LOG
}

#else // Neither Sockets nor RPC

__pure void cleanup() {}

#endif // Neither Sockets nor RPC


#if !defined(USE_MSRPC) && !defined(NO_LIMIT) && !defined(NO_SOCKETS) && !__minix__
// Get a semaphore for limiting the maximum concurrent tasks
static void allocateSemaphore(void)
{
#	ifdef USE_THREADS
#	define sharemode 0
#	else
#	define sharemode 1
#	endif

#	ifndef _WIN32
	sem_unlink("/vlmcsd");
#	endif

	if (MaxTasks < SEM_VALUE_MAX && !InetdMode)
	{
#		ifndef _WIN32

#		if !defined(USE_THREADS) && !defined(CYGWIN)

		if ((MaxTaskSemaphore = sem_open("/vlmcsd", O_CREAT /*| O_EXCL*/, 0700, MaxTasks)) == SEM_FAILED) // fails on many systems
		{
			// We didn't get a named Semaphore (/dev/shm on Linux) so let's try our own shared page

			if (
				(shmid = shmget(IPC_PRIVATE, sizeof(sem_t), IPC_CREAT | 0600)) < 0 ||
				(MaxTaskSemaphore = (sem_t*)shmat(shmid, NULL, 0)) == (sem_t*)-1 ||
				sem_init(MaxTaskSemaphore, 1, MaxTasks) < 0
				)
			{
				int errno_save = errno;
				if (MaxTaskSemaphore != (sem_t*)-1) shmdt(MaxTaskSemaphore);
				if (shmid >= 0) shmctl(shmid, IPC_RMID, NULL);
				printerrorf("Warning: Could not create semaphore: %s\n", vlmcsd_strerror(errno_save));
				MaxTasks = SEM_VALUE_MAX;
			}
		}

#		else // THREADS or CYGWIN

		MaxTaskSemaphore = (sem_t*)vlmcsd_malloc(sizeof(sem_t));

		if (sem_init(MaxTaskSemaphore, sharemode, MaxTasks) < 0) // sem_init is not implemented on Darwin (returns ENOSYS)
		{
			free(MaxTaskSemaphore);

			if ((MaxTaskSemaphore = sem_open("/vlmcsd", O_CREAT /*| O_EXCL*/, 0700, MaxTasks)) == SEM_FAILED)
			{
				printerrorf("Warning: Could not create semaphore: %s\n", vlmcsd_strerror(errno));
				MaxTasks = SEM_VALUE_MAX;
			}
		}

#		endif // THREADS or CYGWIN

#		else // _WIN32

		if (!((MaxTaskSemaphore = CreateSemaphoreA(NULL, MaxTasks, MaxTasks, NULL))))
		{
			printerrorf("Warning: Could not create semaphore: %s\n", vlmcsd_strerror(GetLastError()));
			MaxTasks = SEM_VALUE_MAX;
		}

#		endif // _WIN32
	}
}
#endif // !defined(NO_LIMIT) && !defined(NO_SOCKETS) && !__minix__


#if !defined(NO_SOCKETS) && !defined(USE_MSRPC) && !defined(SIMPLE_SOCKETS)
int setupListeningSockets()
{
	int o;
#	if HAVE_GETIFADDR
	char** privateIPList = NULL;
	int numPrivateIPs = 0;
	if (PublicIPProtectionLevel & 1) getPrivateIPAddresses(&numPrivateIPs, &privateIPList);
	uint_fast8_t allocsockets = maxsockets ? (maxsockets + numPrivateIPs) : ((PublicIPProtectionLevel & 1) ? numPrivateIPs : 2);
#	else // !HAVE_GETIFADDR
	uint_fast8_t allocsockets = maxsockets ? maxsockets : 2;
#	endif // !HAVE_GETIFADDR

	SocketList = (SOCKET*)vlmcsd_malloc((size_t)allocsockets * sizeof(SOCKET));

	int_fast8_t haveIPv4Stack = checkProtocolStack(AF_INET);
	int_fast8_t haveIPv6Stack = checkProtocolStack(AF_INET6);

	// Reset getopt since we've alread used it
	optReset();

	for (opterr = 0; (o = getopt(global_argc, (char* const*)global_argv, (const char*)optstring)) > 0; ) switch (o)
	{
	case 'P':
		defaultport = optarg;
		break;

	case 'L':
		addListeningSocket(optarg);
		break;

	default:
		break;
	}


#	ifndef NO_INI_FILE
	if (maxsockets && !numsockets)
	{
		if (fn_ini && !readIniFile(INI_FILE_PASS_2))
		{
#			ifdef INI_FILE
			if (strcmp(fn_ini, INI_FILE))
#			endif // INI_FILE
				printerrorf("Warning: Can't read %s: %s\n", fn_ini, strerror(errno));
		}
	}
#	endif

#	if HAVE_GETIFADDR
	if (PublicIPProtectionLevel & 1)
	{
		int i;
		for (i = 0; i < numPrivateIPs; i++)
		{
			addListeningSocket(privateIPList[i]);
			free(privateIPList[i]);
		}

		free(privateIPList);
	}
#	endif // HAVE_GETIFADDR

	// if -L hasn't been specified on the command line, use default sockets (all IP addresses)
	// maxsocket results from first pass parsing the arguments
	if (!maxsockets)
	{
#		if HAVE_GETIFADDR
		if (!(PublicIPProtectionLevel & 1) && haveIPv6Stack) addListeningSocket("::");
		if (!(PublicIPProtectionLevel & 1) && haveIPv4Stack) addListeningSocket("0.0.0.0");
#		else // !HAVE_GETIFADDR
		if (haveIPv6Stack) addListeningSocket("::");
		if (haveIPv4Stack) addListeningSocket("0.0.0.0");
#		endif // !HAVE_GETIFADDR
	}

	if (!numsockets)
	{
		printerrorf("Fatal: Could not listen on any socket.\n");
		return(!0);
	}

	return 0;
}
#endif // !defined(NO_SOCKETS) && !defined(USE_MSRPC) && !defined(SIMPLE_SOCKETS)


int server_main(int argc, CARGV argv)
{
	global_argc = argc;
	global_argv = argv;

#	ifdef _NTSERVICE
	DWORD lasterror = ERROR_SUCCESS;

	if (!StartServiceCtrlDispatcher(NTServiceDispatchTable) && (lasterror = GetLastError()) == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
	{
		IsNTService = FALSE;
		return newmain();
	}

	return lasterror;
#	else // !_NTSERVICE
	return newmain();
#	endif // !_NTSERVICE
}


int newmain()
{
#	if !defined(NO_RANDOM_EPID) || !defined(NO_CL_PIDS) || !defined(NO_INI_FILE)
	KmsResponseParameters = (KmsResponseParam_t*)vlmcsd_malloc(sizeof(KmsResponseParam_t) * MIN_CSVLK);
	memset(KmsResponseParameters, 0, sizeof(KmsResponseParam_t) * MIN_CSVLK);
#	endif // !defined(NO_RANDOM_EPID) || !defined(NO_CL_PIDS) || !defined(NO_INI_FILE)

	// Initialize thread synchronization objects for Windows and Cygwin
#	ifdef USE_THREADS

#	ifndef NO_LOG
// Initialize the Critical Section for proper logging
#	if _WIN32 || __CYGWIN__
	InitializeCriticalSection(&logmutex);
#	endif // _WIN32 || __CYGWIN__
#	endif // NO_LOG

#	endif // USE_THREADS

#	ifdef _WIN32

#	ifndef USE_MSRPC
	WSADATA wsadata;
	{
		// Windows Sockets must be initialized
		int error;
		if ((error = WSAStartup(0x0202, &wsadata)))
		{
			printerrorf("Fatal: Could not initialize Windows sockets (Error: %d).\n", error);
			return error;
		}
	}
#	endif // USE_MSRPC

	// Windows can never daemonize
	//nodaemon = 1;

#	else // __CYGWIN__

	// Do not daemonize if we are a Windows service
#	ifdef _NTSERVICE 
	if (IsNTService) nodaemon = 1;
#	endif

#	endif // _WIN32 / __CYGWIN__

	parseGeneralArguments(); // Does not return if an error occurs

#	if !defined(_WIN32) && !defined(NO_SOCKETS) && !defined(USE_MSRPC)

	struct stat statbuf;
	fstat(STDIN_FILENO, &statbuf);

	if (S_ISSOCK(statbuf.st_mode))
	{
		InetdMode = 1;
#		ifndef NO_CLIENT_LIST
		MaintainClients = FALSE;
#		endif // !NO_CLIENT_LIST
		nodaemon = 1;
#		ifndef SIMPLE_SOCKETS
		maxsockets = 0;
#		endif // !SIMPLE_SOCKETS
#		ifndef NO_LOG
		logstdout = 0;
#		endif // !NO_LOG
	}

#	endif // !defined(_WIN32) && !defined(NO_SOCKETS) && !defined(USE_MSRPC)

#	ifndef NO_INI_FILE

	if (fn_ini && !readIniFile(INI_FILE_PASS_1))
	{
#		ifdef INI_FILE
		if (strcmp(fn_ini, INI_FILE))
#		endif // INI_FILE
			printerrorf("Warning: Can't read %s: %s\n", fn_ini, strerror(errno));
	}

#	endif // NO_INI_FILE

	loadKmsData();

#	ifndef NO_CLIENT_LIST
	if (MaintainClients) InitializeClientLists();
#	endif // !NO_CLIENT_LIST

#	if defined(USE_MSRPC) && !defined(NO_PRIVATE_IP_DETECT)
	if (PublicIPProtectionLevel)
	{
		printerrorf("Warning: Public IP address protection using MS RPC is poor. See vlmcsd.8\n");
	}
#	endif // defined(USE_MSRPC) && !defined(NO_PRIVATE_IP_DETECT)

#	if !defined(NO_LIMIT) && !defined(NO_SOCKETS) && !__minix__ && !defined(USE_MSRPC)
	allocateSemaphore();
#	endif // !defined(NO_LIMIT) && !defined(NO_SOCKETS) && __minix__

#	ifdef _NTSERVICE
	if (installService)
		return NtServiceInstallation(installService, ServiceUser, ServicePassword);
#	endif // _NTSERVICE

#	ifndef NO_TAP
	if (tapArgument && !InetdMode) startTap(tapArgument);
#	endif // NO_TAP

#	if !defined(NO_SOCKETS) && !defined(USE_MSRPC)
	if (!InetdMode)
	{
		int error;
#		ifdef SIMPLE_SOCKETS
		if ((error = listenOnAllAddresses())) return error;
#		else // !SIMPLE_SOCKETS
		if ((error = setupListeningSockets())) return error;
#		endif // !SIMPLE_SOCKETS
	}
#	endif // !defined(NO_SOCKETS) && !defined(USE_MSRPC)

	// After sockets have been set up, we may switch to a lower privileged user
#	if !defined(_WIN32) && !defined(NO_USER_SWITCH)

#	ifndef NO_SIGHUP
	if (!IsRestarted)
	{
#	endif // NO_SIGHUP
		if (gid != INVALID_GID)
		{
			if (setgid(gid))
			{
				printerrorf("Fatal: %s for %s failed: %s\n", "setgid", gname, strerror(errno));
				return errno;
			}

			if (setgroups(1, &gid))
			{
				printerrorf("Fatal: %s for %s failed: %s\n", "setgroups", gname, strerror(errno));
				return errno;
			}
		}

		if (uid != INVALID_UID && setuid(uid))
		{
			printerrorf("Fatal: %s for %s failed: %s\n", "setuid", uname, strerror(errno));
			return errno;
		}
#	ifndef NO_SIGHUP
	}
#	endif // NO_SIGHUP

#	endif // !defined(_WIN32) && !defined(NO_USER_SWITCH)

	randomNumberInit();

	// Randomization Level 1 means generate ePIDs at startup and use them during
	// the lifetime of the process. So we generate them now
#	ifndef NO_RANDOM_EPID
	if (RandomizationLevel == 1) randomPidInit();
#	endif

#	if !defined(NO_SOCKETS)
#	ifdef _WIN32
	if (!IsNTService)
	{
#	endif // _WIN32
		int error;
		if ((error = daemonizeAndSetSignalAction())) return error;
#	ifdef _WIN32
	}
#	endif // _WIN32
#	endif // !defined(NO_SOCKETS)

	writePidFile();

#	if !defined(NO_LOG) && !defined(NO_SOCKETS) && !defined(USE_MSRPC)
	if (!InetdMode)
		logger("vlmcsd %s started successfully\n", Version);
#	endif // !defined(NO_LOG) && !defined(NO_SOCKETS) && !defined(USE_MSRPC)

#	if defined(_NTSERVICE) && !defined(USE_MSRPC)
	if (IsNTService) ReportServiceStatus(SERVICE_RUNNING, NO_ERROR, 200);
#	endif // defined(_NTSERVICE) && !defined(USE_MSRPC)

	int rc;
	rc = runServer();

	// Clean up things and exit
#	ifdef _NTSERVICE
	if (!ServiceShutdown)
#	endif
		cleanup();
#	ifdef _NTSERVICE
	else
		ReportServiceStatus(SERVICE_STOPPED, NO_ERROR, 0);
#	endif

	return rc;
}


#if _MSC_VER && !defined(_DEBUG)&& !MULTI_CALL_BINARY
int __stdcall WinStartUp(void)
{
	WCHAR **szArgList;
	int argc;
	szArgList = CommandLineToArgvW(GetCommandLineW(), &argc);

	int i;
	char **argv = (char**)vlmcsd_malloc(sizeof(char*)*argc);

	for (i = 0; i < argc; i++)
	{
		int size = WideCharToMultiByte(CP_UTF8, 0, szArgList[i], -1, argv[i], 0, NULL, NULL);
		argv[i] = (char*)vlmcsd_malloc(size);
		WideCharToMultiByte(CP_UTF8, 0, szArgList[i], -1, argv[i], size, NULL, NULL);
	}

	exit(server_main(argc, argv));
}
#endif // _MSC_VER && !defined(_DEBUG)&& !MULTI_CALL_BINARY
