/*
								+-----------------------------------+
								|                                   |
								|  ***  Auth / security utils  ***  |
								|                                   |
								|   Copyright  © -tHE SWINe- 2015   |
								|                                   |
								|            AuthUtils.h            |
								|                                   |
								+-----------------------------------+
*/

#pragma once
#ifndef __LAME_AUTH_UTILS_INCLUDED
#define __LAME_AUTH_UTILS_INCLUDED

/**
 *	@file AuthUtils.h
 *	@brief authentication and security utilities
 *	@author -tHE SWINe-
 *	@date 2015-03-21
 */

#include "../Integer.h"
#include "../Timer.h"
#include "../Thread.h"
#include "../RandSafe.h"
#include "URI.h"
#include <string>
#include <map>

template <const int n_nonce_size_words = 8>
class CNoncePool_Base {
public:
	enum {
		nonce_Size_Words = n_nonce_size_words,
		nonce_Size_Bits = n_nonce_size_words * 32
	};

	struct TNonce {
		double f_expiration_time;
		uint32_t p_data[nonce_Size_Words];

		inline TNonce()
		{}

		inline TNonce(double _f_expiration_time, CSafeRandom &r_random)
			:f_expiration_time(_f_expiration_time)
		{
			for(int i = 0; i < nonce_Size_Words; ++ i)
				p_data[i] = r_random.n_Rand32();
		}
	};

protected:
	double m_f_tslice_expiration_time;
	CTimer m_timer;
	std::map<std::string, TNonce> m_tslice_map;

public:
	CNoncePool_Base(double f_tslice_expiration_time = 5 * 60)
		:m_f_tslice_expiration_time(f_tslice_expiration_time)
	{}

	void Set_ExpirationTime(double f_tslice_expiration_time)
	{
		_ASSERTE(m_tslice_map.empty());
		m_f_tslice_expiration_time = f_tslice_expiration_time;
	}

	void Prune() // erases old nonces (they otherwise accumulate with each user logged in from a unique address) // t_odo - better name
	{
		double f_time = m_timer.f_Time();
		for(std::map<std::string, TNonce>::iterator p_map_it = m_tslice_map.begin(),
		   p_end_it = m_tslice_map.end(); p_map_it != p_end_it;) {
			if((*p_map_it).second.f_expiration_time > f_time) {
				std::map<std::string, TNonce>::iterator p_erase_it = p_map_it;
				++ p_map_it;
				m_tslice_map.erase(p_erase_it); // p_erase_it invalidated, but not p_map_it
			} else
				++ p_map_it;
		}
		// erase expired nonces
	}

	void Erase_Nonce(const std::string &r_s_client_address)
	{
		std::map<std::string, TNonce>::iterator p_nonce_it = m_tslice_map.find(r_s_client_address);
		if(p_nonce_it != m_tslice_map.end())
			m_tslice_map.erase(p_nonce_it);
	}

	bool Get_Nonce(TNonce &r_t_nonce, const std::string &r_s_client_address)
	{
		double f_time = m_timer.f_Time();
		try {
			if((m_tslice_map.find(r_s_client_address) == m_tslice_map.end()) ||
			   m_tslice_map[r_s_client_address].f_expiration_time < f_time) {
				CSafeRandom generator;
				r_t_nonce = m_tslice_map[r_s_client_address] = TNonce(f_time +
					m_f_tslice_expiration_time, generator);
				// nice, only called limited amount of times / time interval, no DOS attack here
			} else
				r_t_nonce = m_tslice_map[r_s_client_address];
		} catch(std::bad_alloc&) {
			return false;
		}
		// makes sure the nonce does exist, and is valid

		return true;
	}

	bool Get_Nonce(std::string &r_s_nonce, const std::string &r_s_client_address)
	{
		TNonce t_nonce;
		if(!Get_Nonce(t_nonce, r_s_client_address))
			return false;
		try {
			r_s_nonce.erase();
			r_s_nonce.reserve(nonce_Size_Bits / 4);
			for(int i = 0; i < nonce_Size_Words; ++ i) {
				char p_s_word[32 / 4 + 1];
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
				sprintf_s(p_s_word, sizeof(p_s_word), "%08x", htonl(t_nonce.p_data[i]));
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
				sprintf(p_s_word, "%08x", htonl(t_nonce.p_data[i]));
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
				r_s_nonce += p_s_word;
			}
		} catch(std::bad_alloc&) {
			return false;
		}
		return true;
	}

