/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2008 OMC Denmark ApS.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "spawn.h"
#include "utils/macros.h"

/********************************************************************/
/*                        ProxySession                              */
/********************************************************************/

ProxySession::ProxySession(::BRUTUS::IMAPISession_ptr mapi_session,
			   const char * const windows_user,
			   const char * const windows_domain,
			   HANDLE process_handle)
	: mapi_session_(::BRUTUS::IMAPISession::_duplicate(mapi_session)),
	  windows_user_(strdup(windows_user)),
	  windows_domain_(windows_domain ? strdup(windows_domain) : NULL),
	  process_handle_(process_handle)
{ 
};

ProxySession::~ProxySession()
{
	free(windows_user_);
	if (windows_domain_)
		free(windows_domain_);
}

void
ProxySession::Terminate(void)
{
	TerminateProcess(process_handle_, 0);
}

/********************************************************************/
/*                         ProxyCache                               */
/********************************************************************/

::CORBA::ULong
ProxyCache::lookup(const char * const mapi_profile,
		   const char * const windows_user,
		   const char * const windows_domain, // NULL if UPN authentication is used
		   ::BRUTUS::BrutusCheck_ptr lifeline,
		   ::BRUTUS::IMAPISession_out mapi_session)
{
	::CORBA::ULong retv = 0;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);

		cache_iterator ci;

		mapi_session = ::BRUTUS::IMAPISession::_nil();

		if (!proxy_list_.size())
			goto out;

		try {
			ci = proxy_list_.find(mapi_profile);
			if (ci == proxy_list_.end())
				goto out;

			if (strcmp(windows_user, ci->second->windows_user_))
				goto out;

			if (1 == (!!windows_domain + !!ci->second->windows_domain_))
				goto out;

			if ((!windows_domain && !ci->second->windows_domain_) || !strcmp(windows_domain, ci->second->windows_domain_)) {
				mapi_session = ::BRUTUS::IMAPISession::_duplicate(ci->second->mapi_session_.in());
				retv = mapi_session->addLifeline(lifeline);
			}
			goto out;
		}
		catch (const CORBA::Exception &e) {
			char msg_[512] = { '\0' };
			sprintf_s(msg_, sizeof(msg_), "%s (%s)", e._info().c_str(), mapi_profile);
			ACE_DEBUG((LM_INFO, ACE_TEXT("<%T > %N:%l - %s\n"), msg_));
			mapi_session = ::BRUTUS::IMAPISession::_nil();
			retv = 0;
			goto remove_ref;
		}
		catch (...) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T > %N:%l - Unknown C++ exception (%s)\n"), mapi_profile));
			mapi_session = ::BRUTUS::IMAPISession::_nil();
			retv = 0;
			goto remove_ref;
		}
	}
remove_ref:
	ACE_DEBUG((LM_INFO, ACE_TEXT("<%T > %N:%l - about to remove %s\n"), mapi_profile));
	this->remove(mapi_profile, true);
out:
	return retv;
}

void
ProxyCache::insert(const char * const mapi_profile,
		   const char * const windows_user,
		   const char * const windows_domain, // NULL if UPN authentication is used
		   HANDLE process_handle,
		   ::BRUTUS::IMAPISession_ptr mapi_session)
{
	ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);

	ProxySession *proxy_session = NULL;
	try {
		proxy_session = new ProxySession(mapi_session,
						 windows_user,
						 windows_domain,
						 process_handle);
	}
	catch (...) {
		throw CORBA::NO_MEMORY();
	}

	std::pair<std::string, ProxySession*> proxy_entry(mapi_profile, proxy_session);
	std::pair<cache_iterator, bool> p;
	p = proxy_list_.insert(proxy_entry);
	if (!p.second)
		throw CORBA::NO_MEMORY();
};

void
ProxyCache::remove(const char * const mapi_profile,
		   bool terminate_proxy)
{
	ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);

	ACE_DEBUG((LM_INFO, ACE_TEXT("<%T > %N:%l - removing %s\n"), mapi_profile));

	cache_iterator ci;
	ci = proxy_list_.find(mapi_profile);
	if (ci == proxy_list_.end())
		return;

	if (terminate_proxy)
		ci->second->Terminate();
	delete ci->second;
	proxy_list_.erase(ci);

	ci = proxy_list_.find(mapi_profile);
	if (ci == proxy_list_.end()) {
		ACE_DEBUG((LM_INFO, ACE_TEXT("<%T > %N:%l - %s removed\n"), mapi_profile));
	} else {
		ACE_DEBUG((LM_INFO, ACE_TEXT("<%T > %N:%l - could not remove %s\n"), mapi_profile));
	}
};


/********************************************************************/
/*                         F2T_Process                              */
/********************************************************************/

HANDLE
F2T_Process::handle_retn(void)
{
	if (!spawned_)
		return NULL;

	HANDLE retv = process_info_.hProcess;
	process_info_.hProcess = NULL;

	return retv;
}

