//
// /bin/adm/master.c -- Vincent's Hollow
// This place is like a lot of little servers/daemons in one! :)
//

// need to add:
//	request_privileges()
//	compile_object()

#include <wizlev.h>
#include <daemons.h>
#include <login.h>

#include "/bin/adm/parse_com.c"

#define KERBEROS

#ifndef KERBEROS

#undef LOGIN
#define LOGIN "/bin/adm/login"

#endif

// locally used daemons.

#define	VIRTUAL		"/bin/adm/virtual"

// some misc files for master.c's use.

#define LOAD            "/bin/adm/preload"

// other "magic" values.

#define PROFILE_TIME	900 // profile to log file every x seconds.

inherit EFUN_OB;

string 	*netdead;
int	startup_time, login_count, debug;

//
// MISC QUERY 
//

string 	get_bb_uid() { return "backbone"; }
string 	get_root_uid() { return "root"; }
int	query_login_count() { return login_count; }
mixed 	query_netdead() { if(netdead) return netdead; }
int 	query_startup_time() { return startup_time; }

//
// SYSTEM STARTUP 
//

// optional flag passed to driver when first starting up 
// driver "-fcall obj func arg" "-fshutdown" 

void 
flag(string str) 
{
    string file,arg;
   
    if(sscanf(str, "for %d", arg) == 1) {
    	int i;
    	for(i = 0 ; i < arg ; i++) {
      	    // empty loop for speed test 
    	}
    	return;
    }
    if(str == "shutdown") {
    	shutdown();
    	return;
    }
    if(sscanf(str, "echo %s", arg) == 1) {
    	write(arg + "\n");
    	return;
    }
    if(sscanf(str, "call %s %s", file, arg) == 2) {
    	arg = (string)call_other(file, arg);
    	write("Got " + arg + " back.\n");
    	return;
    }
    write("master.c: Unknown flag " + str + "\n");
}

// set time for the current world's start up time 

void
set_startup_time()
{
    startup_time = time();
    log_file("SYSLOG","VH started at " + ctime(time()) + " on " +
	query_host_name() + "\n");
}

// preload stuff -- called from epilog(); 

void 
preload(string file)
{
    int t1;
    string err;
   
    if (file_size(file + ".c") == -1)
    	return;
    t1 = time();
    write("Preloading: " + file + ".");
    err = catch(call_other(file, "??"));
    if (err != 0) {
    	write("\nGot error " + err + " when loading " + file + ".\n");
    } else {
    	t1 = time() - t1;
     	write("(" + t1/60 + "." + t1 % 60 + ")\n");
    }
}

// profile for the game 

static void
system_profile()
{
    string profile;

    profile = ctime(time()) + " (" + ctime(startup_time) + ") " + 
	query_host_name() + "\n  " +
	login_count + " login " + 
	sizeof(users()) + " active " + 
	sizeof(netdead) + " netdead " +
	memusage() + " bytes\n"; 
    log_file("SYSPROFILE",profile);
    call_out("system_profile", PROFILE_TIME);
}

// loading various master data 

string 
*epilog(int load_empty)
{
    string *preload_things, str;
    int i;

    if(load_empty) {
    	write("Not preloading.\n");
    	return 0;
    }
    str = read_file(LOAD);
    if(str == 0)
    	return 0;
    preload_things = explode(str, "\n");
    for(i=0; i < sizeof(preload_things); i++) {
    	if(preload_things[i][0] == '#')
      	    preload_things[i] = 0;
    	preload(preload_things[i]);
    }
    set_startup_time();
    system_profile();

    return preload_things;
}

//
// CONNECT SERVER
//

// calls everytime someone tries to connect 

object 
connect() 
{
    object login_ob;
    mixed err;

    err = catch(login_ob = clone_object(LOGIN));
#ifdef DEBUG
	write("master:  connect:  Cloned LOGIN\n");
#endif
    if (err) {
		write("********************************************\n");
    	write("** You sense a strange disturbance in VH! **\n");
    	write("** The Pantheonic Guild's fixing it ASAP. **\n");
    	write("** Bear with us and try again later.      **\n");
		write("********************************************\n");
    	destruct(this_object());
    } 
    return login_ob;
}

