/* Do not remove the headers from this file! see /USAGE for more info. */ // NOTE: // Despite the length of this code, the only outside calls made // are from call_unguarded(). call_other()s to other object will // be extremely dangerous since they imply an unguarded() call. #include <mudlib.h> #include <security.h> inherit M_ACCESS; #define ERR_PRIV "insufficient privilege" #define ERR_NODOMAIN "accessing non-existing domain" #define ERR_NOWIZ "referring to non-existing wizard" #define ERR_BADARG "illegal or unusable parameter" #define ERR_HAZARD "hazardous setting of privileges" private mapping privileges; // mapping containing all privileges // values are arrays of parents and children private mapping read_access; // maps directory -> protection level private mapping write_access; // dto. private mapping wizards; // mapping containing all wizards and their level private mapping domains; // maps domains -> mappings of members/lords // (valued 2 for lords, 1 for members) private mapping domainlists; // wizard -> domains int valid_name( string s ) { // replace valid chars by '*', and compare to a string of all '*'s return map(s, (: ($1 < 'a' || $1 > 'z') ? ' ' : '*' :)) == map(s, (: '*' :)) && s!=""; } private void save_data() { unguarded(1, (: save_object, ACCESS_SAVE :)); unguarded(1, (: save_object, ACCESS_SAVE_BAK :)); } private void syslog(string text) { unguarded(1, (: write_file, ACCESS_LOG, (this_user() ? this_user()->query_userid() : "<Server>") + ": " + text + "\n" :) ); } void create() { if( clonep(this_object()) ) return; set_privilege(1); // The following call is handled specially in the master. // Using unguarded() is impossible, since variables are not yet // initialized. if (!restore_object(ACCESS_SAVE)) { if (eval_cost() < 1000) // *sigh* for (;;); privileges = ([ ]); read_access = allocate_mapping(1); write_access = allocate_mapping(1); domains = ([ ]); domainlists = ([ ]); wizards = ([ ]); save_data(); return; } if (!sizeof(read_access)) read_access = ([]); if (!sizeof(write_access)) write_access = ([]); } int query_allow_shadow() { return 0; } // The following function HAS to be private. Otherwise somebody // might gain write access to the server's datastructures. private mixed *walk_path(string file,int write) { string *path; mixed *result; mixed *tmp; mapping node,subnode; int i; path = explode(file,"/")-({"","."}); if (!sizeof(path)) return 0; if (member_array("..",path)>=0) return 0; result = allocate(sizeof(path)); node = write ? write_access : read_access; result[0] = !!write; for (i=1;i<sizeof(result);i++) { if (undefinedp(tmp = node[path[i-1]])) { result[i]=-1; break; } else { result[i] = tmp[0]; subnode = tmp[1]; } if (!subnode) break; if (!sizeof(subnode)) { tmp[1] = 0; break; } node = subnode; } if (i == sizeof(result)) --i; return ({ result, path[0..<2], i }); } nomask mixed query_protection(mixed file,int write) { mixed *desc; int i; if (objectp(file)) file = file_name(file); desc = walk_path(file,write); if (!desc) return !!write; // root directory i = desc[2]; desc = desc[0]; while (desc[i]==-1) --i; return desc[i]; } nomask varargs int check_privilege(mixed priv,int ignore); nomask int higher_privilege(mixed a,mixed b); nomask int valid_privilege(mixed priv); nomask string set_protection(string file,int write,mixed prot) { mixed *desc; int i,j; mixed *priv; string *path; mapping node,subnode; desc = walk_path(file+"/foo",write); if (sizeof(desc[1])>6) return ERR_BADARG; if (prot != -1 && !valid_privilege(prot)) return "invalid privilege identifier"; if (!desc) return "trying to change root directory's privilege"; priv = desc[0]; i = desc[2]; while (priv[i]==-1) i--; if (i) { j = i-1; while (priv[j]==-1) j--; } else j = i; if (priv[i]==prot) return "directory already has this effective protection level"; if (write && !check_privilege(priv[sizeof(desc[1])==i ? j : i])) return ERR_PRIV; if (!write && !check_privilege(1)) // Only staff can make read restrictions return ERR_PRIV; if (eval_cost()<1000) for (;;); if (prot != -1) { if (write && higher_privilege(prot,priv[i])) return ERR_HAZARD; if (!write && higher_privilege(priv[i],prot)) return ERR_HAZARD; node = write ? write_access : read_access; path = desc[1]; for (i=0; i<sizeof(path)-1; i++) { if (undefinedp(node[path[i]])) node[path[i]] = ({ -1, subnode = allocate_mapping(1) }); else if (!(subnode = node[path[i]][1])) node[path[i]][1] = subnode = allocate_mapping(1); node = subnode; } node[path[<1]]=({ prot, 0}); save_data(); return 0; } else { if (desc[2]!=sizeof(desc[1])) return "directory not linked to a privilege level"; node = write ? write_access : read_access; path = desc[1]; for (i=0; i<sizeof(path)-1; i++) node = node[path[i]][1]; map_delete(node,path[<1]); save_data(); return 0; } } private int valid_domain_name(string name) { return sizeof(regexp( ({ name }), "^[-a-z_]*$")); } nomask varargs string create_domain(string domain) { if (!check_privilege(1)) // Only staff members can create domains return ERR_PRIV; if (eval_cost()<1000) for (;;); if (!valid_domain_name(domain)) return "invalid domain name"; domain = lower_case(domain); if (domains[domain]) return "domain already exists"; domains[domain] = ([ ]); privileges[capitalize(domain)] = ([ "":({}),":":({}) ]); save_data(); syslog("Created domain " + domain); return 0; } nomask string delete_domain(string domain) { string *members; int i; if (!check_privilege(1)) // Only staff members can delete domains return ERR_PRIV; if (eval_cost()<10000) for (;;); if (!domains[domain]) return ERR_NODOMAIN; members = keys(domains[domain]); for (i=0; i<sizeof(members); i++) { mapping m; m = domainlists[members[i]]; map_delete(m,domain); } map_delete(domains,domain); save_data(); syslog("Deleted domain " + domain); return 0; } nomask string add_domain_member(string domain,string member,int lord) { if (!domains[domain]) return ERR_NODOMAIN; if (!wizards[member]) return ERR_NOWIZ; if (!check_privilege(capitalize(domain))) return ERR_PRIV; if (eval_cost()<1000) for (;;); // This is getting boring... domains[domain][member] = (lord ? 2 : 1); if (domainlists[member]) domainlists[member] += ([ domain : 1 ]); else domainlists[member] = ([ domain : 1 ]); save_data(); if (lord) syslog("Made " + member + " lord of " + domain); else syslog("Made " + member + " member of " + domain); return 0; } nomask string remove_domain_member(string domain,string member) { if (!domains[domain]) return ERR_NODOMAIN; if (!domains[domain][member]) return ERR_NOWIZ; if (!check_privilege(capitalize(domain))) return ERR_PRIV; if (eval_cost()<1000) for (;;); map_delete(domains[domain],member); map_delete(domainlists[member],domain); save_data(); syslog("Removed " + member +" from domain " + domain); return 0; } nomask string create_wizard(string wizard) { if (!valid_name(wizard)) return "invalid character name"; if (wizards[wizard]) return "this character is already recorded as a wizard"; if (!check_privilege(1)) return ERR_PRIV; if (eval_cost()<1000) for (;;); wizards[wizard] = 1; privileges[wizard] = ([ "":({}),":":({}) ]); save_data(); syslog("Created wizard " + wizard); return 0; } nomask string delete_wizard(string wizard) { mixed d; if (!wizards[wizard]) return ERR_NOWIZ; if (!check_privilege(1)) return ERR_PRIV; if (eval_cost()<10000) for (;;); map_delete(wizards,wizard); map_delete(privileges,wizard); d = domainlists[wizard]; if (d) { int i; d = keys(d); for (i=0; i<sizeof(d); i++) map_delete(domains[d[i]],wizard); } map_delete(domainlists,wizard); save_data(); syslog("Deleted wizard " + wizard); return 0; } private string get_priv_owner(string priv) { int n; if (!stringp(priv)) return 0; n = member_array(':',priv); if (n >=0) priv = priv[0..n-1]; if (!privileges[priv]) return 0; return priv; } nomask string define_privilege(string priv) { string owner; owner = get_priv_owner(priv); if (!owner) { if (priv[0]!='@' || member_array(':',priv)>=0) return "invalid privilege identifier"; if (!check_privilege(1)) return ERR_PRIV; if (eval_cost()<1000) for (;;); privileges[priv] = ([ "": ({ }), ":": ({ }) ]); save_data(); return 0; } if (!check_privilege(owner)) return ERR_PRIV; if (eval_cost()<1000) for (;;); priv = priv[strlen(owner)..]; if (privileges[owner][priv]) return "attempt to redefine privilege"; privileges[owner][priv] = ({ }); save_data(); return 0; } nomask string undefine_privilege(string priv) { mixed owner; owner = get_priv_owner(priv); if (!owner) return "invalid privilege identifier"; if (strlen(owner)+1 >= strlen(priv)) { if (!check_privilege(1)) return ERR_PRIV; } else { if (!check_privilege(owner)) return ERR_PRIV; } if (eval_cost()<1000) for (;;); if (owner == priv) map_delete(privileges,priv); else { mapping list; list = privileges[owner]; if (!list[priv[strlen(owner)..]]) return "invalid privilege identifier"; map_delete(list,priv[strlen(owner)..]); } save_data(); return 0; } nomask string * query_privilege_owners() { return keys(privileges); } nomask mapping query_privilege_list(string priv) { mapping list,result; string *subs; int i; if (!(list = privileges[priv])) return 0; result = ([ ]); subs = keys(list); for (i=0; i<sizeof(subs); i++) result[priv+subs[i]] = list[subs[i]][0..]; return result; } nomask mapping query_access_tree(string path,int write) { mapping result; string *p; int i; mixed *tmp; p = explode(path,"/")-({"","."}); if (write) result = write_access; else result = read_access; if (!sizeof(p)) return copy(result); for (i=0;i<sizeof(p)-1 && sizeof(result);i++) result = result[p[i]] ? result[p[i]][1] : 0; if (sizeof(result) && !undefinedp(tmp = result[p[<1]])) return ([ p[<1] : ({ tmp[0] == -1 ? query_protection(path+"/foo",write):tmp[0], copy(tmp[1])}) ]); else return ([ p[<1] : ({ query_protection(path+"/foo",write), 0}) ]); } nomask string extend_access(string priv,string add) { string owner; owner = get_priv_owner(priv); if (!owner || !privileges[owner] || !privileges[owner][priv[strlen(owner)..]]) return "invalid privilege identifier"; if (!stringp(add) || !valid_privilege(add)) return "invalid privilege identifier"; if (owner == priv) { if (!check_privilege(1)) return ERR_PRIV; } else { if (!check_privilege(owner)) return ERR_PRIV; } if (eval_cost()<1000) for (;;); priv = priv[strlen(owner)..]; privileges[owner][priv] -= ({ add }); privileges[owner][priv] += ({ add }); save_data(); return 0; } nomask string restrict_access(string priv,string remove) { string owner; owner = get_priv_owner(priv); if (!owner || !privileges[owner] || !privileges[owner][priv[strlen(owner)..]]) return "invalid privilege identifier"; if (owner == priv) { if (!check_privilege(1)) return ERR_PRIV; } else { if (!check_privilege(owner)) return ERR_PRIV; } if (eval_cost()<1000) for (;;); priv = priv[strlen(owner)..]; if (member_array(remove,privileges[owner][priv])<0) return "invalid privilege identifier"; privileges[owner][priv] -= ({ remove }); save_data(); return 0; } nomask int query_is_wizard(string name) { return wizards[name]; } nomask int query_is_domain(string name) { return !!domains[name]; } nomask string *query_domain_members(string domain) { mapping members; members = domains[domain]; return members && keys(members); } nomask string *query_domain_lords(string domain) { mapping members; string *list; int i; members = domains[domain]; if (!members) return 0; list = keys(members); for (i=0; i<sizeof(list); i++) if (members[list[i]]<2) list[i] = 0; return list - ({ 0 }); } nomask varargs string *query_domains(string wizard) { mapping d; if (!wizard) return keys(domains); if (!wizards[wizard]) return 0; d = domainlists[wizard]; return d ? keys(d) : ({ }); } nomask int valid_privilege(mixed p) { int n; mapping list; if (intp(p)) return p==0 || p==1; n = member_array(':',p); if (n<0) n = strlen(p); if (list = privileges[p[0..n-1]]) return !undefinedp(list[p[n..]]); } // returns 1 if a >= b nomask int higher_privilege(mixed a,mixed b) { // This is THE central routine of the security system. // It determines the security hierarchy. int m,n; if (!valid_privilege(a) || !valid_privilege(b)) error("Invalid privilege: "+a+", "+b+"\n"); if (a==b) return 1; if (intp(a)) { if (a) return 1; return 0; } else if (intp(b)) { if (!b) return 1; return 0; } m = member_array(':',a); if (m<0) m = strlen(a); n = member_array(':',b); if (n<0) n = strlen(b); a = ({ a[0..m-1], a[m..] }); b = ({ b[0..n-1], b[n..] }); if (a[0]==b[0] && strlen(a[1])<strlen(b[1]) && b[1][0..strlen(a[1])-1]==a[1]) return 1; switch (b[0][0]) { case 'A'..'Z': if (strlen(a[1])==0 && domains[lower_case(b[0])]) { /* if b is "Foo" then a must be a lord; if b is "Foo:" or "Foo:xxx" then a must be a member */ if (domains[lower_case(b[0])][a[0]] > !strlen(b[1])) return 1; /* fallthru to check privs */ } } return member_array(a[0] + a[1], privileges[b[0]][b[1]]) != -1; } nomask mixed reduced_privilege(mixed priv,mixed max) { if (higher_privilege(max,priv)) return priv; return max; } nomask mixed call_unguarded(function code,mixed *handle) { if (function_exists("verify_privilege_granted",previous_object())!=M_ACCESS) { syslog("Secvio: faked call in " + file_name(previous_object())); return 0; // faked call } if (!previous_object()->verify_privilege_granted(handle)) { syslog("Secvio: handle incorrect in " + file_name(previous_object())); return 0; // faked, too } return evaluate(code); } nomask varargs int check_privilege(mixed prot,int ignore) { object *stack; int stacksize; int i; object ob,next; object next2; if (!prot) return 1; stacksize = (sizeof(stack = all_previous_objects())); for (i=ignore; i<stacksize; i++) { ob = stack[i]; next = (i < stacksize - 1 ? stack[i+1] : 0); next2 = (i < stacksize - 2 ? stack[i+2] : 0); if (!ob) { syslog("Secvio: object destructed in call stack\n" + implode(map_array(stack, (: $1 ? file_name($1) : "0" :)), ", ")); return 0; } if (next == this_object()) // This call is unguarded { next = next2; if (next!=ob && next != this_object()) { syslog(sprintf("Secvio: %O used fptr bound to %O", ob, next)); return 0; // The object defining the fptr did not call it. } if (!next && prot) { syslog("Secvio: ob destructed in call stack\n" + implode(map_array(stack, (: file_name :)), ", ")); return 0; // This object has been destructed. } if (function_exists("query_unguarded_privilege",next)!=M_ACCESS) { syslog("Secvio: faked query_unguarded_privilege in " + file_name(next)); return 0; // faked } if (!higher_privilege(next->query_unguarded_privilege(),prot)) { syslog(sprintf("Secvio: %O has priv %O, asked for %O", next, next->query_unguarded_privilege(), prot)); return 0; // insufficient privilege } if (!higher_privilege(query_protection(next,1),prot)) { syslog(sprintf("Secvio: %O has prot %O, asked for %O", next, query_protection(next, 1), prot)); return 0; // privilege higher than maximum allowed value } return 1; } if ( ob == master() ) continue; if ( file_name(ob) == SIMUL_OB ) continue; if (function_exists("query_privilege",ob)!=M_ACCESS) { string temp,temp2; if (sscanf(file_name(ob),"/wiz/%s/%s",temp,temp2)==2) { return higher_privilege(temp+":",prot); } if (sscanf(file_name(ob),"/domains/%s/%s",temp,temp2)==2) { return higher_privilege(capitalize(temp)+":",prot); } return 0; } if (!higher_privilege(ob->query_privilege(),prot)) return 0; } ob = this_user(); if (!ob) { syslog("Secvio: Missing user\n" + call_trace()[0..<2]); return 0; } if (function_exists("query_privilege",ob)!=M_ACCESS) { syslog(sprintf("Secvio: %O faked query_privilege", ob)); return 0; } if (!higher_privilege(ob->query_privilege(),prot)) { syslog(sprintf("Secvio: %O has priv %O, asked for %O", ob, ob->query_privilege(), prot)); return 0; } return 1; }