// /bin/adm/kerberos.c -- Vincent's Hollow Project.
// Grendel   -- October 1992

#define NOT_FINISHED

// kludge section
#ifdef NOT_FINISHED

#define DEBUG
// #define DEBUG_TICKET
#define NO_GREP
#define NO_SED
#define NO_RESET
#define NO_DIR_OPTOMIZE
/*
 * Must fix valid destruct in master object to refuse to
 *  destruct kerberos (the mappings and arrays it maintains
 *  are not easily replaced)
 *
 * How to handle groups...
 * Store member groups in the password file, or have a groups file?
 *
 * sed & grep needed
 *
 */

#endif // KLUDGE


#include <version.h>
#include <files.h>
#include <daemons.h>
#include <login.h>

#define DIR_INFO "/data/adm/kerberos/dir_info"
#define USER_INFO "/data/adm/kerberos/user_info"
#define VALID_GROUPS "/data/adm/kerberos/valid_groups"
#define RESTRICTED_LIST "/data/adm/kerberos/restrict.sites"
#define RESTRICTED_OK "/data/adm/kerberos/restrict.ok"
#define DIR_PROP_OWNER 0
#define DIR_PROP_GROUP 1
#define DIR_PROP_DEFAULT_READ 2
#define DIR_PROP_SET_UID 3

#define sync_user(x) \
	save_user_info(x); \
	master()->query_kerberos()
#define sync_dirs() \
	save_dir_map(); \
	master()->query_kerberos()
#define sync_groups() \
	save_valid_group_list()

#define valid_groupp(x) (member_array((x),valid_group_list)>=0)

#ifdef DEBUG
#define valid_admin_requestp() (1)
#else
#define valid_admin_requestp() (geteuid(previous_object())==root_uid)
#endif

/*
 * Prototypings.
 */
// Internal functions
void	create();
void	reset();
void 	remove();
private int load_dir_map();
private int save_dir_map();
private mixed *load_user_info(string name);
private int save_user_info(object user);
private int save_valid_group_list();
// Function for telling the master object mappings
mapping query_dir_map();
mapping	query_groups_map();
// Login functions
int		check_restricted(string ip_name);
int		check_restrict_ok(string name);
int		check_access(string name);
object	check_password_and_exec(string name, string pwd);
object	new_pass_and_exec(string name, string passwd);
// File access functions
int		player_add_to_group(object player, string group);
int		player_remove_from_group(object player, string group);
int		group_create(string group);
int		group_freeze(string group);
int		dir_chown(string new_owner, string path);
int		dir_chgrp(string new_group, string path);
int		dir_chmod(int prop, mixed new_value, string path);
// General security
string	query_name(object player);
int		query_wiz(object player);
string	*query_groups(object player);
int		change_player_password(string name, string password);
int		user_exists(string name);

/*
 * Global var declarations.
 *
 * users : object ->
 *   ({ string name , string password, int wiz , string *groups })
 *
 * dirs : string ->
 *   ({ string owner , string group , int default_read , int setuid })
 *
 * preplayers is an array of unassigned player objects, 3 is the most that
 *   are likely to exist at any one time
 * logobj is used to store the object that owns the preplayer object
 *   with the same index
 *
 * restrict is a listing of restricted sites
 * restrict_ok is a listing of people who can log in from sites
 *
 * login_length is the length of the filename LOGIN
 *
 * valid_group_list is just that
 *
 */

private static mapping users;
private static mapping dirs;
private static string root_uid;
private static object *preplayers, *logobj;
private static string *restrict, *restrict_ok;
private static int login_length;
private static string *valid_group_list;

/**********************************************************************
 * Start of internal functions
 */
void
create() 
{
	string file_contents;
	string *uids;
	mixed *pwd_ent;
	
	if(login_length==strlen(LOGIN)) return;
	// intialise uid
	root_uid=(string)master()->get_root_uid();
	seteuid(root_uid);

	// allocate global mappings
	preplayers=allocate(3);
	logobj=allocate(3);
	users=allocate_mapping(30);
	dirs=([ ]);

	// initialise global variables
	if(file_size(RESTRICTED_LIST)>0)
	{
		file_contents=read_file(RESTRICTED_LIST);
		restrict=explode(file_contents,"\n");
	}
	if(file_size(RESTRICTED_OK)>0)
	{
		file_contents=read_file(RESTRICTED_OK);
		restrict_ok=explode(file_contents,"\n");
	}
	if(file_size(VALID_GROUPS)>0)
	{
		file_contents=read_file(VALID_GROUPS);
		valid_group_list=explode(file_contents,"\n");
	}
	else
		valid_group_list=({ "senior" , "elder" , "demi" , "god" });
	login_length=strlen(LOGIN);

	// load up dir info, and inform master object
	load_dir_map();
	sync_dirs();
}

