using System;
using System.Collections;
using System.DirectoryServices;
using System.Diagnostics;
#if MONO
#else
using ActiveDs;
#endif
using OpenPOP.POP3;
using Orciid.Core.Util;

namespace Orciid.Core
{
	/// <summary>
	/// User Authentication class
	/// </summary>
	/// <remarks>
	/// This class must be used as the base class for all user authentication mechanisms.
	/// Every class must implement two methods: <see cref="Authenticate"/>, which takes a user name
	/// and password as arguments and returns a boolean containing the success status, and 
	/// <see cref="GetAuthenticationType"/>, which returns a string constant containing the name
	/// of the authentication method the class represents.
	/// To activate a new authentication method's class, create one instance of the class and pass
	/// it to the static <see cref="AddMethod"/> method. To deactivate methods, call 
	/// <see cref="ResetMethods"/>.
	/// All derived classes must be cloneable, since for every authentication taking place, the originally
	/// passed object is cloned; so it is safe to store user specific information in the clone.
	/// </remarks>
	public abstract class UserAuthentication
	{
		/// <summary>
		/// List of all registered user authentication methods.
		/// </summary>
		/// <remarks>
		/// This field is an <see cref="ArrayList"/> containing a copy of all 
		/// registered authentication methods.
		/// </remarks>
		internal static ArrayList methods = new ArrayList();

		/// <summary>
		/// Add user authentication method
		/// </summary>
		/// <remarks>
		/// Call this method with a user authentication class type to register that method
		/// for user authentication. Authentication methods are tried in the order in which they are
		/// registered. Internal authentication does not have to be registered and is always tried first.
		/// </remarks>
		/// <param name="method">A user authentication class type</param>
		public static void AddMethod(System.Type method)
		{
			methods.Add(method);
		}

		/// <summary>
		/// Remove all user authentication methods
		/// </summary>
		/// <remarks>
		/// This method removes all previously registered user authentication methods.  Only
		/// internal authentication is available until other methods are registered again
		/// using <see cref="AddMethod"/>.
		/// </remarks>
		public static void ResetMethods()
		{
			methods.Clear();
		}

		/// <summary>
		/// Authenticate user
		/// </summary>
		/// <remarks>
		/// This method authenticates a user with the user's name and password.
		/// </remarks>
		/// <param name="username">The login name of the user</param>
		/// <param name="password">The user's password</param>
		/// <returns><c>true</c> if the credentials are correct, <c>false</c> otherwise</returns>
		public abstract bool Authenticate(string username, string password);

		/// <summary>
		/// Authentication method identifier
		/// </summary>
		/// <remarks>
		/// Every authentication method must have a unique identification string.
		/// </remarks>
		/// <returns>The identification string for this authentication method</returns>
		public abstract string GetAuthenticationType();

		/// <summary>
		/// The authenticated user's first name
		/// </summary>
		/// <remarks>
		/// If an authentication method has access to user account information, it
		/// should provide the user's first name with this method.
		/// </remarks>
		/// <returns>The user's first name</returns>
		public abstract string GetFirstName();

		/// <summary>
		/// The authenticated user's last name
		/// </summary>
		/// <remarks>
		/// If an authentication method has access to user account information, it
		/// should provide the user's last name with this method.
		/// </remarks>
		/// <returns>The user's last name</returns>
		public abstract string GetLastName();

		/// <summary>
		/// The authenticated user's email address
		/// </summary>
		/// <remarks>
		/// If an authentication method has access to user account information, it
		/// should provide the user's email address with this method.
		/// </remarks>
		/// <returns>The user's email address</returns>
		public abstract string GetEmail();

		/// <summary>
		/// The authenticated user's password
		/// </summary>
		/// <remarks>
		/// This method is used if users are authenticated against an external directory,
		/// but should be authenticated internally in the future.  If this method returns <c>null</c>,
		/// future authentication attempts will still be performed against the external directory,
		/// if it returns a non-null string, that string is set to be the new internal password.
		/// </remarks>
		/// <returns>The password to be used for internal authentication in the future, or 
		/// <c>null</c> if external authentication should be kept.</returns>
		public abstract string GetPassword();