//
// VARIOUS VALID CHECK SERVERS
//

int valid_exec(object ob)  
{
    string name;

    name = file_name(ob);
    if (debug)
        write("Debug: valid_exec: name = "+name+"\n");

    if (name[0..8] == "/bin/adm/")
	return 1;

    return 0;
}

nomask int
valid_shadow(object ob)
{
    if (debug)
    write("Debug: valid_shadow: ob = "+file_name(ob)
	+", previous_object = "+file_name(previous_object())+"\n");
    if(getuid(ob) == get_root_uid()) return 0;
    if(function_exists("query_wiz", previous_object())) return 0;
    if(function_exists("query_name",previous_object())) return 0;
    if(function_exists("query_real_name",previous_object())) return 0;
    return !ob->query_prevent_shadow(previous_object());
}

// do we need passing eff_user to this darn func? [michael 4/23/92]

valid_shutdown()
{
    if (debug)
    write("Debug: valid_shutdown: previous_object = "
	+ file_name(previous_object()) +"\n");
    if(geteuid(previous_object()) == get_root_uid()) return 1;
    return 0;
}

//
// Added by Dracos - anything can destruct anything for now
//

valid_destruct(object destructor, object destructee)
{
   return 1;
}

//
// Added by Dracos - nothing can hide right now
//

valid_hide(object ob)
{
   return 0;
}

//
// Added by Dracos - allow all socket functions for now
//
// info stucture: info[0] = int fd
//                info[1] = object owner
//                info[2] = string ip_address
//                info[3] = int port
//

valid_socket(object calling_ob, string func, mixed *info)
{
   return 1;
}

valid_snoop(object snooper, object snoopee) 
{
    int snooper_level, snoopee_level, last_snooper_level;
	object cursnoop;
    if (debug)
    write("Debug: valid_snoop: snooper = "+file_name(snooper)
	+", snoopee = "+file_name(snoopee) +"\n");
    if(!snoopee && snooper) return 1;
    snooper_level = (int)snooper->query_wiz();
    snoopee_level = (int)snoopee->query_wiz();
/* Can't snoop someone when someone of higher level is already. */
    cursnoop=query_snoop(snoopee);
    if (cursnoop)
		{
    last_snooper_level = (int)cursnoop->query_wiz();
    if (last_snooper_level>snooper_level) {return 0;}
		}
    if(snooper_level >= snoopee_level || snooper_level >= GOD) {
    	if(snoopee_level >= GOD || snooper_level == snoopee_level)
      	tell_object(snoopee,"You feel like someone is watching you.\n");
    	write("snoop: snooping " + snoopee->query_name() + "\n");
    	return 1;
    }
    return 0;
}

// checks who is snooping whom [michael 4/29/92]

void
check_snoop()
{
    object *ob;
    int i, wiz;

    wiz = (int)this_player(1)->query_wiz();
    ob = users();
    for(i = 0; i < sizeof(ob); i++) {
	object snooper;
	snooper = query_snoop(ob[i]);
	// if the snooper is of higher level than checker, then no-no check :)
	if(!snooper || snooper->query_wiz() > wiz) 
	    continue;
	write(snooper->query_real_name() + " -> " + ob[i]->query_name() + "\n");
    }
}

#ifdef KERBEROS

mapping dirs;
mapping groups;

#define BB_GROUPS ({ "wiz" , "senior" , "elder" , "demi" })

void
query_kerberos()
{
	dirs=(mapping)KERBEROS_D->query_dir_map();
	groups=(mapping)KERBEROS_D->query_groups_map();
	groups[get_bb_uid()]=BB_GROUPS;
}

// fstat returns an array:
// ({ string owner , string group , int default_read, int set_uid })
mixed *fstat(string args)
{
	string *path;
	int i;
	string str;

	if(args=="/") return ({ "root" , "god" , 1 , 0 });
	path=explode(args,"/");
	for(i=sizeof(path)-1;i>0;i--)
	{
		str=implode(path[0..i],"/");
		if(!undefinedp(dirs[str]))
			return ({ dirs[str][0] , dirs[str][1],
					  dirs[str][2] , dirs[str][3] });
	}
	if(!undefinedp(dirs[path[0]]))
		return ({ dirs[path[0]][0] , dirs[path[0]][1],
				  dirs[path[0]][2] , dirs[path[0]][3] });
	return ({ "root" , "god" , 1 , 0 });
}