void
reset()
{
#ifndef NO_RESET
	// integrity checks
	// remove null entries from user map
	object *check;
	mixed *info;
	int i,j;

	// check everyone is in the users mapping
	check=users();
	j=sizeof(check);
	for(i=0;i<j;i++)
		if(undefinedp(users[check[i]]))
		{
			// oh dear
			info=load_user_info((string)check[i]->query_real_name());
			if(info)
			{
				users[check[i]]=info;
				sync_user(check[i]);
			}
			else
			{
				users[check[i]]=({ "unknown" , "" , 0 , ({ }) });
				tell_object(check[i], "Kerberos tells you: "+
					"You will be leaving in 2 minutes.\n");
				check[i]->request_call_out("remove",120);
			}
		}
#endif // NO_RESET
	return;
}

void
remove()
{
	// lets not
	return;
}

private int
load_dir_map()
{
	string file_contents, dir_name;
	string *lines;
	mixed *dir_entry;
	int i,j;

	if(!(file_contents=read_file(DIR_INFO)))
	{
		write("kerberos: cannot read directory information\n");
		return 0;
	}
	lines=explode(file_contents,"\n");
	j=sizeof(lines);
	dirs=allocate_mapping(j);
	for(i=0;i<j;i++)
	{
		dir_entry=allocate(4);
		if(sscanf(lines[i],"%s:%s:%s:%d:%d",
			dir_name, dir_entry[0], dir_entry[1],
			dir_entry[2], dir_entry[3])==5)
			dirs[dir_name]=dir_entry;
		else
		{
			write("kerberos: corrupt dir info entry\n");
			continue;
		}
	}
	return 1;
}

/*
 * Saves the directory information to DIR_INFO
 */
private int
save_dir_map()
{
	string *dir_names;
	int i,j;

	if(file_size(DIR_INFO+".bak")>=0)
		rm(DIR_INFO+".bak");
	if(file_size(DIR_INFO)>=0)
		rename(DIR_INFO, DIR_INFO+".bak");
	write_file(DIR_INFO,"");
	dir_names=keys(dirs);
	j=sizeof(dir_names);
	for(i=0;i<j;i++)
		write_file(DIR_INFO,sprintf("%s:%s:%s:%d:%d\n",
			dir_names[i], dirs[dir_names[i]][0],
			dirs[dir_names[i]][1], dirs[dir_names[i]][2],
			dirs[dir_names[i]][3]));
	if(file_size(DIR_INFO)<0)
	{
		notify_fail("kerberos: WARNING: failed save_dir_info()\n");
		return 0;
	}
	return 1;
}

private mixed *
load_user_info(string name)
{
	// pwd_ent:
	// >>name:password:group1 group2 group3<<
	string pwd_ent;
	string *pwd_fields;
	mixed *info;

#ifdef NO_GREP
	// **************************************************
	// needs grep...
	//
	switch(name)
	{
	  case "zero":        //  wibble
		return ({ "zero" , "KwkKyepkn4k/Q",
				10000,	({ "senior" , "elder" , "demi" , "god" }) });
		break;
	  case "test":        //  wibble
		return ({ "test2" , "KwkKyepkn4k/Q",
				0,	({  }) });
		break;
	  case "grendel":        //  wibble
		return ({ "grendel" , "KwkKyepkn4k/Q",
				500,	({ "senior" }) });
		break;
	  default:
		;
	}

	return 0;
#endif // NO_GREP
	info=allocate(4);

#ifndef NO_GREP
	pwd_ent=grep("^"+name+":",PASSWD);
#endif // !NO_GREP
	if(!pwd_ent) return 0;
	pwd_fields=explode(pwd_ent,":");
	info[0]=name;
	info[1]=pwd_fields[1];
	info[2]=(int)MOIRA->init_user(name,"level");
	info[3]=explode(pwd_fields[2]," ");

	return info;
}

/*
 * Saves user information to USER_INFO
 */