	bool Get_NonceXML(std::string &r_s_nonce_xml, const std::string &r_s_client_address)
	{
		TNonce t_nonce;
		if(!Get_Nonce(t_nonce, r_s_client_address))
			return false;
		try {
			r_s_nonce_xml.erase();
			r_s_nonce_xml.reserve(nonce_Size_Bits / 4 + 100); // todo - see how much is needed
			// alloc

			r_s_nonce_xml = "<?xml version=\"1.0\" encoding=\"utf-8\" "
				"standalone=\"yes\"?><nonce valid_msec=\"";
			char p_s_validity[256];
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
			sprintf_s(p_s_validity, sizeof(p_s_validity), "%u",
				(uint32_t)((t_nonce.f_expiration_time - m_timer.f_Time()) * 1000));
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
			sprintf(p_s_validity, "%u", (uint32_t)((t_nonce.f_expiration_time - m_timer.f_Time()) * 1000));
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
			r_s_nonce_xml += p_s_validity;
			r_s_nonce_xml += "\">";
			// write the header

			for(int i = 0; i < nonce_Size_Words; ++ i) {
				char p_s_word[32 / 4 + 1];
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
				sprintf_s(p_s_word, sizeof(p_s_word), "%08x", htonl(t_nonce.p_data[i]));
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
				sprintf(p_s_word, "%08x", htonl(t_nonce.p_data[i]));
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
				r_s_nonce_xml += p_s_word;
			}
			// concat the long number

			r_s_nonce_xml += "</nonce>";
			// tail
		} catch(std::bad_alloc&) {
			return false;
		}
		return true;
	}
};

template <const int n_nonce_size_words = 8, const int n_nonce_pool_num = 1>
class CNoncePool {
public:
	enum {
		nonce_Size_Words = CNoncePool_Base<n_nonce_size_words>::nonce_Size_Words,
		nonce_Size_Bits = CNoncePool_Base<n_nonce_size_words>::nonce_Size_Bits
	};

	typedef typename CNoncePool_Base<n_nonce_size_words>::TNonce TNonce;

protected:
	CNoncePool_Base<n_nonce_size_words> m_p_pool[n_nonce_pool_num];
	CMutex m_p_mutex[n_nonce_pool_num];

public:
	CNoncePool(double f_tslice_expiration_time = 5 * 60)
	{
		for(int i = 0; i < n_nonce_pool_num; ++ i)
			m_p_pool[i].Set_ExpirationTime(f_tslice_expiration_time);
	}

	/**
	 *	@note This function throws std::runtime_error on mutex error.
	 */
	void Prune() // throw(std::runtime_error)
	{
		for(int i = 0; i < n_nonce_pool_num; ++ i) {
			if(!m_p_mutex[i].Lock())
				throw std::runtime_error("CMutex::Lock() failed");
			m_p_pool[i].Prune();
			if(!m_p_mutex[i].Unlock())
				throw std::runtime_error("CMutex::Unlock() failed");
		}
	}

	/**
	 *	@note This function throws std::runtime_error on mutex error.
	 */
	void Erase_Nonce(const std::string &r_s_client_address) // throw(std::runtime_error)
	{
		int i = (n_nonce_pool_num > 1)? n_Hash(r_s_client_address.c_str()) % n_nonce_pool_num : 0;
		// hash the address to determine which pool to look in (avoids all the threads fighting for the same pool)

		if(!m_p_mutex[i].Lock())
			throw std::runtime_error("CMutex::Lock() failed");
		m_p_pool[i].Erase_Nonce(r_s_client_address);
		if(!m_p_mutex[i].Unlock())
			throw std::runtime_error("CMutex::Unlock() failed");
	}