		/// <summary>
		/// Get user attributes
		/// </summary>
		/// <remarks>
		/// Attributes are certain properties for the user that are retrieved upon login,
		/// e.g. from an external authentication source.  Attributes are not stored within
		/// the system and are not available unless a user is currently logged in.  Not all
		/// authentication sources must or can provide attributes.
		/// The hashtable must have attribute names as keys and either a string (for
		/// single value attributes) or an arraylist of strings (for multiple value attributes)
		/// as values.
		/// </remarks>
		/// <returns>A hashtable containing the available attributes.</returns>
		public abstract Hashtable GetAttributes();

		/// <summary>
		/// Initialize user authentication class
		/// </summary>
		/// <remarks>
		/// Adds a configuration file change event handler to be notified when the configuration
		/// changes.
		/// </remarks>
		[Initialize]
		public static void Initialize()
		{
			Configuration.Instance.OnChange += new OnConfigurationChangeDelegate(Configuration_OnChange);
		}

		private static void Configuration_OnChange(IConfiguration configuration, object arg)
		{
			ResetMethods();
			if (configuration.ContainsKey("authentication.method"))
				foreach (string m in configuration.GetMultiString("authentication.method"))
					AddMethod(Type.GetType(m));
		}
	}

/// <summary>
	/// Active Directory User Authentication class
	/// </summary>
	/// <remarks>
	/// This class implements the Active DIrectory user authentication mechanism.
	/// To activate this method, pass this type
	/// to the static <see cref="UserAuthentication.AddMethod"/> method. 
	/// </remarks>
	public class ActiveDirectoryAuthentication:
		UserAuthentication
	{
		private Hashtable attributes = new Hashtable();

		private object CombineValues(ICollection p)
		{
			if (p.Count == 0)
				return null;
			else if (p.Count == 1)
			{
				foreach (object o in p)
					return o; // return first object
				return null; // dummy statement to satisfy compiler
			}
			else
				return new ArrayList(p);
		}

		/// <summary>
		/// Authenticate user
		/// </summary>
		/// <remarks>
		/// This method authenticates a user with the user's name and password.
		/// </remarks>
		/// <param name="username">The login name of the user</param>
		/// <param name="password">The user's password</param>
		/// <returns><c>true</c> if the credentials are correct, <c>false</c> otherwise</returns>
		public override bool Authenticate(string username, string password)
		{
			// Some LDAP servers return success if no password is specified, 
			// so don't allow blank passwords
			if (password == null || password.Length == 0)
				return false;
			string[] queries = Configuration.Instance.GetMultiString("authentication.activeDirectory.querypath");
			bool secure = Configuration.Instance.ContainsKey("authentication.activeDirectory.ssl") &&
				Configuration.Instance.GetBool("authentication.activeDirectory.ssl");

			string userprefix = "";
			if (Configuration.Instance.ContainsKey("authentication.activeDirectory.userprefix"))
				userprefix = Configuration.Instance.GetString("authentication.activeDirectory.userprefix");

            foreach (string q in queries)
            {
                
                try
                {
                    attributes.Clear();
                    ArrayList requiredprops = new ArrayList();
                    if (Configuration.Instance.ContainsKey("authentication.activeDirectory.fields.email"))
                        requiredprops.Add(Configuration.Instance.GetString("authentication.activeDirectory.fields.email"));
                    if (Configuration.Instance.ContainsKey("authentication.activeDirectory.fields.firstname"))
                        requiredprops.Add(Configuration.Instance.GetString("authentication.activeDirectory.fields.firstname"));
                    if (Configuration.Instance.ContainsKey("authentication.activeDirectory.fields.lastname"))
                        requiredprops.Add(Configuration.Instance.GetString("authentication.activeDirectory.fields.lastname"));
                    if (Configuration.Instance.ContainsKey("authentication.activeDirectory.fields.additional"))
                        requiredprops.AddRange(Configuration.Instance.GetMultiString("authentication.activeDirectory.fields.additional"));
                    AuthenticationTypes flags = AuthenticationTypes.Secure;
                    if (secure)
                        flags |= AuthenticationTypes.SecureSocketsLayer;
                    string ldapBind = String.Format("LDAP://{0}/{1}",
                    Configuration.Instance.GetString("authentication.activeDirectory.server"), q);
                    DirectoryEntry userEntry = new DirectoryEntry(ldapBind, userprefix + username, password, flags);
                    DirectorySearcher search = new DirectorySearcher(userEntry);
                    search.Filter = String.Format("(sAMAccountName={0})", username);
                    search.PropertiesToLoad.Add("distinguishedName");
                    SearchResult result = search.FindOne();
                    string query = result.Properties["distinguishedName"][0].ToString();
                    string path = String.Format("LDAP://{0}/{1}",
                    Configuration.Instance.GetString("authentication.activeDirectory.server"), query);
                    DirectoryEntry entry = new DirectoryEntry(path, userprefix + username, password, flags);
                    if (entry.Properties.PropertyNames.Count > 0)
                    {
#if OLDLDAPAUTH
// the old way of doing things: for some LDAP servers, a loop over all properties is required
						foreach (string property in entry.Properties.PropertyNames)
							if (requiredprops.Contains(property)) 
#else
                        // a more efficient loop over only the required properties, does not seem to work everywhere
                        foreach (string property in requiredprops)
                            if (entry.Properties.Contains(property))
#endif
                                try
                                {
                                    attributes.Add(property, CombineValues(entry.Properties[property]));
                                }
                                catch
                                {
                                    try
                                    {
                                        attributes.Add(property, CombineValues(GetLdapProperty(entry, property)));
                                    }
                                    catch
                                    {
                                    }
                                }
                        return true;
                    }
                    // no properties found, something is wrong
                }
                catch (Exception ex)
                {
                    TransactionLog.Instance.AddException("ActiveDirectoryAuthentication", ex);
                }
            }
			return false;
		}

		// this method is a work around to the regular entry.Properties[property], which
		// causes exceptions for certain properties on certain LDAP servers
		private static object[] GetLdapProperty(DirectoryEntry direntry, string property)
		{
#if MONO
			return null;
#else
			direntry.RefreshCache(new string[] { property });
			IADsPropertyEntry entry = 
				(IADsPropertyEntry)((IADsPropertyList)direntry.NativeObject).GetPropertyItem(
				property, (int)ADSTYPEENUM.ADSTYPE_DN_STRING);
			ArrayList values = new ArrayList();
			foreach (IADsPropertyValue v in (object[])entry.Values)
				values.Add(v.DNString);
			return values.ToArray();
#endif
		}

		/// <summary>
		/// Authentication method identifier
		/// </summary>
		/// <remarks>
		/// The authentication method identifier for this method is <c>ldap</c>.
		/// </remarks>
		/// <returns><c>ldap</c></returns>
		public override string GetAuthenticationType()
		{
			return "activeDirectory";
		}

		private string GetSingleValueAttribute(string name)
		{
			object a = attributes[name];
			if (a == null)
				return null;
			if (a is ArrayList)
				return (string)((ArrayList)a)[0];
			if (a is string)
				return (string)a;
			throw new CoreException("Internal error");
		}

		/// <summary>
		/// The authenticated user's first name
		/// </summary>
		/// <remarks>
		/// This method returns the user's first name, if it is available in
		/// the LDAP directory.
		/// </remarks>
		/// <returns>The user's first name</returns>
		public override string GetFirstName()
		{
			if (Configuration.Instance.ContainsKey("authentication.activeDirectory.fields.firstname"))
				return GetSingleValueAttribute(
					Configuration.Instance.GetString("authentication.activeDirectory.fields.firstname"));
			else
				return null;
		}

		/// <summary>
		/// The authenticated user's last name
		/// </summary>
		/// <remarks>
		/// This method returns the user's last name, if it is available in
		/// the LDAP directory.
		/// </remarks>
		/// <returns>The user's last name</returns>
		public override string GetLastName()
		{
			if (Configuration.Instance.ContainsKey("authentication.activeDirectory.fields.lastname"))
				return GetSingleValueAttribute(
					Configuration.Instance.GetString("authentication.activeDirectory.fields.lastname"));
			else
				return null;		
		}

		/// <summary>
		/// The authenticated user's email address
		/// </summary>
		/// <remarks>
		/// This method returns the user's email address, if it is available in
		/// the LDAP directory.
		/// </remarks>
		/// <returns>The user's email address</returns>
		public override string GetEmail()
		{
			if (Configuration.Instance.ContainsKey("authentication.activeDirectory.fields.email"))
				return GetSingleValueAttribute(
					Configuration.Instance.GetString("authentication.activeDirectory.fields.email"));
			else
				return null;
		}

		/// <summary>
		/// The authenticated user's password
		/// </summary>
		/// <remarks>
		/// This method is used if users are authenticated against an external directory,
		/// but should be authenticated internally in the future.  If this method returns <c>null</c>,
		/// future authentication attempts will still be performed against the external directory,
		/// if it returns a non-null string, that string is set to be the new internal password.
		/// </remarks>
		/// <returns><c>null</c></returns>
		public override string GetPassword()
		{
			return null;
		}

		/// <summary>
		/// Get user attributes
		/// </summary>
		/// <remarks>
		/// Attributes are certain properties for the user that are retrieved upon login,
		/// e.g. from an external authentication source.  Attributes are not stored within
		/// the system and are not available unless a user is currently logged in.  Not all
		/// authentication sources must or can provide attributes.
		/// This authentication class returns attributes and values as specified in the configuration
		/// file.  Only attributes listed under <c>authentication.activeDirectory.fields.additional</c>
		/// will be returned.
		/// </remarks>
		/// <returns>A hashtable containing the available attributes.</returns>
		public override Hashtable GetAttributes()
		{
			if (Configuration.Instance.ContainsKey("authentication.activeDirectory.fields.additional"))
			{
				Hashtable temp = new Hashtable();
				foreach (string s in Configuration.Instance.GetMultiString("authentication.activeDirectory.fields.additional"))
					if (attributes.ContainsKey(s))
						temp.Add(s, attributes[s]);
				return temp;
			}
			return null;
		}
	}

/// <summary>
	/// LDAP User Authentication class
	/// </summary>
	/// <remarks>
	/// This class implements the LDAP user authentication mechanism.
	/// To activate this method, pass this type
	/// to the static <see cref="UserAuthentication.AddMethod"/> method. 
	/// </remarks>
	public class LdapUserAuthentication:
		UserAuthentication
	{
		private Hashtable attributes = new Hashtable();

		private object CombineValues(ICollection p)
		{
			if (p.Count == 0)
				return null;
			else if (p.Count == 1)
			{
				foreach (object o in p)
					return o; // return first object
				return null; // dummy statement to satisfy compiler
			}
			else
				return new ArrayList(p);
		}

		/// <summary>
		/// Authenticate user
		/// </summary>
		/// <remarks>
		/// This method authenticates a user with the user's name and password.
		/// </remarks>
		/// <param name="username">The login name of the user</param>
		/// <param name="password">The user's password</param>
		/// <returns><c>true</c> if the credentials are correct, <c>false</c> otherwise</returns>
		public override bool Authenticate(string username, string password)
		{
			// Some LDAP servers return success if no password is specified, 
			// so don't allow blank passwords
			if (password == null || password.Length == 0)
				return false;
			string[] queries = Configuration.Instance.GetMultiString("authentication.ldap.querypath");
			bool secure = Configuration.Instance.ContainsKey("authentication.ldap.ssl") &&
				Configuration.Instance.GetBool("authentication.ldap.ssl");
			foreach (string q in queries)
			{
				string query = String.Format(q, username);
				string path = String.Format("LDAP://{0}/{1}",
					Configuration.Instance.GetString("authentication.ldap.server"),
					query);
				try 
				{
					attributes.Clear();
					ArrayList requiredprops = new ArrayList();
					if (Configuration.Instance.ContainsKey("authentication.ldap.fields.email"))
						requiredprops.Add(Configuration.Instance.GetString("authentication.ldap.fields.email"));
					if (Configuration.Instance.ContainsKey("authentication.ldap.fields.firstname"))
						requiredprops.Add(Configuration.Instance.GetString("authentication.ldap.fields.firstname"));
					if (Configuration.Instance.ContainsKey("authentication.ldap.fields.lastname"))
						requiredprops.Add(Configuration.Instance.GetString("authentication.ldap.fields.lastname"));
					if (Configuration.Instance.ContainsKey("authentication.ldap.fields.additional"))
						requiredprops.AddRange(Configuration.Instance.GetMultiString("authentication.ldap.fields.additional"));
					AuthenticationTypes flags = AuthenticationTypes.ServerBind;
					if (secure)
						flags |= AuthenticationTypes.SecureSocketsLayer;
					DirectoryEntry entry = new DirectoryEntry(path, query, password, flags);
					if (entry.Properties.PropertyNames.Count > 0)
					{
#if OLDLDAPAUTH
// the old way of doing things: for some LDAP servers, a loop over all properties is required
						foreach (string property in entry.Properties.PropertyNames)
							if (requiredprops.Contains(property)) 
#else
// a more efficient loop over only the required properties, does not seem to work everywhere
						foreach (string property in requiredprops)
							if (entry.Properties.Contains(property))
#endif
								try
								{
									attributes.Add(property, CombineValues(entry.Properties[property]));
								}
								catch 
								{
									try
									{
										attributes.Add(property, CombineValues(GetLdapProperty(entry, property)));
									}
									catch 
									{
									}
								}
						return true;
					}
					// no properties found, something is wrong
				}
				catch (Exception ex)
				{
					TransactionLog.Instance.AddException("LdapUserAuthentication", ex);
				}
			}
			return false;
		}

		// this method is a work around to the regular entry.Properties[property], which
		// causes exceptions for certain properties on certain LDAP servers
		private static object[] GetLdapProperty(DirectoryEntry direntry, string property)
		{
#if MONO
			return null;
#else
			direntry.RefreshCache(new string[] { property });
			IADsPropertyEntry entry = 
				(IADsPropertyEntry)((IADsPropertyList)direntry.NativeObject).GetPropertyItem(
				property, (int)ADSTYPEENUM.ADSTYPE_DN_STRING);
			ArrayList values = new ArrayList();
			foreach (IADsPropertyValue v in (object[])entry.Values)
				values.Add(v.DNString);
			return values.ToArray();
#endif
		}

		/// <summary>
		/// Authentication method identifier
		/// </summary>
		/// <remarks>
		/// The authentication method identifier for this method is <c>ldap</c>.
		/// </remarks>
		/// <returns><c>ldap</c></returns>
		public override string GetAuthenticationType()
		{
			return "ldap";
		}

		private string GetSingleValueAttribute(string name)
		{
			object a = attributes[name];
			if (a == null)
				return null;
			if (a is ArrayList)
				return (string)((ArrayList)a)[0];
			if (a is string)
				return (string)a;
			throw new CoreException("Internal error");
		}

		/// <summary>
		/// The authenticated user's first name
		/// </summary>
		/// <remarks>
		/// This method returns the user's first name, if it is available in
		/// the LDAP directory.
		/// </remarks>
		/// <returns>The user's first name</returns>
		public override string GetFirstName()
		{
			if (Configuration.Instance.ContainsKey("authentication.ldap.fields.firstname"))
				return GetSingleValueAttribute(
					Configuration.Instance.GetString("authentication.ldap.fields.firstname"));
			else
				return null;
		}

		/// <summary>
		/// The authenticated user's last name
		/// </summary>
		/// <remarks>
		/// This method returns the user's last name, if it is available in
		/// the LDAP directory.
		/// </remarks>
		/// <returns>The user's last name</returns>
		public override string GetLastName()
		{
			if (Configuration.Instance.ContainsKey("authentication.ldap.fields.lastname"))
				return GetSingleValueAttribute(
					Configuration.Instance.GetString("authentication.ldap.fields.lastname"));
			else
				return null;		
		}

		/// <summary>
		/// The authenticated user's email address
		/// </summary>
		/// <remarks>
		/// This method returns the user's email address, if it is available in
		/// the LDAP directory.
		/// </remarks>
		/// <returns>The user's email address</returns>
		public override string GetEmail()
		{
			if (Configuration.Instance.ContainsKey("authentication.ldap.fields.email"))
				return GetSingleValueAttribute(
					Configuration.Instance.GetString("authentication.ldap.fields.email"));
			else
				return null;
		}

		/// <summary>
		/// The authenticated user's password
		/// </summary>
		/// <remarks>
		/// This method is used if users are authenticated against an external directory,
		/// but should be authenticated internally in the future.  If this method returns <c>null</c>,
		/// future authentication attempts will still be performed against the external directory,
		/// if it returns a non-null string, that string is set to be the new internal password.
		/// </remarks>
		/// <returns><c>null</c></returns>
		public override string GetPassword()
		{
			return null;
		}

		/// <summary>
		/// Get user attributes
		/// </summary>
		/// <remarks>
		/// Attributes are certain properties for the user that are retrieved upon login,
		/// e.g. from an external authentication source.  Attributes are not stored within
		/// the system and are not available unless a user is currently logged in.  Not all
		/// authentication sources must or can provide attributes.
		/// This authentication class returns attributes and values as specified in the configuration
		/// file.  Only attributes listed under <c>authentication.ldap.fields.additional</c>
		/// will be returned.
		/// </remarks>
		/// <returns>A hashtable containing the available attributes.</returns>
		public override Hashtable GetAttributes()
		{
			if (Configuration.Instance.ContainsKey("authentication.ldap.fields.additional"))
			{
				Hashtable temp = new Hashtable();
				foreach (string s in Configuration.Instance.GetMultiString("authentication.ldap.fields.additional"))
					if (attributes.ContainsKey(s))
						temp.Add(s, attributes[s]);
				return temp;
			}
			return null;
		}
	}

