/* /secure/daemon/master.c
* from the Dead Souls LPC Library
* the master object, responsible for security
* created by Descartes of Borg 940910
* error handling by Beek@The Idea Exchange 941004
* Version: @(#) master.c 1.7@(#)
* Last modified: 96/12/14
*/
#include <config.h>
#include <rooms.h>
#include <cfg.h>
#include <lib.h>
#include <objects.h>
#include <privs.h>
#include <dirs.h>
#include <save.h>
#include <daemons.h>
#include <commands.h>
#include <network.h>
#include <parser_error.h>
#include <message_class.h>
#include "master.h"
private static int ResetNumber;
private static object Unguarded;
private static string PlayerName;
private static object NewPlayer;
private static mapping Groups, ReadAccess, WriteAccess;
void create() {
Unguarded = 0;
NewPlayer = 0;
PlayerName = 0;
ResetNumber = 1;
new_read();
new_write();
new_groups();
call_out( (: eventReset :), TIME_TO_RESET );
}
void new_read() {
mapping tmp;
tmp = ([]);
load_access(CFG_READ, tmp);
ReadAccess = tmp;
}
void new_write() {
mapping tmp;
tmp = ([]);
load_access(CFG_WRITE, tmp);
WriteAccess = tmp;
}
void new_groups() {
mapping tmp;
tmp = ([]);
load_access(CFG_GROUPS, tmp);
Groups = tmp;
}
private static void load_access(string cfg, mapping resource) {
string *lines;
string file;
if( !(file = read_file(cfg)) ) {
error("Failed to find config file: "+cfg);
}
lines = filter(explode(file, "\n"), function(string line) {
int i;
if( !line || trim(line) == "" ) {
return 0;
}
if( line[0] == '#' ) {
return 0;
}
return 1;
});
foreach(string line in lines) {
string fl, ac;
if( sscanf(line, "(%s) %s", fl, ac) != 2 ) {
error("Error in loading config file " + cfg + ".");
}
resource[fl] = explode(ac, ":");
}
}
void flag(string str) {
string file, arg;
object ob;
int i, x;
if(previous_object()) return;
if(sscanf(str, "for %d", x) == 1) {
for(i=0; i<x; i++) {}
return;
}
if(sscanf(str, "call %s %s", file, arg)) {
write("Got "+(string)call_other(file, arg)+" back.\n");
return;
}
write("Master: unknown flag.\n");
}
string *epilog(int x) {
string *lines, *files;
string content;
int i;
if(!(content = read_file(CFG_PRELOAD))) return ({});
i = sizeof(lines = explode(content, "\n"));
files = ({});
while(i--) {
if(!lines[i] || lines[i] == "" || lines[i][0] == '#') continue;
files += ({ lines[i] });
}
return files;
}
string privs_file(string file) {
string nom = 0;
int ext;
if( !strsrch(file, DIR_PLAYERS) ) sscanf(file, DIR_PLAYERS "/%*s/%s",nom);
else if( !strsrch(file, DIR_CRES) ) sscanf(file, DIR_CRES "/%*s/%s",nom);
if( nom ) {
if( file == DIR_CRES + "/" + nom[0..0] + "/" + nom ) {
string str, grp;
str = nom;
foreach( grp in keys(Groups) )
if( member_array(nom, Groups[grp]) != -1) str = str + ":" + grp;
return str;
}
else if( file == DIR_PLAYERS + "/" + nom[0..0] + "/" + nom )
return nom;
else return 0;
}
return file_privs(file);
}
void preload(string str) {
string err;
int t;
if( !file_exists(str + ".c") ) return;
t = time();
write("Preloading: " + str + "...");
if( err = catch(call_other(str, "???")) )
write("\nGot error "+err+" when loading "+str+".\n");
else {
t = time() - t;
write("("+(t/60)+"."+(t%60)+")\n");
}
}
int valid_write(string file, object ob, string fun) {
string *ok;
if( ob == master() ) return 1;
ok = match_path(WriteAccess, file);
return check_access(ob, fun, file, ok, "write");
}
int valid_read(string file, object ob, string fun) {
string *ok;
if( ob == master() ) return 1;
ok = match_path(ReadAccess, file);
return check_access(ob, fun, file, ok, "read");
}
int valid_apply(string *ok) {
return check_access(previous_object(1),0,previous_object(0), ok, "apply");
}
int check_access(object ob, string fun, mixed file, string *ok, string oper) {
object *stack;
string *privs;
string priv;
int i;
if( objectp(file) ) file = base_name(file);
if( ok && sizeof(ok) && ok[0] == "all" ) return 1;
if( Unguarded == ob ) {
string tmp;
if( (tmp = base_name(ob)) == LIB_PLAYER || tmp == LIB_CREATOR) {
if( !PlayerName ) i = sizeof(stack = ({ob})+previous_object(-1));
else if( file == DIR_PLAYERS+"/"+PlayerName[0..0]+"/"+
PlayerName + __SAVE_EXTENSION__ )
return 1;
else if( file == DIR_CRES+"/"+PlayerName[0..0]+"/"+
PlayerName + __SAVE_EXTENSION__ )
return 1;
else i = sizeof(stack = ({ ob }));
}
else if( tmp + __SAVE_EXTENSION__ == file ) return 1;
else i = sizeof(stack = ({ ob }));
}
else if(Unguarded && base_name(ob) == SEFUN) {
if(Unguarded == previous_object(1))
stack = ({ previous_object(1) });
else stack = ({ ob }) + previous_object(-1);
}
else i = sizeof(stack = previous_object(-1) + ({ ob }));
while(i--) {
if(!stack[i] || stack[i] == this_object()) continue;
if(file_name(stack[i]) == SEFUN) continue;
if(!(priv = query_privs(stack[i]))) return 0;
if(!ok && oper == "read") continue;
privs = explode(priv, ":");
if(member_array(PRIV_SECURE, privs) != -1) continue;
if(member_array(file_privs(file), privs) != -1) continue;
if(!ok && oper == "write") {
if(userp(stack[i]) && check_user(stack[i], fun, file, oper))
continue;
else return 0;
}
if(sizeof(privs & ok)) continue;
if(userp(stack[i]) && check_user(stack[i], fun, file, oper)) continue;
if(userp(stack[i]) && check_domain(stack[i], fun, file,oper)) continue;
return 0;
}
return 1;
}
nomask static int check_user(object ob, string fun, string file, string oper) {
string nom, tmp;
int x;
if( !sscanf(file, REALMS_DIRS "/%s", nom) ) return 0;
if( sscanf(nom, "%s/%*s", tmp) ) nom = tmp;
nom = user_path(nom)+"adm/access";
if(file_size(nom+".c") < 0) return 0;
catch(x = (int)call_other(nom, "check_access", ob, fun, file, oper));
return x;
}
nomask static int check_domain(object ob, string fun, string file, string o) {
string nom;
int x;
if( !sscanf(file, DOMAINS_DIRS+"/%s/%*s", nom) ) return 0;
nom = DOMAINS_DIRS+"/"+nom+"/adm/access";
if(file_size(nom+".c") < 0) return 0;
catch(x = (int)call_other(nom, "check_access", ob, fun, file, o));
return x;
}
object connect(int port) {
object ob;
string err;
string file;
file = LIB_CONNECT;
if( err = catch(ob = new(file)) ) {
write("It looks like someone is working on the user object.\n");
write(err);
destruct(ob);
}
return ob;
}
object compile_object(string str) {
string nom, tmp, where, which;
object ob;
if(sscanf(str, REALMS_DIRS+"/%s/%*s", nom))
tmp = sprintf("%svirtual/server", user_path(nom));
else if(sscanf(str, DOMAINS_DIRS+"/%s/%*s", nom))
tmp = sprintf("%s/%s/virtual/server", DOMAINS_DIRS, nom);
else if(strsrch(str, ESTATES_DIRS) == 0)
tmp = sprintf("%s/adm/server", ESTATES_DIRS);
else if(sscanf(str, DIR_PLAYERS+"/%*s/%s", nom)) {
if(!NewPlayer) return 0;
if((string)NewPlayer->GetKeyName() != nom) return 0;
PlayerName = nom;
ob = new(LIB_PLAYER);
if(file_size(str+__SAVE_EXTENSION__) > 0) ob->restore_player(nom);
else if(file_size(DIR_PLAYERS) != -2) mkdir(DIR_PLAYERS);
else if(file_size(DIR_PLAYERS+"/"+nom[0..0]) != -2)
mkdir(DIR_PLAYERS+"/"+nom[0..0]);
ob->SetKeyName(nom);
PlayerName = 0;
return ob;
}
else if( sscanf(str, DIR_CRES+"/%*s/%s", nom) ) {
if(!NewPlayer) return 0;
if((string)NewPlayer->GetKeyName() != nom) return 0;
PlayerName = nom;
ob = new(LIB_CREATOR);
if(file_size(str+__SAVE_EXTENSION__) > 0) ob->restore_player(nom);
ob->SetKeyName(nom);
PlayerName = 0;
return ob;
}
if(file_size(tmp+".c") < 0) {
if(sscanf(str, "%s.%s", where, which) != 2) return 0;
if(sscanf(str, REALMS_DIRS+"/%s/%*s", nom))
tmp = sprintf("%svirtual/%s_server", user_path(nom), which);
else if(sscanf(str, DOMAINS_DIRS+"/%s/%*s", nom))
tmp = sprintf("%s/%s/virtual/%s_server", DOMAINS_DIRS, nom, which);
if(file_size(tmp+".c") < 0) return 0;
else return (object)call_other(tmp, "compile_object", where);
}
return (object)call_other(tmp, "compile_object", str);
}
static void crash(string err) {
write_file(DIR_LOGS "/crashes",
mud_name() + " crashed " + ctime(time()) + " with error " +
err+".\n");
message("system", "Reality implosion!!! Everyone duck!!!", users());
message("system", "You are being forced to quit.", users());
users()->cmdQuit();
}
int valid_bind(object binder, object old_owner, object new_owner) {
if( binder == master() ) return 1;
if( member_array(PRIV_SECURE, explode(query_privs(binder), ":")) != -1 )
return 1;
return 0;
}
int valid_hide(object who) {
string priv;
if(!objectp(who)) return 0;
if(environment(who) && hiddenp(environment(who))) return 1;
if(!(priv = query_privs(who))) return 0;
else return (member_array(PRIV_SECURE, explode(priv, ":")) != -1);
}
int valid_override(string file, string nom) { return (file == SEFUN); }
int valid_save_binary(string str) { return 1; }
int valid_shadow(object ob) {
object targ = previous_object();
return (!virtualp(targ) && !strsrch(file_name(targ), DIR_SHADOWS));
}
int valid_object(object ob) {
string file;
file = file_name(ob);
if( !strsrch(file, DIR_TMP) ) return 0;
else if( !strsrch(file, DIR_FTP) ) return 0;
else if( !strsrch(file, DIR_LOGS) ) return 0;
else return 1;
}
int valid_socket(object ob, string fun, mixed *info) {
object *obs;
object who;
int port;
string tmp;
int i;
if( info && sizeof(info) == 4 ) {
ob = info[1];
port = info[3];
if( port == PORT_ADMIN && ob != find_object(ADMIN_D) ) {
return 0;
}
if( port == PORT_RCP && ob != find_object(REMOTE_D) ) {
return 0;
}
}
i = sizeof(obs = previous_object(-1));
while(i--) {
if( !obs[i] ) continue;
if( userp(obs[i]) ) continue;
if( !(tmp = query_privs(obs[i])) ) return 0;
if( !sizeof(explode(tmp, ":") &
({ PRIV_SECURE, PRIV_MUDLIB, PRIV_CMDS, PRIV_GENERAL })) ) return 0;
}
return 1;
}
mixed apply_unguarded(function f) {
object previous_unguarded;
string base, err, tmp;
mixed val;
if(base_name(previous_object(0)) != SEFUN) {
error("Illegal unguarded apply.");
return 0;
}
previous_unguarded = Unguarded;
Unguarded = previous_object(1);
err = catch(val = (mixed)(*f)());
Unguarded = previous_unguarded;
if(err) error(err);
return val;
}
string error_handler(mapping mp, int caught) {
string ret, file;
ret = "---\n" + standard_trace(mp);
if( caught ) write_file(file = "/log/catch", ret);
else write_file(file = "/log/runtime", ret);
if( this_player(1) && find_object(SEFUN) ) {
this_player(1)->SetLastError(mp);
if( creatorp(this_player(1)) ) {
this_player(1)->eventPrint(ret + "Trace written to " + file,
MSG_SYSTEM);
}
else {
if( !strsrch(file_name(this_player(1)), LIB_CONNECT) ) {
return "/log/login";
}
this_player()->eventPrint("A runtime error occurred, "
"use \"bug -r\" to report it.",
MSG_SYSTEM);
CHAT_D->eventSendChannel("System", "error", "A runtime error "
"occurred to " +
(string)this_player(1)->GetCapName()+".");
}
}
return 0;
}
void log_error(string file, string msg) {
string nom, home, tmp;
if( file[0] != '/' ) {
file = "/" + file;
}
if( sscanf(file, REALMS_DIRS+"/%s/%s", nom, tmp) != 2 &&
sscanf(file, DOMAINS_DIRS+"/%s/%s", nom, tmp) != 2 )
sscanf(file, "/%s/%s", nom, tmp);
if( !nom ) nom = "log";
catch(write_file(DIR_ERROR_LOGS "/" + nom, msg));
}
varargs string standard_trace(mapping mp, int flag) {
string obj, ret;
mapping *trace;
int i,n;
ret = mp["error"] + "Object: " +
trace_line(mp["object"], mp["program"], mp["file"], mp["line"]);
ret += "\n";
trace = mp["trace"];
n = sizeof(trace);
for (i=0; i<n; i++) {
if( flag ) ret += sprintf("#%d: ", i);
ret += sprintf("'%s' at %s", trace[i]["function"],
trace_line(trace[i]["object"], trace[i]["program"],
trace[i]["file"], trace[i]["line"]));
}
return ret;
}
string trace_line(object obj, string prog, string file, int line) {
string ret;
string objfn = obj ? file_name(obj) : "<none>";
ret = objfn;
if( different(objfn, prog) ) ret += sprintf(" (%s)", prog);
if( file != prog ) ret += sprintf(" at %s:%d\n", file, line);
else ret += sprintf(" at line %d\n", line);
return ret;
}
int different(string fn, string pr) {
int tmp;
sscanf(fn, "%s#%d", fn, tmp);
fn += ".c";
return (fn != pr) && (fn != ("/" + pr));
}
void master_log_file(string file, string msg) {
if(file_name(previous_object()) != SEFUN) return;
if(file_size(file) > MAX_LOG_SIZE) rename(file, file+".old");
write_file(file, msg);
}
string make_path_absolute(string file) {
return absolute_path((string)this_player(1)->query_cwd(), file);
}
int player_exists(string nom) {
string str;
if( !nom ) return 0;
str = DIR_PLAYERS "/" + nom[0..0] + "/" + nom + __SAVE_EXTENSION__;
if( file_size(str) > -1 ) return 1;
str = DIR_CRES "/" + nom[0..0] + "/" + nom + __SAVE_EXTENSION__;
return (file_size(str) > -1);
}
string domain_file(string str) {
string nom, tmp;
if(sscanf(str, DOMAINS_DIRS+"/%s/%s", nom, tmp) == 2) return nom;
return 0;
}
string author_file(string str) {
string nom, tmp;
if(sscanf(str, REALMS_DIRS+"/%s/%s", nom, tmp) == 2) return nom;
return 0;
}
static int slow_shutdown() {
write_file(DIR_LOGS "/audit",
"Armageddon loaded by master: "+ctime(time())+".\n");
EVENTS_D->eventRebootMud(2);
return 1;
}
int save_ed_setup(object who, int code) {
string file;
if(!intp(code)) return 0;
rm(file = user_path((string)who->GetKeyName())+".edrc");
return write_file(file, code+"");
}
int retrieve_ed_setup(object who) {
string file;
int x;
file = user_path((string)who->GetKeyName())+".edrc";
if(!file_exists(file)) return 0;
return to_int(read_file(file));
}
string get_save_file_name(string file) {
string str;
str = (string)this_player(1)->GetKeyName();
if(file_size(user_path(str)) == -2)
return user_path(str)+"dead.edit";
else return DIR_TMP+"/"+str+".dead.edit";
}
int is_locked() { return MUD_IS_LOCKED; }
string *parse_command_id_list() { return ({ "one", "thing" }); }
string *parse_command_plural_id_list() { return ({ "ones", "things","them"}); }
string *parse_command_adjectiv_id_list() {
return ({ "the", "an", "a" });
}
string *parse_command_prepos_list() {
return ({ "in", "with", "without", "into", "for", "on", "under", "against",
"out", "within", "of", "from", "between", "at", "to", "over", "near",
"inside", "onto", "off", "through", "across", "up", "down",
"around", "about" });
}
string parse_command_all_word() { return "all"; }
string parser_error_message(int type, object ob, mixed arg, int flag) {
string err;
if( ob ) err = (string)ob->GetShort();
else err = "";
switch(type) {
case ERR_IS_NOT:
if( flag ) err = "There is no such " + remove_article(arg) + " here.";
else err = "There is no " + remove_article(arg) + " here.";
break;
case ERR_NOT_LIVING:
if( flag )
err = "None of the " + pluralize(remove_article(arg)) +" are alive.";
else err = "The " + remove_article(arg) + " is not alive.";
break;
case ERR_NOT_ACCESSIBLE:
if( flag ) err = "You can't get to them.";
else err = "You can't get to it.";
break;
case ERR_AMBIG:
{
mixed *obs;
int i;
obs = unique_array(arg, (: (string)$1->GetShort() :));
if( sizeof(obs) == 1 )
err = "Which of the " +
consolidate(sizeof(arg), (string)obs[0][0]->GetShort()) +
" do you mean?";
else {
err = "Do you mean ";
for(i = 0; i<sizeof(obs); i++) {
if( sizeof(obs[i]) > 1 )
err += "one of the " +
consolidate(sizeof(obs[i]),(string)obs[i][0]->GetShort());
else err += (string)obs[i][0]->GetShort();
if( i == (sizeof(obs)-2) ) err += " or ";
else if( i < sizeof(obs) - 1 ) err += ", ";
}
err += "?";
}
return err;
}
case ERR_ORDINAL:
if( arg > 1 ) err = "There are only " + arg + " of them.";
else err = "There is only one of them.";
break;
case ERR_ALLOCATED:
return arg;
case ERR_THERE_IS_NO:
return "There is no " + remove_article(arg) + " here.";
case ERR_BAD_MULTIPLE:
return "You can't do that to more than one at a time.";
}
return err;
}
void create_save() {
string str;
if(!stringp(str = (string)previous_object()->GetKeyName())) return;
if(file_size(DIR_PLAYERS+"/"+str[0..0]) == -2) return;
if(str[0] < 'a' || str[0] > 'z') return;
mkdir(DIR_PLAYERS+"/"+str[0..0]);
}
object player_object(string nom) {
object ob;
string err, tmp;
int old_limit;
tmp = base_name(ob = previous_object());
if( tmp != CMD_ENCRE && tmp != CMD_DECRE && tmp != LIB_CONNECT )
return 0;
old_limit = max_eval_cost();
set_eval_limit(1000000000);
NewPlayer = ob;
if(file_size(DIR_CRES+ "/" + nom[0..0]+ "/" +nom+__SAVE_EXTENSION__) > -1)
err = catch(ob = load_object(DIR_CRES+"/"+nom[0..0]+"/"+nom));
else err = catch(ob = load_object(DIR_PLAYERS+"/"+nom[0..0]+"/"+nom));
NewPlayer = 0;
set_eval_limit(old_limit);
if(err) error(err);
return ob;
}
string player_save_file(string nom) {
string tmp;
tmp = DIR_CRES + "/" + nom[0..0] + "/" + nom;
if( file_size(tmp + __SAVE_EXTENSION__) > -1 ) return tmp;
else return DIR_PLAYERS + "/" + nom[0..0] + "/" + nom;
}
string *query_group(string grp) { return copy(Groups[grp]); }
mapping query_groups() { return copy(Groups); }
static void eventReset() {
object *obs;
object ob;
int x, y;
ResetNumber++;
call_out( (: eventReset :), TIME_TO_RESET );
x = reclaim_objects();
write_file(DIR_LOGS "/reset", "Reset " + ResetNumber + " occurred at: " +
ctime(time()) + "\n");
obs = objects( (: !environment($1) && (random(100) < 26) :) );
y = 0;
foreach(ob in obs) {
function f;
if( !ob ) {
y++;
continue;
}
f = bind( (: call_other, ob, "clean_up" :), ob );
if( f ) catch(evaluate(f));
if( !ob ) {
y++;
continue;
}
f = bind((: call_other, ob, "reset", ResetNumber :), ob);
if( f ) catch(evaluate(f));
}
write_file(DIR_LOGS "/reset", "\t" + x + " objects reclaimed, " +
(sizeof(obs) - y) + " objects reset, " + y + " objects "
"cleaned.\n");
}
int GetResetNumber() { return ResetNumber; }
object *parse_command_users() {
return filter(users(), (: creatorp($1) || (int)$1->is_living() :));
}