private int
save_user_info(object user)
{
	string pwd_ent;
	int sed_ret;

#ifdef NO_SED
	return 0;
#endif // NO_SED
	if(undefinedp(users[user])) return 0;
	pwd_ent=users[user][0]+":";
	pwd_ent+=users[user][1]+":";
	pwd_ent+=implode(users[users][3]," ")+"\n";

#ifndef NO_SED
	sed_ret=sed("^"+users[user][0]+":.*",pwd_ent,PASSWD);
#endif // !NO_SED

	return sed_ret;
}

private int
save_valid_group_list()
{
	if(file_size(VALID_GROUPS)>0)
		rm(VALID_GROUPS);
	write_file(VALID_GROUPS,implode(valid_group_list,"\n")+"\n");
	if(file_size(VALID_GROUPS)>0) return 1;
	return 0;
}

/**********************************************************************
 * Start of mapping functions
 */

/*
 * tells master object directory info for fstat
 */
mapping
query_dir_map()
{
	if(previous_object()==master()) return dirs;
	return 0;
}

/*
 * tells master object list of groups each uid can access
 */
mapping
query_groups_map()
{
	mapping access;
	object *entries;
	string name, *groups;
	int i,j;

	if(previous_object()!=master()) return 0;
	entries=keys(users);
	j=sizeof(entries);
	access=([ ]);
	for(i=0;i<j;i++)
		if(entries[i])
		{
			name=users[entries[i]][0];
			groups=users[entries[i]][3];
			access[name]=groups;
		}
	return access;
}

/**********************************************************************
 * Start of login functions
 */

/*
 * If called by a login object, allows the exec functions
 * to be accessed.
 *
 */
request_ticket()
{
	int i;
	string str;
#ifdef DEBUG
	write("kerberos: request_ticket\n");
#endif
#ifdef DEBUG_TICKET
	write(
"kerberos: request_ticket: prev obj= "+
file_name(previous_object())[0..login_length-1]+"\n");
	write("kerberos: request_ticket: login obj= "+LOGIN+"\n");
#endif
	if(file_name(previous_object())[0..login_length-1]!=LOGIN)
		return 0;
#ifdef DEBUG_TICKET
	write("kerberos:  request_ticket:  objects cloned ok\n");
#endif
	if((i=member_array((object)0, preplayers))<0)
	{
		preplayers+=({ });
		logobj+=({ });
		i=member_array((object)0, preplayers);
	}
	preplayers[i]=clone_object(PLAYER);
	logobj[i]=previous_object();
#ifdef DEBUG_TICKET
	write("kerberos:  request_ticket:  objects cloned ok\n");
#endif
	if(preplayers[i]&&logobj[i])
	{
#ifdef DEBUG_TICKET
		write("kerberos:  request ticket:  ticket ok\n");
#endif
		return 1;
	}
#ifdef DEBUG_TICKET
	write("kerberos: request_ticket: final check failed\n");
	if(preplayers[i]) write("	                      preplayers[i] ok\n");
	if(logobj[i]) write("	                      logobj[i] ok\n");
#endif
	return 0;
}

/*
 * Check to see if the connection is from a restricted site
 */
check_restricted(string address)
{
	string *from,*comp;
	int i,j,check;

#ifdef DEBUG
	write("kerberos: check_restricted()\n");
#endif
	from=explode(address,".");
	for (i=0; i<sizeof(restrict); i++)
	{
		comp=explode(restrict[i],".");
		check=1;
		for (j=0; j<sizeof(comp); j++)
		{
			if (comp[sizeof(comp)-j-1]!=from[sizeof(from)-j-1])
			{
				check=0;
			}
		}
		if (check==1) {return 1;}
	}
	return 0;
}

/*
 * Check to see if player from a restricted site is OK
 */
check_restrict_ok()
{
	string name;

#ifdef DEBUG
	write("kerberos: check_restrict_ok()\n");
#endif
	if (query_name()=="[guest]") {return 1;}
	sscanf(query_name(),"[%s]",name);
	if (member_array(name,restrict_ok)<0) {return 0;}
	return 1;
}

/*
 * Check to see if a player is allowed to log on
 * occurs before password verification
 */
check_access(string name)
{
#ifdef DEBUG
	write("kerberos: check_access() for "+name+"\n");
#endif
	// Check to see if name is banished
	if(file_size(BANISH + name) >= 0)
	{
		cat(NEWS + "login.banish");
		return 0;
	}

	// check to see if shutdown is in progress --Dirk
	if ("/bin/adm/shutdown"->query_shutdown())
	{
		write("The game will shutdown in "+
		"/bin/adm/shutdown"->query_shutdown()+" minutes.\n"+
		"It will take about 5 minutes to reload after that.\n");
		return 0;
	}
	return 1;
}

