/*
** This program 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 1, 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 General Public License for more details.

** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- December 1997
 */

/*
 * Authen.cpp --- class to manage CVS authentication kind
 */

#include "stdafx.h"

#include <string.h>
#include <errno.h>
#include "Authen.h"
#include "CvsPrefs.h"
#include "AppConsole.h"
#include "MultiFiles.h"
#include "PromptFiles.h"
#include "CvsArgs.h"
#include "TextBinary.h"
#include "uwidget.h"

#ifdef WIN32
#	include <algorithm>
#	include "resource.h"
#endif

static CPersistentT<AuthenKind> gAuthenKind("P_Authen",
#ifdef macintosh
	rhosts,
#else /* !macintosh */
	local,
#endif /* !macintosh */
	kNoClass);

#ifdef WIN32
#	ifdef _DEBUG
#	define new DEBUG_NEW
#	undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
#	endif
#endif /* WIN32 */

#ifdef macintosh
#define putenv(a)
#endif

Authen gAuthen;

// authentication class
AuthenKind Authen::kind(void)
{
	return gAuthenKind;
}

void Authen::setkind(AuthenKind newkind)
{
	gAuthenKind = newkind;
}

const char * Authen::kindstr(void)
{
	static CStr res;

	switch(kind())
	{
		case pserver : res = "password authentication"; break;
		case kserver : res = "kerberos authentication"; break;
		case ssh : res = "ssh authentication"; break;
		case local : res = "local mounted directory"; break;
		case ntserver : res = "NT server services"; break;
		default :
		case rhosts : res = ".rhosts authentication"; break;
	}

	if(kind() == pserver && gCvsPrefs.UseProxy())
	{
		res << " using a proxy";
	}

	return res;
}

const char * Authen::token(void)
{
	static CStr res;

	switch(kind())
	{
		case pserver :
			res = ":pserver";
			if(gCvsPrefs.UseProxy())
			{
				res << ";proxy=";
				res << gCvsPrefs.ProxyHost();
				res << ";proxyport=";
				res << gCvsPrefs.ProxyPort();
			}
			res << ':';
			break;
		case kserver : res = ":kserver:"; break;
		case ssh : res = ":ext:"; break;
		case local : res = ":local:"; break;
		case ntserver : res = ":ntserver:"; break;
		default :
		case rhosts : res = ""; break;
	}

	return res;
}

const char * Authen::skiptoken(const char *cvsroot)
{
	const char *secondColon;
	if(cvsroot[0] != ':' || (secondColon = strchr(cvsroot + 1, ':')) == NULL)
		return cvsroot;
	return secondColon + 1;
}

const char *Authen::kindToToken(AuthenKind k)
{
	AuthenModel *model = AuthenModel::GetInstance(k);
	return model->GetToken();
}

AuthenKind Authen::tokenToKind(const char *t)
{
	std::vector<AuthenModel *> & allInstances = AuthenModel::GetAllInstances();
	std::vector<AuthenModel *>::const_iterator i;
	for(i = allInstances.begin(); i != allInstances.end(); ++i)
	{
		AuthenModel *model = *i;
		if(strcmp(model->GetToken(), t) == 0)
			return model->GetKind();
	}

	cvs_err("Unknown authentication kind '%s'\n", t);
	return local;
}

short Authen::kindToNum(AuthenKind k)
{	
	std::vector<AuthenModel *> & allInstances = AuthenModel::GetAllInstances();
	std::vector<AuthenModel *>::const_iterator i;
	int cnt = 0;
	for(i = allInstances.begin(); i != allInstances.end(); ++i, cnt++)
	{
		AuthenModel *model = *i;
		if(model->GetKind() == k)
			return cnt;
	}

	return 0;
}

AuthenKind Authen::numToKind(short num)
{
	std::vector<AuthenModel *> & allInstances = AuthenModel::GetAllInstances();
	std::vector<AuthenModel *>::const_iterator i;
	int cnt = 0;
	for(i = allInstances.begin(); i != allInstances.end(); ++i, cnt++)
	{
		AuthenModel *model = *i;
		if(cnt == num)
			return model->GetKind();
	}

	return local;
}

bool Authen::parse_cvsroot (const char *CVSroot, UStr & theMethod, UStr & theUser, UStr & theHost, UStr & thePath)
{
    char *p;
	UStr copyroot(CVSroot);
	char *cvsroot_copy = copyroot;
	
	theUser = "";
	theHost = "";
	thePath = "";

    if (*cvsroot_copy == ':')
	{
		char *method = ++cvsroot_copy;
		bool have_semicolon;

		/* Access method specified, as in
		* "cvs -d :pserver:user@host:/path",
		* "cvs -d :local:e:\path",
		* "cvs -d :kserver:user@host:/path", or
		* "cvs -d :fork:/path".
		* We need to get past that part of CVSroot before parsing the
		* rest of it.
		*/

		if (! (p = strchr (method, ':')))
			return false;

		p = strpbrk (method, ":;#");
		if (p == 0L)
			return false;

		have_semicolon = (*p == ';' || *p == '#');
		*p = '\0';
		cvsroot_copy = ++p;

		theMethod = method;

		while (have_semicolon)
		{
			/* More elaborate implementation would allow multiple
			semicolons, for example:

			:server;rsh=34;command=cvs-1.6:

			we will allow
			:server;port=22;proxy=www-proxy;proxyport=8080:

			we will also allow # as well as ; as a separator to
			avoid having to quote the root in a shell.
			*/
			/* FIXME: lots of error conditions should be better handled,
			e.g. garbage after the number or no valid number.

			Would be nice to have testcases for some of these cases
			including the error cases.  */
			p = strpbrk (cvsroot_copy, ":;#");
			if (p == NULL)
				return false;

			/* pick up more options if we have them */
			have_semicolon = (*p == ';' || *p == '#');
			*p = '\0';

			cvsroot_copy = ++p;
		}
	}
    else
    {
		theMethod = "";
    }

    if (theMethod != "local")
    {
		/* Check to see if there is a username in the string. */

		if ((p = strchr (cvsroot_copy, '@')) != NULL)
		{
		    *p = '\0';
		    theUser = cvsroot_copy;
		    cvsroot_copy = ++p;
		}

		if ((p = strchr (cvsroot_copy, ':')) != NULL)
		{
		    *p = '\0';
		    theHost = cvsroot_copy;
		    cvsroot_copy = ++p;
		}
    }

    thePath = cvsroot_copy;
    
    return true;
}

