/* -*- 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/>.
 */

#pragma warning( disable : 4996 )

#include "utils/macros.h"
#include "utils/globals.h"
#include "utils/config.h"
#include "utils/logging.h"
#include "utils/fileops.h"
#include "utils/poa_utils.h"

#include "idl/definesC.h"
#include "idl/conv_utils.h"
#include "idl/BrutusCheckC.h"
#include "idl/return_codesC.h"
#include "idl/brutus_defines.h"
#include "idl/IMAPISessionS_impl.h"

#include <signal.h>
#include <ace/OS.h>
#include <ace/Task.h>
#include <ace/UUID.h>
#include <tao/TimeBaseC.h>
#include <tao/Messaging/Messaging.h>

#include <mapiutil.h>

static char *LOG_FILE = NULL;

namespace
{
	::CORBA::ORB_var theORB = ::CORBA::ORB::_nil();
}

static void
shutDown(void)
{
//   	int fd = open(LOG_FILE, _O_RDWR);
//  	_chsize_s(fd, 0);
//  	close(fd);

	ExitProcess(EXIT_SUCCESS);
}

static inline bool
wspace(char c)
{
	switch (c) {
	case ' ' :
	case '\t' :
	case '\r' :
	case '\n' :
		return true;
	default:
		return false;
	}
}

// MAPI_ALLOW_OTHERS is ignored due to security concerns
// MAPI_NEW_SESSION is forced due to security concerns
// All UI related flags are ignored
// MAPI_FORCE_DOWNLOAD is ignored. Unclear how it should be interpreted
static FLAGS
native_logon_flags(const ::BRUTUS::BDEFINE Flags)
{
	::BRUTUS::BDEFINE flags = Flags;
	FLAGS retval = 0;

	retval |= MAPI_EXTENDED;
	retval |= MAPI_NEW_SESSION;

	if (flags & ::BRUTUS::BRUTUS_UNIQUE_SESSION) // ignore this flag here
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_UNIQUE_SESSION);

	if (flags & ::BRUTUS::BRUTUS_MAPI_NO_MAIL) {
		retval |= MAPI_NO_MAIL;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_MAPI_NO_MAIL);
	}
	if (flags & ::BRUTUS::BRUTUS_MAPI_DEFAULT_SERVICES) {
		retval |= MAPI_DEFAULT_SERVICES;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_MAPI_DEFAULT_SERVICES);
	}
	if (flags & ::BRUTUS::BRUTUS_MAPI_TIMEOUT_SHORT) {
		retval |= MAPI_TIMEOUT_SHORT;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_MAPI_TIMEOUT_SHORT);
	}
	if (flags & ::BRUTUS::BRUTUS_MAPI_USE_DEFAULT) {
		retval |= MAPI_USE_DEFAULT;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_MAPI_USE_DEFAULT);
	}
	if (flags & ::BRUTUS::BRUTUS_MAPI_EXPLICIT_PROFILE) {
		retval |= MAPI_EXPLICIT_PROFILE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_MAPI_EXPLICIT_PROFILE);
	}
	// 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 : 0x%#.8lx", flags);
		BRUTUS_LOG_BUG(msg);
	}

	return retval;
}

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

	if (flags & ::BRUTUS::BRUTUS_MAPI_DEFAULT_SERVICES) {
		BRUTUS_LOG_INF("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) {
		BRUTUS_LOG_INF("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;
}

template<typename T>
static typename T::_ptr_type resolve_init(CORBA::ORB_ptr Orb,
					  const char *Name)
{
	std::string ref(Name);

	CORBA::Object_var obj = CORBA::Object::_nil();
	try {
		obj = Orb->resolve_initial_references(Name);
	}
	catch (const CORBA::ORB::InvalidName &e) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - %s\n"), e._info().c_str()));
		throw;
	}
	catch (const CORBA::Exception &e) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - %s\n"), e._info().c_str()));
		throw;
	}
	catch (...) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Unknown C++ exception\n")));
		throw;
	}
	if (CORBA::is_nil(obj.in())) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Could not resolve initial reference\n")));
		throw;
	}

	typename T::_var_type retval = T::_nil();
	try {
		retval = T::_narrow(obj.in());
	}
	catch (const CORBA::Exception &e) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - %s\n"), e._info().c_str()));
		throw;
	}
	catch (...) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Unknown C++ exception\n")));
		throw;
	}
	if (CORBA::is_nil(retval.in())) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Could not narrow\n")));
		throw;
	}

	return retval._retn();
}