/*
 * Checks password!  If password is ok, it execs the caller
 *  with the new preplayer object.
 * Needless to say, the caller has to be valid
 */
object check_password_and_exec(string name, string pwd)
{
	int error, ticket_no;
	string tmp, password;
	mixed *info;

#ifdef DEBUG
	write("\nkerberos password authentication.\n");
#endif

	ticket_no=member_array(previous_object(),logobj);
	if(ticket_no<0)
	{
		write("kerberos error: illegal verify and exec request\n");
		return 0;
	}
	info=load_user_info(name);
	password=info[1];
	if(password == crypt(pwd, password)) 
	{
		object temp1, temp2;
		if(temp1=find_living(name))
		{
#ifdef DEBUG
			write("kerberos: netdead found\n");
#endif
			destruct(preplayers[ticket_no]);
			preplayers[ticket_no]=temp1;
		}
		error=catch(exec(preplayers[ticket_no], logobj[ticket_no]));
		if(!error)
		{
#ifdef DEBUG
			write("kerberos: exec ok\n");
#endif
			temp2=preplayers[ticket_no];
			preplayers[ticket_no]=(object)0;
			users[temp2]=info;
			sync_user(temp2);;
			return temp2;
		}
		write("kerberos error: FATAL ERROR: failed exec\n");
	}
#ifdef DEBUG
	write("pwd= "+password+"\n");
	write("encrypted password= "+crypt(pwd,password)+"\n");
#endif
	return 0;
}

/*
 * Adds a new player
 */
new_pass_and_exec(string name, string passwd )
{
	int err, ticket_no;

	ticket_no=member_array(previous_object(),logobj);
	if((ticket_no<0)||(load_user_info(name)))
	{
		write("kerberos error: illegal addition and exec request\n");
		return 0;
	}
	err=catch(exec(preplayers[ticket_no], logobj[ticket_no]));
	if(!err)
	{
		object temp;
		
		temp=preplayers[ticket_no];
		preplayers[ticket_no]=(object)0;
		users[temp]=({ name , passwd , 0 , ({ }) });
		sync_user(temp);
		return temp;
	}
	write("kerberos error: FATAL ERROR: failed exec\n");
	return 0;
}

/**********************************************************************
 * Start of file access functions
 */

int player_add_to_group(object player, string group)
{
	if(valid_admin_requestp())
	{
		users[player][3]+=group;
		sync_user(player);
		return 1;
	}
	return 0;
}

int player_remove_from_group(object player, string group)
{
	string *tmp;
	int i;
	if(valid_admin_requestp() &&
	   (i=member_array(group,users[player][3]))>=0)
	{
		users[player][3] -= ({ group });
		sync_user(player);
		return 1;
	}
	return 0;
}

int group_create(string group)
{
	if(valid_admin_requestp())
	{
		valid_group_list+=({ group });
		sync_groups();
		return 1;
	}
	return 0;
}

int group_freeze(string group)
{
	int i;
	if( valid_admin_requestp() &&
	    valid_groupp(group) )
	{
		valid_group_list -= ({ group });
		sync_groups();
		return 1;
	}
	return 0;
}