std::vector<AuthenModel *> * AuthenModel::m_instances = 0L;

AuthenModel::AuthenModel()
{
	if(m_instances == 0L)
	{
		m_instances = new std::vector<AuthenModel *>;
		if(m_instances == 0L)
			throw ENOMEM;
	}
	
	m_instances->push_back(this);
}

AuthenModel::~AuthenModel()
{
	std::vector<AuthenModel *>::iterator i;
	i = std::find(m_instances->begin(), m_instances->end(), this);
	if(i != m_instances->end())
		m_instances->erase(i);
	
	if(m_instances->empty())
		delete m_instances;
}

std::vector<AuthenModel *> & AuthenModel::GetAllInstances(void)
{
	return *AuthenModel::m_instances;
}

AuthenModel *AuthenModel::GetInstance(AuthenKind k)
{
	std::vector<AuthenModel *> & allInstances = GetAllInstances();
	std::vector<AuthenModel *>::const_iterator i;
	for(i = allInstances.begin(); i != allInstances.end(); ++i)
	{
		AuthenModel *model = *i;
		if(model->GetKind() == k)
			return model;
	}
	
	// default to local
	cvs_err("Unknown authentication kind %d\n", k);
	return GetInstance(local);
}

void AuthenModel::OnSetupEnv(void)
{
	UStr home("HOME=");
	home << gCvsPrefs.Home();
	putenv(home);
}

void AuthenModel::OnRestoreEnv(void)
{
	putenv("HOME=");
}

class AuthenLocal : public AuthenModel
{
public :
	AuthenLocal() {}
	virtual ~AuthenLocal() {}

	virtual AuthenKind GetKind(void) const { return local; }
	virtual const char *GetToken(void) const { return "local"; }
};

#if qMacCvsPP
class AuthenLSH : public AuthenModel
{
public :
	AuthenLSH() {}
	virtual ~AuthenLSH() {}

	virtual AuthenKind GetKind(void) const { return ssh; }
	virtual const char *GetToken(void) const { return "ssh"; }

	virtual bool HasSettings(void) const { return true; }
	virtual bool HasHost(void) const { return true; }
	virtual bool HasUser(void) const { return true; }

	virtual void DoSettings(void);
	virtual void GetSettingsDesc(UStr & desc);

	virtual char * OnGetEnv(char *env);

	static CPersistentInt gLSHEncrypt;
	static CPersistentInt gLSHAuthen;
	static CPersistentInt gLSHCompress;
	static CPersistentInt gLSHPort;
	static CPersistentBool gLSHVerbose;
	static CPersistentBool gLSHTrace;
	static CPersistentBool gLSHDebug;
	static CPersistentBool gLSHKeychain;
	static CPersistentBool gLSHIdentity;

};

class UAuthen_LSH : public UWidget
{
	UDECLARE_DYNAMIC(UAuthen_LSH)
public:
	UAuthen_LSH();
	virtual ~UAuthen_LSH() {}

	enum
	{
		kOK = EV_COMMAND_START,	// 0
		kCancel,			// 1
		kEncryptPopup,		// 2
		kAuthenPopup,		// 3
		kCompressPopup,		// 4
		kCheckPort,			// 5
		kEditPort,			// 6
		kCheckVerbose,		// 7
		kCheckTrace,		// 8
		kCheckDebug,		// 9
		kKeychain,			// 10
		kSendIdentity		// 11
	};

	virtual void DoDataExchange(bool fill);

protected:
	
	ev_msg int OnCheckPort(void);

	UDECLARE_MESSAGE_MAP()
};

UIMPLEMENT_DYNAMIC(UAuthen_LSH, UWidget)

UBEGIN_MESSAGE_MAP(UAuthen_LSH, UWidget)
	ON_UCOMMAND(kCheckPort, OnCheckPort)
UEND_MESSAGE_MAP()

UAuthen_LSH::UAuthen_LSH() : UWidget(::UEventGetWidID())
{
}

