/* Copyright (C) 2005 Martin Koegler
 * Copyright (C) 2006 OCCAM Financial Technology
 * Copyright (C) 2012-2023 m-privacy GmbH, Berlin
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef HAVE_GNUTLS
#error "This source should not be compiled without HAVE_GNUTLS defined"
#endif

#include <gsasl.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <syslog.h>
#if !defined(__APPLE__)
#include <b64/decode.h>
#endif
#if !defined(WIN32) && !defined(WIN64)
#include <syslog.h>
#include <sys/wait.h>
#include <sys/stat.h>
#if !defined(__APPLE__)
#include <sys/capability.h>
#endif
#endif
#include <mp_utils.h>

#include <rfb/SSecurityKrb.h>
#include <rfb/SConnection.h>
#include <rfb/Exception.h>
#include <rfb/LogWriter.h>
#include <rfb/SSecurityTLS.h>
#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)
#include <rfb/ServerCore.h>
#endif

using namespace rfb;

StringParameter SSecurityKrb::KrbService
("krbservice", "specifies name of Kerberos service", "", ConfServer);
StringParameter SSecurityKrb::KrbHostname
("krbhostname", "specifies Kerberos hostname (defaults to system name)", "", ConfServer);
StringParameter SSecurityKrb::KrbLdapBind
("krbldapbind", "specifies Kerberos LDAP server URI for group retrieval, e.g. ldaps://my.server", "", ConfServer);
StringParameter SSecurityKrb::KrbLdapBind2
("krbldapbind2", "specifies additional Kerberos LDAP server URI for group retrieval, e.g. ldaps://my.server2", "", ConfServer);
StringParameter SSecurityKrb::KrbLdapBase
("krbldapbase", "specifies Kerberos LDAP base for group retrieval", "", ConfServer);
BoolParameter SSecurityKrb::KrbProvideCcache
("krbprovideccache", "Provide ccache file to prepareuser in KRBCCACHE", false);
StringParameter SSecurityKrb::KrbDomainFile
("krbdomainfile", "specifies file for REALM to domain transition", "/etc/cu/krbrealmdomains", ConfServer);
StringParameter SSecurityKrb::KrbLdapAuto
("krbldapauto", "specifies how the list of ldap servers must be initialized", "", ConfServer);

static LogWriter vlog("SSecurityKrb");

/* This is a workaround to be able to use some kerberos/active directory functions and just implement them
   in libmprivacy. Being able to use different loggers is the reason for these #defines */
#define mplog_error(...) vlog.error(__VA_ARGS__);
#define mplog_info(...) vlog.info(__VA_ARGS__);
#define mplog_debug(...) vlog.debug(__VA_ARGS__);
#define mplog_verbose(...) vlog.verbose(__VA_ARGS__);

#include <activedirectory-dev.h>

char * SSecurityKrb::username = NULL;

#if 0
static char * gen_krbldapbind(const char * domain)
{
	char * bindstr;
	int reslen;
	u_int port;
	char line[4096];
	char server[4096];
	FILE * ppipe;
	char command[4096];

	if (!domain)
		return NULL;

	snprintf(command, 4094,
		"/usr/bin/timeout -k 10 60 /usr/bin/host -t SRV \"_ldap._tcp.dc._msdcs.%s\" 2>/dev/null|grep \"has SRV record\"|/usr/bin/head -n 1|/usr/bin/cut -d \" \" -f 7-8",
		domain);
	command[4095] = 0;
	ppipe = popen(command, "r");
	if (!ppipe)
	{
		vlog.error("Failed to execute SRV lookup command: %s", command);
		syslog(LOG_ERR, "Failed to execute SRV lookup command: %s", command);
		return NULL;
	}
	if (!fgets(line, sizeof(line), ppipe))
	{
		pclose(ppipe);
		vlog.error("gen_krbldapbind(): failed to read output from SRV lookup command: %s",
			command);
		syslog(LOG_ERR, "gen_krbldapbind(): failed to read output from SRV lookup command: %s",
			command);
		return NULL;
	}
	pclose(ppipe);
	reslen = sscanf(line, "%u %4095s", &port, server);
        if (reslen < 2)
	{
		vlog.error("gen_krbldapbind(): parsing srv record failed: %s",
			line);
		syslog(LOG_ERR, "gen_krbldapbind(): parsing srv record failed: %s",
			line);
		return NULL;
	}

	bindstr = (char *) malloc(4096);
	if (!bindstr)
	{
		vlog.error("gen_krbldapbind(): malloc failed");
		syslog(LOG_ERR, "gen_krbldapbind(): malloc failed!");
		return NULL;
	}
	snprintf(bindstr, 4095, "ldap://%s:%u", server, port);
	vlog.debug("gen_krbldapbind(): created bindstr %s for domain %s", bindstr, domain);
	return bindstr;
}
#endif /* #if 0 */