valid_seteuid(object ob, string str)
{
	mixed *dir_entry;

    if (debug)
		write("Debug: valid_seteuid: ob = "+file_name(ob)+", str = "+str+", uid = "+getuid(ob)+"\n");

    if(getuid(ob) == get_root_uid()) return 1;
    if(str == "TEMP") return 1;
    if(getuid(ob) == str) return 1;
	dir_entry=fstat(file_name(ob));
    if(dir_entry[3] && dir_entry[0] == str) return 1;
    return 0;
}

valid_read(string path, mixed who, string func)
{
	string euid;
	mixed *dir_info;
	object caller;

	if(objectp(who))
		caller=who;
	else
		caller=find_living(who);

	if(caller)
		euid=geteuid(caller);
	else
		return 0;

	if(euid==get_root_uid()) return 1;

	dir_info=fstat(path);

	if(dir_info[1]=="everyone") return 1;
	if(euid==dir_info[0]) return 1;
	if(member_array(dir_info[1],groups[euid])>=0) return 1;
	return dir_info[2];
}

valid_write(string path, mixed who, string func)
{
	string euid;
	mixed *dir_info;
	object caller;

	// these files are not writable from within the game
	if(path[0..7]=="/bin/adm") return 0;

	if(objectp(who))
		caller=who;
	else
		caller=find_living(who);

	if(caller)
		euid=geteuid(caller);
	else
		return 0;

	if(euid==get_root_uid()) return 1;

	dir_info=fstat(path);

	if(dir_info[1]=="everyone") return 1;
	if(euid==dir_info[0]) return 1;
	if(member_array(dir_info[1],groups[euid])>=0) return 1;
	return 0;
}

#else

valid_seteuid(object ob, string str)
{
    if (debug)
    write("Debug: valid_seteuid: ob = "+file_name(ob)+", str = "+str+", uid = "+getuid(ob)+"\n");
    if(getuid(ob) == get_root_uid()) return 1;
    if(str == "TEMP") return 1;
    if(getuid(ob) == str) return 1;
    if(creator_file(file_name(ob)) == str) return 1;
    return 0;
}

// soon to add a kerberos.c server to /bin/daemon and take over this task
// same with valid_read();

