/
CDC-1.1/
parent $root
object $sys

var $root child_index 1
var $root owners [$sys]
var $root fertile 0
var $root inited 1
var $root owned [$sys]
var $root manager $sys
var $root writable [$sys]
var $root readable ['parameters, 'methods, 'code]
var $root info ["The system object has special server-granted permissions, and performs tasks which require these permisions. It receives the 'heartbeat message, as well as being pivitol in such tasks as generating new connections and creating objects."]
var $root dbref 'sys
var $sys admins []
var $sys new_user_class $admin
var $sys starting_room $creation
var $sys exit_starting_room $void
var $sys server_port 1138
var $sys current_receiver 0
var $sys backup_interval 3600
var $sys last_backup 784687240
var $sys startup_time 784684815
var $sys startup_objects []
var $sys agents [$housekeeper, $sys, $scheduler, $old_connection, $root, $daemon]
var $sys default_heartbeat_interval 5
var $sys default_port 1138
var $sys user_starting_quota 75000
var $sys server_address #[['ip, ""], ['hostname, "somewhere"]]
var $sys anonymous_class $guest
var $sys system_email_addresses #[['default, "somebody@somewhere"]]
var $sys core_version "1.1"

method startup
    arg args;
    var opt, str, obj;
    
    args = sublist(args, 3);
    catch any {
        if (sender() != 0)
            throw(~perm, "Sender is not the server.");
    
        // Get rid of any lingering connection objects.
        for obj in ($old_connection.children()) {
            catch any {
                obj.destroy();
            } with handler {
                .log($parse.traceback(traceback(), -1, ""));
            }
        }
    
        // Look for a port specification -- this will go away
        opt = "-p" in args;
        if (opt) {
            server_port = opt ? (| toint(args[ind + 1]) |) | default_port;
            args = (| delete(args, opt) |);
            args = (| delete(args, opt) |);
        }
    
        // Bind to the port.
        catch ~socket, ~bind {
            bind(server_port, $sys);
        } with handler {
            .log(("Can't bind to port " + tostr(server_port)) + ", exiting...");
            shutdown();
        }
    
        // Initialize variables and log startup message.
        .new_connection();
        log((("Server starting on port " + tostr(server_port)) + " ") + ($time.date()));
    
        // Set up five-second heartbeat.
        set_heartbeat_freq(default_heartbeat_interval);
    
        // set the startup time.
        startup_time = time();
    
        // tell objects who should know, that we are up.
        for obj in (startup_objects) {
            .log(("Calling " + (obj.dbref())) + ".startup()");
            (| obj.startup(@args) |);
        }
    } with handler {
        .log(("Startup ERROR at " + ctime()) + ":");
        .log($parse.traceback(traceback(), -1, ""));
    }
.

method uptime
    return time() - startup_time;
.

method startup_time
    return startup_time;
.

method new_connection
    // will move this to the new network heirarchy
    if ((sender() != this()) || (caller() != definer()))
        throw(~perm, "Invalid call to private method.");
    current_receiver = $old_connection.spawn();
    current_receiver.set_manager(current_receiver);
    bind(server_port, current_receiver);
.

method new_user_class
    return new_user_class;
.

method set_new_user_class
    arg obj;
    
    if (!(.is_admin(sender())))
        throw(~perm, "Sender not an admin.");
    if (!(obj.has_ancestor($user)))
        throw(~type, "Argument is not a user object.");
    new_user_class = obj;
.

method create_user
    arg name, password, email, [type];
    var user;
    
    if ((!(| .perms(caller(), $old_connection) |)) && (!(| .perms(sender(), 'system) |)))
        throw(~perm, "caller and sender are not allowed to call this method.");
    type = [@type, 'new_user_class][1];
    catch any {
        user = .(type)().spawn(name);
        user.set_name(name);
        if (type == 'new_user_class)
            user.set_password(password);
        else
            user.set_password(crypt(substr(crypt("", substr(name, 1, 2)), 1, random(13))) + "12");
        user.set_manager(user);
        user.set_email(email);
        user.chown([user]);
    } with handler {
        // Failed to initialize the child; destroy it.
        if (!(| user.destroy() |)) {
            (| user.uninitialize() |);
            (| del_name(user.dbref('symbol)) |);
            (| del_name(tosym(name)) |);
            (| destroy(user) |);
        }
        rethrow(error());
    }
    return user;
.

method connection_starting
    // will move this to the new network heirarchy
    if (caller() != $old_connection)
        throw(~perm, "Caller is not $old_connection.");
    .new_connection();
.

method admins
    return admins;
.

method is_admin
    arg obj;
    
    return (obj == $sys) || (obj in admins);
.

method binary_dump
    if (!($sys.is_admin(sender())))
        throw(~perm, "Sender is not an admin.");
    return binary_dump();
.

method shutdown
    arg [why];
    var line1, line2;
    
    if ((!($sys.is_admin(sender()))) || (definer() != this()))
        throw(~perm, "Sender is not an admin.");
    why = [@why, ""][1];
    
    // tell everybody everything
    line1 = "*** SHUTDOWN called by " + (sender().namef('ref));
    if (why) {
        line1 = line1 + " because:";
        line2 = ("*** " + why) + " ***";
    }
    .log(line1 + " ***");
    $channels.announce('all, line1 + " ***");
    if (why) {
        .log(line2);
        $channels.announce('all, line2);
    }
    return shutdown();
.

method change_sender_parents
    arg parents;
    var p;
    
    if (caller() != $root)
        throw(~perm, "Caller is not $root.");
    (> chparents(sender(), parents) <);
.

method spawn_sender
    arg suffix, manager, [owners];
    var namestr;
    
    (> .perms(caller(), $root, $sys) <);
    if (!owners)
        owners = [manager];
    namestr = (tostr(sender().dbref('symbol)) + "_") + suffix;
    return .create([sender()], tosym(namestr), manager, owners);
.

method assign_dbref
    arg name;
    var keepers, x;
    
    (> .perms(caller(), $root, $sys) <);
    if (type(name) != 'symbol)
        throw(~type, "Name must be given as a symbol.");
    
    // don't run this for now--tosym is fucked.
    if (0) {
        // yeah, we change it to a string, but they don't have to know that.
        name = tostr(name);
    
        // lowercase all names:
        name = lowercase(name);
    
        // If it isn't a keeper toss the good old error
        keepers = "abcdefghijklmnopqrstuvwxyz1234567890_";
        for x in [1 .. strlen(name)] {
            if (!((name[x]) in keepers))
                throw(~type, "Name has one or more non-alphanumeric characters.");
        }
        name = tosym(name);
    }
    
    // make sure nobody has it yet
    if ((| get_name(name) |) != ~namenf)
        throw(~perm, "Name already assigned to " + (get_name(name).namef('ref)));
    
    // woo woo, i'm filled with joy, lets give them the name.
    set_name(name, sender());
.

method destroy_sender
    (> .perms(caller(), $root) <);
    del_name(sender().dbref('symbol));
    destroy(sender());
.

method is_system
    arg obj;
    
    return (obj in admins) || (obj in agents);
.

method log
    arg text;
    var l;
    
    if ((!(| .perms(sender(), 'system) |)) && (!(| .perms(caller(), 'system) |)))
        throw(~perm, "NOT");
    if (type(text) == 'list) {
        for l in (text)
            .log(l);
    } else {
        log((($time.time_stamp()) + "> ") + text);
    }
.

method connect
    arg [args];
    
    (> .perms(caller(), $network_root) <);
    (> connect(@args) <);
.

method heartbeat
    if (sender() != 0)
        throw(~perm, "Sender is not the server.");
    
    // personally I prefer more granularity, so changed it.
    // if (time() / backup_interval > last_backup / backup_interval)
    if (time() > (last_backup + backup_interval))
        .do_backup(this());
    $scheduler.pulse();
.

method do_backup
    arg who;
    var line, name;
    
    .perms(sender(), 'system);
    catch any {
        name = who.namef('ref);
        .log(("BACKUP (" + name) + ") ");
        line = (("It is: " + ($time.ltime())) + " -- BACKUP -- called by ") + name;
        $channels.announce('System, line);
    }
    last_backup = time();
    pause();
    pause();
    .text_dump();
.

method backup_interval
    return backup_interval;
.

method set_backup_interval
    arg val;
    
    if (!(.is_admin(sender())))
        throw(~perm, "Sender not an admin");
    backup_interval = val;
.

method sender_data
    var output, i;
    
    if (caller() != $root)
        throw(~perm, "Caller is not $root.");
    return data(sender());
.

method get_system_email
    arg what;
    var email;
    
    // email directory for system services, such as arch admins.
    email = (| system_email_addresses[what] |);
    if (!email)
        email = (| system_email_addresses['default] |) || "<no email set>";
    return email;
.

method new_admin
    if (caller() != $admin)
        throw(~perm, "Caller is not $admin.");
    admins = setadd(admins, sender());
.

method admin_going_away
    if (caller != $admin)
        throw(~perm, "Caller is not $admin.");
    admins = setremove(admins, sender());
.

method add_startup_object
    arg obj;
    
    (> .perms(sender(), 'manager) <);
    startup_objects = setadd(startup_objects, obj);
.

method agents
    return agents;
.

method text_dump
    .perms(sender(), 'this);
    pause();
    return text_dump();
.

method del_startup_object
    arg obj;
    
    (> .perms(sender(), 'manager) <);
    startup_objects = setremove(startup_objects, obj);
.

method server_address
    arg [type];
    
    type = [@type, 'hostname][1];
    return server_address[type];
.

method user_starting_quota
    return user_starting_quota;
.

method execute
    arg script, args, [background];
    
    (> .perms(sender(), @admins) <);
    (> run_script(script, args, @background) <);
.

method anonymous_class
    return anonymous_class;
.

method create
    arg parents, name, manager, [owners];
    var new;
    
    .perms(sender(), 'system);
    new = create(parents);
    catch any {
        new.set_dbref(name);
        new.initialize();
        new.set_manager(manager);
        new.chown([@owners, [new]][1]);
    } with handler {
        // Failed to initialize the child; destroy it.
        if (!(| new.destroy() |)) {
            (| new.uninitialize() |);
            (| del_name(new.dbref('symbol)) |);
            (| del_name(tosym(name)) |);
            (| destroy(new) |);
        }
        rethrow(error());
    }
    return new;
.

method system
    return admins + agents;
.

method run_script
    arg script, args, [background];
    
    .perms(sender(), @admins);
    (> run_script(script, args, @background) <);
.

method bind
    arg port, obj;
    
    (> .perms(caller(), $network_root) <);
    (> bind(port, obj) <);
.

method deassign_dbref
    arg name;
    
    if (caller() != $root)
        throw(~perm, "Caller is not $root.");
    del_name(name);
.

method del_system_email
    arg key;
    
    (> .perms(sender(), 'manager) <);
    system_email_addresses = dict_del(system_email_addresses, key);
.

method add_system_email
    arg key, email;
    
    (> .perms(sender(), 'manager) <);
    if (type(key) != 'symbol)
        throw(~type, "Key is not a symbol.");
    if (type(email) != 'string)
        throw(~type, "Email address is not a string.");
    system_email_addresses = dict_add(system_email_addresses, key, email);
.

method compile
    arg code, name;
    var line;
    
    (> .perms(sender()) <);
    line = ("SYSTEM: ." + tostr(name)) + "() MODIFIED";
    line = (line + " by ") + (sender().namef('ref));
    .log(line);
    return (> pass(code, name) <);
.

method unbind
    arg port, obj;
    
    (> .perms(caller(), $network_root) <);
    (> unbind(port) <);
.

method version
    arg [args];
    var ver;
    
    args = [@args, 'driver][1];
    switch (args) {
        case 'driver:
            ver = version();
            return (((tostr(ver[1]) + ".") + tostr(ver[2])) + "-") + tostr(ver[3]);
        case 'core:
            return core_version;
    }
.

method startup_objects
    return startup_objects;
.

method last_backup
    return last_backup;
.