void UAuthen_LSH::DoDataExchange(bool fill)
{
	if(fill)
	{
		UEventSendMessage(GetWidID(), EV_COMBO_SETSEL, UMAKEINT(kEncryptPopup, (int)AuthenLSH::gLSHEncrypt), 0L);
		UEventSendMessage(GetWidID(), EV_COMBO_SETSEL, UMAKEINT(kAuthenPopup, (int)AuthenLSH::gLSHAuthen), 0L);
		UEventSendMessage(GetWidID(), EV_COMBO_SETSEL, UMAKEINT(kCompressPopup, (int)AuthenLSH::gLSHCompress), 0L);

		int port = (int)AuthenLSH::gLSHPort > 0 ? (int)AuthenLSH::gLSHPort : -1;
		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditPort, port != -1), 0L);
		UEventSendMessage(GetWidID(), EV_SETINTEGER, UMAKEINT(kEditPort, port == -1 ? 0 : port), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckPort, port != -1), 0L);

		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckVerbose, (bool)AuthenLSH::gLSHVerbose), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckTrace, (bool)AuthenLSH::gLSHTrace), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckDebug, (bool)AuthenLSH::gLSHDebug), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kKeychain, (bool)AuthenLSH::gLSHKeychain), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kSendIdentity, (bool)AuthenLSH::gLSHIdentity), 0L);
	}
	else
	{
		AuthenLSH::gLSHEncrypt = UEventSendMessage(GetWidID(), EV_COMBO_GETSEL, kEncryptPopup, 0L);
		AuthenLSH::gLSHAuthen = UEventSendMessage(GetWidID(), EV_COMBO_GETSEL, kAuthenPopup, 0L);
		AuthenLSH::gLSHCompress = UEventSendMessage(GetWidID(), EV_COMBO_GETSEL, kCompressPopup, 0L);

		int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckPort, 0L);
		int value = UEventSendMessage(GetWidID(), EV_GETINTEGER, kEditPort, 0L);
		AuthenLSH::gLSHPort = state ? value : -1;
		
		AuthenLSH::gLSHVerbose = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckVerbose, 0L) != 0;
		AuthenLSH::gLSHTrace = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckTrace, 0L) != 0;
		AuthenLSH::gLSHDebug = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckDebug, 0L) != 0;
		AuthenLSH::gLSHKeychain = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kKeychain, 0L) != 0;
		AuthenLSH::gLSHIdentity = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kSendIdentity, 0L) != 0;
	}
}

int UAuthen_LSH::OnCheckPort(void)
{
	int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckPort, 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditPort, state), 0L);

	return 0;
}

void AuthenLSH::DoSettings(void)
{
	UAuthen_LSH *dlg = new UAuthen_LSH();
	
	try
	{
		dlg->DoModal(3004);
	}
	catch(...)
	{
	}
	
	delete dlg;
}

void AuthenLSH::GetSettingsDesc(UStr & desc)
{
	if((int)gLSHPort <= 0)
		desc << "Using default port";
	else
		desc << "Using port " << (int)gLSHPort;

	if((bool)gLSHKeychain)
		desc << " and key chain";

	if((bool)gLSHIdentity)
		desc << " and identity file";
}

char * AuthenLSH::OnGetEnv(char *env)
{
	if(strcmp(env, "LSH_SEND_IDENTITY") == 0)
	{
		return (bool)gLSHIdentity ? (char *)"yes" : (char *)"no";
	}
	else if(strcmp(env, "LSH_ENCRYPTION") == 0)
	{
		static char *encryptions[] = 
		{
			"-call",
			"-c3des",
			"-ctwofish",
			"-ccast128",
			"-cserpent",
			"-crijndael",
			"-cblowfish",
			"-carcfour",
			"-cnone"
		};
		return encryptions[(int)gLSHEncrypt];
	}
	else if(strcmp(env, "LSH_AUTHENTICATION") == 0)
	{
		static char *authentications[] = 
		{
			"",
			"-msha1",
			"-mmd5",
			"-mnone"
		};
		return authentications[(int)gLSHAuthen];
	}
	else if(strcmp(env, "LSH_ZLIB") == 0)
	{
		static char *zlib[] = 
		{
			"",
			"-znone",
			"-zzlib"
		};
		return zlib[(int)gLSHCompress];
	}
	else if(strcmp(env, "LSH_MORE_ARGS") == 0)
	{
		static UStr res;
		
		res = "";
		if((bool)gLSHVerbose)
			res << " --verbose";
		if((bool)gLSHTrace)
			res << " --trace";
		if((bool)gLSHDebug)
			res << " --debug";
		
		return res;
	}
	else if(strcmp(env, "LSH_PORT") == 0)
	{
		int port = (int)gLSHPort;
		if(port > 0)
		{
			static char portstr[10];
			sprintf(portstr, "%d", port);
			return portstr;
		}
	}

	return 0L;
}

CPersistentInt AuthenLSH::gLSHEncrypt("P_LSHEncrypt", 0, kAddSettings);
CPersistentInt AuthenLSH::gLSHAuthen("P_LSHAuthen", 0, kAddSettings);
CPersistentInt AuthenLSH::gLSHCompress("P_LSHCompress", 2, kAddSettings);
CPersistentInt AuthenLSH::gLSHPort("P_LSHPort", -1, kAddSettings);
CPersistentBool AuthenLSH::gLSHVerbose("P_LSHVerbose", false, kAddSettings);
CPersistentBool AuthenLSH::gLSHTrace("P_LSHTrace", false, kAddSettings);
CPersistentBool AuthenLSH::gLSHDebug("P_LSHDebug", false, kAddSettings);
CPersistentBool AuthenLSH::gLSHKeychain("P_LSHKeychain", false, kAddSettings);
CPersistentBool AuthenLSH::gLSHIdentity("P_LSHIdentity", true, kAddSettings);
#else // !qMacCvsPP
class AuthenSSH : public AuthenModel
{
public :
	AuthenSSH() {}
	virtual ~AuthenSSH() {}