	/// <summary>
	/// IMAP User Authentication class
	/// </summary>
	/// <remarks>
	/// This class implements the IMAP user authentication mechanism.
	/// To activate this method, pass this type
	/// to the static <see cref="UserAuthentication.AddMethod"/> method. 
	/// </remarks>
	public class ImapUserAuthentication:
		UserAuthentication
	{
		private string username = null;

		/// <summary>
		/// Authenticate user
		/// </summary>
		/// <remarks>
		/// This method authenticates a user with the user's name and password.
		/// </remarks>
		/// <param name="username">The login name of the user</param>
		/// <param name="password">The user's password</param>
		/// <returns><c>true</c> if the credentials are correct, <c>false</c> otherwise</returns>
		public override bool Authenticate(string username, string password)
		{
			string host = Configuration.Instance.GetString("authentication.imap.server");
			int port = Configuration.Instance.GetInt("authentication.imap.port");
			bool secure = Configuration.Instance.ContainsKey("authentication.imap.ssl") &&
				Configuration.Instance.GetBool("authentication.imap.ssl");
			
			SimpleImapAuth imap = new SimpleImapAuth();
			try
			{
				if (imap.Connect(host, port, secure) && 
					imap.Authenticate(username, password))
				{
					this.username = username;
					return true;
				}
			}
			finally
			{
				if (imap != null)
					imap.Disconnect();
			}
			return false;
		}

		/// <summary>
		/// Get user attributes
		/// </summary>
		/// <remarks>
		/// This type of authentication does not support attributes.
		/// </remarks>
		/// <returns><c>null</c></returns>
		public override Hashtable GetAttributes()
		{
			return null;
		}

		/// <summary>
		/// Authentication method identifier
		/// </summary>
		/// <remarks>
		/// The authentication method identifier for this method is <c>imap</c>.
		/// </remarks>
		/// <returns><c>imap</c></returns>
		public override string GetAuthenticationType()
		{
			return "imap";
		}

		/// <summary>
		/// The authenticated user's email address
		/// </summary>
		/// <remarks>
		/// This method returns the user's email address, which is assumed to be the login name
		/// combined with the email domain specified in the configuration file.
		/// </remarks>
		/// <returns>The user's email address</returns>
		public override string GetEmail()
		{
			return String.Format("{0}@{1}", username, Configuration.Instance.GetString("authentication.imap.emaildomain"));
		}

		/// <summary>
		/// The authenticated user's first name
		/// </summary>
		/// <remarks>
		/// This type of authentication does not support this property
		/// </remarks>
		/// <returns><c>null</c></returns>
		public override string GetFirstName()
		{
			return null;
		}

		/// <summary>
		/// The authenticated user's last name
		/// </summary>
		/// <remarks>
		/// This type of authentication does not support this property.  The user's login name
		/// is returned instread.
		/// </remarks>
		/// <returns>The user's login name</returns>
		public override string GetLastName()
		{
			return username;
		}

		/// <summary>
		/// The authenticated user's password
		/// </summary>
		/// <remarks>
		/// This method is used if users are authenticated against an external directory,
		/// but should be authenticated internally in the future.  If this method returns <c>null</c>,
		/// future authentication attempts will still be performed against the external directory,
		/// if it returns a non-null string, that string is set to be the new internal password.
		/// </remarks>
		/// <returns><c>null</c></returns>
		public override string GetPassword()
		{
			return null;
		}
	}

