/* Copyright (C) 2005 Martin Koegler
 * Copyright (C) 2006 OCCAM Financial Technology
 * Copyright (C) 2014-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.
 */

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

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

#include <rfb/SSecurityCert.h>
#include <rfb/SConnection.h>
#include <rfb/Exception.h>
#include <rfb/LogWriter.h>

#include <rfb/SSecurityTLS.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#if !defined(WIN32) && !defined(WIN64)
#include <pwd.h>
#include <syslog.h>
#include <sys/wait.h>
#include <sys/stat.h>
#if !defined(__APPLE__)
#include <sys/capability.h>
#endif
#endif
#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)
#include <rfb/ServerCore.h>
#endif

#include <mp_utils.h>

using namespace rfb;

static LogWriter vlog("SSecurityCert");

char * SSecurityCert::username = NULL;

SSecurityCert::SSecurityCert(SConnection* sc) : SSecurity(sc)
{
	return;
}

bool SSecurityCert::processMsg()
{
#if !defined(WIN32) && !defined(WIN64)
	struct passwd * pw;
	char * tmp;
	char importname[256];
	int systemerr;
#endif

	if (!sc->CertUserName)
		throw AuthFailureException("no CN=username in client certificate for X509Cert authentication");

	username = strdup(sc->CertUserName);
	if (!username)
		throw AuthFailureException("could not duplicate username");

#if !defined(WIN32) && !defined(WIN64)
	/* 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);
	}

	setenv("USER", username, 1);

	if(!access("/usr/local/bin/importsingleuser", X_OK)) {
		snprintf(importname, 255, "/usr/local/bin/importsingleuser \"%s\"", username);
		importname[255] = 0;
		system(importname);
	}
	if ((systemerr = system("/usr/local/bin/prepareuser &>/dev/null"))) {
		char reason[256];
		systemerr = WEXITSTATUS(systemerr);
		snprintf(reason, 255, "Could not setup user environment:\n(%i) %s", systemerr, prepareuser_error(systemerr));
		reason[255] = 0;
		throw AuthFailureException(reason);
	}

	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, "SSecurityCert::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, "SSecurityCert::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");

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

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

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

#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
#endif
		throw AuthFailureException("user creation has failed");

	return true;
}