	virtual AuthenKind GetKind(void) const { return ssh; }
	virtual const char *GetToken(void) const { return "ssh"; }

	virtual bool HasSettings(void) const { return true; }
	virtual bool HasHost(void) const { return true; }
	virtual bool HasUser(void) const { return true; }

	virtual void DoSettings(void);
	virtual void GetSettingsDesc(UStr & desc);

	virtual char * OnGetEnv(char *env);

	virtual void OnSetupEnv(void);
	virtual void OnRestoreEnv(void);

	static PCStr gSSHName;
	static PCStr gSSHOptions;
	static CPersistentBool gRsaIdentity;
	static CMString gRsaIdentityFile;
	static PCStr gCurRsaIdentity;

    static const char * RsaIdentityFile(void);
};

PCStr AuthenSSH::gSSHName("P_SSHName", "ssh", kAddSettings);
PCStr AuthenSSH::gSSHOptions("P_SSHOptions", "", kAddSettings);
CPersistentBool AuthenSSH::gRsaIdentity("P_RsaIdentity", false, kAddSettings);
CMString AuthenSSH::gRsaIdentityFile(10, "P_RsaIdentityFiles");
PCStr AuthenSSH::gCurRsaIdentity("P_CurrentRsaIdentity", 0L, kAddSettings);

const char * AuthenSSH::RsaIdentityFile(void)
{
	if(gRsaIdentityFile.GetList().size() != 0)
		gCurRsaIdentity = gRsaIdentityFile.GetList()[0];

#ifdef WIN32
	// Fill in a default value, if one empty slot
	if( gCurRsaIdentity.empty())
	{
		char *env = getenv( "HOME" );
		if(env != 0L )
		{
			char default_identity_folder[] = ".ssh/identity";
			char default_identity[ MAX_PATH ];
			_makepath( default_identity, NULL, env, default_identity_folder, NULL );
			gCurRsaIdentity = default_identity;
		}
	}
#endif

	return gCurRsaIdentity.empty() ? 0L : (const char *)gCurRsaIdentity;
}

class UAuthen_SSH : public UWidget
{
	UDECLARE_DYNAMIC(UAuthen_SSH)
public:
	UAuthen_SSH();
	virtual ~UAuthen_SSH() {}

	enum
	{
		kOK = EV_COMMAND_START,	// 0
		kCancel,			// 1
		kRSACheck,			// 2
		kRSAEdit,			// 3
		kRSABtn,			// 4
		kSSHCheck,			// 5
		kSSHName,			// 6
		kOptionsText,		// 7
		kOptionsCheck,		// 8
		kOptionsEdit,		// 9
		kSSHBtn,			// 10
	};

	virtual void DoDataExchange(bool fill);
	
protected:
	bool m_rsaidentity;
	UStr m_rsaidentityfile;
	UStr m_options;
	
	ev_msg int OnCheckRSA(void);
	ev_msg int OnBtnRSA(void);
	ev_msg int OnCheckName(void);
	ev_msg int OnBtnName(void);
	ev_msg int OnCheckOptions(void);
	
	UDECLARE_MESSAGE_MAP()
};

UIMPLEMENT_DYNAMIC(UAuthen_SSH, UWidget)

UBEGIN_MESSAGE_MAP(UAuthen_SSH, UWidget)
	ON_UCOMMAND(kRSACheck, OnCheckRSA)
	ON_UCOMMAND(kRSABtn, OnBtnRSA)
	ON_UCOMMAND(kSSHCheck, OnCheckName)
	ON_UCOMMAND(kSSHBtn, OnBtnName)
	ON_UCOMMAND(kOptionsCheck, OnCheckOptions)
UEND_MESSAGE_MAP()

UAuthen_SSH::UAuthen_SSH() : UWidget(::UEventGetWidID())
{
}

int UAuthen_SSH::OnCheckRSA(void)
{
	m_rsaidentity = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kRSACheck, 0L) != 0;
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kRSAEdit, m_rsaidentity), 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kRSABtn, m_rsaidentity), 0L);

	return 0;
}

int UAuthen_SSH::OnCheckName(void)
{
	bool state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kSSHCheck, 0L) != 0;
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kSSHName, state), 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kSSHBtn, state), 0L);

	return 0;
}

int UAuthen_SSH::OnCheckOptions(void)
{
	int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kOptionsCheck, 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kOptionsEdit, state), 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kOptionsText, state), 0L);

	return 0;
}

int UAuthen_SSH::OnBtnRSA(void)
{
	MultiFiles mf;
	if(!BrowserGetMultiFiles("Select a RSA identity :", mf))
		return 0;

	CvsArgs args(false);
	mf.next();
	const char *dir = mf.add(args);
	char * const *argv = args.Argv();
	UStr fullpath;
	fullpath = dir;
	if(!fullpath.endsWith(kPathDelimiter))
		fullpath << kPathDelimiter;
	fullpath << argv[0];

	UEventSendMessage(GetWidID(), EV_SETTEXT, kRSAEdit, (void *)fullpath.c_str());

	return 0;
}

int UAuthen_SSH::OnBtnName(void)
{
	MultiFiles mf;
	if(!BrowserGetMultiFiles("Select SSH :", mf, "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*||"))
		return 0;

	CvsArgs args(false);
	mf.next();
	const char *dir = mf.add(args);
	char * const *argv = args.Argv();
	UStr fullpath;
	fullpath = dir;
	if(!fullpath.endsWith(kPathDelimiter))
		fullpath << kPathDelimiter;
	fullpath << argv[0];

	UEventSendMessage(GetWidID(), EV_SETTEXT, kSSHName, (void *)fullpath.c_str());

	return 0;
}