	/// <summary>
	/// POP3 User Authentication class
	/// </summary>
	/// <remarks>
	/// This class implements the POP3 user authentication mechanism.
	/// To activate this method, pass this type
	/// to the static <see cref="UserAuthentication.AddMethod"/> method. 
	/// </remarks>
	public class Pop3UserAuthentication:
		UserAuthentication
	{
		private string username = null;

		/// <summary>
		/// Authenticate user
		/// </summary>
		/// <remarks>
		/// This method authenticates a user with the user's name and password.
		/// </remarks>
		/// <param name="username">The login name of the user</param>
		/// <param name="password">The user's password</param>
		/// <returns><c>true</c> if the credentials are correct, <c>false</c> otherwise</returns>
		public override bool Authenticate(string username, string password)
		{
			string host = Configuration.Instance.GetString("authentication.pop3.server");
			int port = Configuration.Instance.GetInt("authentication.pop3.port");
			bool secure = Configuration.Instance.ContainsKey("authentication.pop3.ssl") &&
				Configuration.Instance.GetBool("authentication.pop3.ssl");

			POPClient client = new POPClient();
			try
			{
				client.Connect(host, port, secure);
				client.Authenticate(username, password, AuthenticationMethod.TRYBOTH);
				this.username = username;
				return true;
			}
			catch
			{
				return false;
			}
			finally
			{
				if (client != null)
					client.Disconnect();
			}
		}

		/// <summary>
		/// Get user attributes
		/// </summary>
		/// <remarks>
		/// This type of authentication does not support attributes.
		/// </remarks>
		/// <returns><c>null</c></returns>
		public override Hashtable GetAttributes()
		{
			return null;
		}

		/// <summary>
		/// Authentication method identifier
		/// </summary>
		/// <remarks>
		/// The authentication method identifier for this method is <c>pop3</c>.
		/// </remarks>
		/// <returns><c>pop3</c></returns>
		public override string GetAuthenticationType()
		{
			return "pop3";
		}

		/// <summary>
		/// The authenticated user's email address
		/// </summary>
		/// <remarks>
		/// This method returns the user's email address, which is assumed to be the login name
		/// combined with the email domain specified in the configuration file.
		/// </remarks>
		/// <returns>The user's email address</returns>
		public override string GetEmail()
		{
			return String.Format("{0}@{1}", username, Configuration.Instance.GetString("authentication.pop3.emaildomain"));
		}

		/// <summary>
		/// The authenticated user's first name
		/// </summary>
		/// <remarks>
		/// This type of authentication does not support this property
		/// </remarks>
		/// <returns><c>null</c></returns>
		public override string GetFirstName()
		{
			return null;
		}

		/// <summary>
		/// The authenticated user's last name
		/// </summary>
		/// <remarks>
		/// This type of authentication does not support this property.  The user's login name
		/// is returned instread.
		/// </remarks>
		/// <returns>The user's login name</returns>
		public override string GetLastName()
		{
			return username;
		}

		/// <summary>
		/// The authenticated user's password
		/// </summary>
		/// <remarks>
		/// This method is used if users are authenticated against an external directory,
		/// but should be authenticated internally in the future.  If this method returns <c>null</c>,
		/// future authentication attempts will still be performed against the external directory,
		/// if it returns a non-null string, that string is set to be the new internal password.
		/// </remarks>
		/// <returns><c>null</c></returns>
		public override string GetPassword()
		{
			return null;
		}
	}