valid_write(string file, mixed user, string func)
{
   string *path, eff_user, junk, junk2;
   int f_WIZ, f_SEN, f_ELD, f_DEM;
   string *special;
   int i, ender;
 
   if (debug && objectp(user))
      write("Debug: valid_write: file = "+file+", effuser = " +geteuid(user) +"\n");
   else
      if (debug)
         write("Debug: valid_write: file = "+file+", effuser = "+ user +"\n");
 
   if (!objectp(user))
      user = find_living(user);
 
   eff_user = geteuid(user);
   if (!eff_user)
      return 0;
 
   file = resolv_path("/", file);
   path = explode(file, "/");
   if (!path || sizeof(path) == 0)
      return 0;
 
   /* These files can never be writable from within */
 
   if ((path[0] == "bin") && (sizeof(path) > 1) && (path[1] == "adm"))
      return 0;
 
   /* Root can always do as they please.  */
 
   if (eff_user == get_root_uid())
      return 1;
 
   /* Backbone users have some special privs */
 
   if (eff_user == get_bb_uid()) {
      /* can write to /data/usr */
 
      if (sscanf(file, "/data/usr/%s", junk))
         return 1;
   }
 
   /* If there is forcing going on -- refuse permission. */
 
   if (this_player() != this_player(1))
      return 0;
 
   /* Let's let the gods have most fun! */
 
   if ((int)this_player()->query_wiz() >= GOD)
      return 1;
 
   /* special log write access */
 
   if (path[0] == "log")
      if (func == "log_file")
         return 1;
 
   /* Here we check for special access privs. */
 
   special = (string *)this_player()->list_access_list();
 
   if (file_size(file) == -2)
      ender = 0;
   else
      ender = 1;
 
   if (special) {
      string makepath;

      makepath = "";
      for (i = 0; i < (sizeof(path) - ender); i++)
         makepath = makepath + "/" + path[i];
 
      if (debug)
         write("Debug valid_write: makepath = "+makepath+"\n");

      /* FILE SECURITY RISK!  Disable write immediately */
 
      if (member_array("FSR", special) > -1)
         return 0;
 
      /* grant permission if permission is in access list */
 
      if ((member_array(makepath, special) > -1) ||
               (member_array(makepath+"/", special) > -1))
         return 1;
 
      /* Granting permission for special wiz levels */
 
      if (member_array("WIZ", special) > -1)
         f_WIZ = 1;
 
      if (member_array("SEN", special) > -1)
         f_SEN = 1;
 
      if (member_array("ELD", special) > -1)
         f_ELD = 1;
 
      if (member_array("DEM", special) > -1)
         f_DEM = 1;
   }
 
   while (sizeof(path) < 5)
      path += ({ "" });
 
   /* as long as the user is not FSR, he/she can write to these */
 
   if (path[0] == "tmp" || path[0] == "open")
      return 1;
 
   /* Here we check for access to some other directories */
 
   if (f_WIZ || f_SEN || f_ELD || f_DEM)
      if (path[0] == "w")
         if (path[1] == eff_user)
            return 1;
 
   if (f_SEN || f_ELD || f_DEM)
      if (path[0] == "w")
         return 1;
 
   if (f_ELD || f_DEM)
      if (path[0] == "doc")
         return 1;
 
   if (f_DEM) {
      if (path[0] == "room" || path[0] == "obj")
         return 1;
 
      if (path[0] == "std" )
         return 1;
 
      if (path[0] == "log" && path[1] == "history")
         return 1;
   }
 
   /* No write permission. (DEFAULT) */
 
   return 0;
}

valid_read(string file, mixed user, string func)
{ 
    string *path, eff_user;

    if (debug && objectp(user))
write("Debug: valid_read: file = "+file+", effuser = " +geteuid(user) +"\n");

    else
	if (debug)
	   write("Debug: valid_read: file = "+file+", effuser = "+ user +"\n");

    if(!objectp(user))
	user = find_living(user);
    eff_user = geteuid(user);
    if(!eff_user) return 0;
    
    file = resolv_path("/", file);

    /* if you can write to it, you can read it */
    if(valid_write(file, user, func)) return 1; 

    path = explode(file, "/");
    if(!path)
        path = allocate(5);
    else
        while(sizeof(path) < 5)
            path += ({ "" });

/* root reads all */
  if (eff_user == "root") return 1;
    /* Here we check for directories of no read */
    if(path[0] == "w" && path[2] == "closed") return 0;
    if(path[0] == "data" && path[1] == "usr") return 0;
    if(path[0] == "data" && path[1] == "adm") return 0;



    /* I, dirk, am sick and tired of people reading history logs!!! */
    if(path[0] == "log" && path[1] == "history") return 0;

    /* read (default) */
    return 1;
}

#endif

//
// SAVE/RESTORE_OBJECT SERVERS
//

save_player()
{
    object pobj;
    int res;
   
    pobj = previous_object();
    export_uid(pobj);
    res = (int)pobj->save_player(pobj->query_real_name());
    seteuid((string)pobj->query_real_name());
    export_uid(pobj);
    seteuid(get_root_uid());
    return res;
}

load_player_from_file(string name, object player) 
{
    int res;
   
    export_uid(player);
    res = (int)player->actually_restore_player(name);
    seteuid(name);
    export_uid(player);
    seteuid(get_root_uid());
    return res;
}

void 
save_player_to_file(object player) 
{
    string name;

    name = (string)player->query_real_name();
    if(!name) return;
    if(!seteuid(get_root_uid()))
    	write("Seteuid failed!\n");
    export_uid(player);
    player->actually_save_player(name);
    seteuid(name);
    export_uid(player);
    seteuid(get_root_uid());
}

//
// MISC SYSTEM SERVERS
//