void UAuthen_SSH::DoDataExchange(bool fill)
{
	if(fill)
	{
		m_rsaidentity = AuthenSSH::gRsaIdentity;
		m_rsaidentityfile = AuthenSSH::RsaIdentityFile();
		UStr helpMsg = "* Some Cygwin/OpenSSH options :\n"
			"-v : Verbose mode\n"
			"-C : Request compression\n"
			"-1 : Forces ssh to try protocol version 1 only\n"
			"-2 : Forces ssh to try protocol version 2 only\n"
			"-4 : Forces ssh to try protocol version 2 only\n"
			"-6 : Forces ssh to use IPv6 addresses only";

		UEventSendMessage(GetWidID(), EV_SETTEXT, kOptionsText, (void *)helpMsg.c_str());

		UEventSendMessage(GetWidID(), EV_COMBO_RESETALL, kRSAEdit, 0L);
		const CMString::list_t & rsastrings = AuthenSSH::gRsaIdentityFile.GetList();
		CMString::list_t::const_iterator i;
		for(i = rsastrings.begin(); i != rsastrings.end(); ++i)
		{
			UEventSendMessage(GetWidID(), EV_COMBO_APPEND, kRSAEdit,
				(void *)(const char *)*i);
		}
		
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kRSACheck, m_rsaidentity), 0L);
		UEventSendMessage(GetWidID(), EV_SETTEXT, kRSAEdit, (void *)m_rsaidentityfile.c_str());

		if(!AuthenSSH::gSSHName.empty())
		{
			UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kSSHCheck, 1), 0L);
			UEventSendMessage(GetWidID(), EV_SETTEXT, kSSHName, (void *)AuthenSSH::gSSHName.c_str());
		}

		if(!AuthenSSH::gSSHOptions.empty())
		{
			UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kOptionsCheck, 1), 0L);
			UEventSendMessage(GetWidID(), EV_SETTEXT, kOptionsEdit, (void *)AuthenSSH::gSSHOptions.c_str());
		}

		OnCheckRSA();
		OnCheckName();
		OnCheckOptions();
	}
	else
	{
		AuthenSSH::gRsaIdentity = m_rsaidentity;
		if(m_rsaidentity)
		{
			UEventSendMessage(GetWidID(), EV_GETTEXT, kRSAEdit, &m_rsaidentityfile);
			AuthenSSH::gRsaIdentityFile.Insert(m_rsaidentityfile);
			AuthenSSH::gCurRsaIdentity = m_rsaidentityfile;
		}

		if(UEventSendMessage(GetWidID(), EV_QUERYSTATE, kSSHCheck, 0L))
			UEventSendMessage(GetWidID(), EV_GETTEXT, kSSHName, &AuthenSSH::gSSHName);
		else
			AuthenSSH::gSSHName = "";

		if(UEventSendMessage(GetWidID(), EV_QUERYSTATE, kOptionsCheck, 0L))
			UEventSendMessage(GetWidID(), EV_GETTEXT, kOptionsEdit, &AuthenSSH::gSSHOptions);
		else
			AuthenSSH::gSSHOptions = "";
	}
}

void AuthenSSH::DoSettings(void)
{
	UAuthen_SSH *dlg = new UAuthen_SSH();
	
	try
	{
#ifdef WIN32
		dlg->DoModal(IDD_SSH);
#endif
	}
	catch(...)
	{
	}
	
	delete dlg;
}

void AuthenSSH::GetSettingsDesc(UStr & desc)
{
	if(!gSSHOptions.empty())
	{
		desc << "Using ";
		desc << gSSHOptions.c_str();
		desc << " ";
	}
	
	if((bool)gRsaIdentity)
	{
		if(gSSHOptions.empty())
			desc << "Using ";
		desc << "-i ";
		desc << RsaIdentityFile();
	}
}

void AuthenSSH::OnSetupEnv(void)
{
	AuthenModel::OnSetupEnv();

	UStr rsh("CVS_RSH=");
	if(gSSHName.empty())
		rsh << "ssh";
	else
		rsh << gSSHName.c_str();

	putenv(rsh);

	UStr options("CVS_RSA_OPTIONS=");
	if(!gSSHOptions.empty())
	{
		options << gSSHOptions.c_str();
		putenv(options);
	}

	UStr identity("CVS_RSA_IDENTITY=");
	if((bool)gRsaIdentity)
	{
		identity << RsaIdentityFile();
		putenv(identity);
	}
}

void AuthenSSH::OnRestoreEnv(void)
{
	AuthenModel::OnRestoreEnv();

	putenv("CVS_RSH=");
	putenv("CVS_RSA_OPTIONS=");
	putenv("CVS_RSA_IDENTITY=");
}

char * AuthenSSH::OnGetEnv(char *env)
{
	if(strcmp(env, "CVS_RSH") == 0)
	{
		return gSSHName.empty() ? "ssh" : (char *)gSSHName.c_str();
	}
	else if( strcmp(env, "CVS_RSA_OPTIONS") == 0)
	{
		return gSSHOptions.empty() ? 0L : (char *)gSSHOptions.c_str();
	}
	else if( strcmp(env, "CVS_RSA_IDENTITY") == 0 && (bool)gRsaIdentity)
	{
		const char *tmp = RsaIdentityFile();
		if( tmp && *tmp )
		{
#ifdef WIN32
			while( *tmp && iswspace( *tmp ) )
#else
			while( *tmp && isspace( *tmp ) )
#endif
				tmp++;

			return *tmp ? (char *)tmp : 0L;
		}
		return 0L;
	}
	return 0L;
}
#endif // qMacCvsPP