	/// <summary>
	/// Demo Authentication class
	/// </summary>
	/// <remarks>
	/// This class is used for the MDID2 demo web site.  It allows users to create their
	/// own accounts.
	/// </remarks>
	public class DemoUserAuthentication:
		UserAuthentication
	{
		private string firstname = null;
		private string lastname = null;
		private string email = null;
		private string password = null;
	
		private static Hashtable fullnames = new Hashtable();

		/// <summary>
		/// Store users name for upcoming authentication
		/// </summary>
		/// <remarks>
		/// This method stores a name, so that an autocreated account which will be
		/// created in the near future can be supplied with this name.
		/// </remarks>
		/// <param name="email">The login name of the account to be created</param>
		/// <param name="first">The first name of the user</param>
		/// <param name="last">The last name of the user</param>
		public static void AddFullName(string email, string first, string last)
		{
			if (!fullnames.ContainsKey(email))
				fullnames.Add(email, new string[] { first, last });
		}

		/// <summary>
		/// Authenticate user
		/// </summary>
		/// <remarks>
		/// This method authenticates a user with the user's name and password.
		/// </remarks>
		/// <param name="username">The login name of the user</param>
		/// <param name="password">The user's password</param>
		/// <returns><c>true</c> if the credentials are correct, <c>false</c> otherwise</returns>
		public override bool Authenticate(string username, string password)
		{
			email = username;
			if (fullnames.ContainsKey(email))
			{
				string[] n = (string[])fullnames[email];
				fullnames.Remove(email);
				firstname = n[0];
				lastname = n[1];
				this.password = password;
				return true;
			}
			else
			{
				return false;
			}
		}

		/// <summary>
		/// Authentication method identifier
		/// </summary>
		/// <remarks>
		/// The authentication method identifier for this method is <c>ldap</c>.
		/// </remarks>
		/// <returns><c>ldap</c></returns>
		public override string GetAuthenticationType()
		{
			return "demo";
		}

		/// <summary>
		/// The authenticated user's first name
		/// </summary>
		/// <remarks>
		/// This method returns the user's first name, if it is available in
		/// the LDAP directory.
		/// </remarks>
		/// <returns>The user's first name</returns>
		public override string GetFirstName()
		{
			return firstname;
		}

		/// <summary>
		/// The authenticated user's last name
		/// </summary>
		/// <remarks>
		/// This method returns the user's last name, if it is available in
		/// the LDAP directory.
		/// </remarks>
		/// <returns>The user's last name</returns>
		public override string GetLastName()
		{
			return lastname;
		}

		/// <summary>
		/// The authenticated user's email address
		/// </summary>
		/// <remarks>
		/// This method returns the user's email address, if it is available in
		/// the LDAP directory.
		/// </remarks>
		/// <returns>The user's email address</returns>
		public override string GetEmail()
		{
			return email;
		}

		/// <summary>
		/// The authenticated user's password
		/// </summary>
		/// <remarks>
		/// This method is used if users are authenticated against an external directory,
		/// but should be authenticated internally in the future.  If this method returns <c>null</c>,
		/// future authentication attempts will still be performed against the external directory,
		/// if it returns a non-null string, that string is set to be the new internal password.
		/// </remarks>
		/// <returns>The password to be used for internal authentication in the future</returns>
		public override string GetPassword()
		{
			return password;
		}

		/// <summary>
		/// Get user attributes
		/// </summary>
		/// <remarks>
		/// This authentication class does not provide any attributes.
		/// </remarks>
		/// <returns><c>null</c></returns>
		public override Hashtable GetAttributes()
		{
			return null;
		}

	}
}