static int callback (Gsasl * ctx, Gsasl_session * sctx, Gsasl_property prop)
{
	int rc = GSASL_NO_CALLBACK;

	switch (prop)
	{
	case GSASL_VALIDATE_GSSAPI:
        {
		const char * login_name;
		const char * constdisplay;
		const char * constccache;
		char * realm = NULL;

		openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
		login_name = gsasl_property_fast(sctx, GSASL_AUTHZID);
		if (!login_name) {
			vlog.debug("no authzid provided, trying display name");
		} else {
			vlog.debug("authzid is %s", login_name);
		}
		constdisplay = gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
		if (!constdisplay) {
			if (!login_name) {
				vlog.error("Neither authzid nor display name provided, aborting");
				syslog(LOG_ERR, "Neither authzid nor display name provided, aborting!");
				rc = GSASL_NO_AUTHZID;
			} else {
				vlog.error("No display name provided, aborting");
				syslog(LOG_ERR, "No display name provided, aborting!");
				rc = GSASL_NO_AUTHZID;
			}
		} else {
			char * displayname = strdup(constdisplay);
			char * p = displayname;

			vlog.debug("display name is %s", constdisplay);
			while(*p) {
				if (*p == '@') {
					*p = 0;
					realm = strdup(p + 1);
					vlog.debug("realm is %s", realm);
				} else {
					p++;
				}
			}
			if (!login_name || !login_name[0]) {
				vlog.debug("setting AUTHZID to %s", displayname);
				gsasl_property_set(sctx, GSASL_AUTHZID, displayname);
				rc = GSASL_OK;
			} else {
				if (!strcmp(displayname, login_name)) {
					vlog.debug("AUTHZID matches GSSAPI_DISPLAY_NAME");
					rc = GSASL_OK;
				} else {
					vlog.error("AUTHZID does not match GSSAPI_DISPLAY_NAME!");
					syslog(LOG_ERR, "AUTHZID does not match GSSAPI_DISPLAY_NAME!");
					rc = GSASL_NO_AUTHZID;
				}
			}
			free(displayname);
		}
		if (rc != GSASL_OK) {
			break;
		}

		vlog.debug("User successfully authenticated with Active Directory server");

		login_name = gsasl_property_fast(sctx, GSASL_AUTHZID);

		char* usergroups = (char*) malloc(TGGROUPS_BUF_SIZE);
		if (!usergroups) {
			vlog.error("%s: Failed to allocate memory for user groups", __func__);
			rc = GSASL_AUTHENTICATION_ERROR;
			break;
		}
		size_t usergroups_size = TGGROUPS_BUF_SIZE;

		if (is_lightshadow_auth()) {
			vlog.debug("Using 'lightshadow' authmethod (from config menu)");
			int lightshadow_result = get_tg_groups_from_lightshadow(login_name, &usergroups, &usergroups_size);
			if (lightshadow_result == 0) {
				if (*usergroups) {
					portable_setenv("TGGROUPS", usergroups);
				} else {
					portable_setenv("TGGROUPS", "empty");
				}
				rc = GSASL_OK;
			} else {
				vlog.error("%s: get_tg_groups_from_lightshadow(login_name, &usergroups, &usergroups_size) call failed", __func__);
				rc = GSASL_AUTHENTICATION_ERROR;
			}
			if (usergroups) {
				vlog.info("%s: usergroups: <%s>", __func__, usergroups);
			} else {
				vlog.info("%s: empty usergroups", __func__);
			}
			break;
		}

		if (is_ad_openldap()) {
			vlog.debug("Using 'AD-OpenLDAP' authmethod (from config menu)");
			char usergroups[TGGROUPS_BUF_SIZE];
			rc = get_tg_groups_from_openldap(login_name, &usergroups);
			if (rc == MP_LDAP_OK) {
				if (*usergroups) {
					portable_setenv("TGGROUPS", usergroups);
				} else {
					portable_setenv("TGGROUPS", "empty");
				}
				rc = GSASL_OK;
			} else {
				rc = GSASL_AUTHENTICATION_ERROR;
			}
			break;
		}

		vlog.debug("Using 'AD' authmethod (from config menu)");
		if (!is_check_ad_tg_groups()) {
			vlog.debug("Not checking tg groups. Allowing login directly.");
			break;
		}

		constccache = gsasl_property_fast(sctx, GSASL_GSSAPI_CCACHE);
		if (!constccache || !constccache[0]) {
			vlog.debug("No CCache provided (delegation not working): won't use client's ticket. Running kinit with keytab...");
			if (!run_kinit_with_realm(realm)) {
				vlog.error("No CCache provided (delegation not working). Also couldn't 'kinit' with keytab to query the Active Directory server");
				syslog(LOG_ERR, "No CCache provided (delegation not working). Also couldn't 'kinit' with keytab to query the Active Directory server");
				rc = GSASL_NO_AUTHZID;
				break;
			}
		} else {
			vlog.debug("CCache provided: using user's ticket to query the Active Directory server.");
			portable_setenv("KRB5CCNAME", constccache);
		}

		char* domain = get_domain(realm, SSecurityKrb::KrbDomainFile);

		char* ldap_base = NULL; // generate_ldap_base mallocs mem!
		rc = generate_ldap_base(&ldap_base, domain);
		if (rc != MP_LDAP_OK || !ldap_base) {
			vlog.error("Failed to generate ldap base");
			if (ldap_base) {
				free(ldap_base);
			}
			rc = GSASL_NO_AUTHZID;
			break;
		}

		vlog.debug("Generated ldap base: %s", ldap_base);

		rc = perform_ldap_searches(domain, NULL, ldap_base, login_name, NULL);

		vlog.verbose("perform_ldap_searches returned: rc (%d)", rc);
		if (rc == MP_LDAP_OK) {
			if (SSecurityKrb::KrbProvideCcache) {
				if (constccache && constccache[0]) {
					portable_setenv("KRBCCACHE", constccache);
				} else {
					vlog.error("KrbProvideCcache option is set to true, but constccache is null or empty, so we couldn't set the KRBCCACHE environment variable. Possibly delegation isn't allowed for tgpro's computer account. This is necessary in order to ask for authorization in the name of the user for a specific service (e.g. for an Active Directory proxy authentication).");
				}
			}
			free(ldap_base);
			return GSASL_OK; // If not, continue with ldap_server_2
		}

		portable_unsetenv("KRB5CCNAME");
		return GSASL_NO_AUTHZID;
        }

	case GSASL_AUTHID:
        {
		const char * constdisplay;

		constdisplay = gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
		if (constdisplay)
		{
			char * displayname = strdup(constdisplay);
			char * p = displayname;

			vlog.debug("display name is %s", constdisplay);
			while(*p)
			{
				if (*p == '@')
					*p = 0;
				else
					p++;
			}
			if (displayname[0])
			{
				vlog.debug("setting AUTHID to %s", displayname);
				gsasl_property_set(sctx, GSASL_AUTHID, displayname);
				rc = GSASL_OK;
			}
			free(displayname);
		}
        }
        break;
	case GSASL_AUTHZID:
        {
		const char * constdisplay;

		constdisplay = gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
		if (constdisplay)
		{
			char * displayname = strdup(constdisplay);
			char * p = displayname;

			vlog.debug("display name is %s", constdisplay);
			while(*p)
			{
				if (*p == '@')
					*p = 0;
				else
					p++;
			}
			if (displayname[0])
			{
				vlog.debug("setting AUTHZID to %s", displayname);
				gsasl_property_set(sctx, GSASL_AUTHZID, displayname);
				rc = GSASL_OK;
			}
			free(displayname);
		}
        }
        break;
	case GSASL_SERVICE:
		gsasl_property_set(sctx, GSASL_SERVICE, SSecurityKrb::KrbService.getData());
		vlog.debug("set SERVICE to %s", SSecurityKrb::KrbService.getData());
		rc = GSASL_OK;
		break;
	case GSASL_HOSTNAME:
        {
		char * krbhostname = SSecurityKrb::KrbHostname.getData();
		if (!krbhostname || !krbhostname[0])
		{
			krbhostname = (char *) malloc(256);
			if (!krbhostname)
				throw AuthFailureException("Could not allocate hostname string!");
			if (gethostname(krbhostname, 255))
				throw AuthFailureException("Could not get hostname!");
			krbhostname[255] = 0;
		}
		//		gsasl_property_set(sctx, GSASL_HOSTNAME, krbhostname);
		//		vlog.debug("set HOSTNAME to %s", krbhostname);
		gsasl_property_set(sctx, GSASL_HOSTNAME, "not-used-hostname.invalid");
		vlog.debug("set HOSTNAME to %s (this is on purpose - it is just an easily revertible test to see whether we actually need the krbhostname on the server side, which I believe we might not)", "not-used-hostname.invalid");
		rc = GSASL_OK;
		break;
        }

	default:
		break;
	}
	return rc;
}

