// $Id: master.c,v 1.66 2008/07/20 21:26:04 lynx Exp $ // vim:syntax=lpc:ts=8 // #ifdef INIT_FILE #undef INIT_FILE #endif #define INIT_FILE "/local/init.ls" // protos mixed valid_read(string path, string eff_user, string call, object caller); mixed valid_write(string path, string eff_user, string call, object caller); // bei amylaar und ldmud braucht master.c den absoluten pfad.. #include "/local/config.h" #ifdef Dmaster # undef DEBUG # define DEBUG Dmaster #endif #include NET_PATH "include/net.h" #include DRIVER_PATH "sys/driver_hook.h" #include DRIVER_PATH "sys/debug_message.h" // go fetch the rest of the master object code #ifdef PRO_PATH inherit PRO_PATH "master"; #else inherit DRIVER_PATH "master/accept"; #endif #include DRIVER_PATH "master/classic.i" /* Since the normal operation mode of psyced is not to have any * access by "wizards" all of the security functions of LPMUD are * disabled by the following dummy functions */ int valid_exec(string name, object ob, object obfrom) { // switch(name) { // case "secure/login.c": // case "obj/master.c": #ifdef SANDBOX // sucks here. i'd love to use uids, but we get a $%!!" // program name string #ifndef __COMPAT_MODE__ if (name[0] == '/') name = name[1..]; #endif if (abbrev(name, "users/")) return 0; #endif if (!interactive(ob)) { return 1; } // } return 0; } mixed valid_write(string path, string eff_user, string call, object callo) { P4(("valid_write(%O,%O,%O,%O)\n", path,eff_user,call,caller)) #ifdef SANDBOX int i; if (stringp(eff_user)) { if (eff_user[0] == '/') { return 1; } else if (eff_user[0] == '@') { string file = eff_user[1..]; PT((">> %O %O\n", path[12..], file)); return abbrev(DATA_PATH "place/", path) && path[12..] == file; } } return 0; #else return 1; #endif } mixed valid_read(string path, string eff_user, string call, object callo) { P4(("valid_read(%O,%O,%O,%O)\n", path,eff_user,call,caller)) #ifdef SANDBOX int i; if (stringp(eff_user)) { if (eff_user[0] == '/') { return 1; } else if (eff_user[0] == '@') { string file = eff_user[1..]; PT((">> %O %O\n", path[12..], file)); return abbrev(DATA_PATH "place/", path) && path[12..] == file; } } return 0; #else return 1; #endif } mixed privilege_violation(string op, mixed who, mixed arg3, mixed arg4) { P4(("privilege %s(%O,%O) granted to %O\n", op,arg3,arg4,who)) #ifdef SANDBOX unless(objectp(who)) { P0(("ΒΆΒΆ master: privilege_violation(%O, %O, %O) from !obj %O\n", op, arg3, arg4, who)); return 0; } if (stringp(geteuid(who)) && geteuid(who)[0] == '/') { return 1; } return 0; #else return 1; #endif } string *include_dirs() { return ({ #ifdef PRO_PATH PRO_PATH "include/", #endif #ifdef NET_PATH NET_PATH "include/", #endif #ifdef DRIVER_PATH DRIVER_PATH "include/", DRIVER_PATH "sys/", // this should be faded out TODO DRIVER_PATH, #else "/sys/", #endif "/include/" }); } #if 0 void log_error(string file, string err, int warn) { debug_message(S("\n\n\n\n\n%O %O in %O\n", warn ? "WARNING" : "ERROR", err, file), 1+2+4); } #endif void runtime_error(string error, string program, string current_object, int line) { string msg; if (program && current_object) { msg = sprintf("EXCEPTION in %s:%d (%s):\n\t%s", program, line, current_object, error); debug_message(msg, DMSG_STDERR | DMSG_LOGFILE); // | DMSG_STAMP); //, DMSG_STDOUT | DMSG_STDERR | DMSG_LOGFILE | DMSG_STAMP); // DMSG_DEFAULT: /* log to stdout and .debug.log */ // it's on old mud tradition to show the error to the current player // but in psycworld these are protocols of potentially very delicate // syntaxes, so let's be kind and not break them. #if DEBUG > 0 // let's tell the object about it, // hoping that it will not recurse into a further error -lynX // obviously i forgot master can be interactive itself! if (this_interactive() && this_interactive() != ME) this_interactive()->runtime_error(error, program, current_object, line, msg); #endif #if 0 } else { // the else case isn't interesting as the driver puts a comment // about it into the debug log anyway. looks like this: // "Object 'psyc:...' the closure was bound to has been destructed" // "No program to trace." // and happens when an outgoing connection has been made for a circuit // that has been destroyed in the meantime.. like by shutdown() // i don't think we can avoid such a circumstance really, so consider // it a regular behaviour - a warning message, but nothing's wrong really. // i mean.. we don't want to wait for connections to establish while we // are shutting down.. do we? msg = "EXCEPTION: "+ error; debug_message(msg, DMSG_LOGFILE | DMSG_STAMP); #endif } } void disconnect(object ob, string remaining) { string host = query_ip_name(ob); string name = object_name(ob); // happens when first clone fails: unless (ob && objectp(ob) && ob != ME) return; // this disconnect was expected behaviour: if (ob->disconnected(remaining)) return; // unexpected disconnection. // // disconnected() must return true when the // socket disconnection was expected and is no // cause for concern. if we got here, it is. // // 'remaining' is empty in most cases, still // we don't output 'remaining' on console // as it occasionally triggers a utf8 conversion error // instead we drop this into a logfile SIMUL_EFUN_FILE -> log_file("DISC", "%O %O %O\n", name, host, remaining); P2(("Unexpected disconnect in %s from %O%s\n", name, host, remaining && strlen(remaining) ? " with "+ strlen(remaining) +" bytes remaining" : "")) } volatile object psycd; #ifdef SPYC_PATH volatile object spycd; #endif #ifdef SIP_PATH volatile object sipd; #endif void receive_udp(string host, string msg, int port) { if (strlen(msg) > 1 && msg[1] == '\n') switch(msg[0]) { #ifdef SPYC_PATH # if !__EFUN_DEFINED__(psyc_parse) # echo libpsyc is not enabled in driver. Using old protocol parser instead. # else case '|': unless (spycd) { spycd = SPYC_PATH "udp" -> load(); P1(("SPYC UDP daemon created.\n")) unless (spycd) return; } spycd -> parseUDP(host, port, msg); return; # endif #endif case '.': unless (psycd) { psycd = PSYC_PATH "udp" -> load(); P1(("PSYC UDP daemon created.\n")) unless (psycd) return; } psycd -> parseUDP (host,port,msg); return; } #ifdef SIP_PATH string mc, rcpt; if (abbrev("SIP", msg) || sscanf(msg, "%s sip:%s", mc, rcpt) == 2) { unless (sipd) { sipd = SIP_PATH "udp" -> load(); PT(("SIP UDP daemon created.\n")) unless (sipd) return; } sipd -> parseUDP (host, port, msg, mc, rcpt); return; } #endif P1(("Caught unknown UDP packet from %s:%d\n", host,port)) P2(("Content: %O\n", msg)) SIMUL_EFUN_FILE -> log_file("UDP", "[%s] %s:%d %O\n", ctime(), host, port, msg); return; } #ifndef __LDMUD__ // older versions may still need this.. void receive_imp(string host, string msg, int port) { receive_udp(string host, string msg, int port); } #endif // this master function is called at the end of the shutdown procedure void notify_shutdown(string crash_reason) { object o; int i; P0(("notify_shutdown(%O) from %O\n", crash_reason, previous_object())) if (previous_object() && previous_object() != this_object()) return; #if DEBUG > 0 if (crash_reason) PP(("CRASH! %O\n", crash_reason)); #endif SIMUL_EFUN_FILE -> log_file("LOGON", "[%s] _ SERVER SHUTDOWN (%O)\n", ctime(), crash_reason); #ifdef DEBUG_LOG SIMUL_EFUN_FILE -> log_file(DEBUG_LOG, "[%s] _ SERVER SHUTDOWN (%O)\n", ctime(), crash_reason); #endif // walk thru the shutdown path a third time in case this is a // shutdown by kill -1 process. SIMUL_EFUN_FILE -> server_shutdown("notify_shutdown: "+ crash_reason, 4404, 2); // save_wiz_file(); } // called when memory gets low or something like that.. never seen this happen void slow_shut_down(int minutes) { SIMUL_EFUN_FILE -> shout(0, "_notice_broadcast_shutdown_panic", "Server is slowly running out of memory. Restart imminent."); SIMUL_EFUN_FILE -> server_shutdown("slow_shut_down: "+ minutes, 1); } // called by driver at shutdown for every user // but *after* all network sockets have been shut // so its mostly useless void remove_player(object victim) { if (victim) { // this message is normal for psyc circuits // they need to be available til the bitter end P2(("%O found still alive after reboot()\n", victim)) //catch(victim->reboot()); .. pointless to try again } // if (victim) destruct(victim); } /* This should be called when everything else is done, * but standard drivers do not provide that, and its not worth a patch */ void preload_done() { #ifdef MASTER_LINK D1( D("Loading master link.\n"); ) (PSYC_PATH "active") -> connect(MASTER_LINK); #endif #if DEBUG > 1 D("Loading done. Dumping objects.\n"); debug_info(5, "objects", "/log/objects.dump"); #else // D("Starting service.\n"); #endif SIMUL_EFUN_FILE -> log_file("LOGON", "[%s] ^ SERVER START\n", ctime()); #ifdef DEBUG_LOG SIMUL_EFUN_FILE -> log_file(DEBUG_LOG,"[%s] ^ SERVER START\n",ctime()); #endif #ifdef HALT shutdown(); #endif } mixed current_time; string *epilog(int eflag) { if (eflag) return ({}); //#if __VERSION_MINOR__ > 2 // as long as the driver doesn't provide it // we have to call it ourselves *before* preloading // which means that all involved compilations will have wrong times preload_done(); //#endif D1( D("Preloading from " INIT_FILE "\n"); ) #ifndef BROKEN_RUSAGE D1( current_time = rusage(); ) D1( current_time = current_time[0] + current_time[1]; ) #endif return explode(read_file(INIT_FILE), "\n"); } void preload(string file) { D1( int last_time; ) if (strlen(file) && file[0] != '#') { if (file[0..1] == "./") file = file[2..]; D1( last_time = current_time; ) P1(("Loading: %s", file)) // call_other(file, ""); call_other(file, "load"); #ifndef BROKEN_RUSAGE D1( current_time = rusage(); ) D1( current_time = current_time[0] + current_time[1]; ) #endif P1((" %.2f\n", (current_time - last_time)/1000.)) } } void inaugurate_master(int arg) { if (!arg) { if (previous_object() && previous_object() != this_object()) return; } #if 0 // enable_telnet is better for what we want set_driver_hook(H_TELNET_NEG, "telnet_negotiation"); #endif #ifndef SANDBOX set_driver_hook(H_LOAD_UIDS, (: "/" :)); set_driver_hook(H_CLONE_UIDS, (: "/" :)); #else set_driver_hook( H_LOAD_UIDS, function string (string obn) { //string bp = program_name(obn), mp; string bp = program_name(find_object(obn)), mp, t; array(string) path; #ifndef __COMPAT_MODE__ if (bp[0] == '/') bp = bp[1..]; #endif path = explode(bp, "/"); path = sizeof(path) > 1 ? path[..<2] : ({ "/" }); mp = path[0]; switch (mp) { case "place": return "/place"; case "user": sscanf(bp, "%!s/%s.c", t); return "@" + t; case "net": if (sizeof(path) > 2) switch (path[1]) { case "library": return "/library"; case "d": return "/daemon"; } return "/system"; case "drivers": if (abbrev("world/drivers/ldmud/master", bp)) { return "/master"; } if (abbrev("world/drivers/ldmud/library", bp)) { return "/library"; } } return "/else"; }); set_driver_hook(H_CLONE_UIDS, function array(string) (object bp, string new) { return ({ getuid(bp), geteuid(bp) }); }); #endif set_driver_hook(H_INCLUDE_DIRS, include_dirs()); #ifdef SUPER_XMLRPC // such superfancy driver hacks make psyced less portable to muds :( set_driver_hook(H_DEFAULT_METHOD, function int (mixed res, object ob, string fun, varargs mixed *args) { if (program_name(ob) == NET_PATH "http/xmlrpc.c"[1..] && fun != "__INIT") { res = ob->request(fun, args[..<2], args[<1]); return 1; } return 0; }); #endif // we use create() even if we are in compat mode -lynX 2004 set_driver_hook(H_CREATE_SUPER, "create"); set_driver_hook(H_CREATE_OB, "create"); set_driver_hook(H_CREATE_CLONE, "create"); set_driver_hook(H_RESET, "reset"); set_driver_hook(H_CLEAN_UP, "clean_up"); //set_driver_hook(H_MODIFY_COMMAND_FNAME, "modify_command"); // actually this should never be spit out.. we must realize we // are about to run out of sockets before it actually happens // and take action! TODO set_driver_hook(H_NO_IPC_SLOT, ".\n\n_failure_exhausted_sockets\n.\n\n"); //set_driver_hook(H_NOTIFY_FAIL, "_failure_broken_parser\n.\n\n"); set_driver_hook(H_NOTIFY_FAIL, unbound_lambda(({ 'entered_command, 'cmd_giver }), ({ #'?, ({ #'=, 'cl, ({ #'symbol_function, "internalError", ({ #'this_object }) }) }), ({ #'funcall, 'cl, 'entered_command }), ({ #'write, "Huh?\n" }) }))); #ifdef H_DEFAULT_PROMPT set_driver_hook(H_DEFAULT_PROMPT, ""); #endif } string get_master_uid() { return "/master"; } string get_bb_uid() { return "undefined"; } #ifdef __EFUN_DEFINED(shadow)__ /* * The master object is asked if it is ok to shadow object ob. Use * previous_object() to find out who is asking. * * In this example, we allow shadowing as long as the victim object * hasn't denied it with a query_prevent_shadow() returning 1. */ int query_allow_shadow(object ob) { #ifdef SANDBOX unless (stringp(geteuid(previous_object())) && geteuid(previous_object())[0] == '/') { return 0; } #endif return !ob->query_prevent_shadow(previous_object()); } #endif #ifdef __EFUN_DEFINED(snoop)__ int valid_query_snoop(object wiz) { // return this_player()->query_level() >= 22; # ifdef SANDBOX unless (stringp(geteuid(previous_object())) && geteuid(previous_object())[0] == '/') { return 0; } # endif return 1; } #endif mixed prepare_destruct(object ob) { //object super; // we don't use environment() in psyced //mixed *errors; //int i; object sh, next; #if 6 * 7 != 42 || 6 * 9 == 42 # echo master.c detected a preprocessor error. return "Preprocessor error"; #endif #ifdef SANDBOX unless (previous_object() == ob || stringp(geteuid(previous_object())) && geteuid(previous_object())[0] == '/') { return sprintf("INVALID DESTRUCT: %O tried to destruct %O\n", previous_object(), ob); } #endif #ifdef __EFUN_DEFINED(shadow)__ if (!query_shadowing(ob)) for (sh = shadow(ob, 0); sh; sh = next) { next = shadow(sh, 0); funcall(bind_lambda(#'unshadow, sh)); /* avoid deep recursion */ destruct(sh); } #endif #ifdef DEVELOPMENT // tricky trade-off: catch is costy to have for each and every destruct // but to have objects which can no longer be /reload'ed is impractical // too. ok, if it happens on a non-development-server you need to reboot. catch(ob->remove()); #else ob->remove(); // popular lfun to notify destruction.. #endif return 0; /* success */ } /* sys/debug_message.h * To test a new function xx in object yy, do * psyclpc -f " " */ protected void flag(string str) { string file; mixed rc; #ifndef _flag_execute_test_performance debug_message("-f called with \""+ str +"\"\n", DMSG_STAMP); if (sscanf(str, "%s %s", file, str) == 2) catch(rc = call_direct(file, explode(str, " ")...)); else catch(rc = call_direct(str, "load")); debug_message(sprintf("-f result: %O\n", rc), DMSG_STAMP); #else string a = "_what_ever"; rc = 99999; # ifdef _execute_test_performance_regmatch debug_message("performance test: regmatch\n"); for (int j=rc; j; j--) rc = regmatch(a, "[^_0-9A-Za-z]"); # endif # ifdef _execute_test_performance_loopmatch debug_message("performance test: heavy code\n"); for (int j=rc; j; j--) for (int i=strlen(a)-1; i>=0; i--) unless (a[i] == '_' || (a[i] >= 'a' && a[i] <= 'z') || (a[i] >= '0' && a[i] <= '9') || (a[i] >= 'A' && a[i] <= 'Z')) rc = -1; # endif #endif shutdown(); }