// /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.