// when the system goes down, this will try to make it less "deadly"

static void
crash(string str, object cmd_giver, object curob)
{
    log_file("SYSLOG","VH crashed at " + ctime(time()) + " on " +
	query_host_name() + "\n");
    log_file("SYSLOG", "\tReason: " + str + "\n");
//    log_file("SYSLOG", "\tCommand Giver: "+cmd_giver->query_name()+"\n");
//    log_file("SYSLOG", "\tCurrent Object: "+file_name(curob)+"\n");
}

// when room is destroyed, the contents in it are "saved"

void 
destruct_environment_of(object ob) 
{
    string place, res;

    if(!interactive(ob)) return;
    place = file_name(environment(ob));
    tell_object(ob,
	"A fiery burst of light erupts out of nowhere as the area around\n"+
	"you disappears from view.  You sense that a mighty wizard has\n"+
	"transformed the place back into pure magic.  Then, everything\n"+
	"slowly comes back together again....\n");
    res = catch(ob->move(place));
    if(res) 
    	ob->move("/room/void");
}

// keep track of number of logins

void
add_login_count()
{
    login_count++;    
}

// keep track of netdead players/wiz

void 
add_netdead(string str)
{
    if(this_player(1) != this_player()) return;
    if(netdead)
	if(member_array(str, netdead) != -1) return;
    if(netdead && sizeof(netdead) > 0)
	netdead += ({ str });
    else
	netdead = ({ str });
    log_file("NETDEAD", str + " (" + query_ip_name() + ") " +
	ctime(time()) + "\n");
}

void
remove_netdead(string str)
{
    /* have to check whether netdead==0 or nobody can log in - Quag */
    if(netdead && member_array(str, netdead) != -1)
    	netdead -= ({ str });
}

// Simple enough, most folks don't have read in /data/save
// do we still need this? [michael 4/23/92]

player_exists(string name) 
{
   if(file_size("/data/adm/save/"+ extract(name,0,0) + "/" +name+ ".o") < 0 )
      return 0;
   return 1;
}

// create error log files

string 
get_wiz_name(string file) 
{
    string name, rest;
    int tmp;
   
    if(sscanf(file, "w/%s/%s", name, rest) == 2) return "/w/" + name;
    if(sscanf(file, "bin/%s", rest) == 1) return "bin";
    if(sscanf(file, "obj/%s", rest) == 1) return "obj";
    if(sscanf(file, "open/%s", rest) == 1) return "open";
    if(sscanf(file, "room/%s", rest) == 1) return "room";
    if(sscanf(file, "std/%s", rest) == 1) return "std";
    return "log";
}

void 
log_error(string file, string message)
{
    string name, log, tmp;
   
    name = get_wiz_name(file);

/* New version of error logs by sunnywiz */
    log = sprintf("%s %s\n> %s",query_time("date"),query_time("time"),
		    message);

//    log = extract(ctime(time()),11,18) + "\n" + message + "\n";

    if(sscanf(name, "/w/%s", tmp) == 1) {
	write_file(name + "/.error", log);
    } else 
    	log_file(name, log);
}

// For wizard who wants to save ed setup. It is saved in the file 
// /w/wiz_name/.edrc.   

save_ed_setup(object who, int code) 
{
    string file;
   
    if (!intp(code)) return 0;
    file = "/w/" + (string)who->query_real_name() + "/.edrc";
    rm(file);
    return write_file(file, code + "");
}

retrieve_ed_setup(object who) 
{
    string file;
    int code;
   
    file = "/w/" + (string)who->query_real_name() + "/.edrc";
    if (file_size(file) <= 0)
    	return 0;
    sscanf(read_file(file), "%d", code);
    return code;
}

// This is called by the game driver to resolve path names in ed.

string 
make_path_absolute(string file) 
{
    file = resolv_path((string)this_player()->query_path(), file);
    return file;
}

// virtual object server.

mixed
compile_object(string file)
{
    return (mixed) VIRTUAL->compile_object(file);
}

// This lets us turn on debugging info when we want.
// This gets noisy.  Currently only checks valid_* functions.

void
set_debug_info()
{
    debug = !debug;
}

// EOF