	/**
	 *	@note This function throws std::runtime_error on mutex error.
	 */
	bool Get_Nonce(TNonce &r_t_nonce, const std::string &r_s_client_address) // throw(std::runtime_error)
	{
		int i = (n_nonce_pool_num > 1)? n_Hash(r_s_client_address.c_str()) % n_nonce_pool_num : 0;
		// hash the address to determine which pool to look in (avoids all the threads fighting for the same pool)

		if(!m_p_mutex[i].Lock())
			throw std::runtime_error("CMutex::Lock() failed");
		bool b_result = m_p_pool[i].Get_Nonce(r_t_nonce, r_s_client_address);
		if(!m_p_mutex[i].Unlock())
			throw std::runtime_error("CMutex::Unlock() failed");
		return b_result;
	}

	/**
	 *	@note This function throws std::runtime_error on mutex error.
	 */
	bool Get_Nonce(std::string &r_s_nonce, const std::string &r_s_client_address) // throw(std::runtime_error)
	{
		int i = (n_nonce_pool_num > 1)? n_Hash(r_s_client_address.c_str()) % n_nonce_pool_num : 0;
		// hash the address to determine which pool to look in (avoids all the threads fighting for the same pool)

		if(!m_p_mutex[i].Lock())
			throw std::runtime_error("CMutex::Lock() failed");
		bool b_result = m_p_pool[i].Get_Nonce(r_s_nonce, r_s_client_address);
		if(!m_p_mutex[i].Unlock())
			throw std::runtime_error("CMutex::Unlock() failed");
		return b_result;
	}

	/**
	 *	@note This function throws std::runtime_error on mutex error.
	 */
	bool Get_NonceXML(std::string &r_s_nonce_xml, const std::string &r_s_client_address) // throw(std::runtime_error)
	{
		int i = (n_nonce_pool_num > 1)? n_Hash(r_s_client_address.c_str()) % n_nonce_pool_num : 0;
		// hash the address to determine which pool to look in (avoids all the threads fighting for the same pool)

		if(!m_p_mutex[i].Lock())
			throw std::runtime_error("CMutex::Lock() failed");
		bool b_result = m_p_pool[i].Get_NonceXML(r_s_nonce_xml, r_s_client_address);
		if(!m_p_mutex[i].Unlock())
			throw std::runtime_error("CMutex::Unlock() failed");
		return b_result;
	}

protected:
	static uint32_t n_Hash(const char *p_s_string)
	{
		const char *_p_s_string = p_s_string;

		uint32_t n_hash = 4294966007U;
		for(; *p_s_string; ++ p_s_string)
			n_hash = (n_hash * 1050787103U) ^ ((unsigned char)(*p_s_string) * 2108925839U);
		// note that this does not need to be extremely secure,
		// it should just spread the strings randomly enough

		//printf("debug: \'%s\' hashes to %u (mod 32 = %u)\n", _p_s_string, n_hash, n_hash % 32);

		/*
		FILE *p_fr = fopen("C:\\Users\\ipolok\\Documents\\_ftp_me_\\primes.32b", "rb");
		fseek(p_fr, 0, SEEK_END);
		fseek(p_fr, -(150000000 * 4), SEEK_CUR);
		for(;;) {
			uint32_t n;
			if(fread(&n, sizeof(n), 1, p_fr) != 1)
				break;
			printf("%u\n", n);
		}
		fclose(p_fr);
		*/

		return n_hash;
	}
};

template <class CHash = CSHA1>
class CHMAC {
public:
	typedef CHash THash;

public:
	static inline THash t_HMAC(const char *p_s_key, const char *p_s_message)
	{
		return t_HMAC(p_s_key, strlen(p_s_key) * sizeof(char),
			p_s_message, strlen(p_s_message) * sizeof(char));
	}