bool CvsPrefs::UseKeyChain(void) const
{
#if qMacCvsPP
	return (bool)AuthenLSH::gLSHKeychain;
#else
	return false;
#endif
}

class AuthenPserver : public AuthenModel
{
public :
	AuthenPserver() {}
	virtual ~AuthenPserver() {}

	virtual AuthenKind GetKind(void) const { return pserver; }
	virtual const char *GetToken(void) const { return "pserver"; }

	virtual bool HasSettings(void) const { return true; }
	virtual bool HasHost(void) const { return true; }
	virtual bool HasUser(void) const { return true; }

	virtual void DoSettings(void);
	virtual void GetSettingsDesc(UStr & desc);

	virtual char * OnGetEnv(char *env);

	virtual void OnSetupEnv(void);
	virtual void OnRestoreEnv(void);
};

class UAuthen_Pserver : public UWidget
{
	UDECLARE_DYNAMIC(UAuthen_Pserver)
public:
	UAuthen_Pserver();
	virtual ~UAuthen_Pserver() {}

	enum
	{
		kOK = EV_COMMAND_START,	// 0
		kCancel,			// 1
		kCheckPort,			// 2
		kEditPort,			// 3
		kCheckProxy,		// 4
		kNameProxy,			// 5
		kEditProxy			// 6
	};

	virtual void DoDataExchange(bool fill);
	
protected:
	int m_port;
	int m_pport;
	UStr m_pname;
	
	ev_msg int OnCheckPort(void);
	ev_msg int OnCheckProxy(void);
	
	UDECLARE_MESSAGE_MAP()
};

UIMPLEMENT_DYNAMIC(UAuthen_Pserver, UWidget)

UBEGIN_MESSAGE_MAP(UAuthen_Pserver, UWidget)
	ON_UCOMMAND(kCheckPort, OnCheckPort)
	ON_UCOMMAND(kCheckProxy, OnCheckProxy)
UEND_MESSAGE_MAP()

UAuthen_Pserver::UAuthen_Pserver() : UWidget(::UEventGetWidID())
{
}

int UAuthen_Pserver::OnCheckPort(void)
{
	int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckPort, 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditPort, state), 0L);

	return 0;
}

int UAuthen_Pserver::OnCheckProxy(void)
{
	int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckProxy, 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kNameProxy, state), 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditProxy, state), 0L);

	return 0;
}

void UAuthen_Pserver::DoDataExchange(bool fill)
{
	if(fill)
	{
		m_port = gCvsPrefs.PserverPort();

		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditPort, m_port != -1), 0L);
		UEventSendMessage(GetWidID(), EV_SETINTEGER, UMAKEINT(kEditPort, m_port == -1 ? 0 : m_port), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckPort, m_port != -1), 0L);

		m_pport = gCvsPrefs.ProxyPort();
		m_pname = gCvsPrefs.ProxyHost();
		bool useProxy = gCvsPrefs.UseProxy();
		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditProxy, useProxy), 0L);
		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kNameProxy, useProxy), 0L);
		UEventSendMessage(GetWidID(), EV_SETINTEGER, UMAKEINT(kEditProxy, m_pport), 0L);
		UEventSendMessage(GetWidID(), EV_SETTEXT, kNameProxy, (void *)(const char *)m_pname);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckProxy, useProxy), 0L);
	}
	else
	{
		int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckPort, 0L);
		int value = UEventSendMessage(GetWidID(), EV_GETINTEGER, kEditPort, 0L);
		gCvsPrefs.SetPserverPort(state ? value : -1);
		
		state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckProxy, 0L);
		value = UEventSendMessage(GetWidID(), EV_GETINTEGER, kEditProxy, 0L);
		UEventSendMessage(GetWidID(), EV_GETTEXT, kNameProxy, &m_pname);
		gCvsPrefs.SetUseProxy(state != 0);
		if(state != 0)
		{
			gCvsPrefs.SetProxyHost(m_pname);
			gCvsPrefs.SetProxyPort(value);
		}
	}
}

void AuthenPserver::DoSettings(void)
{
	UAuthen_Pserver *dlg = new UAuthen_Pserver();
	
	try
	{
#if qMacCvsPP
		dlg->DoModal(3002);
#endif
#ifdef WIN32
		dlg->DoModal(IDD_PSERVER);
#endif
	}
	catch(...)
	{
	}
	
	delete dlg;
}

void AuthenPserver::GetSettingsDesc(UStr & desc)
{
	if(gCvsPrefs.PserverPort() == -1)
		desc << "Using default port and ";
	else
		desc << "Using port " << gCvsPrefs.PserverPort() << " and ";

	if(!gCvsPrefs.UseProxy())
		desc << "no proxy";
	else
		desc << "using proxy host " << gCvsPrefs.ProxyHost() << " with port " << gCvsPrefs.ProxyPort();
}

void AuthenPserver::OnSetupEnv(void)
{
	AuthenModel::OnSetupEnv();

	UStr options("CVS_PSERVER_PORT=");
	if(gCvsPrefs.PserverPort() != -1)
	{
		options << gCvsPrefs.PserverPort();
		putenv(options);
	}
}

