/* -*- 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 "BrutusLogOnS_impl.h"
#include "idl/brutus_defines.h"
#include "utils/fileops.h"
#include "utils/globals.h"
#include "utils/config.h"
#include "utils/macros.h"
#include "spawn.h"

#ifdef BRUTUS_HAS_ESELLERATE
#include "esellerate/esellerate_keys.h"
#include <Validate Static Library/validate.h>
#endif

#include <tao/PortableServer/ServantActivatorC.h>
#include <ace/string_base.h>
#include <ace/Task.h>

#include <Windows.h>
#include "idl/edkmdb.h"

#define MAPIGUID_H // do not include mapiguid.h
#include <mapiutil.h>

// thread entry class
class Proxy_Thread_Base : public ACE_Task_Base
{
public:
	Proxy_Thread_Base(void)
		: proxy_handle_(NULL)
		{
		};

	void set_proxy_data(ProxyCache *proxy_cache,
			    HANDLE process_handle,
			    const char * const mapi_profile)
		{
			proxy_cache_ = proxy_cache;
			proxy_handle_ = process_handle;
			mapi_profile_ = strdup(mapi_profile);
		};

	virtual int svc(void)
		{
			if (!proxy_handle_)
				return 0;

			ProxyCache *proxy_cache = proxy_cache_;
			HANDLE proxy_handle = proxy_handle_;
			char *mapi_profile = mapi_profile_;

			ACE_DEBUG((LM_INFO, ACE_TEXT("<%T > %N:%l - Launched proxy for = %s\n"), mapi_profile_));

			WaitForSingleObject(proxy_handle, INFINITE);
			proxy_cache->remove(mapi_profile);
			CloseHandle(proxy_handle);

			ACE_DEBUG((LM_INFO, ACE_TEXT("<%T > %N:%l - Observed proxy death of  %s\n"), mapi_profile_));
			free(mapi_profile);

			return 0;
		};

private:
	ProxyCache *proxy_cache_;
	HANDLE proxy_handle_;
	char *mapi_profile_;
};

static FLAGS
native_create_profile_flags(const ::BRUTUS::BDEFINE Flags)
{
	::BRUTUS::BDEFINE flags = Flags;
	FLAGS retval = 0;

	if (flags & ::BRUTUS::BRUTUS_MAPI_DEFAULT_SERVICES) {
		retval |= MAPI_DEFAULT_SERVICES;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_MAPI_DEFAULT_SERVICES);
	}

	// Must(?) fix handling of Unicode characters
	if (flags & ::BRUTUS::BRUTUS_MAPI_UNICODE) {
		retval |= MAPI_UNICODE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_MAPI_UNICODE);
	}

	if (flags) {
		char msg[128] = {0};
		sprintf_s(msg, sizeof(msg), "Unknown or unsupported flag(s) from BRUTUS : %X", flags);
		BRUTUS_LOG_BUG(msg);
	}

	return retval;
}

static HANDLE
authenticate_user(const char *WindowsUserName,
		  const char *WindowsDomainName,
		  const char *WindowsUserPassword)
{
	HANDLE credentials = NULL;

	if (LogonUser((char*)WindowsUserName,
		      (char*)WindowsDomainName,
		      (char*)WindowsUserPassword,
		      LOGON32_LOGON_NETWORK,
		      LOGON32_PROVIDER_DEFAULT,
		      &credentials)) {
		return credentials;
	}
	if (credentials)
		CloseHandle(credentials);

	DWORD err = GetLastError();
	char msg[512] = {0};
	LPVOID msg_buf;

	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		      NULL,
		      err,
		      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		      (LPTSTR) &msg_buf,
		      0,
		      NULL );
	((char*)msg_buf)[strlen((char*)msg_buf) - 2] = '\0';

	_snprintf_s(msg, sizeof(msg), sizeof(msg), "LogonUser(%s\\%s) failed - GetLastError() returned: 0x%X (%s)", WindowsDomainName, WindowsUserName, err, (const char*)msg_buf);
	LocalFree(msg_buf);
	BRUTUS_LOG_PRIVATE(msg);

	return NULL;
}

static bool
validate_license(bool & keep_checking)
{
	keep_checking = false;

#ifdef BRUTUS_HAS_ESELLERATE
        BRUTUS::Config config;
        int config_load = config.load(BRUTUS_CONF_FILE);
        if (config_load) {
                if (config_load > 0)
                        ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T >%N:%l - Configuration error: Error in configuration file at line %d"), config_load));
                else
                        ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T >%N:%l - Configuration error: Could not open %s"), BRUTUS_CONF_FILE));
                return false;
        }

	eSellerate_DaysSince2000 esel_today = eSellerate_Today();
	eSellerate_DaysSince2000 esel_trial_days = 0;
	eSellerate_DaysSince2000 esel_server_days = 0;
		
	BRUTUS_LOG_INF("Validating server license");


	eSellerate_String serial_name = NULL;
	serial_name = config.get_value("SERIAL_NAME");
	if (!serial_name) {
		BRUTUS_LOG_CRITICAL("No serial name");
		return false;
	}
		
	eSellerate_String serial_number = NULL;
	serial_number = config.get_value("SERIAL_NUMBER");
	if (!serial_name) {
		free(serial_name);
		BRUTUS_LOG_CRITICAL("No serial number");
		return false;
	}
		
	esel_trial_days = eSellerate_ValidateSerialNumber(serial_number, serial_name, NULL, ESELLERATE_TRIAL_KEY);
	esel_server_days = eSellerate_ValidateSerialNumber(serial_number, serial_name, NULL, ESELLERATE_SERVER_KEY);
	free(serial_name);
	free(serial_number);

	if (!esel_trial_days && !esel_server_days) {
		BRUTUS_LOG_CRITICAL("Invalid serial name or number");
		return false;
	}

	if (esel_server_days > 0)
		goto esellerate_ok;

	if (esel_trial_days < esel_today) {
		BRUTUS_LOG_CRITICAL("Your trial license has expired");
		return false;
	} else {
		keep_checking = true;
		if (esel_trial_days == esel_today) {
			BRUTUS_LOG_INF("Trial license validated OK: This day remaining");
		} else {
			char msg[128] = {0};
			sprintf_s(msg, sizeof(msg), "Trial license validated OK: %d days remaining", esel_trial_days - esel_today);
			BRUTUS_LOG_INF(msg);
		}
	}
esellerate_ok:
	BRUTUS_LOG_INF("Server license validated OK");
	return true;
#else
	BRUTUS_LOG_INF("Server running as free software");
	return true;
#endif // BRUTUS_HAS_ESELLERATE
}

static bool
hresult_to_bresult(const HRESULT HValue,
		   ::BRUTUS::BRESULT& BValue)
{
#pragma warning( disable : 6219 )
#pragma warning( disable : 6221 )

	switch (HValue) {
	case S_OK :
		BValue = ::BRUTUS::BRUTUS_S_OK;
		break;
	case MAPI_E_CALL_FAILED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_CALL_FAILED;
		break;
	case MAPI_E_NOT_ENOUGH_MEMORY :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NOT_ENOUGH_MEMORY;
		break;
	case MAPI_E_INVALID_PARAMETER :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;
		break;
	case MAPI_E_INTERFACE_NOT_SUPPORTED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_INTERFACE_NOT_SUPPORTED;
		break;
	case MAPI_E_NO_ACCESS :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NO_ACCESS;
		break;
	case MAPI_E_NO_SUPPORT :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NO_SUPPORT;
		break;
	case MAPI_E_BAD_CHARWIDTH :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_BAD_CHARWIDTH;
		break;
	case MAPI_E_STRING_TOO_LONG :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_STRING_TOO_LONG;
		break;
	case MAPI_E_UNKNOWN_FLAGS :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_UNKNOWN_FLAGS;
		break;
	case MAPI_E_INVALID_ENTRYID :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_INVALID_ENTRYID;
		break;
	case MAPI_E_INVALID_OBJECT :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_INVALID_OBJECT;
		break;
	case MAPI_E_OBJECT_CHANGED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_OBJECT_CHANGED;
		break;
	case MAPI_E_OBJECT_DELETED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_OBJECT_DELETED;
		break;
	case MAPI_E_BUSY :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_BUSY;
		break;
	case MAPI_E_NOT_ENOUGH_DISK :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NOT_ENOUGH_DISK;
		break;
	case MAPI_E_NOT_ENOUGH_RESOURCES :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NOT_ENOUGH_RESOURCES;
		break;
	case MAPI_E_NOT_FOUND :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NOT_FOUND;
		break;
	case MAPI_E_VERSION :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_VERSION;
		break;
	case MAPI_E_LOGON_FAILED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_LOGON_FAILED;
		break;
	case MAPI_E_SESSION_LIMIT :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_SESSION_LIMIT;
		break;
	case MAPI_E_USER_CANCEL :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_USER_CANCEL;
		break;
	case MAPI_E_UNABLE_TO_ABORT :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_UNABLE_TO_ABORT;
		break;
	case MAPI_E_NETWORK_ERROR :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NETWORK_ERROR;
		break;
	case MAPI_E_DISK_ERROR :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_DISK_ERROR;
		break;
	case MAPI_E_TOO_COMPLEX :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_TOO_COMPLEX;
		break;
	case MAPI_E_BAD_COLUMN :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_BAD_COLUMN;
		break;
	case MAPI_E_EXTENDED_ERROR :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_EXTENDED_ERROR;
		break;
	case MAPI_E_COMPUTED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_COMPUTED;
		break;
	case MAPI_E_CORRUPT_DATA :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_CORRUPT_DATA;
		break;
	case MAPI_E_UNCONFIGURED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_UNCONFIGURED;
		break;
	case MAPI_E_FAILONEPROVIDER :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_FAILONEPROVIDER;
		break;
	case MAPI_E_UNKNOWN_CPID :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_UNKNOWN_CPID;
		break;
	case MAPI_E_UNKNOWN_LCID :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_UNKNOWN_LCID;
		break;
	case MAPI_E_PASSWORD_CHANGE_REQUIRED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_PASSWORD_CHANGE_REQUIRED;
		break;
	case MAPI_E_PASSWORD_EXPIRED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_PASSWORD_EXPIRED;
		break;
	case MAPI_E_INVALID_WORKSTATION_ACCOUNT :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_INVALID_WORKSTATION_ACCOUNT;
		break;
	case MAPI_E_INVALID_ACCESS_TIME :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_INVALID_ACCESS_TIME;
		break;
	case MAPI_E_ACCOUNT_DISABLED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_ACCOUNT_DISABLED;
		break;
	case MAPI_E_END_OF_SESSION :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_END_OF_SESSION;
		break;
	case MAPI_E_UNKNOWN_ENTRYID :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_UNKNOWN_ENTRYID;
		break;
	case MAPI_E_MISSING_REQUIRED_COLUMN :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_MISSING_REQUIRED_COLUMN;
		break;
	case MAPI_W_NO_SERVICE :
		BValue = ::BRUTUS::BRUTUS_MAPI_W_NO_SERVICE;
		break;
	case MAPI_E_BAD_VALUE :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_BAD_VALUE;
		break;
	case MAPI_E_INVALID_TYPE :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_INVALID_TYPE;
		break;
	case MAPI_E_TYPE_NO_SUPPORT :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_TYPE_NO_SUPPORT;
		break;
	case MAPI_E_UNEXPECTED_TYPE :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_UNEXPECTED_TYPE;
		break;
	case MAPI_E_TOO_BIG :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_TOO_BIG;
		break;
	case MAPI_E_DECLINE_COPY :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_DECLINE_COPY;
		break;
	case MAPI_E_UNEXPECTED_ID :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_UNEXPECTED_ID;
		break;
	case MAPI_W_ERRORS_RETURNED :
		BValue = ::BRUTUS::BRUTUS_MAPI_W_ERRORS_RETURNED;
		break;
	case MAPI_E_UNABLE_TO_COMPLETE :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_UNABLE_TO_COMPLETE;
		break;
	case MAPI_E_TIMEOUT :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_TIMEOUT;
		break;
	case MAPI_E_TABLE_EMPTY :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_TABLE_EMPTY;
		break;
	case MAPI_E_TABLE_TOO_BIG :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_TABLE_TOO_BIG;
		break;
	case MAPI_E_INVALID_BOOKMARK :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_INVALID_BOOKMARK;
		break;
	case MAPI_W_POSITION_CHANGED :
		BValue = ::BRUTUS::BRUTUS_MAPI_W_POSITION_CHANGED;
		break;
	case MAPI_W_APPROX_COUNT :
		BValue = ::BRUTUS::BRUTUS_MAPI_W_APPROX_COUNT;
		break;
	case MAPI_E_WAIT :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_WAIT;
		break;
	case MAPI_E_CANCEL :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_CANCEL;
		break;
	case MAPI_E_NOT_ME :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NOT_ME;
		break;
	case MAPI_W_CANCEL_MESSAGE :
		BValue = ::BRUTUS::BRUTUS_MAPI_W_CANCEL_MESSAGE;
		break;
	case MAPI_E_CORRUPT_STORE :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_CORRUPT_STORE;
		break;
	case MAPI_E_NOT_IN_QUEUE :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NOT_IN_QUEUE;
		break;
	case MAPI_E_NO_SUPPRESS :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NO_SUPPRESS;
		break;
	case MAPI_E_COLLISION :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_COLLISION;
		break;
	case MAPI_E_NOT_INITIALIZED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NOT_INITIALIZED;
		break;
	case MAPI_E_NON_STANDARD :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NON_STANDARD;
		break;
	case MAPI_E_NO_RECIPIENTS :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_NO_RECIPIENTS;
		break;
	case MAPI_E_SUBMITTED :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_SUBMITTED;
		break;
	case MAPI_E_HAS_FOLDERS :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_HAS_FOLDERS;
		break;
	case MAPI_E_HAS_MESSAGES :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_HAS_MESSAGES;
		break;
	case MAPI_E_FOLDER_CYCLE :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_FOLDER_CYCLE;
		break;
	case MAPI_W_PARTIAL_COMPLETION :
		BValue = ::BRUTUS::BRUTUS_MAPI_W_PARTIAL_COMPLETION;
		break;
	case MAPI_E_AMBIGUOUS_RECIP :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_AMBIGUOUS_RECIP;
		break;
	case OLEOBJ_S_CANNOT_DOVERB_NOW :
		BValue = ::BRUTUS::BRUTUS_OLEOBJ_S_CANNOT_DOVERB_NOW;
		break;
	case STG_E_INVALIDFUNCTION :
		BValue = ::BRUTUS::BRUTUS_STG_E_INVALIDFUNCTION;
		break;
	case STG_E_FILENOTFOUND :
		BValue = ::BRUTUS::BRUTUS_STG_E_FILENOTFOUND;
		break;
	case STG_E_PATHNOTFOUND :
		BValue = ::BRUTUS::BRUTUS_STG_E_PATHNOTFOUND;
		break;
	case STG_E_TOOMANYOPENFILES :
		BValue = ::BRUTUS::BRUTUS_STG_E_TOOMANYOPENFILES;
		break;
	case STG_E_ACCESSDENIED :
		BValue = ::BRUTUS::BRUTUS_STG_E_ACCESSDENIED;
		break;
	case STG_E_INVALIDHANDLE :
		BValue = ::BRUTUS::BRUTUS_STG_E_INVALIDHANDLE;
		break;
	case STG_E_INSUFFICIENTMEMORY :
		BValue = ::BRUTUS::BRUTUS_STG_E_INSUFFICIENTMEMORY;
		break;
	case STG_E_INVALIDPOINTER :
		BValue = ::BRUTUS::BRUTUS_STG_E_INVALIDPOINTER;
		break;
	case STG_E_NOMOREFILES :
		BValue = ::BRUTUS::BRUTUS_STG_E_NOMOREFILES;
		break;
	case STG_E_DISKISWRITEPROTECTED :
		BValue = ::BRUTUS::BRUTUS_STG_E_DISKISWRITEPROTECTED;
		break;
	case STG_E_SEEKERROR :
		BValue = ::BRUTUS::BRUTUS_STG_E_SEEKERROR;
		break;
	case STG_E_WRITEFAULT :
		BValue = ::BRUTUS::BRUTUS_STG_E_WRITEFAULT;
		break;
	case STG_E_READFAULT :
		BValue = ::BRUTUS::BRUTUS_STG_E_READFAULT;
		break;
	case STG_E_SHAREVIOLATION :
		BValue = ::BRUTUS::BRUTUS_STG_E_SHAREVIOLATION;
		break;
	case STG_E_LOCKVIOLATION :
		BValue = ::BRUTUS::BRUTUS_STG_E_LOCKVIOLATION;
		break;
	case STG_E_FILEALREADYEXISTS :
		BValue = ::BRUTUS::BRUTUS_STG_E_FILEALREADYEXISTS;
		break;
	case STG_E_INVALIDPARAMETER :
		BValue = ::BRUTUS::BRUTUS_STG_E_INVALIDPARAMETER;
		break;
	case STG_E_MEDIUMFULL :
		BValue = ::BRUTUS::BRUTUS_STG_E_MEDIUMFULL;
		break;
	case STG_E_PROPSETMISMATCHED :
		BValue = ::BRUTUS::BRUTUS_STG_E_PROPSETMISMATCHED;
		break;
	case STG_E_ABNORMALAPIEXIT :
		BValue = ::BRUTUS::BRUTUS_STG_E_ABNORMALAPIEXIT;
		break;
	case STG_E_INVALIDHEADER :
		BValue = ::BRUTUS::BRUTUS_STG_E_INVALIDHEADER;
		break;
	case STG_E_INVALIDNAME :
		BValue = ::BRUTUS::BRUTUS_STG_E_INVALIDNAME;
		break;
	case STG_E_UNKNOWN :
		BValue = ::BRUTUS::BRUTUS_STG_E_UNKNOWN;
		break;
	case STG_E_UNIMPLEMENTEDFUNCTION :
		BValue = ::BRUTUS::BRUTUS_STG_E_UNIMPLEMENTEDFUNCTION;
		break;
	case STG_E_INVALIDFLAG :
		BValue = ::BRUTUS::BRUTUS_STG_E_INVALIDFLAG;
		break;
	case STG_E_INUSE :
		BValue = ::BRUTUS::BRUTUS_STG_E_INUSE;
		break;
	case STG_E_NOTCURRENT :
		BValue = ::BRUTUS::BRUTUS_STG_E_NOTCURRENT;
		break;
	case STG_E_REVERTED :
		BValue = ::BRUTUS::BRUTUS_STG_E_REVERTED;
		break;
	case STG_E_CANTSAVE :
		BValue = ::BRUTUS::BRUTUS_STG_E_CANTSAVE;
		break;
	case STG_E_OLDFORMAT :
		BValue = ::BRUTUS::BRUTUS_STG_E_OLDFORMAT;
		break;
	case STG_E_OLDDLL :
		BValue = ::BRUTUS::BRUTUS_STG_E_OLDDLL;
		break;
	case STG_E_SHAREREQUIRED :
		BValue = ::BRUTUS::BRUTUS_STG_E_SHAREREQUIRED;
		break;
	case STG_E_NOTFILEBASEDSTORAGE :
		BValue = ::BRUTUS::BRUTUS_STG_E_NOTFILEBASEDSTORAGE;
		break;
	case STG_E_EXTANTMARSHALLINGS :
		BValue = ::BRUTUS::BRUTUS_STG_E_EXTANTMARSHALLINGS;
		break;
	case STG_E_DOCFILECORRUPT :
		BValue = ::BRUTUS::BRUTUS_STG_E_DOCFILECORRUPT;
		break;
	case STG_E_BADBASEADDRESS :
		BValue = ::BRUTUS::BRUTUS_STG_E_BADBASEADDRESS;
		break;
	case STG_E_INCOMPLETE :
		BValue = ::BRUTUS::BRUTUS_STG_E_INCOMPLETE;
		break;
	case STG_E_TERMINATED :
		BValue = ::BRUTUS::BRUTUS_STG_E_TERMINATED;
		break;
	case STG_S_CONVERTED :
		BValue = ::BRUTUS::BRUTUS_STG_S_CONVERTED;
		break;
	case STG_S_BLOCK :
		BValue = ::BRUTUS::BRUTUS_STG_S_BLOCK;
		break;
	case STG_S_RETRYNOW :
		BValue = ::BRUTUS::BRUTUS_STG_S_RETRYNOW;
		break;
	case STG_S_MONITORING :
		BValue = ::BRUTUS::BRUTUS_STG_S_MONITORING;
		break;
	case STG_S_MULTIPLEOPENS :
		BValue = ::BRUTUS::BRUTUS_STG_S_MULTIPLEOPENS;
		break;
	case STG_S_CONSOLIDATIONFAILED :
		BValue = ::BRUTUS::BRUTUS_STG_S_CONSOLIDATIONFAILED;
		break;
	case STG_S_CANNOTCONSOLIDATE :
		BValue = ::BRUTUS::BRUTUS_STG_S_CANNOTCONSOLIDATE;
		break;
	case E_PENDING :
		BValue = ::BRUTUS::BRUTUS_E_PENDING;
		break;
	case SYNC_E_OBJECT_DELETED :
		BValue = ::BRUTUS::BRUTUS_SYNC_E_OBJECT_DELETED;
		break;
	case SYNC_E_IGNORE :
		BValue = ::BRUTUS::BRUTUS_SYNC_E_IGNORE;
		break;
	case SYNC_E_CONFLICT :
		BValue = ::BRUTUS::BRUTUS_SYNC_E_CONFLICT;
		break;
	case SYNC_E_NO_PARENT :
		BValue = ::BRUTUS::BRUTUS_SYNC_E_NO_PARENT;
		break;
	case SYNC_E_INCEST :
		BValue = ::BRUTUS::BRUTUS_SYNC_E_INCEST;
		break;
	case SYNC_E_UNSYNCHRONIZED :
		BValue = ::BRUTUS::BRUTUS_SYNC_E_UNSYNCHRONIZED;
		break;
	case SYNC_W_PROGRESS :
		BValue = ::BRUTUS::BRUTUS_SYNC_W_PROGRESS;
		break;
	case SYNC_W_CLIENT_CHANGE_NEWER :
		BValue = ::BRUTUS::BRUTUS_SYNC_W_CLIENT_CHANGE_NEWER;
		break;
	case EC_EDK_E_FAIL :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_FAIL;
		break;
	case EC_EDK_E_OUTOFMEMORY :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_OUTOFMEMORY;
		break;
	case EC_EDK_E_INVALIDARG :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_INVALIDARG;
		break;
	case EC_EDK_E_NOTIMPL :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_NOTIMPL;
		break;
	case EC_EDK_E_NOINTERFACE :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_NOINTERFACE;
		break;
	case EC_EDK_E_ACCESSDENIED :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_ACCESSDENIED;
		break;
	case EC_EDK_E_UNKNOWN :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_UNKNOWN;
		break;
	case EC_EDK_E_NOT_FOUND :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_NOT_FOUND;
		break;
	case EDK_E_NOT_FOUND :
		BValue = ::BRUTUS::BRUTUS_EDK_E_NOT_FOUND;
		break;
	case EC_EDK_E_SHUTDOWN_SERVICE :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_SHUTDOWN_SERVICE;
		break;
	case EDK_E_SHUTDOWN_SERVICE :
		BValue = ::BRUTUS::BRUTUS_EDK_E_SHUTDOWN_SERVICE;
		break;
	case EC_EDK_E_ALREADY_EXISTS :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_ALREADY_EXISTS;
		break;
	case EDK_E_ALREADY_EXISTS :
		BValue = ::BRUTUS::BRUTUS_EDK_E_ALREADY_EXISTS;
		break;
	case EC_EDK_E_END_OF_FILE :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_END_OF_FILE;
		break;
	case EDK_E_END_OF_FILE :
		BValue = ::BRUTUS::BRUTUS_EDK_E_END_OF_FILE;
		break;
	case EC_EDK_E_AMBIGUOUS :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_AMBIGUOUS;
		break;
	case EDK_E_AMBIGUOUS :
		BValue = ::BRUTUS::BRUTUS_EDK_E_AMBIGUOUS;
		break;
	case EC_EDK_E_PARSE :
		BValue = ::BRUTUS::BRUTUS_EC_EDK_E_PARSE;
		break;
	case EDK_E_PARSE :
		BValue = ::BRUTUS::BRUTUS_EDK_E_PARSE;
		break;
	case 0x81002746 :
		BValue = ::BRUTUS::BRUTUS_MAPI_E_UNKNOWN_MAILBOX;
		break;
	default:
	{
		char msg[128] = {0};
		sprintf_s(msg, sizeof(msg), "Unknown HRESULT from MAPI : %X", HValue);
		BRUTUS_LOG_BUG(msg);
	}
	BValue = ::BRUTUS::BRUTUS_UNKNOWN_ERROR;
	return false;
	}

	return true;
}

BRUTUS_BrutusLogOn_i::BRUTUS_BrutusLogOn_i(const char * const basedir,
					   ::PortableServer::POA_ptr Poa,
					   ::CORBA::ORB_ptr ORB)
	: basedir_(strdup(basedir)),
	  poa_(::PortableServer::POA::_duplicate(Poa)),
	  internal_orb_(::CORBA::ORB::_duplicate(ORB))
{
}

void
BRUTUS_BrutusLogOn_i::GetVersion(::BRUTUS::BrutusLogOn::BrutusVersion_out version)
{
	BRUTUS_LOG_INF("Entering BRUTUS_BrutusLogOn_i::GetVersion()");

	::BRUTUS::BrutusLogOn::BrutusVersion_var ver;

	try {
		ver = new ::BRUTUS::BrutusLogOn::BrutusVersion;
	}
	catch (std::bad_alloc &) {
		BRUTUS_LOG_ERR("No memory");
		throw ::CORBA::NO_MEMORY();
	}
	ver->major = (::CORBA::UShort)BRUTUS_VERSION_MAJOR;
	ver->minor = (::CORBA::UShort)BRUTUS_VERSION_MINOR;
	ver->micro = (::CORBA::UShort)BRUTUS_VERSION_MICRO;
	ver->release_tag = (::CORBA::UShort)BRUTUS_VERSION_RELEASE_TAG;

	version = ver._retn();

	BRUTUS_LOG_INF("Leaving BRUTUS_BrutusLogOn_i::GetVersion()");
}

::BRUTUS::BRESULT
BRUTUS_BrutusLogOn_i::Logon(::BRUTUS::BrutusCheck_ptr LifeLine,
			    const char *MAPIProfileName,
			    const char *MAPIProfilePassword,
			    const char *WindowsUserName,
			    const char *WindowsDomainName,
			    const char *WindowsUserPassword,
			    const char *MailboxName,
			    const char *ServerName,
			    ::BRUTUS::BDEFINE LogonFlags,
			    ::BRUTUS::BDEFINE ProfileFlags,
			    ::BRUTUS::SERVER_TYPE ServerType,
			    ::CORBA::ULong_out InstanceID,
			    ::BRUTUS::IMAPISession_out Session)
{
	static bool license_valid = false;
	static bool keep_checking = true;
	::BRUTUS::BRESULT br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	char *life_line_ior = NULL;
	Session = ::BRUTUS::IMAPISession::_nil();

	// license check
	if (keep_checking) 
		license_valid = validate_license(keep_checking);
	if (!license_valid) {
		BRUTUS_LOG_BUG("Invalid license");
		return ::BRUTUS::BRUTUS_MAPI_E_NO_ACCESS;
	}

	{
		char msg[512] = { '\0' };
		if (MAPIProfileName)
			sprintf_s(msg, sizeof(msg), "Attempting logon for MAPI profile \"%s\"", MAPIProfileName);
		else
			sprintf_s(msg, sizeof(msg), "No MAPI Profile name ?");

		BRUTUS_LOG_INF(msg);
	}

	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(create_proxy_mutex_);

		// we are using the profile name as a file name
		if (!file_name_valid(TMP_DIR, MAPIProfileName, ",ior")) {
			BRUTUS_LOG_BUG("Invalid MAPI Profile name");
			br = ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;
			goto exit;
		}
	}

	if (::CORBA::is_nil(LifeLine)) {
		BRUTUS_LOG_BUG("LifeLine parameter was NIL");
		br = ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;
		goto exit;
	}

	try {
		life_line_ior = internal_orb_->object_to_string(LifeLine);
		if (NULL == life_line_ior) {
			BRUTUS_LOG_BUG("IOR is NULL");
			br = ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;
			goto exit;
		}
		BRUTUS_LOG_INF("Validating LifeLine object reference");
		BRUTUS_LOG_INF(life_line_ior);
		LifeLine->ping();
	}
	catch (const ::CORBA::TIMEOUT &) {
		BRUTUS_LOG_BUG("Timeout trying to validate lifeline");
		br = ::BRUTUS::BRUTUS_LIFELINE_TIMEOUT;
		goto exit;
	}
	catch (const ::CORBA::Exception &e) {
		BRUTUS_LOG_BUG(e._info().c_str());
		br = ::BRUTUS::BRUTUS_EXCEPTION_FROM_CLIENT;
		goto exit;
	}
	catch (...) {
		BRUTUS_LOG_BUG("Unknown exception caught");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
		goto exit;
	}

	br = create_session(life_line_ior,
			    MAPIProfileName,
			    (const char*)(strlen(MAPIProfilePassword) ? MAPIProfilePassword : NULL),
			    WindowsUserName,
			    (const char*)(strlen(WindowsDomainName) ? WindowsDomainName : NULL), // take care of UPN authentication
			    WindowsUserPassword,
			    MailboxName,
			    ServerName,
			    LogonFlags,
			    ProfileFlags,
			    ServerType,
			    InstanceID,
			    Session);
exit:
	CORBA::string_free(life_line_ior);
	return br;
}

::BRUTUS::BRESULT
BRUTUS_BrutusLogOn_i::create_session(const char *life_line_ior,
				     const char *MAPIProfileName,
				     const char *MAPIProfilePassword,
				     const char *WindowsUserName,
				     const char *WindowsDomainName, // NULL if UPN authentication is used
				     const char *WindowsUserPassword,
				     const char *MailboxName,
				     const char *ServerName,
				     ::BRUTUS::BDEFINE LogonFlags,
				     ::BRUTUS::BDEFINE ProfileFlags,
				     ::BRUTUS::SERVER_TYPE ServerType,
				     ::CORBA::ULong_out InstanceID,
				     ::BRUTUS::IMAPISession_out Session)
{
	BRUTUS_LOG_INF("Entering BRUTUS_BrutusLogOn_i::Logon()");

	static Proxy_Thread_Base ace_thread;
	static ProxyCache proxy_cache;

	bool retry = true;
	char *file_ior = NULL;
	char *ior_file_name = NULL;
	::BRUTUS::IMAPISession_var session = ::BRUTUS::IMAPISession::_nil();
	::BRUTUS::BRESULT br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;

	InstanceID = 0;
	Session = ::BRUTUS::IMAPISession::_nil();

	HANDLE user_token = authenticate_user(WindowsUserName, WindowsDomainName, WindowsUserPassword);
	if (!user_token) {
		return ::BRUTUS::BRUTUS_MAPI_E_LOGON_FAILED;
	} else {
		ACE_Write_Guard<ACE_RW_Mutex> guard(sec_mutex_);

		bool dacls_adjusted = false;
		PSID psid = NULL;

		ObtainUserSid(user_token, &psid);
		if (!psid) {
			CloseHandle(user_token);
			return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
		}

		PSID domain_sid = NULL;
		DWORD sid_size = SECURITY_MAX_SID_SIZE;
		if (!create_domain_sid(psid, domain_sid)) {
			BRUTUS_LOG_GETLASTERROR("");

			HeapFree(GetProcessHeap(), 0, (LPVOID)psid);
			if (domain_sid)
				FreeSid(domain_sid);
			CloseHandle(user_token);
			return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
		}
		if (!domain_sid) {
			CloseHandle(user_token);
			return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
		}
		dacls_adjusted = adjust_dacls(domain_sid);

		HeapFree(GetProcessHeap(), 0, (LPVOID)psid);
		FreeSid(domain_sid);
		CloseHandle(user_token);

		if (!dacls_adjusted) {
			BRUTUS_LOG_CRITICAL("Could not add ACEs to windows station and desktop");
			return ::BRUTUS::BRUTUS_MAPI_E_LOGON_FAILED;
		} else {
			BRUTUS_LOG_INF("ACEs added to windows station and desktop");
		}

		char msg[512] = { '\0' };
		if (WindowsDomainName)
			sprintf_s(msg, sizeof(msg), "%s\\%s authenticated sucessfully - proceeding with logon", WindowsDomainName, WindowsUserName);
		else
			sprintf_s(msg, sizeof(msg), "%s authenticated sucessfully - proceeding with logon", WindowsUserName);

		BRUTUS_LOG_PRIVATE(msg);
	}

	if (!MAPIProfileName || !strlen(MAPIProfileName))
		return ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;
	else if (strlen(MAPIProfileName) > 64)
		return ::BRUTUS::BRUTUS_MAPI_E_STRING_TOO_LONG;
	if (MAPIProfilePassword && (strlen(MAPIProfilePassword) > 64))
		return ::BRUTUS::BRUTUS_MAPI_E_STRING_TOO_LONG;

	const char *server_type = NULL;
	switch (ServerType) {
	case ::BRUTUS::SERVER_TYPE_EXCHANGE:
		server_type = "MSEMS";
		break;
	case ::BRUTUS::SERVER_TYPE_DOMINO:
		server_type = "NOTES";
		break;
	default:
		BRUTUS_LOG_BUG("Message service not supported");
		return ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;
	}

	::CORBA::Object_var obj = ::CORBA::Object::_nil();
	{
		// needs protection from now on
		ACE_Write_Guard<ACE_RW_Mutex> guard(create_proxy_mutex_);

		::BRUTUS::BrutusCheck_var lifeline = ::BRUTUS::BrutusCheck::_nil();;
		obj = internal_orb_->string_to_object(life_line_ior);
		try {
			lifeline = ::BRUTUS::BrutusCheck::_narrow(obj.in());;
		}
		catch (...) {
			BRUTUS_LOG_CRITICAL("Exception caught");
			return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
		}
		InstanceID = proxy_cache.lookup(MAPIProfileName,
						WindowsUserName,
						WindowsDomainName,
						lifeline.in(),
						session.out());
		if (InstanceID) {
			BRUTUS_LOG_INF("Session re-used");
			Session = session._retn();
			return ::BRUTUS::BRUTUS_S_OK;
		}
		BRUTUS_LOG_INF("No available session to re-use... Creating new");

		char logon_flags[16] = { '\0' };
		sprintf_s(logon_flags, sizeof(logon_flags), "%X", LogonFlags);

		char profile_flags[16] = { '\0' };
		sprintf_s(profile_flags, sizeof(profile_flags), "%X", ProfileFlags);

		// data exchange
		ior_file_name = make_ior_file_name(this->basedir_, MAPIProfileName);
		file_ior = make_ior_file_ref(ior_file_name);
		remove(ior_file_name); // getting ready - it will be created by the proxy

		pid_t pid = -1;
		ProxyProcess proxy(life_line_ior,
				   MAPIProfileName,
				   WindowsUserName,
				   WindowsDomainName,   // may be NULL
				   WindowsUserPassword,
				   MAPIProfilePassword, // may be NULL
				   MailboxName,
				   ServerName,
				   server_type,
				   logon_flags,
				   profile_flags);
		try {
			pid = proxy.spawn(WindowsUserName, WindowsDomainName, WindowsUserPassword);
			if (-1 == pid) {
				char msg_[1024] = { '\0' };
				sprintf_s(msg_, sizeof(msg_), "Could not spawn proxy process for %s/%s. User privileges?", WindowsUserName, WindowsDomainName);

				ACE_DEBUG((LM_CRITICAL, ACE_TEXT("%s\n"), msg_));
				br = ::BRUTUS::BRUTUS_MAPI_E_LOGON_FAILED;
				goto out;
			}
		}
		catch (...) {
			BRUTUS_LOG_CRITICAL("Exception caught");
			br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
			goto out;
		}

		// make ample time for the proxy to get up and running
		unsigned int attempts = 0;
		while (!file_exist(ior_file_name)) {
			BRUTUS_LOG_INF("waiting..");
			if (50 <= attempts++)
				break;
			ACE_OS::sleep(ACE_Time_Value(0, 100000)); // 0.1 second
		}
		ACE_OS::sleep(ACE_Time_Value(0, 300000)); // 0.3 second, enough?

		// get return value
		if (!file_exist(ior_file_name)) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Spawn failed\n")));
			goto out;
		}
		try {
			obj = internal_orb_->string_to_object((const char*)file_ior);
		}
		catch (const CORBA::Exception &e) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - %s\n"), e._info().c_str()));
			goto out;
		}
		catch (...) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Unknown C++ exception\n")));
			goto out;
		}

		try {
			session = ::BRUTUS::IMAPISession::_narrow(obj.in());
		}
		catch (const CORBA::Exception &e) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - %s\n"), e._info().c_str()));
			session = ::BRUTUS::IMAPISession::_nil();
			goto out;
		}
		catch (...) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Unknown C++ exception\n")));
			session = ::BRUTUS::IMAPISession::_nil();
			goto out;
		}
		if (::CORBA::is_nil(session.in())) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Spawn error, session is NIL\n")));
			goto out;
		}

		// final sanity check
		if (!proxy.is_running()) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Spawn error, the proxy has died...\n")));
			session = ::BRUTUS::IMAPISession::_nil();
			goto out;
		}

		proxy_cache.insert(MAPIProfileName,
				   WindowsUserName,
				   WindowsDomainName,
				   proxy.gethandle(),
				   session.inout());

		{ // register in the proxy cache now that it is known to run
			ACE_DEBUG((LM_INFO, ACE_TEXT("<%T > %N:%l - Launching proxy supervisor thread for %s\n"), MAPIProfileName));
			ace_thread.set_proxy_data(&proxy_cache,
						  proxy.handle_retn(),
						  MAPIProfileName);
			ace_thread.activate(THR_DETACHED);
		}

		BRUTUS_LOG_INF("New session created");

		br = ::BRUTUS::BRUTUS_S_OK;
		Session = session._retn();
	out:
		if (ior_file_name) {
			remove(ior_file_name);
			free(ior_file_name);
		}
		if (file_ior)
			free(file_ior);
	}
	BRUTUS_LOG_INF("Leaving BRUTUS_BrutusLogOn_i::Logon()");

	return br;
}