	static THash t_HMAC(const void *p_key, size_t n_key_size,
		const void *p_message, size_t n_message_size)
	{
		const char *p_s_key = (const char*)p_key;
		size_t n_key_size_bits = n_key_size * 8;
		// this is used as key for HMAC

		//_ASSERTE(THash::word_Is_BigEndian); // untested with little endians, might give wrong results // t_odo - just test with wiki, there are examples for both md5 and sha1

		//printf("hmac key is %s (%d bits)\n", p_s_key, n_key_size_bits); // debug

		// t_odo - wrap HMAC to a function
		// t_odo - test examples on wiki // works well on a PC

		typedef int TBlockSizeCheck[!(THash::block_Size_Bits % 32)];
		typedef int THashSizeCheck[!(THash::hash_Size_Bits % 32)];
		// make sure these are multiple of 32 (not done a nice way, but it will work)

		uint32_t p_key_to_use[THash::block_Size_Bits / 32] = {0}; // 64 bytes for MD5 / SHA1
		if(n_key_size > THash::block_Size_Bits / 8) {
			CStreamHash<THash> hash;
			hash.Process_Data(p_s_key, n_key_size_bits / 8);
			THash t_key = hash.t_Result();
			if(THash::word_Is_BigEndian) {
				for(int i = 0; i < THash::hash_Size_Bits / 32; ++ i)
					p_key_to_use[i] = htonl(t_key[i]); // note this is untested, especially the use of htonl
			} else {
				memcpy(p_key_to_use, &t_key[0], THash::hash_Size_Bits / 8);
				// just copy, no endian conversion
			}
			// zero-padded hash as a key
		} else if(n_key_size <= THash::block_Size_Bits / 8) {
#if 0
			for(int i = 0; i < n_key_size_bits; i += 8)
				p_key_to_use[i >> 5] |= uint32_t(uint8_t(p_s_key[i / 8])) << (i % 32); // this doesn't solve endianness
#else // 0
			memcpy(p_key_to_use, p_s_key, n_key_size_bits / 8); // this doesn't solve endianness
#endif // 0
			// convert the key to 4 x 16 bytes, one byte at a time
		}
		// create block key

		uint32_t p_i_key_pad[THash::block_Size_Bits / 32],
			p_o_key_pad[THash::block_Size_Bits / 32];
		for(int i = 0; i < THash::block_Size_Bits / 32; ++ i) {
			p_i_key_pad[i] = p_key_to_use[i] ^ 0x36363636U;
			p_o_key_pad[i] = p_key_to_use[i] ^ 0x5c5c5c5cU;
		}
		// calculate pads

		THash t_hmac;
		{
			THash t_intermed_hash;
			{
				CStreamHash<THash> hash;
				hash.Process_Data(p_i_key_pad, THash::block_Size_Bits / 8); // p_i_key_pad
#if 0
				// this doesn't solve hash size, etc. this was written for TSHA1
				std::vector<uint32_t> str_to_hash;
				if(!stl_ut::Resize_To_N(str_to_hash, (s_orig_request_uri.length() + 3) / 4, 0))
					return false;
				for(size_t i = 0, n = s_orig_request_uri.length() * sizeof(char) * 8; i < n; i += 8)
					str_to_hash[i >> 5] |= uint32_t(uint8_t(s_orig_request_uri[i / 8])) << (i % 32); // this doesn't solve endianness
				hash.Process_Data(&str_to_hash[0], s_orig_request_uri.length() * sizeof(char)); // message
#else // 0
				hash.Process_Data(p_message, n_message_size); // message // this doesn't solve endianness
#endif // 0
				t_intermed_hash = hash.t_Result();
				if(THash::word_Is_BigEndian)
					t_intermed_hash = t_EndianFlip(t_intermed_hash);
			}
			CStreamHash<THash> hash;
			hash.Process_Data(p_o_key_pad, THash::block_Size_Bits / 8); // p_o_key_pad
			hash.Process_Data(&t_intermed_hash[0], THash::hash_Size_Bits / 8); // t_intermed_hash
			t_hmac = hash.t_Result();
		}
		// calculate HMAC

		return t_hmac;
	}

	static THash t_EndianFlip(THash t_hash) // copy intended
	{
		typedef int TWordSizeCheck[sizeof(THash::TWord) == 4 || sizeof(THash::TWord) == 8];

		static const uint32_t n_msb = 0xff000000;
		const bool b_big_endian_host = *(const uint8_t*)&n_msb != 0; // seems correct

		if(sizeof(THash::TWord) == 4) {
			for(int j = 0; j < THash::hash_Size_Bits / (8 * sizeof(THash::TWord)); ++ j)
				t_hash[j] = htonl(t_hash[j]);
		} else /*if(sizeof(THash::TWord) == 8)*/ {
			_ASSERTE(sizeof(THash::TWord) == 8);

			if(!b_big_endian_host) {
				for(int i = 0; i < THash::hash_Size_Bits / (8 * sizeof(THash::TWord)); ++ i) {
					t_hash[i] = (uint64_t(htonl(t_hash[i] & uint32_t(-1))) << 32) |
						htonl((uint64_t(t_hash[i]) >> 32) & uint32_t(-1)); // !!
					// the second cast only avoids big shift warning if sizeof(THash::TWord) == 4
				}
				// untested
			}
		}
		// todo - does this even do what we want? don't we simply want the hash in a specific order, regardless of host?

		return t_hash;
	}