static int checkKeytabFilePermissions() {
	const char* keytab = "/etc/krb5.keytab";
	struct stat fileStat;

	if (stat(keytab, &fileStat) == -1) {
		vlog.error("%s: failed to read stats for %s. AD or OpenLDAP login might not work...", __func__, keytab);
		return -1;
	}

	unsigned int wrongOwnership = 0;
	unsigned int wrongPermissions = 0;
	if (fileStat.st_uid != 510 || fileStat.st_gid != 0) {
		wrongOwnership = 1;
		vlog.error("%s: %s has wrong ownership, please check (it should belong to 510:0). AD or OpenLDAP login might not work...", __func__, keytab);
	}

	if (!(fileStat.st_mode & S_IRUSR) || (fileStat.st_mode & S_IWUSR) || (fileStat.st_mode & S_IXUSR) ||
		(fileStat.st_mode & S_IRGRP) || (fileStat.st_mode & S_IWGRP) || (fileStat.st_mode & S_IXGRP) ||
		(fileStat.st_mode & S_IROTH) || (fileStat.st_mode & S_IWOTH) || (fileStat.st_mode & S_IXOTH) ) {
		wrongPermissions = 1;
		vlog.error("%s: %s has wrong permissions, please check (it should only have read access (400). AD or OpenLDAP login might not work...", __func__, keytab);
	}

	if (wrongOwnership || wrongPermissions) {
		return 0;
	} else {
		return 1;
	}
}