static CORBA::ORB_ptr 
create_orb(int argc,
	   char **argv)
{
	CORBA::ORB_var orb = CORBA::ORB::_nil();
	int init_argc = 7;
	char **init_argv = (char**)malloc(sizeof(char*) * (init_argc));
	if (!init_argv)
		return CORBA::ORB::_nil();
	memset((void*)init_argv, 0, init_argc);

	init_argv[0] = "brutus_proxy.exe";
	init_argv[1] = "-ORBDottedDecimalAddresses";
	init_argv[2] = "1";
	init_argv[3] = "-ORBObjRefStyle"; 
	init_argv[4] = "IOR";
        init_argv[5] = "-ORBSvcConf"; 
        init_argv[6] = SERVER_ORB_SVC_CONF;

	// initialize the ORB
        orb = CORBA::ORB_init(init_argc, init_argv, "com.42tools.brutus.proxy");

	free(init_argv);

	return orb._retn();
}

static ::BRUTUS::BRESULT
create_profile(const char *MAPIProfileName,
	       const char *MAPIProfilePassword,
	       const char *ExchangeMailbox,
	       const char *ExchangeServer,
	       const char *ServerType,
	       const bool IgnoreNoPF,
	       ::BRUTUS::BDEFINE ProfileFlags)
{
	BRUTUS_LOG_REF;

	if (!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;

	BRUTUS_LOG_PRIVATE(MAPIProfileName);
	BRUTUS_LOG_PRIVATE(MAPIProfilePassword ? MAPIProfilePassword : "NULL");
	BRUTUS_LOG_PRIVATE(ExchangeMailbox);
	BRUTUS_LOG_PRIVATE(ExchangeServer);
	BRUTUS_LOG_PRIVATE(ServerType);

	const char *service_display_name = NULL;
	LPPROFSECT lpProfSect = NULL;
	LPPROFADMIN lpProfAdmin = NULL;
	LPSERVICEADMIN lpSvcAdmin = NULL;
	LPMAPITABLE lpMsgSvcTable = NULL;
	SRestriction sres;
	SPropValue SvcProps;
	LPSRowSet lpSvcRows = NULL; // Rowset to hold results of table query.
	SPropValue rgval[2]; // Property structure to hold values we want to set.
	::BRUTUS::BRESULT br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	HRESULT hr = S_OK;

	FLAGS flags = native_create_profile_flags(ProfileFlags);

	// This indicates columns we want returned from HrQueryAllRows.
	SizedSPropTagArray(2, sptCols) = { 2, PR_SERVICE_NAME, PR_SERVICE_UID };

	// Get an IProfAdmin interface.
	hr = MAPIAdminProfiles(0, &lpProfAdmin);
	if (hr != S_OK) {
		BRUTUS_LOG_BUG("MAPIAdminProfiles() failed");
		goto out;
	}

	// Create a new profile.
	hr = lpProfAdmin->CreateProfile((char*)MAPIProfileName,
					(char*)MAPIProfilePassword,
					NULL,
					flags);
	if (hr != S_OK) {
		char __msg[128] = { 0 };
		sprintf_s(__msg, sizeof(__msg), "Could not create profile, HRESULT = %#.8lx (", hr);
		strcat(__msg, hresult_to_str(hr));
		strcat(__msg, ")");
		BRUTUS_LOG_BUG(__msg);
		goto out;
	}

	// Get an IMsgServiceAdmin interface off of the IProfAdmin interface.
	hr = lpProfAdmin->AdminServices((char*)MAPIProfileName,
					(char*)MAPIProfilePassword,
					NULL,
					flags,
					&lpSvcAdmin);
	if (hr != S_OK) {
		BRUTUS_LOG_BUG("Could not get IMsgServiceAdmin interface");
		goto out;
	}

	// Create the new message service for Exchange.
	if (!strcmp(ServerType, "MSEMS"))
		service_display_name = "Microsoft Exchange";
	else if (!strcmp(ServerType, "NOTES"))
		service_display_name = "Lotus Notes Mail";
	else {
		BRUTUS_LOG_BUG("Message service not supported");
		goto out;
	}
	hr = lpSvcAdmin->CreateMsgService((LPSTR)ServerType, // Name of service from MAPISVC.INF.
					  (LPSTR)service_display_name,
					  NULL,
					  flags);
	if (hr != S_OK) {
		char __msg[1024] = { 0 };
		sprintf_s(__msg, sizeof(__msg), "The %s message service is not in the [Services] section of \"mapisvc.inf\". Please reinstall MAPI.", ServerType);
		BRUTUS_LOG_ERR(__msg);
		goto out;
	}

	// We now need to get the entry id for the new service.
	// This can be done by getting the message service table
	// and getting the entry that corresponds to the new service.
	hr = lpSvcAdmin->GetMsgServiceTable(flags, &lpMsgSvcTable);
	if (hr != S_OK) {
		BRUTUS_LOG_BUG("Could not get message service table");
		goto out;
	}

	// Set up restriction to query table.
	sres.rt = RES_CONTENT;
	sres.res.resContent.ulFuzzyLevel = FL_FULLSTRING;
	sres.res.resContent.ulPropTag = PR_SERVICE_NAME;
	sres.res.resContent.lpProp = &SvcProps;
	SvcProps.ulPropTag = PR_SERVICE_NAME;
	SvcProps.Value.lpszA = (LPSTR)ServerType;

	// Query the table to get the entry for the newly created message service.
	hr = HrQueryAllRows(lpMsgSvcTable,
			    (LPSPropTagArray)&sptCols,
			    &sres,
			    NULL,
			    0,
			    &lpSvcRows);
	if (hr != S_OK) {
		BRUTUS_LOG_BUG("HrQueryAllRows() failed");
		goto out;
	}
	if (1 != lpSvcRows->cRows) {
		BRUTUS_LOG_BUG("Wrong number of rows");
		goto out;
	}

	// Setup a SPropValue array for the properties you need to configure.

	// the mailbox name.
	ZeroMemory(&rgval[0], sizeof(SPropValue) );
	rgval[0].ulPropTag = PR_PROFILE_UNRESOLVED_NAME;
	rgval[0].Value.lpszA = (char*)ExchangeMailbox; // or SMTP email address

	// the server name.
	ZeroMemory(&rgval[1], sizeof(SPropValue) );
	rgval[1].ulPropTag = PR_PROFILE_UNRESOLVED_SERVER;
	rgval[1].Value.lpszA = (char*)ExchangeServer;

	// Configure the message service with the above properties.
	hr = lpSvcAdmin->ConfigureMsgService((LPMAPIUID)lpSvcRows->aRow[0].lpProps[1].Value.bin.lpb, // Entry ID of service to configure.
					     NULL,
					     flags,
					     2, // Number of properties we are setting.
					     rgval);
	if (hr != S_OK) {
		BRUTUS_LOG_HR;
		BRUTUS_LOG_BUG("Could not configure message service");
	} else
		BRUTUS_LOG_INF("Message service configured");

	if (IgnoreNoPF) {
		LPSPropValue flags_pv = NULL;

		hr = lpSvcAdmin->OpenProfileSection((LPMAPIUID)pbGlobalProfileSectionGuid,
						    NULL,
						    MAPI_MODIFY,
						    &lpProfSect);
		if (hr != S_OK) {
			hr = lpSvcAdmin->OpenProfileSection((LPMAPIUID)pbGlobalProfileSectionGuid,
							    NULL,
							    MAPI_MODIFY | MAPI_FORCE_ACCESS,
							    &lpProfSect);
			if (hr != S_OK) {
				BRUTUS_LOG_BUG("OpenProfileSection() failed");
				goto out;
			}
		}

		hr = HrGetOneProp(lpProfSect, PR_PROFILE_CONNECT_FLAGS, &flags_pv);
		if (hr != S_OK) {
			BRUTUS_LOG_BUG("HrGetOneProp() failed");
			goto out;
		}

		flags_pv->Value.l |= CONNECT_IGNORE_NO_PF;
		hr = HrSetOneProp(lpProfSect, flags_pv);
		MAPIFreeBuffer(flags_pv);
		if (hr != S_OK) {
			BRUTUS_LOG_BUG("HrSetOneProp() failed");
			goto out;
		}

		hr = lpProfSect->SaveChanges(FORCE_SAVE);
		if (hr != S_OK) {
			BRUTUS_LOG_BUG("HrSetOneProp() failed");
			goto out;
		}
		BRUTUS_LOG_INF("CONNECT_IGNORE_NO_PF flag set");
	}

out:
	if ((S_OK != hr) && (MAPI_E_NO_ACCESS != hr))
		lpProfAdmin->DeleteProfile((char*)MAPIProfileName, 0);

	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		BRUTUS_EXIT;
	exit:
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	if (lpSvcRows)
		FreeProws(lpSvcRows);

	if (lpMsgSvcTable)
		lpMsgSvcTable->Release();

	if (lpSvcAdmin)
		lpSvcAdmin->Release();

	if (lpProfAdmin)
		lpProfAdmin->Release();

	if (lpProfSect)
		lpProfSect->Release();

	return br;
}

struct Create_Profile_Args {
	const char *MAPIProfileName;
	const char *MAPIProfilePassword;
	const char *ExchangeMailbox;
	const char *ExchangeServer;
	const char *ServerType;
	const bool IgnoreNoPF;
	::BRUTUS::BDEFINE ProfileFlags;
	::BRUTUS::BRESULT br;
};

class Create_Profile_Thread_Base : public ACE_Task_Base {
public:
        Create_Profile_Thread_Base(void)
                {
                };

        void set_args(struct Create_Profile_Args *args)
                {
                        args_ = args;
                };

        virtual int svc(void)
                {
			args_->br = create_profile(args_->MAPIProfileName,
						   args_->MAPIProfilePassword,
						   args_->ExchangeMailbox,
						   args_->ExchangeServer,
						   args_->ServerType,
						   args_->IgnoreNoPF,
						   args_->ProfileFlags);
			return 0;
                };

private:
        struct Create_Profile_Args *args_;
};

static ::BRUTUS::BRESULT
create_session(const unsigned long LifeCheckDelay,
	       ::BRUTUS::BrutusCheck_ptr LifeLine,
	       const char *ProfileName,
	       const char *ProfilePassword,
	       ::BRUTUS::BDEFINE Flags,
	       ::PortableServer::POA_ptr POA,
	       ::BRUTUS::IMAPISession_out Session,
	       ::CORBA::ORB_ptr orb)
{
	Session = ::BRUTUS::IMAPISession::_nil();

	LPMAPISESSION mapi_session = NULL;
	FLAGS flags = native_logon_flags(Flags);

	HRESULT hr = MAPILogonEx(0,
				 (char*)ProfileName,
				 (char*)ProfilePassword, // may be NULL
				 flags,
				 &mapi_session);

	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		if (mapi_session) {
			BRUTUS_LOG_ERR("MAPI session is non-NIL but MAPILogonEx failed.");
			try {
				mapi_session->Release();
			}
			catch (...) {
				BRUTUS_LOG_CRITICAL("Exception caught");
			}
		}
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}
	if (S_OK != hr) {
		if (mapi_session) {
			BRUTUS_LOG_ERR("MAPI session is non-NIL but MAPILogonEx failed.");
			try {
				mapi_session->Release();
			}
			catch (...) {
				BRUTUS_LOG_CRITICAL("Exception caught");
			}
		}
		{
			char msg[128] = {0};
			const char *reason;

			switch (hr) {
			case MAPI_E_TIMEOUT :
				reason = "MAPI_E_TIMEOUT";
				break;
			case MAPI_E_NOT_FOUND :
				reason = "MAPI_E_NOT_FOUND";
				break;
			default :
				BRUTUS_LOG_HR;
				reason = "MAPI_E_LOGON_FAILED";
			}
			sprintf_s(msg, sizeof(msg), "MAPILogonEx(%s) failed - %s", ProfileName, reason);
			BRUTUS_LOG_PRIVATE(msg);
		}
		return br;
	}

	// create the child poa for this mapi session
	::PortableServer::POA_var session_poa = ::PortableServer::POA::_duplicate(POA);
	if (::CORBA::is_nil(session_poa)) {
		BRUTUS_LOG_ERR("Could not create session POA");
		try {
			mapi_session->Release();
		}
		catch (...) {
			BRUTUS_LOG_CRITICAL("Exception caught");
		}
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	// create the IMAPISession servant
	BRUTUS_IMAPISession_i *session_servant = NULL;
	try {
		session_servant = new BRUTUS_IMAPISession_i(mapi_session,
							    session_poa.in(),
							    orb,
							    LifeLine,
							    LifeCheckDelay,
							    &shutDown);
	}
	catch (std::bad_alloc &) {
		BRUTUS_LOG_ERR("No memory");
		try {
			mapi_session->Release();
		}
		catch (...) {
			BRUTUS_LOG_CRITICAL("Exception caught");
		}
		return ::BRUTUS::BRUTUS_MAPI_E_NOT_ENOUGH_MEMORY;
	}
	if (!session_servant) {
		BRUTUS_LOG_ERR("No memory");
		try {
			mapi_session->Release();
		}
		catch (...) {
			BRUTUS_LOG_CRITICAL("Exception caught");
		}
		return ::BRUTUS::BRUTUS_MAPI_E_NOT_ENOUGH_MEMORY;
	}

	// create the IMAPISession object and register it in the session poa
	::PortableServer::ObjectId_var oid;
	try {
		::PortableServer::ServantBase_var owner_transfer(session_servant);
		oid = session_poa->activate_object(session_servant);
	}
	catch (const ::PortableServer::POA::WrongPolicy &) {
		BRUTUS_LOG_CRITICAL("::PortableServer::POA::WrongPolicy exception caught");
		try {
			mapi_session->Release();
		}
		catch (...) {
			BRUTUS_LOG_CRITICAL("Exception caught");
		}
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}
	catch (const ::PortableServer::POA::ServantAlreadyActive &) {
		BRUTUS_LOG_CRITICAL("::PortableServer::POA::ServantAlreadyActive exception caught");
		try {
			mapi_session->Release();
		}
		catch (...) {
			BRUTUS_LOG_CRITICAL("Exception caught");
		}
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	::CORBA::Object_var obj = ::CORBA::Object::_nil();
	try {
		obj = session_poa->id_to_reference(oid);
	}
	catch (const ::PortableServer::POA::WrongPolicy &) {
		BRUTUS_LOG_CRITICAL("::PortableServer::POA::WrongPolicy exception caught");
	}
	catch (const ::PortableServer::POA::ObjectNotActive &) {
		BRUTUS_LOG_CRITICAL("::PortableServer::POA::ObjectNotActive exception caught");
	}
	if (::CORBA::is_nil(obj)) {
		BRUTUS_LOG_CRITICAL("::PortableServer::POA::id_to_reference() returned NIL");
		try {
			mapi_session->Release();
		}
		catch (...) {
			BRUTUS_LOG_CRITICAL("Exception caught");
		}
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}
	::BRUTUS::IMAPISession_var session = ::BRUTUS::IMAPISession::_narrow(obj);
	if (::CORBA::is_nil(session)) {
		BRUTUS_LOG_CRITICAL("::BRUTUS::IMAPISession::_narrow() returned NIL");
		try {
			mapi_session->Release();
		}
		catch (...) {
			BRUTUS_LOG_CRITICAL("Exception caught");
		}
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	Session = session._retn();
	{
		char msg[128] = {0};
		sprintf_s(msg, sizeof(msg), "%s has logged on", ProfileName);
		BRUTUS_LOG_PRIVATE(msg);
	}

	BRUTUS_LOG_INF("Leaving create_session()");

	return ::BRUTUS::BRUTUS_S_OK;
}

static void
signal_handler(int sig)
{
	ACE_DEBUG((LM_INFO, ACE_TEXT("<%T> %N:%l - Signal caught : %d\n"), sig));

	if (!::CORBA::is_nil(theORB.in())) {
		try {
			theORB->shutdown(true);
		}
		catch (::CORBA::BAD_INV_ORDER&) {
			// the orb is already gone - not an error
		}
		catch (const CORBA::Exception &e) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - %s\n"), e._info().c_str()));
		}
		catch (...) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Unknown C++ exception\n")));
		}
	}
}

// argv[1]  LifeLineIOR,
// argv[2]  MAPIProfileName,
// argv[3]  WindowsUserName,
// argv[4]  WindowsDomainName,
// argv[5]  WindowsUserPassword,
// argv[6]  MAPIProfilePassword,
// argv[7]  MailboxName,
// argv[8]  ServerName,
// argv[9]  server_type,
// argv[10] logon_flags,
// argv[11] profile_flags,
int
ACE_TMAIN(int argc, ACE_TCHAR *argv[])
{
  	if (12 != argc)
  		ExitProcess(EXIT_FAILURE);

	// scramble data?
	char *scramble_str = NULL;

	// MAPI
	HRESULT hr = E_FAIL;
	bool retry = true;
	bool mapi_init_ok = false;
	bool profiles_on_disk = false;
	bool ignore_no_pf_flag = false;

	// CORBA
	long giop_timeout = 60;
	unsigned long life_check_delay = 120; 
	::PortableServer::POA_var root_poa = ::PortableServer::POA::_nil();
	::PortableServer::POAManager_var root_poa_manager = ::PortableServer::POAManager::_nil();
	::PortableServer::POA_var session_poa = ::PortableServer::POA::_nil();
	::PortableServer::POAManager_var session_poa_manager = ::PortableServer::POAManager::_nil();
	::BRUTUS::BrutusCheck_var life_line;
	::BRUTUS::IMAPISession_var mapi_session_obj;

	// command line 
	const char *life_line_ior = argv[1];
	const char *mapi_profile = argv[2];
	const char *windows_user = argv[3];
	const char *windows_domain = (!strcmp("0", argv[4]) ? NULL : argv[4]); // NULL to allow for UPN authentication
	const char *windows_password = (!strcmp("0", argv[5]) ? "" : argv[5]);
	const char *mapi_profile_password = (!strcmp("0", argv[6]) ? NULL : argv[6]);
	const char *server_identity = argv[7]; // mailbox or SMTP address 
	const char *server = argv[8];
	const char *server_type = argv[9];
	ULONG logon_flags = strtoul(argv[10], NULL, 16);
	ULONG profile_flags = strtoul(argv[11], NULL, 16);
	char cwd[_MAX_PATH] = { '\0' };

	{ // set current working directory
		char *c = NULL;

		if (!GetFullPathName(argv[0],
				     _MAX_PATH,
				     cwd,
				     NULL))
			goto error;

		c = &cwd[strlen(cwd)-1];
		while ('\\' != *c)
			*(c--) = '\0';
		*c = '\0';
		SetCurrentDirectory(cwd);

#if _WIN32_WINNT > 0x0501
		// use this when Windows 2000 has become irrelevant
		SetDllDirectory(cwd); 
#endif
	}

	{ // start logging

// 		ACE_Utils::UUID uuid(*ACE_Utils::UUID_GENERATOR::instance ()->generate_UUID());
// 		const ACE_CString *uuid_str(uuid.to_string());

//		LOG_FILE = make_log_file_name(cwd, (const char *const)uuid_str->c_str());
		LOG_FILE = make_log_file_name(cwd, mapi_profile);
		create_file(false, LOG_FILE);

		LOG_MANAGER->redirectToFile(LOG_FILE);
	}

	BRUTUS_LOG_INF("Starting Brutus Proxy");
	{
		char msg_[1024] = { '\0' };
		sprintf_s(msg_, sizeof(msg_), "Starting proxy for \"%s\" with server identity \"%s\"", windows_user, server_identity);
		BRUTUS_LOG_INF(msg_);
	}

	// signal handling
	signal(SIGABRT, signal_handler);
	signal(SIGFPE, signal_handler);
	signal(SIGILL, signal_handler);
	signal(SIGINT, signal_handler);
	signal(SIGSEGV, signal_handler);
	signal(SIGTERM , signal_handler);

	// disable error dialogs
	SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);

	///////////////////////////////////////////////////
	//                Configuration                  //
	///////////////////////////////////////////////////
	{
		BRUTUS::Config conf;
		
		int cret = conf.load(BRUTUS_CONF_FILE);
		if (0 < cret)
			ACE_DEBUG((LM_WARNING, ACE_TEXT("<%T> %N:%l - Configuration file parse error at line %d. Assuming sensible defaults\n"), cret));
		else if (0 > cret)
			ACE_DEBUG((LM_WARNING, ACE_TEXT("<%T> %N:%l - Configuration file could not be opened. Assuming sensible defaults\n")));
		
		if (cret)
			goto conf_done;

		char *ignore_no_pf_flag_str = NULL;
		ignore_no_pf_flag_str = conf.get_value("EXCHANGE_SERVER_VERSION");
		if ((ignore_no_pf_flag_str) && (!strcmp(ignore_no_pf_flag_str, "2007")))
			ignore_no_pf_flag = true;
		else
			ignore_no_pf_flag = false;
		free(ignore_no_pf_flag_str);

		// Execution options
		char *life_check_delay_str = NULL;
		life_check_delay_str = conf.get_value("CLIENT_LIFE_CHECK_DELAY");
		if (life_check_delay_str)
			life_check_delay = strtoul(life_check_delay_str, NULL, 10);
		else
			life_check_delay = 120;
		free(life_check_delay_str);
		if ((life_check_delay <= 0) || (life_check_delay > 86400))
			life_check_delay = 86400;

		char *giop_timeout_str = NULL;
		giop_timeout_str = conf.get_value("CLIENT_GIOP_TIMEOUT");
		if (giop_timeout_str)
			giop_timeout = strtol(giop_timeout_str, NULL, 10);
		else
			giop_timeout = 30;
		free(giop_timeout_str);
		if ((giop_timeout <= 0) || (giop_timeout > 86400))
			giop_timeout = 86400;


		///////////////////////////////////////////////////
		//                  SCRAMBLE                     //
		///////////////////////////////////////////////////
		char *scramble_str_tmp = conf.get_value("CONTENT_SCRAMBLED_USERS");
		if (!scramble_str_tmp) {
			scramble_str = NULL;
			goto no_scrambling;
		}
		const size_t scramble_str_len = strlen(scramble_str_tmp) + 3;

		// make sure that the scramble string starts and ends with at least one ' '
		scramble_str = (char*)malloc(scramble_str_len);
		*scramble_str = '\0';
		strcat_s(scramble_str, scramble_str_len, (const char*)" "); // start
		strcat_s(scramble_str, scramble_str_len, (const char*)scramble_str_tmp); // middle
		strcat_s(scramble_str, scramble_str_len, " "); // end
		free(scramble_str_tmp);
		scramble_str_tmp = NULL;

		if (_strlwr_s(scramble_str, scramble_str_len)) {
			free(scramble_str);
			scramble_str = NULL;
			goto error;
		}
		{ // convert all whitespace to ' 's
			int n = 0;
			while ('\0' != scramble_str[n]) {
				if (wspace(scramble_str[n]))
					scramble_str[n] = ' ';
				n++;
			}
		}
		if (NULL != strstr(scramble_str, " none ")) {
			free(scramble_str);
			scramble_str = NULL;
		}
	no_scrambling:
		;
	}
conf_done:

	if (scramble_str) {
		const size_t scramble_user_len = strlen(windows_user) 
			+ (windows_domain ? strlen(windows_domain) : 0)
			+ 4;
		char *scramble_user = (char*)malloc(scramble_user_len);
		*scramble_user = '\0';

		strcat_s(scramble_user, scramble_user_len, " ");
		if (windows_domain && strlen(windows_domain)) { // windows_domain\user
			strcat_s(scramble_user, scramble_user_len, windows_user);
			strcat_s(scramble_user, scramble_user_len, "\\");
			strcat_s(scramble_user, scramble_user_len, windows_domain);
		} else { // UPN (user@dns_windows_domain_name) or any other authentication format were no windows_domain name is specified
			strcat_s(scramble_user, scramble_user_len, windows_user);
		}
		strcat_s(scramble_user, scramble_user_len, " ");

		scramble_content = (NULL == strstr(scramble_str, scramble_user)) ? false : true;
		free(scramble_user);
		free(scramble_str);
		scramble_str = NULL;
	} 

	///////////////////////////////////////////////////
	//                    CORBA                      //
	///////////////////////////////////////////////////

	BRUTUS_LOG_INF("Starting CORBA");
	try {
		theORB = create_orb(argc, argv);
	}
	catch (const CORBA::Exception &e) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - %s\n"), e._info().c_str()));
		goto error;
	}
	catch (...) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Unknown C++ exception\n")));
		goto error;
	}

	// root poa
	root_poa = resolve_init<::PortableServer::POA>(theORB.in(), "RootPOA");
	if (::CORBA::is_nil(root_poa.in())) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Could not create session root POA\n")));
		goto error;
	}

	// root poa manager
	root_poa_manager = root_poa->the_POAManager();
	if (::CORBA::is_nil(root_poa_manager.in())) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Could not create session root POAManager\n")));
		goto error;
	}
	root_poa_manager->activate();

	// create session POA
	session_poa = create_poa((const char*)"", ::PortableServer::POAManager::_nil(), root_poa.in());
	if (::CORBA::is_nil(session_poa.in())) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Could not create session POA\n")));
		goto error;
	}

	// session poa manager
	session_poa_manager = session_poa->the_POAManager();
	if (::CORBA::is_nil(session_poa_manager.in())) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Could not create session POAManager\n")));
		goto error;
	}
	session_poa_manager->activate();

	// get the lifeline
	try {
		::CORBA::Object_var object = theORB->string_to_object(life_line_ior);

		// Set the RelativeRoundtripTimeout value
		TimeBase::TimeT rt_timeout = giop_timeout * 10000000; // in 100 nanosecond units
		::CORBA::Any rt_timeout_as_any;
		::CORBA::PolicyList policy_list(1);

		rt_timeout_as_any <<= rt_timeout;
		policy_list.length(1);
		policy_list[0] = theORB->create_policy(Messaging::RELATIVE_RT_TIMEOUT_POLICY_TYPE,
						       rt_timeout_as_any);
		// set the timeout
		object = object->_set_policy_overrides(policy_list, ::CORBA::SET_OVERRIDE);
		life_line = ::BRUTUS::BrutusCheck::_narrow(object.in());
		policy_list[0]->destroy();
		policy_list[0] = ::CORBA::Policy::_nil();
		if (::CORBA::is_nil(life_line.in())) {
			ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - LifeLine is NIL\n")));
			goto error;
		}
	}
	catch (const CORBA::Exception &e) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - %s\n"), e._info().c_str()));
		goto error;
	}
	catch (...) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - Unknown C++ exception\n")));
		goto error;
	}

	//////////////////////////////////////
	//               MAPI               //
	//////////////////////////////////////

	BRUTUS_LOG_INF("Initializing MAPI");

	// MAPI initialization
	MAPIINIT_0 mapi_init;
	mapi_init.ulFlags = MAPI_MULTITHREAD_NOTIFICATIONS | MAPI_TEMPORARY_PROFILES;
	mapi_init.ulVersion = MAPI_INIT_VERSION;
	hr = MAPIInitialize(&mapi_init);
	if (hr != S_OK) {
		BRUTUS_LOG_CRITICAL(hresult_to_str(hr));
		goto error;
	} else {
		mapi_init_ok = true;
	}

	BRUTUS_LOG_INF("Logging on to MAPI");

	// MAPI logon