	static THash t_SaltPassword(const void *p_password, size_t n_password_size,
		const void *p_salt, size_t n_salt_size, int n_round_num = 997) // todo - implement this in javascript
	{
		THash t_salted_pwd;
		{
			/*CStreamHash<THash> hash;
			hash.Process_Data(p_password, n_password_size);
			hash.Process_Data(p_s_time_slice_id, strlen(p_s_time_slice_id) * sizeof(char));
			THash t_prev = hash.t_Result();*/ // wacky v1 lame function
			THash t_prev = t_HMAC(p_password, n_password_size, p_salt, n_salt_size); // salt the password
			t_salted_pwd = t_prev;

			//printf("password + salt is %s%s\n", s_password.c_str(), s_time_slice_id.c_str()); // debug
			//printf("hash(password + salt) is %s\n", t_salted_pwd.p_s_ToString()); // debug

			for(int i = 0; i < n_round_num; ++ i) { // t_odo - make this a prime
				/*char p_s_buffer[128];
				const char *p_s_hash = t_salted_pwd.p_s_ToString(p_s_buffer, sizeof(p_s_buffer));
				_ASSERTE(strlen(p_s_hash) == 40);
				hash.Reset();
				hash.Process_Data(p_s_hash, 40 * sizeof(char));
				char p_s_rot[64];
				sprintf(p_s_rot, "%u", n_Rotate16(i, 10));
				hash.Process_Data(p_s_rot, strlen(p_s_rot) * sizeof(char));
				t_salted_pwd = hash.t_Result();*/ // wacky v1 lame function; makes it very easy to precompute, as the password is not required for the later stages

				//char p_s_buffer[128];
				//const char *p_s_hash = t_prev.p_s_ToString(p_s_buffer, sizeof(p_s_buffer));
				//THash t_next = t_HMAC(p_password, n_password_size, p_s_hash, strlen(p_s_hash) * sizeof(char));

				if(THash::word_Is_BigEndian)
					t_prev = t_EndianFlip(t_prev);
				// required by the JS to match

				THash t_next = t_HMAC(p_password, n_password_size, &t_prev[0], THash::hash_Size_Bits / 8); // probably faster and just as secure
				// calculate the next round, using also the password

				for(int j = 0; j < THash::hash_Size_Bits / (8 * sizeof(THash::TWord)); ++ j)
					t_salted_pwd[j] ^= t_next[j];
				// xor with the result so far

				t_prev = t_next;
				// remember for the next round
			}
		}
		// calculate salted password, this is used as key for HMAC

		return t_salted_pwd;
	}
};

template <class CHash = TSHA1, class CNoncePoolType = CNoncePool<8, 32> >
class CLameAuth {
public:
	typedef CNoncePoolType TNoncePool;
	typedef CHash THash;
	typedef CHMAC<THash> THMAC;

	enum {
		hash_Characters = THash::hash_Size_Bits / 4 + 1 /**< @brief size of the hash when converted to a hex string */
	};

protected:
	TNoncePool m_nonce_pool;

public:
	CLameAuth(double f_tslice_expiration_time = 5 * 60)
		:m_nonce_pool(f_tslice_expiration_time)
	{}

	bool b_IsLameAuth(const char *p_s_filename) const
	{
		return strstr(p_s_filename, "_lauth") != 0; // if it begins with _lauth, it is lame auth
	}

