psyced/world/drivers/ldmud/master/master.c

581 lines
16 KiB
C

// $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 New PSYC syntax will not work: Driver compiled without libpsyc!
# 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;
P3(("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(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(1, 0);
}
// 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 "<file> <method> <args>"
*/
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();
}