void AuthenPserver::OnRestoreEnv(void)
{
	AuthenModel::OnRestoreEnv();

	putenv("CVS_PSERVER_PORT=");
}

char * AuthenPserver::OnGetEnv(char *env)
{
	if(strcmp(env, "CVS_PSERVER_PORT") == 0)
	{
		if(gCvsPrefs.PserverPort() != -1)
		{
			static char port[10];
			sprintf(port, "%d", gCvsPrefs.PserverPort());
			return port;
		}
		
		return 0L;
	}
	return 0L;
}

class AuthenRhosts : public AuthenModel
{
public :
	AuthenRhosts() {}
	virtual ~AuthenRhosts() {}

	virtual AuthenKind GetKind(void) const { return rhosts; }
	virtual const char *GetToken(void) const { return "rhosts"; }

	virtual bool HasSettings(void) const { return true; }
	virtual bool HasHost(void) const { return true; }
	virtual bool HasUser(void) const { return true; }

	virtual void DoSettings(void);
	virtual void GetSettingsDesc(UStr & desc);

	virtual char * OnGetEnv(char *env);

	virtual void OnSetupEnv(void);
	virtual void OnRestoreEnv(void);
};

class UAuthen_Rhosts : public UWidget
{
	UDECLARE_DYNAMIC(UAuthen_Rhosts)
public:
	UAuthen_Rhosts();
	virtual ~UAuthen_Rhosts() {}

	enum
	{
		kOK = EV_COMMAND_START,	// 0
		kCancel,			// 1
		kCheckPort,			// 2
		kEditPort,			// 3
		kCheckRSHName,		// 4
		kEditRSHName,		// 5
		kCheckServerName,	// 6
		kEditServerName		// 7
	};

	virtual void DoDataExchange(bool fill);
	
protected:
	int m_port;
	UStr m_rshname;
	UStr m_servername;
	
	ev_msg int OnCheckPort(void);
	ev_msg int OnCheckRshName(void);
	ev_msg int OnCheckServerName(void);
	
	UDECLARE_MESSAGE_MAP()
};

UIMPLEMENT_DYNAMIC(UAuthen_Rhosts, UWidget)

UBEGIN_MESSAGE_MAP(UAuthen_Rhosts, UWidget)
	ON_UCOMMAND(kCheckPort, OnCheckPort)
	ON_UCOMMAND(kCheckRSHName, OnCheckRshName)
	ON_UCOMMAND(kCheckServerName, OnCheckServerName)
UEND_MESSAGE_MAP()

UAuthen_Rhosts::UAuthen_Rhosts() : UWidget(::UEventGetWidID())
{
}

int UAuthen_Rhosts::OnCheckPort(void)
{
	int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckPort, 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditPort, state), 0L);

	return 0;
}

int UAuthen_Rhosts::OnCheckRshName(void)
{
	int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckRSHName, 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditRSHName, state), 0L);

	return 0;
}

int UAuthen_Rhosts::OnCheckServerName(void)
{
	int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckServerName, 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditServerName, state), 0L);

	return 0;
}

void UAuthen_Rhosts::DoDataExchange(bool fill)
{
	if(fill)
	{
		m_port = gCvsPrefs.RhostPort();
		m_rshname = gCvsPrefs.RshName();
		m_servername = gCvsPrefs.ServerName();

		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditPort, m_port != -1), 0L);
		UEventSendMessage(GetWidID(), EV_SETINTEGER, UMAKEINT(kEditPort, m_port == -1 ? 0 : m_port), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckPort, m_port != -1), 0L);

		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditRSHName, !m_rshname.empty()), 0L);
		UEventSendMessage(GetWidID(), EV_SETTEXT, kEditRSHName, (void *)m_rshname.c_str());
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckRSHName, !m_rshname.empty()), 0L);

		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditServerName, !m_servername.empty()), 0L);
		UEventSendMessage(GetWidID(), EV_SETTEXT, kEditServerName, (void *)m_servername.c_str());
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckServerName, !m_servername.empty()), 0L);
	}
	else
	{
		int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckPort, 0L);
		int value = UEventSendMessage(GetWidID(), EV_GETINTEGER, kEditPort, 0L);
		gCvsPrefs.SetRhostPort(state ? value : -1);
		
		state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckRSHName, 0L);
		UEventSendMessage(GetWidID(), EV_GETTEXT, kEditRSHName, &m_rshname);
		gCvsPrefs.SetRshName(state ? m_rshname.c_str() : "");

		state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckServerName, 0L);
		UEventSendMessage(GetWidID(), EV_GETTEXT, kEditServerName, &m_servername);
		gCvsPrefs.SetServerName(state ? m_servername.c_str() : "");
	}
}

void AuthenRhosts::DoSettings(void)
{
	UAuthen_Rhosts *dlg = new UAuthen_Rhosts();
	
	try
	{
#if qMacCvsPP
		dlg->DoModal(3001);
#endif
#if WIN32
		dlg->DoModal(IDD_RSH);
#endif
	}
	catch(...)
	{
	}
	
	delete dlg;
}

void AuthenRhosts::GetSettingsDesc(UStr & desc)
{
	if(gCvsPrefs.RhostPort() == -1)
		desc << "Using default port and ";
	else
		desc << "Using port " << gCvsPrefs.RhostPort() << " and ";

	if(gCvsPrefs.RshName() == 0L)
		desc << "default shell name";
	else
		desc << "shell named " << gCvsPrefs.RshName();
}