	bool Hanlde_LameAuth(bool &r_b_valid_request,
		std::string &s_response, const CURI_Context &r_req_context) // t_odo - remove the buffer, add response as std::string reference
	{
		r_b_valid_request = true;
		// just assume ...

		// t_odo - split this to funcions, don't repeat code, don't make a mess
		// todo - use user logged in state, don't let the account be used even if not logged in
		// (anyone can send messages containing scores and screw the scores for the user)
		// todo - add request id counter per user to make hashes different without forcing too low
		// secret time
		// todo - it is actually pretty easy, just maintain the counter at client side,
		// make it a part of request. the server side just makes sure that user increments
		// the counter (requests with the same value of counter will be ignored)
		// t_odo - add mutexes arround lameauth
		// todo - catch uncaught exceptions

		if(!r_req_context.s_Filename().compare("_lauth/nonce.xml")) {
			if(r_req_context.b_HasArgs()) {
				r_b_valid_request = false;
				return true;
			}
			// no '?'s allowed

			try{
				if(!m_nonce_pool.Get_NonceXML(s_response, r_req_context.s_ClientAddress()))
					return false;
			} catch(std::runtime_error&) {
				return false; // not sure if it is ok to continue at this point
			}
			// get the nonce for the given user
		} else {
			r_b_valid_request = false;
			return true;
			// unknown lauth request
		}

		return true;
	}

	bool b_LameAuth_VerifyAuthentication(bool &r_b_valid_request,
		const CURI_Context &r_req_context, const std::string &s_password)
	{
		r_b_valid_request = true;

		const std::string &r_s_hmac = r_req_context.s_Arg("hmac");
		if(r_s_hmac.empty()/*r_req_context.arg_map.find("hmac") == r_req_context.arg_map.end()*/) {
			r_b_valid_request = false;
			return true;
		}
		// get username and HMAC

		TNoncePool::TNonce t_nonce;
		try {
			if(!m_nonce_pool.Get_Nonce(t_nonce, r_req_context.s_ClientAddress()))
				return false;
		} catch(std::runtime_error&) {
			return false; // not sure if it is ok to continue at this point
		}
		// read time slice from the list, give it some reserve before expiring

		THash t_salted_pwd = THMAC::t_SaltPassword(s_password.data(),
			s_password.length() * sizeof(char), t_nonce.p_data, sizeof(t_nonce.p_data), 347); // t_odo - see if the time slice is allocated! might be producing random numbers here
		// calculate salted password, this is used as key for HMAC

		char p_s_buffer[hash_Characters];
		t_salted_pwd.p_s_ToString(p_s_buffer, sizeof(p_s_buffer));
		std::string s_orig_request_uri;
		if(!CURI_Utils::Remove_URI_Param(s_orig_request_uri, r_req_context.s_URI(), "hmac", true))
			return false; // t_odo - handle this error better // this is ok, leads to internal server error
		//printf("debug: salted password is %s\n", p_s_buffer); // debug
		THash t_hmac = THMAC::t_HMAC(p_s_buffer, s_orig_request_uri.c_str());
		// calculate HMAC

		char p_s_hmac[hash_Characters];
#if 0
		t_hmac.p_s_ToString(p_s_hmac, sizeof(p_s_buffer2)); // no flips required here
#else // 0
		if(THash::word_Is_BigEndian)
			t_hmac = THMAC::t_EndianFlip(t_hmac); // required by the JS to match
		CModifiedBase64::Encode(sizeof(p_s_hmac), p_s_hmac, THash::hash_Size_Bits / 8, &t_hmac[0]);
		p_s_hmac[CModifiedBase64::n_EncodedSize(THash::hash_Size_Bits / 8)] = 0; // !!
#endif // 0
		//printf("calculated expected HMAC: %s\n", p_s_hmac); // debug
		// this is how is the HMAC supposed to look like

		r_b_valid_request = !strcmp(p_s_hmac, r_s_hmac.c_str()); // base-64 is case sensitive!
		// in case HMAC is valid, access is granted

		if(r_b_valid_request) {
			try {
				m_nonce_pool.Erase_Nonce(r_req_context.s_ClientAddress());
			} catch(std::runtime_error&) {
				return false; // not sure if it is ok to continue at this point
			}
		}
		// force remove time slice (one use only)

		return true;
	}
};

#endif // !__LAME_AUTH_UTILS_INCLUDED
