PRIVILEGES & PROTECTIONS In most multi-user environments it is necessary to restrict manipulation of files and data in certain ways. MUDs are now exceptions. For example, as the administrator of a MUD, you don't want the average wizard to tamper with the login object (for example by logging passwords) and most wizards don't want their files writeable by other wizards unless they are specifically granted permission to do so. Therefore, we need means to enforce a scheme that protects data (in particular files) from arbitrary manipulation. To do so, we introduce the concept of privileges (what a particular piece of code is allowed to do) and protections (the barrier that must be bypassed in order to manipulate a particular file). Both privileges and protections are drawn from the same set of values: The integers 1 and 0 (representing the top- and bottommost levels) as well as almost arbitrary strings. Most values will fit one of the following templates: "wizard" The privilege that this wizard's player object has. "wizard:" The privilege that most of this wizard's objects have by default (unless changed). "wizard:sub" One of many possible sub-privileges that a wizard may create (for example, to make a sub directory where other wizards may write to, it will typically have such a string as its protection and - as we will see later - the maximum privilege for objects residing therein). "Domain" The controlling privilege for a domain. Note that the domain name is capitalized. Although there will not be a 'player object' with this privilege, it is used for certain internal manipulation. "Domain:" Default privilege/protection for the domain's objects. "Domain:sub" Special privileges/protections for the domain. "@data" Privileges beginning with "@" are reserved for administrative purposes. 1 Not a string, but the integer 1. It is the topmost privilege in the hierarchy. Objects with this privilege can do anything if they desire to do so. Using 1 as a file protection means restricting access to administrators. 0 Again not a string, but the integer 0. It is the bottommost privilege in the hierarchy. Objects with this privilege can do almost nothing in terms of file access except accessing publicly available data. But since most files are publicly readable, you only need a different privilege for write access. THE HIERARCHY As mentioned above, there is a highest privilege (1) and a lowest privilege (0). All the other privileges lie in between. In fact, we may write a graph for a simple system comprising two wizards "a" and "b" and a domain "D": 1 / | \ / | \ "a" "D" "b" | | | "a:" "D:" "b:" \ | / \ | / 0 It is noteworthy that none of "a", "b" or "D" is higher or lower than any of the other privileges except 0. This is what we want, because by default neither wizard "a" nor wizard "b" should be able to write to each other's directory. On the other hand, what if both "a" and "b" are members of the domain "D"? Let us assume that "a" becomes member of "D". Then we will have the following diagram: 1 / | \ / | \ "a" "D" "b" | \ | | | \ | | "a:" "D:" "b:" \ | / \ | / 0 Note that there is no link between nodes "a" and "D" in the above graph. This is because privileges without a ":" are so-called control privileges and privileges with a ":" are so-called data privileges. This means that only by by access to a control privilege new data privileges with this prefix can be created or deleted. Example: Wizard "a" can create privileges "a:data", "a:log", "a:foo" or in general anything with an "a:" in front. He cannot create something with a "D:" in front, because he'd need access to privilege "D" for that and access to a domain's control privilege is reserved for the lords of that domain. Besides privileges bound to wizards or domains there is also a third type for general purpose stuff. Such privileges start with a "@". For example, "@doc" or "@syslog". Normally they can only be created by wizards with privilege 1. However, once a wizard is given access to, say, "@doc", it acts as a control privilege and he can create data privileges like "@doc:open" etc. FILES AND ACCESS TO THEM In general, access is controlled in a simple manner. Files have a protection level and objects have access to them if and only if they have the same or a higher privilege. However, not every single file will have a different protection level. In fact, protection levels are assigned on a per-directory basis. Thus you can for example assign the privilege "@doc:open" to /doc/open if it is a directory, but not "@doc:open1" to /doc/open/file1 and "@doc:open2" to /doc/open/file2. Note that a directory can have different protection levels for read and write access. There will, however, be only a few files that are read-protected, like mail files or players' data files. The access mechanism is a bit more complicated than outlined above, but far from difficult. All security is based on the simul_efun check_privilege(). It gets a privilege as its argument, checks whether it is safe to do operations at this privilege level and returns 1 (true) or 0 (false) accordingly. It works as follows: The entire object stack is checked. For every single object on the stack its privilege is compared to the argument of check_privilege(). If the object's privilege is too low, computation is aborted and 0 is returned. Otherwise we proceed until the entire object stack is checked. You may wonder what this thing called 'object stack' is. A simple example will illustrate this: Let us assume, a wizard uses an alias tool to execute a roommaker command. In particular this will imply that the wizard calls some routine in the alias tool which in turn calls a routine in the roommaker. Thus the object stack is made up of the three element list (wizard, alias tool, roommaker). This procedure seems overly complicated but prevents 99% of all possibilities to do mischief. For example, if someone gave you a fake alias tool (or roommaker), its privilege would be his and it would be impossible to access your files unless you explicitly gave him access to them in which case there wouldn't be a need for him to use such a complicated way anyway. The same idea enables us to give most system tools privilege 1 without having to worry about security since all file accesses will be restricted by the wizard's privilege. There is, however, a problem. For example, there will be no interactive player in a heart_beat() or call_out() and thus the top of the object stack would be undefined and all file accesses would fail. Or what if a player wants to do a 'save'? Surely we can't make the players' data files publicly accessible lest somebody goes there and edits the password or other security realetd stuff and poses as somebody else? To solve these problems, there is the unguarded() call. It takes two or three arguments. The first is a privilege, the second a closure and the third a list of optional arguments to that closure. unguarded() will proceed to evaluate the closure at the privilege level given by the first argument. It will also pass the third parameter as arguments to the closure as in the efun apply(). In addition, all objects in the object list above the one that did that call will no longer be checked for their privilege. In particular, if in the above example the roommaker wants to save its current state to "/save/roommaker.o" for debugging, independent of who uses it, it would do something like: unguarded(1,#'save_object,"/save/roommaker"); This would (a) perform the save at privilege level 1 (of course the roommaker must be able to use that privilege) and (b) ignore the alias tool and the wizard in the above example. TECHNICAL DETAILS If you just were trying the above example, you'd notice that it fails spectacularly. In particular, the compiler will complain about a missing function unguarded() (and probably a lot more). The reason is that most of the above stuff is hidden in a module that you have to inherit to use it. This is done as follows: #include <kernel.h> inherit ACCESS; Including kernel.h provides you, among other things, with a definition of ACCESS. Inheriting ACCESS then gives you the following routines: set_privilege() This routine takes a privilege as its argument and will set the object's current privilege to it (if possible). query_privilege() Returns the current privilege an object has. However, it is preferable to use the simul_efun get_privilege(obj) to get the current privilege for an object, because there is a chance that obj->query_privilege() is faked if somebody has been careless. get_privilege() has appropriate safeguards built in. unguarded() This routine has been described above. Take care, however, when using it. It is the only possibility to introduce security leaks in the system (besides other stupid stuff like inheriting files like "/open/anybody_can_change_me.c" or the like). However, a few simple precautions make it quite safe to use. For example, if all the arguments are safe, like if they are constants or have been checked for correctness then there is no problem. Avoid, however, stuff like: foo(file,data) { unguarded(1,#'write-file, ({ file, data }) ); } This would make a nice security leak. COMMANDS Several command to manipulate the access structure are supplied in the tool /bin/admin. First, there are the commands beginning with 'access': access define <privilege> Defines a new privilege. You need to have access to the control privilege, though. access undefine <privilege> Gets rid of this privilege again. access link <privilege> to <directory> Binds the given privilege to the given directory as its protection level for write access. access link -read <privilege> to <directory> Same as above, but for read access. access unlink <directory> Gives the directory its default write protection level again (i.e. the one it has inherited from the directory containing it. access unlink -read <directory> Same as above, but for read access. access open <privilege1> for <privilege2> Gives any object with <privilege2> access to <privilege1> access close <privilege1> for <privilege2> Denies access again. access show <privilege> (not yet implemented) Shows the privilege, all dependent privileges and all other privileges that have access to them. access list <directory> (not yet implemented) Shows the directory tree beginning at <directory> and all protection levels therein. access makewiz <name> Tells the security server that the named player is now a wizard. This is required to define privileges for him. Note that this is normally done implicitly by the player object when promoting him to wizard and there is no need to repeat it manually. access zapwiz <name> Tells the security server that the named player is not longer a wizard. Second, there are commands to administrate domains: domain create <name> Creates the domain <name>. domain delete <name> Deletes it. domain add <member> to <domain> Adds a new member to said domain. domain add -lord <member> to <domain> Adds a new lord to a domain or promotes an existing member to lord. domain remove <member> from <domain> Gets rid of him again. domain show <domain> Shows the lords and members of a domain (or of more than one, if given). domain list Lists all existing domains. domain list <wizard> Lists all domains, the wizard is a member of. (remark: the add, remove, show and list commands take also multiple arguments.) EXAMPLES This section has yet to be written.