The Nightmare Object Library uses a security system based on something called stack security. In this system, objects have a set of privileges and files a set of protections. The mudlib handles the question of whether a given object can access a given file based on the object's privileges and the file's protections. Actually, it checks the privileges of the entire call stack to see if an operation may be performed. This document explains what a call stack is, how privileges are assigned to objects, how protections are assigned to files, and how the whole system works together.
Each time one object calls a method in another object, that object is added to the call stack. A call stack starts in one of the following ways:
A typical call stack might look somthing like:
driver calls process input for new input: ({ })
user object calls command daemon: ({ user })
processing returns to user objects: ({})
user object calls command object: ({ user })
command object tries to delete a file: ({ command_object, user })
processing returns to command object: ({ user })
processing returns to user object: ({})
processing ends
The call stack is thus all objects which have been involved in processing up to this point. Your code can access the call stack using the MudOS previous_object() and all_previous_objects() efuns. previous_object() gives you the last object on the call stack if there is any. previous_object(-1) and all_previous_objects() (all_previous_objects() is just a synonym for pevious_object(-1)) give you the entire call stack as an array of objects.
Each mud object has one or more privileges associated with it. The Nightmare Object Library defines privileges in the "/secure/include/privs.h" file. When an object is created, the driver calls privs_file() in the master object in order to allow the mudlib to assign privileges to an object. The Nightmare Object Library in turn calls the simul efun file_privs() which determines what privileges an object may be assigned.
The Nightmare Object Library assigns privileges to an object based on its object name. One reason for doing this is because you never want to give an object privileges greater than the protection on its source file. If you give an object coming from "/dir/file.c" global privileges but minimal protection, an object which could write to "/dir/file.c" woudld, by extension, have global privileges. This is because that object could overwrite "/dir/file.c" with malicious source code that uses the object privs of "/dir/file" to do damage.
User objects are actually allowed multiple privs. Their privs string is a ":" separated list of privs. For example, an admin might have the privs "descartes:SECURE". Every user object has at least their user name. Users with greater accss may have additional privs. Extra privs can be assigned to a user via the "/secure/cfg/groups.cfg" file. This file lists privs along with the name of users having those privs.
The groups file takes the following format:
# Names are added to this file to determine groups. # Groups should be named in all caps. # The format is: # (GROUP) name1:name2 (SECURE) descartes:kalinash:lassondra:rush:billgates (ASSIST) descartes:rush
Nightmare specifies two types of object protections:
Read access by default is granted. The "/secure/cfg/read.cfg" file is thus used to restrict access to files. If you specify a directory, then an object must have the named privs in order to read something from that directory. Any directory without an entry, however, will grant read access to anyone requesting it.
In contrast to read accss, write access is denied by default. The "/secure/cfg/write.cfg" file is used to grant access to files. If you do not specify a directory, write access will be denied to everyone except SECURE. SECURE is granted any access it desires at any time regardless of what the configuration files state.
The read protection configuration file takes the following format:
# Directories and their protection should be entered here. # The format is: # (/dir) GROUP1:GROUP2 (/realms/) ASSIST (/secure/etc/) ASSIST (/secure/save/) ASSIST
The write protection configuration file takes the following format:
# Directories and their protection should be entered here. # The format is: # (/dir) GROUP1:GROUP2 (/cfg/) ASSIST (/cmds/) ASSIST (/daemon/) ASSIST (/doc/) ASSIST (/domains/) ASSIST (/estates/) ASSIST:MUDLIBPRIV (/ftp/) all (/include/) ASSIST (/log/errors/) all (/log/personal/) all (/log/reports/) all (/news/) ASSIST (/obj/) ASSIST (/realms/) ASSIST:CMDS (/save/) MUDLIBPRIV:ASSIST (/secure/save/) ASSIST (/tmp/) all (/verbs/) ASSIST (/www/) ASSIST
So far, I have only discussed the privs of individual objects. As I mentioned in the beginning, however, the Nightmare system is stack based. Whenever any file operation is attempted, the driver calls either valid_read() or valid_write() in the master object to see if that file access is allowed. The Nightmare master object checks the privs of the entire call stack at that point against the protections of the file being accessed.
For file operations, the master object checks to see if any one object is too weak to read or write against the protections for the file in question. If any object in the call stack is too weak, the operation fails. If they all have the privs required to read or write to that file, then it succeeds.
Sometimes you do not want an operation to be subject to these strict call stack checks. For example, a creator might want to create a wand that logs information to the creators home directory. Under stack security, however, any such attempt will fail since players using the wand do not have access to that creators home directory. What the creator really wants is for the logging to be done using only the wand's privs.
Nightmare provides a simul efun, unguarded(), that allows you to signal that only the privs of the current object should be used for file operations. The code for the wand might look like:
void log_use() {
unguarded((: write_file, "/realms/descartes/log",
sprintf("%O", environment()) + " used the wand." :));
}
The argument to unguarded is a function pointer that provides the
specific operation which should occur in an unguarded manner. When the
unguarded() simul efun executes this function, only the
wand's privs will be checked against the log file's protections.
Unguarded file operations should only be performed in extremely rare cimrcumstances where you fully understand the implications of a less secure object spoofing a call. Never ever use this_player() as a means of securing any operation.