create_mapi_session:
	::BRUTUS::BRESULT br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	br = create_session(life_check_delay,
			    life_line.in(),
			    mapi_profile,
			    mapi_profile_password, 
			    logon_flags,
			    session_poa.in(),
			    mapi_session_obj.out(),
			    theORB.in());
	if (retry) {
		if (::BRUTUS::BRUTUS_MAPI_E_NOT_FOUND == br) {
			// to avoid deadlock when creating a profile for non-existing Exchange server
			Create_Profile_Thread_Base create_profile_thread;
			struct Create_Profile_Args thread_args = 
				{
					mapi_profile,
					mapi_profile_password,
					server_identity,
					server,
					server_type,
					ignore_no_pf_flag,
					profile_flags,
					::BRUTUS::BRUTUS_INTERNAL_ERROR
				};
			create_profile_thread.set_args(&thread_args);
			create_profile_thread.activate(THR_JOINABLE | THR_CANCEL_ENABLE | THR_SCHED_DEFAULT);

			// wait for the thread to complete
			ACE_Time_Value timeout(10, 0); // 10 seconds
			ACE_Thread_Manager *thr_manager = create_profile_thread.thr_mgr();
			if (0 != thr_manager->wait(&timeout, true, false)) {
				br = ::BRUTUS::BRUTUS_MAPI_E_TIMEOUT;
				BRUTUS_LOG_CRITICAL("Could not create MAPI Profile (timeout) - Possibly trying to contact non-existing Exchenge server");
				goto error;
			}
			br = thread_args.br;

			if (::BRUTUS::BRUTUS_S_OK != br) {
				BRUTUS_LOG_CRITICAL("Could not create MAPI Profile - Incorrect user credentials?");
				goto error;
			}
			retry = false;
			goto create_mapi_session;
		}
	}

	// write session IOR to file
	if (::BRUTUS::BRUTUS_S_OK == br) {
		BRUTUS_LOG_INF("MAPI Session created");

		char *ior_file_name = make_ior_file_name(cwd, mapi_profile);
		remove(ior_file_name);
		create_file(false, ior_file_name);

		char *ior = theORB->object_to_string(mapi_session_obj.in());
		ofstream ior_file(ior_file_name, ios::out | ios::trunc);
		free(ior_file_name);
		if (!ior_file.is_open()) {
			BRUTUS_LOG_CRITICAL("Could not write IMAPISession IOR file");
			goto error;
		}
		ior_file << ior << std::endl;
		ior_file.close();
		CORBA::string_free(ior);
	}

	////////////////////////////////////////////
	//             Activate proxy             //
	////////////////////////////////////////////

	BRUTUS_LOG_INF("Calling ORB::run()");
	try {
		theORB->run();
	}
	catch (const CORBA::Exception &e) {
		ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T> %N:%l - %s\n"), e._info().c_str()));
		goto error;
	}
	catch (...) {
		BRUTUS_LOG_INF("Unknown C++ exception");
		goto error;
	}
	BRUTUS_LOG_INF("Leaving ORB::run()");
//   	int fd = open(LOG_FILE, _O_RDWR);
//  	_chsize_s(fd, 0);
//  	close(fd);

	ExitProcess(EXIT_SUCCESS);

error:
	BRUTUS_LOG_CRITICAL("Damn... something bad happened");
	ExitProcess(EXIT_FAILURE);
}

