/*
 *
 *   auth.h
 *
 *   Header file for classes used for PAM-style authentication.
 *
 *   =========================================================================
 *
 *   Copyright (C) 2003 by Matt Roberts, KK5JY.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *   =========================================================================
 *
 *   TODO:
 *      + add delay functionality
 *
 */


#ifndef __AUTH_H
#define __AUTH_H
	#include <deque>
	#include <string>
	#include <security/pam_appl.h>
	//#include <security/pam_misc.h>
	#include <termios.h>
	#include <unistd.h>
	#include <sys/types.h>

	namespace my {
		namespace auth {

			/*
			 *
			 *   class userinfo
			 *
			 */

			class userinfo {
				private: // internal data
					std::string ui_name;
					std::string ui_gecos;
					std::string ui_home;
					std::string ui_shell;
					uid_t ui_uid;
					gid_t ui_gid;

				public:  //public interface
					// assignment functions
					void assign (const std::string &n);
					void assign (const userinfo &n);
					void assign (uid_t i);

					// assignment operators
					const userinfo & operator = (const std::string &n) { assign(n); return (*this); };
					const userinfo & operator = (const userinfo &n) { assign(n); return (*this); };
					const userinfo & operator = (uid_t n) { assign(n); return (*this); };

					// erase the structure members
					void clear (void);

					// return the user name
					const std::string & name (void) const { return ui_name; };
					// return the user's full name
					const std::string & gecos (void) const { return ui_gecos; };
					// return the user's home directory
					const std::string & home (void) const { return ui_home; };
					// return the user's shell
					const std::string & shell (void) const { return ui_shell; };
					// return the UID of the user
					uid_t uid (void) const { return ui_uid; };
					// return the primary GID of the user
					gid_t gid (void) const { return ui_gid; };

					// check to see if the structure contains valid data
					bool valid (void) const { return ! ui_name.empty(); };

				public: // public [cd]tors
					explicit userinfo (const std::string &un) { assign(un); };
					explicit userinfo (uid_t ui)              { assign(ui); };
					explicit userinfo (const userinfo &ui)    { assign(ui); };
					userinfo (void) { clear(); };

					~userinfo (void) { /* noop */ };
			};



			/*
			 *
			 *   class my::groupinfo
			 *
			 */

			class groupinfo {
				private: // private data
					std::string gi_name;
					gid_t gi_number;
					std::deque<std::string> gi_members;

				public: // public types
					typedef std::deque<std::string>::iterator iterator;
					typedef std::deque<std::string>::reverse_iterator reverse_iterator;

					typedef std::deque<std::string>::const_iterator const_iterator;
					typedef std::deque<std::string>::const_reverse_iterator const_reverse_iterator;

				public: // public interface
					void assign (const std::string &n);
					void assign (const groupinfo &n);
					void assign (gid_t gi);

					void clear (void);

					const groupinfo & operator = (const std::string &n) { assign(n); return(*this); };
					const groupinfo & operator = (const groupinfo &g)   { assign(g); return(*this); };
					const groupinfo & operator = (gid_t gi)             { assign(gi); return(*this); };

					const_iterator begin  (void) const { return gi_members.begin();  };
					const_iterator end    (void) const { return gi_members.end();    };

					const_reverse_iterator rbegin (void) const { return gi_members.rbegin(); };
					const_reverse_iterator rend   (void) const { return gi_members.rend();   };

					iterator begin  (void) { return gi_members.begin();  };
					iterator end    (void) { return gi_members.end();    };

					reverse_iterator rbegin (void) { return gi_members.rbegin(); };
					reverse_iterator rend   (void) { return gi_members.rend();   };

					size_t members (void) const { return gi_members.size(); };
					size_t size    (void) const { return gi_members.size(); };

					const std::string &name (void) const { return gi_name; };
					gid_t gid (void) const { return gi_number; };

					bool valid (void) const { return ! gi_name.empty(); };

				public: // public [cd]tors
					explicit groupinfo (const std::string &gn) { assign(gn); };
					explicit groupinfo (const gid_t &gi)       { assign(gi); };
					explicit groupinfo (const groupinfo &gi)   { assign(gi); };
					groupinfo (void) { clear(); };

					~groupinfo (void) { /* noop */ };

			};

			namespace pam {

				/*
				 *
				 *   class my::pam::base_session
				 *
				 */

				class base_session {
					public:
						enum flags {
							noflags               = 0,
							silent                = PAM_SILENT,
							no_null_token         = PAM_DISALLOW_NULL_AUTHTOK,
							change_expired_token  = PAM_CHANGE_EXPIRED_AUTHTOK
								/* add more flags here - FIXME */
						};

						enum errors {
							account_expired   = PAM_ACCT_EXPIRED,
							auth_error        = PAM_AUTH_ERR,
							cred_unavailable  = PAM_CRED_UNAVAIL,
							cred_expired      = PAM_CRED_EXPIRED,
							cred_error        = PAM_CRED_ERR,
							disable_aging     = PAM_AUTHTOK_DISABLE_AGING,
							permission_denied = PAM_PERM_DENIED,
							session_error     = PAM_SESSION_ERR,
							success           = PAM_SUCCESS,
							token_error       = PAM_AUTHTOK_ERR,
							token_locked      = PAM_AUTHTOK_LOCK_BUSY,
#ifdef PAM_AUTHTOK_RECOVERY_ERR
							token_recovery    = PAM_AUTHTOK_RECOVERY_ERR,
#endif /* PAM_AUTHTOK_RECOVERY_ERR */
#ifdef PAM_NEW_AUTHTOK_REQD
							token_required    = PAM_NEW_AUTHTOK_REQD,
#endif /* PAM_NEW_AUTHTOK_REQD */
							try_again         = PAM_TRY_AGAIN,
							unknown_user      = PAM_USER_UNKNOWN
						};

						struct message {
							/* message style */
							enum message_style {
								prompt_echo = PAM_PROMPT_ECHO_OFF,
								prompt_noecho = PAM_PROMPT_ECHO_ON,
								error_message = PAM_ERROR_MSG,
								info_message = PAM_TEXT_INFO,
#ifdef PAM_BINARY_MSG
								binary_message = PAM_BINARY_MSG,
#endif
#ifdef PAM_BINARY_MSG
								binary_prompt = PAM_BINARY_PROMPT,
#endif
							} style;

							/* holds message/response text */
							const std::string text;
							std::string resp;

							/* standard constructor */
							message (const std::string &msg, const message_style &st)
								: style(st), text(msg), resp("") { };
							void setResponse (const std::string &msg) { resp = msg; };
						};

						typedef std::deque<message> message_list;

						class exception : public std::exception {
							private:
								const pam_handle_t *handle;
								const std::string message;
								const int e_number;
							public:
								exception (const pam_handle_t *h, const std::string &msg)
									: handle(h), message(msg), e_number(0) { };
								exception (const pam_handle *h, const int num)
									: handle(h), message (pam_strerror((pam_handle_t*)h, num)), e_number(num) { };
								~exception (void) throw() { /* noop */ };
								const char *what (void) const throw() { return message.c_str(); };
								const int error (void) const throw() { return e_number; };
						};

					private:
						struct cargs {
							base_session *obj;
							void *real_args;
							cargs (void) { obj = 0; real_args = 0; };
						};

					private:
						pam_handle_t *pamh;
						pam_conv conv;
						std::string app_name;
						cargs args;

						int rtn;
						bool session_open;
						bool started;

						static int int_conv (int, const pam_message **, pam_response **, void *);
						static int int_delay (int, unsigned, void *);

						bool set_string_item (int item, const std::string &s);
						bool get_string_item (int item, std::string &s);

					protected:
						/* this is never called directly, but is supplied by derived class for PAM module */
						virtual bool conversation (message_list &) = 0;

					protected:
						/* generally, functions should be called in this order */
						bool start (const std::string &);

						bool set_service_name (const std::string &s) { return set_string_item(PAM_SERVICE, s); };
						bool set_user_name (const std::string &s)    { return set_string_item(PAM_USER, s); };
						bool set_user_prompt (const std::string &s) { return set_string_item(PAM_USER_PROMPT,s); };
						bool set_tty (const std::string &s)          { return set_string_item(PAM_TTY, s); };
						bool set_remote_user (const std::string &s)  { return set_string_item(PAM_RUSER, s); };
						bool set_remote_host (const std::string &s)  { return set_string_item(PAM_RHOST, s); };

						bool get_service_name (std::string &s) { return get_string_item(PAM_SERVICE, s); };
						bool get_user_name (std::string &s)    { return get_string_item(PAM_USER, s); };
						bool get_user_prompt (std::string &s)  { return get_string_item(PAM_USER_PROMPT, s); };
						bool get_tty (std::string &s)          { return get_string_item(PAM_TTY, s); };
						bool get_remote_user (std::string &s)  { return get_string_item(PAM_RUSER, s); };
						bool get_remote_host (std::string &s)  { return get_string_item(PAM_RHOST, s); };

						bool authenticate (const flags f = noflags);
						bool set_credentials (const flags f = noflags);
						bool set_env (const std::string &name, const std::string &val);
						bool get_env (const std::string &name, std::string &val);
						bool change_token (const flags f = noflags);
						bool manage_account (const flags f = noflags);
						bool open_session (const flags f = noflags);
						bool close_session (const flags f = noflags);
						bool end (void);

					public:
						base_session (const std::string &an);
						virtual ~base_session (void) throw ();

						virtual bool doAuth (void) = 0;
				};
			}
		}
	}



	/*
	 *
	 *   class pam::ttysession
	 *
	 *   Uses tty input/output for conversation.  Mainly for demonstration purposes.
	 *
	 */

	namespace my {
		namespace auth {
			namespace pam {
				class ttysession : public base_session {
					private:
						std::string user;
					public:
						ttysession (const std::string &an, const std::string &u)
							: base_session (an), user(u) { };
						ttysession (const std::string &an) : base_session (an) { };
						~ttysession (void) throw() { /* noop */ };

						bool conversation (message_list &ml);
						bool doAuth (void);
				};
			}
		}
	}

#endif /* __AUTH_H */