SSecurityKrb::SSecurityKrb(SConnection* sc) : SSecurity(sc)
{
	vlog.debug("SSecurityKrb::SSecurityKrb() started");

	checkKeytabFilePermissions();

	/* init */
	if ((rc = gsasl_init(&gsasl_ctx)) != GSASL_OK)
		throw AuthFailureException("gsasl_init() failed!");
	else
		vlog.debug("gsasl_init() done");
	gsasl_callback_set (gsasl_ctx, callback);
	if ((rc = gsasl_server_start(gsasl_ctx, "GSSAPI", &gsasl_session)) != GSASL_OK)
	{
		vlog.error("gsasl_server_start() failed: %s", gsasl_strerror (rc));
		throw AuthFailureException("gsasl_server_start() failed!");
	}
	else
		vlog.debug("gsasl_server_start() done");

	state = 0;
	maxbuflen = 256;
	buflen = 0;
	buf = (char *)malloc(maxbuflen);
	if(!buf)
		throw AuthFailureException("failed to allocate buffer!");
	buf[0] = 0;
}

SSecurityKrb::~SSecurityKrb()
{
	vlog.debug("SSecurityKrb::~SSecurityKrb() started");
}

bool SSecurityKrb::processMsg()
{
	char *p;
	size_t plen;
	rdr::InStream* is = sc->getInStream();
	rdr::OutStream* os = sc->getOutStream();
	const char * constuser;
	char * tmp;
	char importname[256];
	int systemerr;

	vlog.debug("SSecurityKrb::processMsg() started");

	/* authenticate */
	do {
		if (state == 0) {
			if (!is->hasData(4))
				return false;
			buflen = is->readU32();
			if(buflen > maxbuflen)
			{
				free(buf);
				maxbuflen = buflen;
				buf = (char *)malloc(maxbuflen);
				if(!buf)
					throw AuthFailureException("failed to allocate buffer!");
			}
			vlog.debug("gsasl expecting from client: %zu bytes", buflen);
			state = 1;
		}
		if (state == 1) {
			if (buflen > 0)
			{
				if (!is->hasData(buflen))
					return false;
				is->readBytes(buf, buflen);
				vlog.debug("gsasl received from client: %zu bytes", buflen);
			}
			rc = gsasl_step(gsasl_session, buf, buflen, &p, &plen);
			if (rc == GSASL_NEEDS_MORE || (rc == GSASL_OK && plen > 0))
			{
				vlog.debug("gsasl sending to client: %zu bytes", plen);
				os->writeU32(plen);
				if (plen > 0)
				{
					os->writeBytes(p,plen);
					gsasl_free(p);
				}
				os->flush();
				vlog.debug("gsasl sent to client: %zu bytes", plen);
			}
			state = 0;
		}
	}
	while (rc == GSASL_NEEDS_MORE);

	if (rc != GSASL_OK)
	{
		openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
		syslog(LOG_ERR, "Kerberos authentication error (%d): %s",
			rc, gsasl_strerror (rc));
		vlog.error("Kerberos authentication error (%d): %s\n",
			   rc, gsasl_strerror (rc));
		throw AuthFailureException("Kerberos authentication failed!");
	}
	vlog.debug("gsasl Kerberos authentication successful!");

	constuser = gsasl_property_fast(gsasl_session, GSASL_AUTHZID);
	if (!constuser)
		throw AuthFailureException("No username provided by Kerberos!");
	username = strdup(constuser);
	vlog.debug("gsasl/Kerberos provided username: %s", username);

	if(buf)
		free(buf);

	/* m-privacy */
	if(!access("/etc/cu/usertolower", F_OK)) {
		tmp = username;

		while(*tmp) {
			*tmp = tolower(*tmp);
			tmp++;
		}
	}

	tmp = username;
	while(*tmp >= '0' && *tmp <= '9') {
		tmp++;
	}
	if (!*tmp) { /* numeric */
		int len = strlen(username);

		tmp = username;
		username = (char *) malloc(len + 2);
		strncpy(username, tmp, len + 1);
		username[len] = '#';
		username[len+1] = 0;
		free(tmp);
	}

	portable_setenv("USER", username);

	if(!access("/usr/local/bin/importsingleuser", X_OK)) {
		vlog.debug("%s: Running 'importsingleuser'...", __func__);
		snprintf(importname, 255, "/usr/local/bin/importsingleuser \"%s\"", username);
		importname[255] = 0;
		system(importname);
		vlog.debug("%s: Done running 'importsingleuser'...", __func__);
	}
	vlog.debug("%s: Running 'prepareuser'...", __func__);
	if ((systemerr = system("/usr/local/bin/prepareuser &>/dev/null"))) {
		char reason[256];
#if !defined(WIN32) && !defined(WIN64)
		systemerr = WEXITSTATUS(systemerr);
#endif
		snprintf(reason, 255, "Could not setup user environment:\n(%i) %s", systemerr, prepareuser_error(systemerr));
		reason[255] = 0;
		throw AuthFailureException(reason);
	}
	vlog.debug("%s: Finished running 'prepareuser'...", __func__);

	if (KrbProvideCcache) {
		portable_unsetenv("KRBCCACHE");
	}

	/* finish GSASL */
	vlog.debug("Finishing GSASL session...");
	gsasl_finish(gsasl_session);
	vlog.debug("gsasl_finish() done");
	gsasl_done(gsasl_ctx);
	vlog.debug("gsasl_done() done");

	pw = getpwnam(username);

	if (pw) {
		char * UserProgram = getenv("VNCUserProgram");
#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)
		char idleFilename[256];
#endif

		/* if we are not called from inetd, we should not setup environment
		 * or subsequent connections will probably fail */
		if (getenv("inetd") == NULL)
			return true;

		if (setuid(getuid())) {
#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)
			cap_t proccap;

			proccap = cap_get_proc();
			if (!proccap) {
				openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
				syslog(LOG_DEBUG, "processMsg(): failed cap_get_proc() as user %u, error: %s", getuid(), strerror(errno));
			} else {
				char * capstext;

				capstext = cap_to_text(proccap, NULL);
				if (capstext) {
					openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
					syslog(LOG_DEBUG, "SSecurityKrb::processMsg(): failed setuid(%u) as user %u, caps %s, error: %s", getuid(), getuid(), capstext, strerror(errno));
					cap_free(capstext);
				}
				cap_free(proccap);
			}
#endif
			throw AuthFailureException("could not setuid");
		}
		if (setgid(pw->pw_gid))
			throw AuthFailureException("Could not setgid");
		char * displaynum = getenv("DISPLAYNUM");
#if !defined(WIN32) && !defined(WIN64)
		if (displaynum) {
			char tmp[256];

			snprintf(tmp, 255, "/tmp/.X11-unix/X%s", displaynum);
			tmp[255] = 0;
			vlog.debug("chown and chmod %s to %u:%u and 0600", tmp, pw->pw_uid, pw->pw_gid);
			chown(tmp, pw->pw_uid, pw->pw_gid);
			chmod(tmp, 0600);
		}
#endif
		if (setuid(pw->pw_uid)) {
#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)
			cap_t proccap;

			proccap = cap_get_proc();
			if (!proccap) {
				openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
				syslog(LOG_DEBUG, "processMsg(): failed cap_get_proc() as user %u, error: %s", getuid(), strerror(errno));
			} else {
				char * capstext;

				capstext = cap_to_text(proccap, NULL);
				if (capstext) {
					openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
					syslog(LOG_DEBUG, "SSecurityKrb::processMsg(): failed setuid(%u) as user %u, caps %s, error: %s", pw->pw_uid, getuid(), capstext, strerror(errno));
					cap_free(capstext);
				}
				cap_free(proccap);
			}
#endif
			throw AuthFailureException("could not setuid");
		}
		if (chdir(pw->pw_dir))
			throw AuthFailureException("Could not chdir to home dir");

		portable_setenv("USER", username);
		portable_setenv("LOGNAME", username);
		portable_setenv("HOME", pw->pw_dir);
		portable_setenv("SHELL", pw->pw_shell);

		if (!getenv("DISPLAY")) {
			if (displaynum) {
				char displaystring[20];

				snprintf(displaystring, 19, "unix:%s.0", displaynum);
				displaystring[19] = 0;
				portable_setenv("DISPLAY", displaystring);
			}
		}

#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)
		snprintf(idleFilename, 255, "%s%s", NOIDLETIMOUTBASE, pw->pw_name);
		idleFilename[255] = 0;
		if (!access(idleFilename, F_OK)) {
			vlog.debug("Found %s, increasing IdleTimeout to maximum %u", idleFilename, MAXIDLETIMEOUT);
			rfb::Server::idleTimeout.setParam(MAXIDLETIMEOUT);
		}
#endif

		if (UserProgram != NULL) {
			pid_t childpid;

			childpid = fork();
			if (childpid < 0) {
				throw AuthFailureException("Sie können im Moment nicht angemeldet werden.");
			} else
				if (childpid == 0) {
					/* the child */
					char * execargs[2];
					int fd;

					for (fd = 0; fd < 20; fd++)
						close(fd);
					execargs[0] = UserProgram;
					execargs[1] = NULL;
					execv(UserProgram, execargs);
					vlog.error("Could not execute user program: %s", UserProgram);
					exit(1);
				}
		}
	} else
		throw AuthFailureException("User creation has failed");

	return true;
}
#endif /* #if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__) from the very beginning. We do not need this file for windows or apple at all */