void AuthenRhosts::OnSetupEnv(void)
{
	AuthenModel::OnSetupEnv();

	UStr rsh("CVS_RSH=");
	if(gCvsPrefs.RshName() != 0L)
	{
		rsh << gCvsPrefs.RshName();
		putenv(rsh);
	}

	UStr port("CVS_RCMD_PORT=");
	if(gCvsPrefs.RhostPort() != -1)
	{
		port << gCvsPrefs.RhostPort();
		putenv(port);
	}

	UStr server("CVS_SERVER=");
	if(gCvsPrefs.ServerName() != 0L)
	{
		server << gCvsPrefs.ServerName();
		putenv(server);
	}
}

void AuthenRhosts::OnRestoreEnv(void)
{
	AuthenModel::OnRestoreEnv();

	putenv("CVS_RSH=");
	putenv("CVS_RCMD_PORT=");
	putenv("CVS_SERVER=");
}

char * AuthenRhosts::OnGetEnv(char *env)
{
	if(strcmp(env, "CVS_RSH") == 0)
	{
		return (char *)gCvsPrefs.RshName();
	}
	else if(strcmp(env, "CVS_RCMD_PORT") == 0)
	{
		if(gCvsPrefs.RhostPort() != -1)
		{
			static char port[10];
			sprintf(port, "%d", gCvsPrefs.RhostPort());
			return port;
		}
		
		return 0L;
	}
	else if(strcmp(env, "CVS_SERVER") == 0)
	{
		return (char *)gCvsPrefs.ServerName();
	}
	return 0L;
}

class AuthenKserver : public AuthenModel
{
public :
	AuthenKserver() {}
	virtual ~AuthenKserver() {}

	virtual AuthenKind GetKind(void) const { return kserver; }
	virtual const char *GetToken(void) const { return "kserver"; }

	virtual bool HasSettings(void) const { return true; }
	virtual bool HasHost(void) const { return true; }
	virtual bool HasUser(void) const { return true; }

	virtual void DoSettings(void);
	virtual void GetSettingsDesc(UStr & desc);

	virtual char * OnGetEnv(char *env);
};

class UAuthen_Kserver : public UWidget
{
	UDECLARE_DYNAMIC(UAuthen_Kserver)
public:
	UAuthen_Kserver();
	virtual ~UAuthen_Kserver() {}

	enum
	{
		kOK = EV_COMMAND_START,	// 0
		kCancel,			// 1
		kCheckPort,			// 2
		kEditPort			// 3
	};

	virtual void DoDataExchange(bool fill);
	
protected:
	int m_port;
	
	ev_msg int OnCheckPort(void);
	
	UDECLARE_MESSAGE_MAP()
};

UIMPLEMENT_DYNAMIC(UAuthen_Kserver, UWidget)

UBEGIN_MESSAGE_MAP(UAuthen_Kserver, UWidget)
	ON_UCOMMAND(kCheckPort, OnCheckPort)
UEND_MESSAGE_MAP()

UAuthen_Kserver::UAuthen_Kserver() : UWidget(::UEventGetWidID())
{
}

int UAuthen_Kserver::OnCheckPort(void)
{
	int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckPort, 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditPort, state), 0L);

	return 0;
}

void UAuthen_Kserver::DoDataExchange(bool fill)
{
	if(fill)
	{
		m_port = gCvsPrefs.KserverPort();

		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kEditPort, m_port != -1), 0L);
		UEventSendMessage(GetWidID(), EV_SETINTEGER, UMAKEINT(kEditPort, m_port == -1 ? 0 : m_port), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckPort, m_port != -1), 0L);
	}
	else
	{
		int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckPort, 0L);
		int value = UEventSendMessage(GetWidID(), EV_GETINTEGER, kEditPort, 0L);
		gCvsPrefs.SetKserverPort(state ? value : -1);
	}
}

void AuthenKserver::DoSettings(void)
{
	UAuthen_Kserver *dlg = new UAuthen_Kserver();
	
	try
	{
#if qMacCvsPP
		dlg->DoModal(3003);
#endif
	}
	catch(...)
	{
	}
	
	delete dlg;
}

void AuthenKserver::GetSettingsDesc(UStr & desc)
{
	if(gCvsPrefs.KserverPort() == -1)
		desc << "Using default port";
	else
		desc << "Using port " << gCvsPrefs.KserverPort();
}

char * AuthenKserver::OnGetEnv(char *env)
{
	return 0L;
}

class AuthenNtserver : public AuthenModel
{
public :
	AuthenNtserver() {}
	virtual ~AuthenNtserver() {}

	virtual AuthenKind GetKind(void) const { return ntserver; }
	virtual const char *GetToken(void) const { return "ntserver"; }

	virtual bool HasSettings(void) const { return false; }
	virtual bool HasHost(void) const { return true; }
	virtual bool HasUser(void) const { return true; }

	virtual char * OnGetEnv(char *env);
};

char * AuthenNtserver::OnGetEnv(char *env)
{
	return 0L;
}

static AuthenLocal localInstance;
static AuthenPserver pserverInstance;
static AuthenRhosts rhostsInstance;
#ifndef WIN32
static AuthenKserver kserverInstance;
#else
static AuthenNtserver ntserverInstance;
#endif
#if qMacCvsPP
static AuthenLSH sshInstance;
#else
static AuthenSSH sshInstance;
#endif