int dir_chmod(int prop, mixed new_value, string path)
{
	string *path_elems, temp_str;
	mixed *dir_info, temp_array;
	int i;

	// may add more properties...
	if(valid_admin_requestp() && file_size(path)==-2)
	{
#ifdef DEBUG
		write("kerberos: dir_chmod(): doing chmod\n");
#endif
		// following 2 lines ensure path has no leading
		// or trailing /
		path=implode(explode(path,"/"),"/");

		// if there is no entry for this path, make it the same
		// as its parent
		if(undefinedp(dirs[path]))
		{
#ifdef DEBUG
			write("kerberos: dir_chmod: adding new dir entry\n");
#endif
			dir_info=(mixed *)master()->fstat(path);
			dirs[path]=dir_info;
		}
#ifdef DEBUG
		if(undefinedp(dirs[path]))
		{
			write("kerberos error: fstat failed\n");
			return 0;
		}
		write("kerberos: dir_chmod():\n");
		write(dirs[path]+"\n");
		write("owner: "+dirs[path][0]+"\n");
		write("group: "+dirs[path][1]+"\n");
		write("def_read: "+dirs[path][2]+"\n");
		write("set_uid: "+dirs[path][3]+"\n");
#endif
		switch(prop)
		{
		  case DIR_PROP_OWNER:
			if(!stringp(new_value)) return 0;
			if(!user_exists(new_value)) return 0;
#ifdef DEBUG
			write("kerberos: dir_chmod: change owner\n");
#endif
			dirs[path][0]=new_value;
			break;
		  case DIR_PROP_GROUP:
			if(!stringp(new_value)) return 0;
			if(!valid_groupp(new_value)) return 0;
#ifdef DEBUG
			write("kerberos: dir_chmod: change group\n");
#endif
			dirs[path][1]=new_value;
			break;
		  case DIR_PROP_DEFAULT_READ:
			if(!intp(new_value)) return 0;
#ifdef DEBUG
			write("kerberos: dir_chmod: change def_read\n");
#endif
			dirs[path][2]=new_value;
			break;
		  case DIR_PROP_SET_UID:
			if(!intp(new_value)) return 0;
#ifdef DEBUG
			write("kerberos: dir_chmod: change set_uid\n");
#endif
			dirs[path][3]=new_value;
			break;
		}
#ifndef NO_DIR_OPTOMIZE
		// optomization to remove redundant entries
		path_elems=explode(path,"/");
		if((i=sizeof(path_elems))>1)
		{
			temp_str=implode(path_elems[0..(i-2)], " ");
			temp_array=(mixed *)master()->fstat(temp_str);
			if( (temp_array[0] == dirs[path][0]) &&
				(temp_array[1] == dirs[path][1]) &&
				(temp_array[2] == dirs[path][2]) &&
				(temp_array[3] == dirs[path][3]) )
			{
				dir_info=dirs[path];
				map_delete(dir_info, dirs);
			}
		}
#endif
		sync_dirs();
		return 1;
	}
#ifdef DEBUG
		write("kerberos: dir_chmod(): failed attempt\n");
#endif
	return 0;
}

/**********************************************************************
 * Start of general security functions
 */

/*
 * Returns the name of an object.
 */
string query_name(object player)
{
	if(undefinedp(users[player])) return 0;
	return users[player][0];
}

/*
 * Returns the wiz level of an object.
 */
int query_wiz(object player)
{
	if(undefinedp(users[player])) return 0;
	return users[player][2];
}

/*
 * returns groups a given player belongs to
 */
string *query_groups(object player)
{
	int i,j;
	string *orig, *new;

	if(undefinedp(users[player])) return 0;
	orig=users[player][3];
	new=({ });
	j=sizeof(orig);
	for(i=0;i<j;i++)
		new+=({ orig[i] });
	return new;
}

/*
 * changes a player password
 * if the player isn't logged on, then it uses a dirty hack
 */
int change_player_password(mixed player, string password)
{
	mixed *user_info;
	object obj;

	if(valid_admin_requestp())
	{
		if(objectp(player))
		{
			users[player][1]=password;
			sync_user(player);
			return 1;
		}
		if(!stringp(player)) return 0;
		user_info=load_user_info(player);
		user_info[1]=crypt(password,user_info[1]);
		obj=previous_object();
		if(undefinedp(users[obj]))
		{
			users[obj]=user_info;
			save_user_info(obj);
			map_delete(users, obj);
			return 1;
		}
		return 0;
	}
	return 0;
}

/*
 * returns 1 if a player has an entry in the password file
 */
int user_exists(string name)
{
	return load_user_info(name)?1:0;
}

string *list_valid_groups()
{
	string *list;
	int i,j;

	j=sizeof(valid_group_list);
#ifdef DEBUG
	write("kerberos: list_valid_groups(): "+j+" groups.\n");
#endif
	list=({ });
	for(i=0;i<j;i++)
		list+= ({ valid_group_list[i] });
	return list;
}

#ifdef DEBUG

void write_dir_map()
{
	int i,j;
	string *key_list, *val_list;
	object you_know_who;

	key_list=keys(dirs);
	val_list=values(dirs);
	you_know_who=this_player();
	j=sizeof(key_list);
	for(i=0;i<j;i++)
		tell_object(you_know_who,
			key_list[i]+" : "+val_list[i][0]+" : "+val_list[i][1]+
			" : "+val_list[i][2]+" : "+val_list[i][3]+"\n");
}
#endif // DEBUG

// EOF.