void
F2T_Process::kill(void)
{
	if (process_info_.hProcess)
		TerminateProcess(process_info_.hProcess, 0);
}

pid_t
F2T_Process::getpid(void)
{
	if (!spawned_)
		return -1;

	return process_info_.dwProcessId;
};

HANDLE
F2T_Process::gethandle(void)
{
	if (!spawned_)
		return NULL;

	return process_info_.hProcess;
};

bool
F2T_Process::is_running (void)
{
	if (!spawned_)
		return false;

	DWORD code;
	BOOL result = ::GetExitCodeProcess(process_info_.hProcess,
					   &code);
	return (result && (STILL_ACTIVE == code));
};

void
F2T_Process::prepare(char **CommandLine)
{
	cmd_line_ = *CommandLine;
	*CommandLine = NULL;

	return;
};

pid_t
F2T_Process::spawn(const char * const user,
		   const char * const domain,
		   const char * const password)
{
	try {
		pid_t pid = spawn_process(user,
					  domain,
					  password,
					  cmd_line_,
					  &process_info_);
		if (-1 == pid)
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("%N:%l - Spawn failed\n")));
		else
			spawned_ = true;
	}
	catch (...) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("%N:%l - Unknown C++ exception\n")));
		process_info_.dwProcessId = -1;
	}
	if (cmd_line_) {
		free(cmd_line_);
		cmd_line_ = NULL;
	}

	return this->getpid();
};

pid_t
F2T_Process::spawn_process(const char * const user,
			   const char * const domain,
			   const char * const password,
			   LPTSTR command,
			   PROCESS_INFORMATION *pi)
{
	HANDLE hToken = NULL;
	STARTUPINFO si;

	// obtain an access token for the user
	if (!LogonUser(user,
		       domain,
		       password,
		       LOGON32_LOGON_INTERACTIVE,
		       LOGON32_PROVIDER_DEFAULT,
		       &hToken)) {
		BRUTUS_LOG_GETLASTERROR("LogonUser() failed");
		return -1;
	}

	// initialize STARTUPINFO structure
	ZeroMemory(&si, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	si.lpDesktop = "winsta0\\default";
	si.dwFlags = STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_HIDE;

	// start the process
	if (!CreateProcessAsUser(hToken,
				 NULL,
				 command,
				 NULL,
				 NULL,
				 FALSE,
				 BELOW_NORMAL_PRIORITY_CLASS | CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW,
				 NULL,
				 NULL,
				 &si,
				 pi))
		goto error;

	// not needed
	CloseHandle(hToken);

	// close the thread handle
	if (pi->hThread)
		CloseHandle(pi->hThread);

	return pi->dwProcessId;

error:
	if (NULL != hToken)
		CloseHandle(hToken);

	return -1;
}

/********************************************************************/
/*                         ProxyProcess                             */
/********************************************************************/

ProxyProcess::ProxyProcess(const char * const LifeLineIOR,
			   const char * const MAPIProfileName,
			   const char * const WindowsUserName,
			   const char * const WindowsDomainName,
			   const char * const WindowsUserPassword,
			   const char * const MAPIProfilePassword,
			   const char * const MailboxName,
			   const char * const ServerName,
			   const char * const server_type,
			   const char * const logon_flags,
			   const char * const profile_flags)
	: F2T_Process()
{
	const char *domain = ((WindowsDomainName && strlen(WindowsDomainName)) ? WindowsDomainName : "0");
	const char *password = (strlen(WindowsUserPassword) ? WindowsUserPassword : "0"); // may be the empty string
	const char *mapi_profile_password = ((MAPIProfilePassword && strlen(MAPIProfilePassword)) ? MAPIProfilePassword : "0");

	size_t len = strlen("brutus_proxy.exe")
		+ strlen(LifeLineIOR)
		+ strlen(MAPIProfileName)
		+ strlen(WindowsUserName)
		+ strlen(domain)
		+ strlen(password)
		+ strlen(mapi_profile_password)
		+ strlen(MailboxName)
		+ strlen(ServerName)
		+ strlen(server_type)
		+ strlen(logon_flags)
		+ strlen(profile_flags)
		+ (11 * strlen(" "))
		+ (22 * strlen("\""))
		+ 1; // p x x x x x x x x x x x = 11 spaces + 22 apostrophes + 1 null character

	char *cmdline = (char*)malloc(len * sizeof(char));
	sprintf_s(cmdline, len * sizeof(char), "brutus_proxy.exe \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"",
		  LifeLineIOR,
		  MAPIProfileName,
		  WindowsUserName,
		  domain,
		  password,
		  mapi_profile_password,
		  MailboxName,
		  ServerName,
		  server_type,
		  logon_flags,
		  profile_flags);
	this->prepare(&cmdline);
};
