/*
// File : master.c
//
// Purpose:
// This is the MudOS master object.
// This is the second object loaded after simul_efun.c.
// Everything written with 'write()' at startup will be printed on stdout.
// 1. create() will be called first.
// 2. flag() will be called once for every argument to the flag -f
// supplied to 'parse'.
// 3. epilog() will be called.
// 4. The game will enter multiuser mode, and enable log in.
//
// Revision history:
// Time Who and What
// (mudlib.n)
// : Originally written by unknown authors, probably Lars
// The Mud Institute (TMI)
// : Sulam and Buddha added or removed functions as they became
// necessary or obsolete.
slk
// 91-12-** : Sulam mostly rewrote it.
dldlk
// during the development of the new MudOS enhanced LPmud driver
ddk
// 92-03-12 : Buddha revised it again to support more than one mud.
// 92-04-11 : Sulam hacked and slashed some serious bugs in access and groups
// necessitated inheriting those files.
// (TMI-2)
// 93-05-28 : Mobydick altered valid_write so that certain log_files could be
// written by certain objects (/std/user and /cmds/file/_ed)
// even when their EUID's didn't permit. This was necessary so that
// the editing log and USAGE log could be maintained, since the
// log_file calls for those logs come from objects that
// don't have ROOT_UID.
// 93-08-28 : Pallando made various additions including valid_link()
// 94-03-14 : Atomic Archimedes added the call to reclaim_objects().
// 94-06-12 : Pallando did UIDs for /www
// 94-09-08 : Pallando fixed bugs in crash()
//
// 94-11-09 : Leto renamed the master applies that changed since 0.9.19.26
// (destruct_environment_of -> destruct_env_of)
// (get_ed_buffer_save_file_name -> get_save_file_name )
// Added valid_object(object ob)
.ddsk;
// Added Beek's error handling code
*/
// 94-12-23 : Leto added Sym's patch for valid_override (for v20.25a4+)
// 95-04-26 : Leto removed the .edrc file use. It's a property now
// Also fixed some simul_efun calls, which were
// preventing the simul_obj from updating (resursive loop)
// 96-03-03 : Leto fixed a hole in creator_file returning Root
// or backbone in case u,d or student.
#pragma save_binary
#include <uid.h>
#include <config.h>
#include <daemons.h>
#include <net/daemons.h>
#include <commands.h>
#include <switches.h>
#include <domains.h>
#include <logs.h>
#include <driver/origin.h>
#include "/adm/obj/master/parse_com.c"
inherit "/adm/obj/master/access";
//inherit "/adm/obj/master/groups"; // Leto, done by access.c now
static int access_loaded = 0;
void preload( string file );
void create()
{
if(origin() == ORIGIN_DRIVER)
call_out("free_objects", 3600);
}
static void free_objects()
{
call_out("free_objects", 3600);
efun::reclaim_objects();
}
// To test a new function xx in object yy, do
// parse "-fcall yy xx arg" "-fshutdown"
static void flag( string str )
{
string file;
mixed arg;
if( sscanf( str, "for %d", arg ) == 1 )
{
int i;
// empty loop for speed test
for( i = 0; i < arg; i++ ) { }
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: Unknown flag " + str + "\n" );
}
// This function is called every time a player connects.
// input_to() can't be called from here.
object connect()
{
object login_ob;
mixed err;
err = catch( login_ob = clone_object( CONNECTION ) );
if( err )
{
receive( "It looks like someone is working on the player object.\n" );
receive( err );
destruct( this_object() );
}
return login_ob;
}
// This is used for loading "virtual objects".
// Again, a separate program handles this task.
mixed compile_object( string file )
{
return (mixed)VIRTUAL_D-> compile_object( file );
}
// This is called when there is a segmentation fault or a bus error,
// As it's static it can't be called by anything but the driver.
static void crash( string error, object command_giver, object current_object )
{
log_file( "crashes", mud_name() + " CRASHED on: " + ctime( time() ) +
" ERROR: " + error + "\n" );
shout( "Game Driver shouts: Ack!!!!!!\n" ); /* polite for the dist copy */
shout( "Someone tells you: I think the game is crashing.\n" );
shout( "Game Driver forced you to: quit\n" );
// idea from MudOS's testsuite
if( command_giver ) log_file( "crashes",
"this_player: " + file_name( command_giver ) + "\n" );
if( current_object )log_file( "crashes",
"this_object: " + file_name( current_object ) + "\n" );
// Added by Inspiral with ideas from Wirehead..hopefully this will
// prevent MudOS to save users if the filesystem is full.
write_file( TMP_DIR "MudOS_crash", "abcdefg" );
if( file_size( TMP_DIR "MudOS_crash" ) )
{
rm( TMP_DIR "MudOS_crash" );
SHUTDOWN_D-> do_shutdown( 0 );
}
}
// Write and Read stuff:
// valid_write: called with the file name, the object initiating the call,
// and the function by which they called it.
// valid_read: called exactly the same as valid_write()
//
// These now use a special feature that enables us to update the access
// of people without rebooting the game, an unfortunate side effect of
// the old system.
//
// Something to be careful of is commands in /bin that do file manipulation,
// as they have root access.
int valid_write( string file, mixed user, string func )
{
int i;
string tmp, eff_user;
// return 1;
if( !access_loaded )
{
access_loaded = 1;
if( !load_groups() )
{
write( "*Error in loading group list\n" );
shutdown();
}
if( !load_access() )
{
write( "*Error in loading access list\n" );
shutdown();
}
}
// Leto debug
if( user == master() && func == "log_file" ) return 1;
if( !objectp( user ) )
user = find_player( user );
if( !user ) return 0;
if( geteuid( user ) == ROOT_UID )/* Should this be allowed? */
return 1;
if( ( func != "rm" ) && !( QUOTA-> quota_check( file ) ) )
{
write( "Warning: Directory quota violation.\n" );
return 0;
}
i = check_access( file, user );
if( i&2 )
return( i&2 );
// added by Tru for /std/save.c, extended by Buddha for /std/user/save.c
else if( objectp( user ) )
return( file == data_file( user ) || file == user_data_file( user ) );
else return 0;
}
int valid_read( string file, mixed user, string func )
{
int i;
// return 1; commented out Leto Dec 27 19:51
if( !access_loaded )
{
access_loaded = 1;
if( !load_groups() )
{
write( "*Error in loading group list\n" );
shutdown();
}
if( !load_access() )
{
write( "*Error in loading access list\n" );
shutdown();
}
}
if( func == "file_size" ) return 1; // make ls a bit faster
if( !objectp( user ) )
user = find_living( user );
if( geteuid( user ) == ROOT_UID )
return 1;
i = check_access( file, user );
if( i&1 )
return( i&1 );
// added by Tru for /std/save.c, extended by Buddha for /std/user/save.c
else if( objectp( user ) )
return( file == data_file( user ) || file == user_data_file( user ) );
else return 0;
}
// valid_save_binary: determines whether an object will save a binary
// image of itself to the directory specified in the runtime config
// file. (TMI-2 uses /data/binaries) See the driver's options.h for
// more information.
int valid_save_binary( string filename )
{
string creator;
creator = creator_file( filename );
if( creator == ROOT_UID || creator == BACKBONE_UID ) return 1;
return 0;
}
// valid_seteuid: determines whether an object ob can become euid str.
// This is very important because the euids still control most of the
// access permissions.
int valid_seteuid( object ob, string str )
{
// ROOT_UID has priveleges...
if( getuid( ob ) == ROOT_UID ) return 1;
if( file_name( ob ) == SIMUL_EFUN_OB ) return 1;
// TEMP, to provide an euid with 'basic' permissions, is able to write
// to the datafiles of some bulletin boards. This may no longer be needed.
if( str == "TEMP" ) return 1;
// The newsreader. Needs special perms for news directories.
if( str == "News" && getuid( ob ) == BACKBONE_UID ) return 1;
// Prevent untraceable objects form doing seteuid, eg. /ftp/
// and /open. Leto 11/11/95
// if( getuid(ob) == "Anonymous" ) return 0;
// An object is lucky enough to be itself...
if( getuid( ob ) == str ) return 1;
// creator_file() is a simul_efun that determines who is 'responsible'
// for an object. It's another way of saying getuid(ob) in general, but
// there are sometimes differences.
if( creator_file( file_name( ob ) ) == str ) return 1;
return 0; // Default case
}
// 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.
nomask int valid_shadow( object ob )
{
// dangerous to allow people to shadow things with global access
if( creator_file( file_name( ob ) ) == ROOT_UID ) return 0;
// best not to let them shadow the simul_efun ob either ;-)
if( file_name( ob ) == SIMUL_EFUN_OB ) return 0;
// Let's protect admins too. Sigh Leto
if(adminp(ob)) return 0;
// the generic properties can be overloaded... but not shadowed.
if( function_exists( "query", previous_object() ) ) return 0;
// this function returns the "link" to the secure information.
if( function_exists( "query_link", previous_object() ) ) return 0;
// this gives an object the chance to stop the shadow also
return !ob-> query_prevent_shadow( previous_object() );
}
// Function name: epilog()
// Description: Loads master data, including list of all domains and
// wizards. Then make a list of preload stuff
// Arguments: load_empty: If true, epilog() does no preloading
// Return: List of files to preload
string *epilog( int load_empty )
{
string *items;
items = update_file( CONFIG_DIR + "preload" );
call_out( "socket_preload", 5 );
return items;
}
// For very odd reasons, this can't be done at the normal preload time.
// while nobody has explained it to me yet, at least know that this works.
void socket_preload()
{
string *items;
int i;
items = update_file( CONFIG_DIR + "socket_preload" );
for( i = 0; i < sizeof( items ); i++ )
if( items[i] && items[i] != "" )
call_other( items[i], "???" );
}
// preload an object
void preload( string file )
{
int t1;
string err;
// if (file_name(previous_object()) != SIMUL_EFUN_OB) return;
if (origin() != ORIGIN_DRIVER) return;
write( "Preloading : " + file + "..." );
err = catch( call_other( file, "??" ) );
if( err != 0 )
{
write( "\nGot error " + err + " when loading " + file + "\n" );
} else {
write( "\n" ); // there should be some statistic given here...
}
}
// Get the owner of a file. Used by log_error().
// This one appears to need some explicit paths.... I'll see how this
// works. Note that the positioning of the home directory can't change
// yet... HOME_DIRS might be something like "/u", if my home directory
// is /u/b/buddha... This should work in most cases.
string get_wiz_name( string file )
{
string name, rest, dir;
if( sscanf( file, HOME_DIRS + "%s/%s/%s", dir, name, rest ) == 3 )
{
return name;
}
return 0;
}
// Write an error message into a log file. The error occured in the object
// <file>, giving the error message <message>.
void log_error( string file, string message ) {
string name, home, dom;
if (file[0] != '/')
file = "/" + file;
if (origin() == ORIGIN_DRIVER &&
(file_name(previous_object()) == CMD_LOAD ||
file_name(previous_object()) == CMD_CLONE ||
file_name(previous_object()) == CMD_UPDATE))
name = geteuid(previous_object(1));
else
name = get_wiz_name(file);
// We can't have simul_efun calls, since this is also called
// when updating the simul_efun
// if (name) home = user_path(name);
if (name)
home = sprintf("/u/%c/%s/", name[0], name);
else if (sscanf(file, "/d/%s/%*s", dom))
home = sprintf("/d/%s/", dom);
else
home = LOG_DIR;
if (file_size(home) == -1)
home = LOG_DIR;
if (file_size(home + "log") == -2) {
write_file(LOG_DIR "master_errors",
sprintf("log_error: %slog is a directory\n", home));
home = LOG_DIR;
}
write_file(home + "log", message);
}
// save_ed_setup and restore_ed_setup are called by the ed to maintain
// individual options settings. These functions are located in the master
// object so that the local gods can decide what strategy they want to use.
// This used to be saved as .edrc in someone's homedir, but has been
// changed to use a property instead.
int save_ed_setup( object who, int code )
{
if( !intp( code ) )
return 0;
return (int) this_player()-> set("ed_settings", code);
}
// Retrive the ed setup. Used to be a file, it's a property now.
int retrieve_ed_setup( object who )
{
return (int)this_player()-> query( "ed_settings" );
}
// If the user gets disconnected while in ed, save what they were editing
// in this file...
string get_ed_buffer_save_file_name( string file )
{
string *path;
if (!file) return file;
path = explode( file, "/" );
file = user_path( geteuid( this_player() ) );
if (sizeof(path) < 1) return 0;
if( file_size(file) == -2)
file += path[sizeof( path ) - 1] + "~";
else
file = "/tmp/" + geteuid( this_player() ) + ":" +
path[sizeof( path ) - 1] + "~";
return file;
}
// When an object is destructed, this function is called with every
// item in that room. We get the chance to save users from being destructed.
void destruct_env_of( object ob)
{
if( !interactive( ob ) )
{
ob-> remove();
if( ob ) destruct( ob );
return;
}
tell_object( ob,
"Everything you see dissolves. " +
"Luckily, you are transported somewhere...\n" +
"" );
ob-> move( VOID );
}
// This is called by the game driver to resolve path names in ed.
string make_path_absolute( string file, object ob, int writeflg )
{
string path;
path = resolv_path( "cwd", file );
#ifdef ED_LOG
if(ob)
log_file(ED_LOG, wrap(capitalize(geteuid(ob)) + " : \"" + path +
"\" [" + extract(ctime(time()), 4, 15) + "]"));
#endif
#ifdef FILE_LOG
if (writeflg)
FLOCK_D->log_change(path, "?");
#endif
return path;
}
// This is called by the mail object. Other things might like it too.
int player_exists( string who )
{
if( file_size( user_data_file(
find_object_or_load( CONNECTION ), who ) ) > 0 )
return 1;
return 0;
}
string get_root_uid() { return ROOT_UID; }
string get_bb_uid() { return BACKBONE_UID; }
string creator_file( string str )
{
string *path;
if( str[0] != '/' ) str = "/" + str;
path = explode( str, "/" ) - ({ "" });
if( !path ) return 0;
if( path[0] == 0 )
{
log_file( "creator_file",
"no first element of array, str = " + str + "\n" );
path = path[1..<1];
}
// Here's where all the permissions are sorted into uid's.
// This is very important.
switch( path[0] )
{
case "adm":
if( str == SIMUL_EFUN_OB ) return "NONAME";
else return ROOT_UID;
break;
case "cmds": return ROOT_UID; break;
case "www":
if( ( sizeof( path ) > 1 ) && path[1] == "gateways" )
return "Anonymous";
else
return "Anonymous";
break;
case "std":
// later, put a dir of bodies that is all Backbone... for now:
// if( base_name( str ) == USER_OB ) return BACKBONE_UID;
return "NONAME"; break;
case "obj": return BACKBONE_UID; break;
case "u":
if( (path[2]!=ROOT_UID)&&(path[2]!=BACKBONE_UID)) {
if (sizeof(path) >= 4 && path[2] && path[3])
return path[2];
break; }
case "student":
if( (path[1]!=ROOT_UID)&&(path[2]!=BACKBONE_UID)) {
if (sizeof(path) >= 3 && path[1] && path[2])
return path[1];
break; }
case "d":
if( (path[1]!=ROOT_UID)&&(path[2]!=BACKBONE_UID)) {
if (sizeof(path) >= 3 && path[2])
// if (sizeof(path[1]) >=2 && path[1])
return capitalize(path[1]);
break; }
case "open": return "Anonymous"; break;
default:
return "Anonymous";
break;
}
return 0;
}
// these are the defaults for author and domain scoring
string author_file( string filename )
{
string *path;
path = explode( filename, "/" );
if( !path ) return "NONAME";
if( path[0] == "u" ) return path[2];
return 0;
}
string domain_file( string filename )
{
string *path;
path = explode( filename, "/" );
if( !path ) return "NONAME";
switch( path[0] )
{
case "adm": return ROOT_UID; break;
case "cmds": return ROOT_UID; break;
case "std": return "NONAME"; break;
case "obj": return BACKBONE_UID; break;
case "student":
case "u": return "User"; break;
case "open": return "Anonymous"; break;
case "d":
if (sizeof(path) > 1 && path[1] != "*")
return capitalize(path[1]);
/* otherwise, fall through */
default: return "NONAME"; break;
}
}
// Check with the telnet daemon to see if the socket attempt
// is permitted or not.
int valid_socket( object calling_ob, string func, mixed *info )
{
#ifdef INTERMUD
return (int)TELNET_D-> telnet_permission( calling_ob, func, info );
#endif /* INTERMUD */
}
// this ought to check things against domains.h
int valid_domain( string dom )
{
if( member_array( dom, DOMAIN_DIRS ) != - 1 )
return 1;
return 0;
}
int valid_override( string bfile, string name, string file )
{
if (file[0] != '/') file = "/" + file;
sscanf(file, "%s.c", file);
if ( (file == SIMUL_EFUN_OB) || (file == "/adm/simul_efun/overrides") ) return 1;
// Changed by Blue, 94-01-14, after Artagel informed me of a bug
// fix in the latest driver (0.9.18.31) which will pass the
// simul_efun filename as an argument here instead of the file
// it includes. With the ||, it should work with old drivers
// too.
if( function_exists( name, find_object( SIMUL_EFUN_OB ) ) ) return 0;
return 1;
}
int valid_hide( object who )
{
string eff_user;
eff_user = geteuid( who );
if (adminp(eff_user) || eff_user == ROOT_UID)
return 1;
return 0;
}
// Called by the link() efun when trying to make the file path <reference>
// synonymous with the original file at filepath <original>
int valid_link( string original, string reference )
{
return 0; // Link not allowed
}
// Used by /std/save.c (added by Tru)
void make_data_dir()
{
string *parts, dir;
string path;
int j;
path = data_dir( previous_object() );
parts = explode( path, "/" );
dir = "";
for( j = 0; j < sizeof( parts ); j++ )
{
dir += parts[j];
mkdir( dir );
dir += "/";
}
}
int different(string fn, string pr) {
int tmp;
sscanf(fn, "%s#%d", fn, tmp);
fn += ".c";
return (fn != pr) && (fn != ("/" + pr));
}
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;
}
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"]
) + "\n";
trace = mp["trace"];
for (i = 0, n = sizeof(trace); 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 error_handler(mapping mp, int caught) {
string ret;
// try to distinguish between different types of errors
ret = "---\n" + standard_trace(mp);
write_file((caught ? "/log/catch" : "/log/runtime"), ret);
// let the player know an error occurred
if (this_player(1)) {
message("error",
sprintf("%sTrace written to %s\n",
mp["error"], (caught ? "/log/catch" : "/log/runtime")),
this_player(1));
this_player(1)->set_error(mp);
} else if (this_player()) {
message("error",
sprintf("%sTrace written to %s\n",
mp["error"], (caught ? "/log/catch" : "/log/runtime")),
this_player());
this_player()->set_error(mp);
}
// whatever we return goes to the debug.log (in this case, nothing)
return 0;
}
int valid_object(object ob) {
string foo;
int dummy;
if (!sscanf(file_name(ob), "%s#%*d", foo)) foo = file_name(ob);
// No cloning from /tmp. That way we can always get an uid
if (foo[0..4] == "/tmp/") return 0;
// No cloning invisible files, nasty to look up irritating 'hidden'
// objects. so "~/foo.c" is ok and "~/.foo.c" is not
if (strsrch(foo,"/.") != -1 ) return 0;
return 1;
}
int valid_bind(object binder, object old_owner, object new_ownwe) {
return 0; // For now, lets disable it, since Tmi-2 doesn't use it yet
}
/* Th'Th'Th'That's All Folks! */