/*
** $Id: fsecure.c,v 1.9 2008/12/18 04:17:02 dredd Exp $
**
** $Source: /cvsroot/swlpc/swlpc/runtime/fsecure.c,v $
** $Revision: 1.9 $
** $Date: 2008/12/18 04:17:02 $
** $State: Exp $
**
** Author: Mike McGaughey, 1993-1998
**
** See the file "Copying" distributed with this file.
*/
#define BACKBONENAME "Game Driver"
/*
* Object/File security For lpmud by mmcg@bruce.cs.monash.edu.au -
* with help and abuse geoff@bruce.cs.monash.edu.
* (from the Shattered World Lpmud)
*
* SECURITY: File security functions.
* These should generally be called from interpret.c and simulate.c,
* as the game driver shouldn't have to keep fiddling with
* current_object. To check whether a *player* can write a particular
* file (and resolve its path), call 'valid_read' etc in the player object,
* or use check_file_name(file, 0/1) - which checks that the object
* calling the function is interactive, and calls the object's valid_read.
* From LPC, objects that can write files for players (i.e. wiztoys, etc)
* should always suid themselves to level 0 and let the wizard trust them;
* there is thus no need for them to call the player's valid_read (their
* commands should only be accessible to the player by typing commands,
* all routines static - cf the wiz_soul stuff).
*/
/*
* General philosophy: We are really only concerned with who can
* read or write what; we don't really care who *uses* what (with clone,
* call_other, etc, it would get too restrictive and expensive). So,
* what we do is assign everything a level based on where it came from.
* Things that come from player directories or standard dirs get CREATE
* and EDIT_STANDARD respectively. Things used by the game driver - from
* /secure and /closed, get no power at all - they are made suid by the
* game driver itself. This means that only if player.c is cloned from
* comm1 will it be given god status; player.c can go on to clone a wiz soul,
* and pass on the appropriate level. And writing the soul etc will be
* easier because we dont have to check for spoofing all the time; such
* attempts (to clone it) will result in an object with no power.
* Stuff in /closed still needs to be difficult to modify by wizards (because
* there are functions that depend on wiz_level but not object level - like
* snoop) but any instance of a soul will never be given a higher level than
* the cloner, so the file security problem is much simpler.
*/
/*
* Functions:
* o_read_ok(ob, file) - is it ok for ob to read file?
* o_save_ok(ob, file) - is it ok to save a savefile here?
* o_write_ok(ob, file) - is it ok to do arbitrary writes here?
* o_append_ok(ob, file) - ok to append? (only used in loggers)
*
* Generally, Write permission requires one level higher than save
* permission, and all objects not in closed directories get only
* save permission.
*/
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <strings.h>
#include "security.h"
#include "fsecure_def.h"
#include "fsecure.h"
#include "consts.h"
#define NOONE (-1) /* noone can access this */
#define NOTHING (0) /* level for no wiz powers */
/*
* test whether a filename has anything nasty in it - . or .. between
* slashes, or 2 consecutive slashes. Will allow a slash at the end of
* the name, but not the start. '/' is an invalid name.
*/
static char *badstr[] = { "/./", "/../", "//", 0 };
int
valid_filename(Shared * str)
{
char **bs;
char *s = str->str;
/* root dir */
if (!strcmp(str->str, "/"))
return 1;
for (bs = badstr; *bs != NULL; bs++)
if (!strncmp(str->str, 1 + *bs, strlen(1 + *bs)))
return 0; /* check for leading nasties - without initial '/' */
while (s && *s)
{
s = index(s, '/');
if (s == NULL)
break;
for (bs = &badstr[0]; *bs != NULL; bs++)
if (!strncmp(*bs, s, strlen(*bs)))
return 0;
s++;
}
/* check for trailers - allow a single '/' */
if (str->str[str->length - 1] == '/')
return 1; /* would have matched if preceded by ugliness */
s = str->str + str->length;
for (bs = badstr; *bs != NULL; bs++)
if (!strncmp(s - strlen(*bs) + 1, *bs, strlen(*bs) - 1))
return 0;
return 1;
}
/*
* test whether a given object filename matches a %s string.
* pname is the invoking player (or object owner).
*/
static int
test_match(char *str, Shared * oname, Shared * pname)
{
char *s, *o, *t;
s = str;
o = oname->str;
while (1)
{
if ((!*s) && (!*o))
return 1;
if (*s == '%')
{
s++;
switch (*s)
{
case 'G':
if ((!pname) || strcmp(pname->str, BACKBONENAME))
return 0; /* Game driver object */
break;
case 'c':
if (!*o)
return 0;
o++;
break; /* matches a single character */
case 's':
return 1; /* matches anything */
case 'p':
if (!pname)
return 0;
if (strncmp(o, pname->str, pname->length))
return 0;
o += pname->length;
break;
case 'n':
if (!strlen(o) || o[0] == '/')
return 0;
t = index(o, '/');
if (t)
o = t;
else
o += strlen(o);
break;
default:
fatal("Bad specifier in security string\n");
exit(1);
}
}
else
{
if (*s != *o)
return 0;
o++;
}
s++;
}
}
static struct access *
find_acc_entry(Shared * file, Shared * player, struct access *list)
{
while (list->dir)
{
if (test_match(list->dir, file, player))
{
/* printf("Match!(wow) %s %s %s\n", list->dir, file, player); */
return list;
}
list++;
}
return 0;
}
/*
* Check that the level of this object is high enough to read this
* file.
*/
int
o_read_ok(Obj * ob, Shared * file)
{
if (!ob)
return 1; /* must be
the mud
(current_object
= 0) */
return p_read_ok(ob->su_name, ob->level, file);
}
/*
* an internal one for directories; LPC functions append a slash and
* call the normal one...
*/
int
o_dir_read_ok(Obj * ob, Shared * file)
{
Shared *tmp;
int ret;
if (file->length == 0 || file->str[file->length - 1] != '/')
{
tmp = stringstr_cat(file, "/");
ret = o_read_ok(ob, file);
free_string(tmp);
return ret;
}
return o_read_ok(ob, file);
}
int
p_read_ok(Shared * suname, int olevel, Shared * file)
{
struct access *l;
if (!valid_filename(file))
return 0;
l = find_acc_entry(file, suname, MudFileAccessList);
if (!l || l->read == NOONE)
return 0;
if (l->read <= olevel)
return 1;
return 0;
}
/*
* Write level.
*/
int
o_save_ok(Obj * ob, Shared * file)
{
if (!ob)
return 1; /* must be
the mud
(current_object
= 0) */
return p_write_ok(ob->su_name, ob->level, file);
}
int
o_write_ok(Obj * ob, Shared * file)
{
if (!ob)
return 1; /* must be
the mud
(current_object
= 0) */
return p_write_ok(ob->su_name, ob->level - 1, file);
}
int
p_write_ok(Shared * suname, int olevel, Shared * file)
{
struct access *l;
/* if (file && *file == '/' && strcmp(file, "/")) file = file++; */
if (!valid_filename(file))
return 0;
l = find_acc_entry(file, suname, MudFileAccessList);
if (!l || l->write == NOONE)
return 0;
if (l->write <= olevel)
return 1;
return 0;
}
/*
* Append level.
*/
int
o_append_ok(Obj * ob, Shared * file)
{
/* must be the mud (current_object = 0) */
if (!ob) return 1;
return p_append_ok(ob->su_name, ob->level, file);
}
int
p_append_ok(Shared * suname, int olevel, Shared * file)
{
struct access *l;
/* if (file && *file == '/' && strcmp(file, "/")) file = file++; */
if (!valid_filename(file))
return 0;
l = find_acc_entry(file, suname, MudFileAccessList);
if (!l || l->append == NOONE)
return 0;
if (l->append <= olevel)
return 1;
return 0;
}
/*
* Assign level to an object. If "add" is < 0, we are loading it for
* the first time, so do an fstat to see whether it is suid.
* Otherwise, add should usually be 0 or 1, and is the number of levels
* to add to clones of this object. Caller should ensure that when add
* is (-1), name is the name of the file as stored on disk (i.e. with a
* .c or .T).
*/
static int file_was_suid;
int
get_object_level(Shared * o, int add)
{
struct ocap *c = caplist;
struct stat sbuf;
file_was_suid = (add < 0) ? 0 : add;
while (c->dir)
{
if (test_match(c->dir, o, NULL))
{
if (add < 0)
if ((-1 != stat(o->str, &sbuf)) && (sbuf.st_mode & 0100))
{
file_was_suid = 1;
}
return c->level + file_was_suid;
}
c++;
}
return NOTHING;
}
static char wname[100];
Shared *
get_wname(Shared * obname)
{
char *en, *x, *t;
if (!strncmp(obname->str, "players/", 8))
{
en = index(obname->str + 8, '/');
if (en)
{
t = (char *) wname;
x = obname->str + 8;
while (x < en)
*(t++) = *(x++);
*t = 0;
return string_copy(wname);
}
}
#ifndef SHATTERED
if (!strncmp(obname->str, "W/", 2))
{
en = index(obname->str + 2, '/');
if (en)
{
t = wname;
x = obname + 2;
while (x < en)
*(t++) = *(x++);
*t = 0;
return string_copy(wname);
}
}
#endif
return string_copy(BACKBONENAME);
}
void
init_object_su(Obj * ob, Shared * origname, int addon)
{
Shared *x;
ob->level = get_object_level(origname, addon);
// FIX? ob->file_was_suid = file_was_suid; /* ugly */
if (ob->su_name)
free_string(ob->su_name);
ob->su_name = 0;
if ((x = get_wname(origname)))
{
// printf("init_object_su name=%s\n", x->str);
ob->su_name = x;
}
}
/*
* setuid_me: make an object into a "trusted" object.
*
* Objects with GOD permission can set the target object's name to an
* arbitrary string; otherwise, the target object's name can only be
* set to the same as the caller's name (if it's a standard object, the
* caller's name is usually (char *)0). This is because even though
* they have EDIT_STANDARD, /player/closed needs GOD perms to write, so
* we can't have /std objects suid'ing themselves to a player name and
* pissing around in his /closed directories. Objects can't suid on
* other objects of higher level (i.e. to lower them).
*/
int
setuid_me(Obj * from, Obj * to, int level, Shared * name)
{
/* 0 = game driver, otherwise current object */
if (from)
{
if (to->level > from->level)
{
return -1; /* it'd be
bad to
decrease
/secure/master
:) */
}
if (from->level < level)
level = from->level;
if (from->level < GOD)
{
/* un-su'd (cloned) objects can't touch anyone else */
if (!from->su_name) return -1;
/* others can touch any un-suid and any of their own
* objects */
if (to->su_name && (from->su_name != to->su_name)) return -1;
/* but can only set to 0 or their own name */
if (name) name = from->su_name;
}
}
to->level = level;
if (from == to)
return level;
if (to->su_name)
{
//printf("setuid_me from=%s\n", to->su_name->str);
free_string(to->su_name);
to->su_name = 0;
}
if (name)
{
//printf("setuid_me name=%s\n", name->str);
to->su_name = shared_string_copy(name);
}
else
{
//printf("setuid_me name=%s\n", BACKBONENAME);
to->su_name = string_copy(BACKBONENAME);
}
return level;
}
/*
* A predicate to compare object levels. Shouldn't be used for
* anything *really* important, as it pretends that objects in a wizards
* directory are not of a lower level than that wizard (and people with
* EDIT_OTHERS could spoof it). Used for query_snoop, etc.
*/
int
lower_level(Obj * o1, Obj * o2)
{
if (!o1 || !o2)
return 1;
if (o1->su_name && o2->su_name)
{
if (o1->su_name == o2->su_name)
return 0;
}
return (o1->level < o2->level);
}
/*
* Given a filename, and an operation (in writeflag - 0 for read,
* 1 for write), and a controlling object (usually a wiz soul), will
* call "valid_read" or "valid_write" in that object, and return the
* result. The routine called should return 0 if the user is not allowed
* to read/write that file, or a string (being the full pathname of the file
* to edit) if the user can edit it. Naturally, the caller (which at this
* point is only ed()) will have to run o_read_ok (or whatever) over
* the new filename, to make sure noone's trying to pull a fast one (we
* don't do this here in case the regularised filename will be useful).
*
* This is used so wizards can have working directories, and so wiz_souls
* can regularise "../", etc (the game driver simply errors), and so things
* like newsrooms can enforce access to less files than their level indicates.
*/
Shared *
check_file_name(Shared * file, int writeflg, Obj * who)
{
Val *v, *ret;
if (!who)
{
efun_error("check_file_name: unknown object\n");
return 0;
}
//if (!Scurrent()->interactive)
if (!who->interactive)
{
efun_error("check_file_name called from non-interactive object %s\n",
who->name->str);
return 0;
}
v = share_string(file);
if (writeflg)
{
ret = apply_single(C("valid_write"), who, v);
}
else
{
ret = apply_single(C("valid_read"), who, v);
}
if (!ret || ret->type != T_STRING)
{
/* free_value(ret); */
add_message("Bad file name.\n");
return 0;
}
if (!legal_path(ret->u.string->str))
{
/* free_value(ret); */
add_message("Illegal path\n");
return 0;
}
/* free v? */
return shared_string_copy(ret->u.string);
}
/*
* Some stuff to manage the setuid bits on files. You can only suid
* a file if you own the file and you have write permission on it (or if
* you are a god) - checks are in interpret.c.
*/
int
force_no_su(Shared * fname)
{
return set_file_su(fname, 0);
}
int
set_file_su(Shared * fname, int val)
{
if (val)
val = (~UMASK) & 0777;
else
val = ((~UMASK) & 0666);
if (-1 == chmod(fname->str, val))
return 0;
else
return 1;
}