Added public files
Roughly added all public files. Probably missed some, though.
diff --git a/secure/master/autoinclude.c b/secure/master/autoinclude.c
new file mode 100644
index 0000000..e0002fa
--- /dev/null
+++ b/secure/master/autoinclude.c
@@ -0,0 +1,101 @@
+// MorgenGrauen MUDlib
+//
+// secure/master/autoinclude.c -- module of the master object: Autoincludes
+//
+// $Id: master.c 7041 2008-10-13 18:18:27Z Zesstra $
+
+#define PRAGMA(x) "#pragma "x"\n"
+
+// fuer alte Homemuds...
+#if __VERSION__ >= "3.5.0"
+#define RTTCHECKS PRAGMA("rtt_checks")
+#define DEFAULTS PRAGMA("save_types")
+#else
+#define RTTCHECKS ""
+#define DEFAULTS PRAGMA("combine_strings, verbose_errors, warn_deprecated")
+#endif
+
+// geschachteltes Mapping in toplevel.region.magier Hierarchie.
+// Wichtig: jede Hierarchiebene _muss_ ein Mapping sein, welches einen Eintrag
+// 0 als Default enthaelt, welcher einen Strings als Wert hat.
+// Ausnahme: die letzte Ebene (Magierebene), die muss ein String ein.
+private nosave mapping autoincludes = ([
+ "d": ([
+ "inseln": ([
+ 0: "",
+ "zesstra": PRAGMA("strong_types") RTTCHECKS,
+ ]),
+ 0: "",
+ ]),
+ "std": ([
+ 0: PRAGMA("strong_types,pedantic") RTTCHECKS,
+ ]),
+ "items": ([
+ 0: PRAGMA("strong_types,pedantic") RTTCHECKS,
+ ]),
+ "secure": ([
+ 0: PRAGMA("strong_types,range_check,pedantic") RTTCHECKS,
+ ]),
+ "p": ([
+ 0: "",
+ "daemon": ([
+ 0: PRAGMA("strong_types") RTTCHECKS
+ ]),
+ "service": ([
+ 0: ""
+ ]),
+ ]),
+ 0: DEFAULTS,
+]);
+
+string autoincludehook(string base_file, string current_file, int sys_include)
+{
+ mapping topleveldir, region; // mappings for spezialisiertere Pfade
+ string ainc_string;
+
+ // Wenn current_file != 0 ist, wird gerade vom kompilierten Objekt
+ // <base_file> etwas (indirekt) inkludiert. Dort duerfen die Pragmas
+ // keinesfalls reingeschrieben werden.
+ if (current_file)
+ return 0;
+
+ string res=autoincludes[0]; // global default.
+
+ string *p_arr = explode(base_file,"/")-({""});
+ //DEBUG(sprintf("AINC: File: %O, Pfad: %O\n",base_file, p_arr));
+
+ if (sizeof(p_arr) && m_contains(&topleveldir, autoincludes, p_arr[0])) {
+ // p_arr[0]: d, p, std, etc.
+ // erst wird der Defaulteintrag 0 genommen
+ res += topleveldir[0];
+ if (sizeof(p_arr) > 1 && m_contains(®ion, topleveldir, p_arr[1])) {
+ // p_arr[1] ebene, polar, unterwelt, service, ...
+ // erst den Defaulteintrag der Region nehmen
+ res += region[0];
+ if (sizeof(p_arr) > 2 && m_contains(&ainc_string, region, p_arr[2])) {
+ // p_arr[2]: magiername. Fuer den Magier was hinterlegt.
+ res += ainc_string;
+ }
+ }
+ }
+ // Fuer aeltere Files schalten wir einige Warnungen explizit aus. :-(
+ // (1407179680 == "Mon, 4. Aug 2014, 21:14:40")
+#if MUDHOST == __HOST_NAME__
+ if (call_sefun("file_time", base_file) < 1407179680) {
+ res += PRAGMA("no_warn_missing_return");
+ }
+#else
+ // Auf anderen Rechnern als dem Mudrechner werden die Warnungen unabhaengig
+ // vom Zeitpunt der letztes Aenderung abgeschaltet, weil bei kopierten
+ // Mudlibs oft die mtimes geaendert werden und dann auf einmal alles scrollt.
+ res += PRAGMA("no_warn_missing_return");
+
+#endif
+ //DEBUG(res);
+ return res;
+}
+#undef RTTCHECKS
+#undef DEFAULTS
+#undef PRAGMA
+
+
diff --git a/secure/master/cidr.c b/secure/master/cidr.c
new file mode 100644
index 0000000..c35ba21
--- /dev/null
+++ b/secure/master/cidr.c
@@ -0,0 +1,111 @@
+// MorgenGrauen MUDlib
+/** \file /secure/master/cidr.c
+* Funktionen zur Interpretation und Umrechnung von IP-Adressen in CIDR-Notation.
+* \author Zesstra
+* \date 05.02.2010
+* \version $Id$
+*/
+/* Changelog:
+*/
+
+private int IPv4_addr2int(string addr) {
+ int mask;
+
+ if (!stringp(addr) || !sizeof(addr))
+ return 0;
+ // irgendwelche Zeichen ausser 0-9, . und / drin?
+ if (sizeof(addr-"0123456789./"))
+ return 0;
+
+ string *parts = explode(addr, "/") - ({""});
+ // Netzmaske gegeben? -> Netzwerkadresse bestimmen
+ if (sizeof(parts) == 2) {
+ // a.b.c.d/x oder a.b.c.d/w.x.y.z
+ int res = IPv4_addr2int(parts[0]); // erstmal Wert von a.b.c.d...
+ if (strstr(parts[1], ".") > -1
+ && sizeof(explode(parts[1],".")) == 4)
+ // a.b.c.d/w.x.y.z -> Wert von w.x.y.z bestimmen
+ mask = IPv4_addr2int(parts[1]);
+ else {
+ mask = to_int(parts[1]);
+ // Wert von /x
+ mask = 0xffffffff - (to_int(pow(2, 32-mask))-1);
+ }
+ // verunden und Ende.
+ return res & mask;
+ }
+
+ parts = explode(parts[0], ".");
+ // Abgrekuerzte Adresse a la a.b.? Rest mit 0 ergaenzen.
+ switch(sizeof(parts - ({""})) ) {
+ case 1:
+ parts += ({"0","0","0"});
+ case 2:
+ parts += ({"0","0"});
+ case 3:
+ parts += ({"0"});
+ case 4:
+ break;
+ default:
+ return 0;
+ }
+
+ return( to_int(parts[0]) << 24)
+ + ( to_int(parts[1]) << 16)
+ + ( to_int(parts[2]) << 8 )
+ + to_int(parts[3]);
+}
+
+private int IPv4_net_size(string addr) {
+ if (!stringp(addr) || !sizeof(addr))
+ return 0;
+ // irgendwelche Zeichen ausser 0-9, . und / drin?
+ if (sizeof(addr-"0123456789./"))
+ return 0;
+
+ string *parts = explode(addr, "/") - ({""});
+ // Netzmaske gegeben? -> Netzwerkadresse bestimmen
+ if (sizeof(parts) == 2) {
+ // a.b.c.d/x oder a.b.c.d/w.x.y.z
+ if (strstr(parts[1], ".") > -1
+ && sizeof(explode(parts[1],".")) == 4)
+ // a.b.c.d/w.x.y.z
+ return 0xffffffff - (IPv4_addr2int(parts[1]) || 1) + 1;
+ else {
+ // Einfach 2^(32-mask) Adressen.
+ return to_int(pow(2, 32 - to_int(parts[1]) ) );
+ }
+ }
+
+ parts = explode(parts[0], ".");
+ switch(sizeof(parts - ({""})) ) {
+ case 1: // Class A
+ return 256*256*256;
+ case 2: // Class B
+ return 256*256;
+ case 3: // Class C
+ return 256;
+ case 4: // einzelne Adresse
+ return 1;
+ default: // ungueltige Adresse
+ return __INT_MAX__;
+ }
+
+ // hier sollte man gar nicht ankommen.
+ return __INT_MAX__;
+}
+
+string IPv4_int2addr(int ip) {
+ int *parts=allocate(4);
+ parts[0] = (ip & 0xff000000) >> 24;
+ parts[1] = (ip & 0x00ff0000) >> 16;
+ parts[2] = (ip & 0x0000ff00) >> 8;
+ parts[3] = (ip & 0x000000ff);
+ // int is signed. Anything > 127 would wrap around to -128 if we are on a
+ // machine with 32-bit wide ints.
+ if (parts[0] < 0)
+ parts[0] = 128 + (128-abs(parts[0]));
+
+ return implode(map(parts,#'to_string), ".");
+}
+
diff --git a/secure/master/destruct.c b/secure/master/destruct.c
new file mode 100644
index 0000000..7e76a11
--- /dev/null
+++ b/secure/master/destruct.c
@@ -0,0 +1,105 @@
+// MorgenGrauen MUDlib
+//
+// secure/master/destruct.inc -- module of the master object: stuff for destruct.
+//
+// $Id: master.c 7041 2008-10-13 18:18:27Z Zesstra $
+
+#include "/sys/object_info.h"
+
+// privilegierte Objekte, die das destruct() abbrechen duerfen (root objekte
+// duerfen auch ohne, dass sie in dieser Liste erfasst sind):
+private nosave string *deny_destruct_list = ({
+ "/obj/shut", "/room/void", "/room/netztot", "/room/jail" });
+
+// Helferfunktion fuer prepare_destruct()
+private void recursive_remove(object ob, int immediate_destruct) {
+
+ if (efun::object_info(ob, OI_ONCE_INTERACTIVE)) {
+ // Spieler werden ins Void bewegt.
+ int res;
+ tell_object(ob, "Ploetzlich loest sich deine Welt in ihre " +
+ "Bestandteile auf. Zum Glueck wirst\nDu irgendwo " +
+ "hin geschleudert ...\n");
+ // wenn Bewegung buggt oder nicht funktioniert und ob noch existiert,
+ // rekursiv zerstoeren.
+ object oldenv=environment(ob);
+ if ( (catch(res=(int)ob->move("/room/void",M_TPORT|M_NOCHECK,0,"faellt");
+ publish) || (ob && environment(ob) == oldenv) )
+ && ob) {
+ // Spieler speichern, dann erst Inventar entleeren, dann remove() und
+ // und destruct() anwenden.
+ catch(ob->save_me(1); publish);
+ filter(all_inventory(ob), #'recursive_remove, immediate_destruct);
+ if (!immediate_destruct)
+ catch(ob->remove(0);publish);
+ if (ob)
+ destruct(ob);
+ }
+ }
+ else {
+ // kein Spieler. Rekursiv entfernen. Hierbei _zuerst_ rekursiv das
+ // Inventar entfernen und dann ob selber, damit nicht erst das Inventar in
+ // das Environment bewegt wird (soll ja eh zerstoert werden).
+ filter(all_inventory(ob), #'recursive_remove, immediate_destruct);
+ // ggf. zuerst remove versuchen
+ if (!immediate_destruct)
+ catch(ob->remove(1);publish);
+ if (ob)
+ destruct(ob);
+ }
+}
+
+// Zerstoerung von ob vorbereiten
+protected mixed prepare_destruct(object ob)
+{
+ object old_env,env,item;
+ mixed res;
+
+ // zuerst das notify_destruct() rufen und ggf. abbrechen, falls ob
+ // privilegiert ist.
+ catch(res = (mixed)ob->NotifyDestruct(previous_object()); publish);
+ if (res &&
+ (getuid(ob) == ROOTID ||
+ (IS_ARCH(ob)) ||
+ member(deny_destruct_list, object_name(ob)) >= 0)) {
+ if (stringp(res) && sizeof(res))
+ return res;
+ else
+ return sprintf("%O verweigert die Zerstoerung mittels destruct(). "
+ "Fehlende Rechte von %O?\n",ob, previous_object());
+ }
+
+ env = environment(ob);
+
+ // Objekt hat kein Env: Alles zerstoeren, Spieler ins Void
+ if (!env) {
+ filter(all_inventory(ob), #'recursive_remove, 1);
+ }
+ else {
+ // Ansonsten alles ins Environment
+ foreach(item : all_inventory(ob))
+ {
+ old_env=environment(item);
+ // M_MOVE_ALL, falls item nen Unitobjekt ist. Sonst clonen die u.U. noch
+ // wieder nen neues Objekt im alten Env.
+ if(catch(item->move(env, M_NOCHECK|M_MOVE_ALL);publish))
+ recursive_remove(item, 1);
+ else if (item && environment(item) == old_env)
+ recursive_remove(item, 1);
+ }
+ }
+
+ return 0; // Erfolg
+}
+
+// Anmerkung: liefert 0 zurueck, wenn die sefuns gerade geladen werden.
+string NotifyDestruct(object caller) {
+ // Nicht jeder Magier muss den Master entsorgen koennen.
+ if ((caller != this_object() &&
+ call_sefun("secure_level") < ARCH_LVL)
+ || call_sefun("process_call") ) {
+ return "Du darfst den Mudlib-Master nicht zerstoeren!\n";
+ }
+ return 0;
+}
+
diff --git a/secure/master/domain.c b/secure/master/domain.c
new file mode 100644
index 0000000..99ec6f6
--- /dev/null
+++ b/secure/master/domain.c
@@ -0,0 +1,95 @@
+// MorgenGrauen MUDlib
+//
+// master/domain.c -- Regionen und Regionsmagier
+//
+// $Id: domain.c 7162 2009-02-26 21:14:43Z Zesstra $
+
+#pragma strict_types
+
+#include "/secure/master.h"
+#include "/sys/files.h"
+
+int domain_master(string user, string domain)
+{
+ string *domains;
+ int i;
+
+ if (!find_userinfo(user)||
+ !pointerp(domains=get_userinfo(user)[USER_DOMAIN+1]))
+ return 0;
+ return (member(domains,domain) != -1);
+}
+
+int domain_member(string user, string domain)
+{
+ if (domain=="erzmagier") return 0;
+ return (IS_DOMAINMEMBER(user)&&file_size("/d/"+domain+"/"+user)==-2);
+}
+
+int add_domain_master(string user,string dom)
+{
+ string *domains;
+
+ if ((call_other(SIMUL_EFUN_FILE, "process_call") ||
+ call_other(SIMUL_EFUN_FILE, "secure_level") < GOD_LVL) ||
+ !find_userinfo(user))
+ return 0;
+ domains=get_userinfo(user)[USER_DOMAIN+1];
+ if (!domains)
+ set_domains(user,({dom}));
+ else
+ {
+ // Doppelte Eintraege vermeiden
+ domains=domains-({dom})+({dom});
+ set_domains(user, domains);
+ }
+ if (get_wiz_level(user) < LORD_LVL+5) {
+ update_wiz_level(user,LORD_LVL+5);
+ SendWizardHelpMail(user, LORD_LVL+5);
+ }
+ return 1;
+}
+
+int remove_domain_master(string user,string dom)
+{
+ string *domains;
+
+ if (!IS_GOD(geteuid(previous_object()))
+ ||!find_userinfo(user)
+ ||!(domains=get_userinfo(user)[USER_DOMAIN+1])
+ || member(domains,dom)==-1)
+ return 0;
+ domains-=({dom});
+ set_domains(user,domains);
+ return 1;
+}
+
+string *get_domains() {
+ string *regions=({});
+ // alle Verzeichnisse in /d/ ermitteln
+ foreach(string region: (get_dir("/"DOMAINDIR"/*")
+ || ({}))-({".","..","erzmagier"})) {
+ if (region[0]!='.' && file_size("/d/"+region) == FSIZE_DIR)
+ //Verzeichnis, kein File und faengt nicht mit "." an: aufnehmen.
+ regions+=({region});
+ }
+ return regions;
+}
+
+// in welchen Regionen hat 'wiz' ein Verzeichnis?
+string *get_domain_homes(string wiz)
+{
+ string *homes=({});
+ string tmp;
+
+ if (query_wiz_level(wiz)<=WIZARD_LVL) return ({});
+
+ tmp = "/d/%s/"+wiz;
+ foreach(string dir: get_domains()) {
+ if (dir[0]!='.' && file_size(sprintf(tmp,dir)) == FSIZE_DIR)
+ //Magierverzeichnis da und faengt nicht mit "." an: aufnehmen.
+ homes+=({dir});
+ }
+ return homes;
+}
+
diff --git a/secure/master/file_access.c b/secure/master/file_access.c
new file mode 100644
index 0000000..5b7e3e4
--- /dev/null
+++ b/secure/master/file_access.c
@@ -0,0 +1,617 @@
+#pragma strict_types, no_warn_deprecated
+
+#include "/secure/master.h"
+
+static mapping projects=([]);
+static string *insecure,*deputy_files;
+
+int ReloadInsecureFile()
+{
+ insecure = efun::explode( read_file("/secure/INSECURE") || "", "\n" );
+ insecure -= ({""});
+ insecure = map( insecure, #'lower_case/*'*/ );
+ return(1);
+}
+
+void LoadDeputyFileList()
+{
+ // Leseberechtigungen fuer /log/ARCH/* setzen
+ deputy_files = efun::explode( read_file("/log/ARCH/DEPUTY_FILELIST") || "",
+ "\n" ) - ({""});
+ return;
+}
+
+#define PATH_ARRAY(x) (explode(x, "/")-({"."}))
+string *full_path_array(string path, string user) {
+ string *strs;
+ string cwd;
+
+ if(!path)
+ return ({"",""}); // additional "" to yield "/" later.
+
+ // remove multiple '/'
+ path = regreplace(path, "/+", "/", 1);
+
+ switch(path[0]) {
+ case '/':
+ if(!path[1]) return ({"",""}); //additional "" to yield "/" later
+ strs=PATH_ARRAY(path);
+ if (!sizeof(strs))
+ strs = ({"",""});
+ break;
+ case '+':
+ if(!path[1]) return ({"","d"});
+ strs=({"","d"})+PATH_ARRAY(path[1..<1]);
+ break;
+ case '~':
+ if(user && !path[1])
+ return ({"","players",user});
+ if(user && path[1]=='/')
+ strs=({"","players",user})+PATH_ARRAY(path[2..<1]);
+ else
+ strs=({"","players"})+PATH_ARRAY(path[1..<1]);
+ break;
+ default:
+ if(user && TP && getuid(TP) == user
+ && (cwd=(string)TP->QueryProp(P_CURRENTDIR)))
+ strs=PATH_ARRAY(cwd + "/" + path);
+ else
+ strs=PATH_ARRAY(path);
+ }
+ // /../ behandeln. Einschraenkungen der RegExp:
+ // /a/b/../../d wuerde nur durch Wiederholung aufgeloest.
+ // /../d wird nicht richtig aufgeloest.
+ //regreplace("/d/inseln/toeter/room/hoehle/../wald/bla.c","(.*)\/[^/]+/\.\.
+ // /(.*)", "\\1\/\\2", RE_GLOBAL)
+
+ // dies sieht schlimmer aus als es ist (member ist O(n)), solange das Array
+ // nicht gross wird.
+ int p;
+ while((p=member(strs, "..")) != -1)
+ strs = strs[0..p-2]+strs[p+1..];
+
+ return strs;
+}
+#undef PATH_ARRAY
+
+string _get_path(string path, string user) {
+ string *p_arr = full_path_array(path, user);
+ // make path absolute
+ if (p_arr[0] != "")
+ p_arr = ({""}) + p_arr;
+
+ return implode(p_arr,"/");
+}
+
+static int project_access(string user, string project)
+{
+ mixed *lines;
+ string s;
+ mapping tmp;
+
+ if (!member(projects,project))
+ {
+ s=read_file(PROJECTDIR+"/"+project+"/ACCESS_RIGHTS");
+ if(!s||s=="")
+ return 0;
+ tmp=([]);
+ for (lines=explode(s,"\n")-({""});sizeof(lines);lines=lines[1..])
+ {
+ if (lines[0][0]=='*')
+ tmp[lines[0][1..]]=2;
+ else
+ tmp[lines[0]]=1;
+ }
+ projects[project]=({tmp,time()});
+ return tmp[user];
+ }
+ projects[project][1]=time();
+ return projects[project][0][user];
+}
+
+void OutdateProjectCache(string project)
+{
+ m_delete(projects,project);
+}
+
+static void _cleanup_projects() {
+ int i;
+ mixed *users;
+
+ for (users=m_indices(projects),i=sizeof(users)-1;i>=0;i--)
+ if((time()-projects[users[i]][1])>1800)
+ m_delete(projects,users[i]);
+}
+
+int access_rights(string *p_arr, string euid)
+{
+ int i;
+
+ i = sizeof(p_arr) - 2;
+
+ while ( i >= 0 &&
+ file_size( implode(p_arr[0..i], "/") + "/access_rights.c" ) < 0 )
+ i--;
+
+ if ( i < 0 )
+ return 0;
+
+ if ( !catch(i = (int)call_other(
+ implode(p_arr[0..i], "/") + "/access_rights",
+ "access_rights", euid,
+ implode(p_arr[i+1..], "/") ); publish) )
+ return i;
+
+ return 0;
+}
+
+string make_path_absolute(string str) {
+ return _get_path(str, getuid(TP));
+}
+
+
+mixed valid_write(string path, string euid, string fun, object obj)
+{
+ int s,lvl;
+ string *strs;
+ int *date;
+
+ if (member(path,' ')!=-1) return 0;
+
+ // Unter LIBDATADIR (/data) sollen komplett identische Schreibrechte
+ // vergeben werden.
+ if (sizeof(path) > 6
+ && path[0..5] == "/"LIBDATADIR"/")
+ return valid_write(path[5..], euid, fun, obj) != 0;
+
+ switch(fun) {
+ case "log_file":
+ strs=full_path_array("/"+path, 0);
+ path = implode(strs, "/");
+ strs -= ({""}); // remove trailing and leading "/".
+ if (sizeof(strs)>1 && strs[0]=="log") return path;
+ return 0;
+ case "save_object":
+ if (!path) return 0;
+ strs=full_path_array("/"+path, 0);
+ break;
+ case "ed_start":
+ if (path && path[0])
+ strs=full_path_array(path, euid);
+ else strs=({"players",euid,".err"});
+ break;
+ default:
+ strs=full_path_array(path, euid);
+ }
+
+ if (!euid || euid=="NOBODY" || euid=="ftp" || euid=="anonymous") return 0;
+
+ // Pfad soll ab jetzt auf jeden Fall absolut sein.
+ if (strs[0] != "")
+ path = "/" + implode(strs, "/");
+ else
+ path=implode(strs, "/");
+
+ // fuer die Rechtebestimmung "/" an Anfang und Ende entfernen
+ strs -= ({""});
+
+ //Root-Objekte und Master duerfen immer.
+ if (euid == ROOTID || obj==TO) return path;
+
+ lvl=query_wiz_level(euid);
+
+ //Toplevel: nur EM+
+ if ((s=sizeof(strs))<=1) return lvl>=ARCH_LVL;
+
+ switch(strs[0]) {
+ case TMPDIR:
+ return 1;
+
+ case STDDIR:
+ case SYSDIR:
+ case LIBOBJDIR:
+ case ETCDIR:
+ case LIBROOMDIR:
+ return lvl>=ARCH_LVL;
+
+ case LIBITEMDIR:
+ return lvl>=ELDER_LVL;
+
+ case SPELLBOOKDIR:
+ // wenn unterhalb eines unterverzeichnisses des Stils "gilde", dann ists
+ // einfach
+ if (sizeof(strs) > 2
+ && guild_master(euid, strs[1]))
+ return 1;
+
+ // sonst nur EMs bzw. access_rights.c fragen.
+ return ((lvl>=ARCH_LVL) || access_rights(strs,euid));
+
+ case GUILDDIR:
+ // wenn unterhalb eines unterverzeichnisses des Stils "filde.gilde",
+ // dann ists einfach
+ if (sizeof(strs) > 2) {
+ string *tmp = explode(strs[1],"files.");
+ if (sizeof(tmp) == 2) {
+ if (guild_master(euid, tmp[1]))
+ return 1;
+ // Objekte dort sollen auch schreiben duerfen.
+ if (euid == ("GUILD."+tmp[1]))
+ return 1;
+ }
+ }
+ // sonst nur EMs bzw. access_rights.c fragen.
+ return ((lvl>=ARCH_LVL) || access_rights(strs,euid));
+
+ case DOCDIR:
+ if ( s > 1 && (strs[1] == "balance") )
+ return ((lvl>=ARCH_LVL) || access_rights(strs,euid));
+ else
+ return ((lvl>=SPECIAL_LVL) || access_rights(strs,euid));
+
+ case FTPDIR:
+ if ( s > 1 && (strs[1] == "incoming" || strs[1] == "tmp" ||
+ s == 3 && strs[1] == "News" && strs[2] == euid+".news") )
+ return 1;
+
+ if (lvl>=ELDER_LVL)
+ return 1;
+
+ return 0;
+
+ case MAILDIR:
+ if (euid==MAILID || strs[1]=="spool") break;
+ return 0;
+
+ case LIBSAVEDIR:
+ if (lvl>=ARCH_LVL) return 1;
+ if (s==3 && strs[1] == euid[0..0] &&
+ (strs[2]==euid+".o" || strs[2]==euid)) break;
+ return 0;
+
+ case LIBLOGDIR:
+ /* auf /log/ARCH/ haben wirklich nur EMs Zugriff */
+ if (strs[1]=="ARCH" && lvl<ARCH_LVL) return 0;
+
+ /* alles andere duerfen auch Weise... */
+ if (lvl>=ELDER_LVL) return 1;
+
+ /* Allgemeine logfiles in /log duerfen wirklich nur geschrieben werden */
+ if (s==2 && strs[1][0]>='A' && strs[1][0]<='Z' && fun != "write_file")
+ return 0;
+
+ /* Unterhalb von /log/syslog darf nur geschrieben werden */
+ if (s>1 && strs[1]=="syslog" && fun != "write_file")
+ return 0;
+
+ /* loggen ins eigene repfile immer erlauben */
+ if (s==3 && strs[1]=="report" &&
+ strs[2]==explode(euid, ".")[<1]+".rep") break;
+
+ /* in fremden Verzeichnissen hat niemand was zu loggen */
+ if (get_wiz_level(strs[1]) >= WIZARD_LVL &&
+ strs[1] != efun::explode(euid, ".")[<1])
+ return 0;
+ break;
+
+ case WIZARDDIR:
+ /* kein Zugriff auf Objekte mit hoeheren Rechten */
+ if (query_wiz_level(strs[1]) > lvl) return 0;
+
+ /* Magier selbst oder Weise duerfen hier schreiben */
+ if ((IS_WIZARD(euid) && euid==strs[1])||(lvl>=ELDER_LVL)) break;
+
+ /* Es steht jedoch frei, auch anderen Schreibrechte zu geben... */
+ return access_rights(strs,euid);
+
+ case DOMAINDIR:
+ /* neue Regionen duerfen nur Erzmagier anlegen */
+ if (s<2 && lvl<ARCH_LVL) return 0;
+
+ /* kein Zugriff auf Objekte mit hoeheren Rechten */
+ if (s>2 && query_wiz_level(creator_file(path)) > lvl)
+ return 0;
+
+ /* Auf Regionfiles von erzmagier haben nur EMs Zugriff */
+ if (creator_file(path)=="erzmagier" && lvl<ARCH_LVL) return 0;
+
+ /* innerhalb der Region haben RMs und Weise volle Schreibrechte */
+ if (lvl>=ELDER_LVL || domain_master(euid,strs[1])) break;
+
+ /* neue Verzeichnisse in der Region kann nur RM+ anlegen */
+ if (s<=3 && (fun=="mkdir" || fun=="rmdir")) return 0;
+
+ /* Magier auf ihre eigenen Files... */
+ if (s>2 && strs[2]==euid) break;
+
+ /* Files der Magier in der Region in ihre eigenen Verzeichnisse... */
+ if (s>2 && euid==sprintf("d.%s.%s", strs[1], strs[2])) break;
+
+ /* Files mit Regionsuid */
+ if (euid==strs[1]) break;
+
+ /* Es ist moeglich anderen Magiern Rechte bei sich einzuraeumen. */
+ if (access_rights(strs,euid)>0) break;
+
+ /* Auf das Verzeichniss 'alle' haben alle Regionsmitglieder Zugriff.
+ Ausser, wenn sie ueber access_rights.c explizit ausgeschlossen
+ werden (Returncode < 0). */
+ if (s>2 && strs[2]=="alle" && domain_member(euid, strs[1]) &&
+ access_rights(strs,euid)>=0) break;
+ return 0;
+
+ case PROJECTDIR:
+ /* Nur Erzmagier duerfen neue Projektverzeichnisse anlegen... */
+ if (s<3 && lvl<ARCH_LVL) return 0;
+
+ /* alles weitere duerfen auch Weise tun */
+ if (lvl>=ELDER_LVL) break;
+
+ /* in den Serviceverzeichnissen muss lediglich der Name stimmen */
+ if (s>3 && strs[1]=="service" && euid==strs[2]) break;
+
+ /* Objekte eines Projektes haben Schreibzugriffe auf ihr Projekt */
+ if (s>3 && (implode(strs[0..1], ".") == euid
+ || implode(strs[0..2], ".") == euid) ) return 1;
+
+ /* Schreibrechte koennen natuerlich auch vergeben werden... */
+ if (project_access(euid,strs[1])) break;
+ // Alternativ besteht neben dem ACCESS_RIGHTS auch noch die
+ // Moeglichkeit, per access_rights.c Rechte einzuraeumen.
+ if (access_rights(strs,euid)>0) break;
+
+ return 0;
+
+ default: return 0;
+ }
+ return path;
+}
+
+mixed valid_read(string path, string euid, string fun, object obj)
+{
+ int s, lev;
+ string *strs, suf;
+
+ if (member(path,' ')!=-1) return 0;
+
+ // Unter LIBDATADIR (/data) sollen komplett identische Leserechte
+ // vergeben werden.
+ if (sizeof(path) > 6
+ && path[0..5] == "/"LIBDATADIR"/")
+ return valid_read(path[5..], euid, fun, obj) != 0;
+
+ if (!euid) euid="-";
+
+ strs=full_path_array(path, euid);
+ // Pfad soll ab jetzt auf jeden Fall absolut sein.
+ if (strs[0] != "")
+ path = "/" + implode(strs, "/");
+ else
+ path=implode(strs, "/");
+
+ // fuer die Rechtebestimmung "/" an Anfang und Ende entfernen
+ strs -= ({""});
+
+ if (!sizeof(strs) || !sizeof(path) || euid == ROOTID || obj==TO) return path;
+
+ if ((s=sizeof(strs)) <= 1) return path;
+
+ lev=query_wiz_level(euid);
+
+ switch(strs[0]) {
+ case "core":
+ case "wahl":
+ return 0;
+
+ case NEWSDIR:
+ if (strs[1] == "archiv.pub") // oeffentliches archiv
+ return path;
+ else if (strs[1] == "archiv.magier" // Magier-Archiv
+ && lev >= WIZARD_LVL)
+ return path;
+
+ return 0; // kein Zugriff auf /news/ oder alles andere.
+
+ case "maps":
+ if (lev<=WIZARD_LVL) return 0;
+
+ case "":
+ case ETCDIR:
+ case STDDIR:
+ case SYSDIR:
+ case DOCDIR:
+ case LIBOBJDIR:
+ case LIBROOMDIR:
+ case LIBITEMDIR:
+ case FTPDIR:
+ return path;
+
+ case MAILDIR:
+ return (euid==MAILID);
+
+ case SECUREDIR:
+ if (strs[1]=="save") return 0;
+ if (path[<2..]==".o" || path[<5..]==".dump") return 0;
+ if (strs[1]!="ARCH" || lev>=ARCH_LVL) return path;
+
+ case LIBLOGDIR:
+ if ( strs[1] == "ARCH" && !IS_DEPUTY(euid) )
+ return 0;
+
+ if ( strs[1] == "ARCH" && lev < ARCH_LVL && s == 3 &&
+ strs[2] != "*" && strs[2][0..2] != "bb." &&
+ member( deputy_files, strs[2] ) < 0 )
+ return 0;
+
+ if ( lev > WIZARD_LVL )
+ return path;
+
+ if ( s == 2 && (strs[1] == "report" || strs[1] == "error") )
+ return path; // fuer 'cd'
+
+ // /log sollte jeder auflisten koennen
+ if ( s == 2 && strs[1]=="*" && fun=="get_dir")
+ return path;
+
+ // unter /log/report/, /log/error und /log/warning/ duerfen alle lesen
+ if ( s==3 &&
+ (strs[1] == "report" || strs[1] == "error"
+ || strs[1] =="warning"))
+ return path;
+
+ // /log/<Magiername> darf von <Magiername> natuerlich auch
+ // gelesen werden
+ if ( s >= 2 && strs[1] == euid )
+ return path;
+
+ return 0;
+
+ case "backup":
+ case LIBSAVEDIR:
+ if (lev>WIZARD_LVL) return path;
+
+ /* Objekte in /p/* haben bisher leider wizlevel 0 */
+ //if (lev==0) return path;
+
+ if (fun=="file_size") return path;
+
+ // das eigene Savefile darf man natuerlich immer. ;-)
+ if (s==3 && (strs[2]==euid+".o" || strs[2]==euid))
+ return path;
+ return 0;
+
+ case PROJECTDIR:
+ /* Pruefen ob ein File existiert darf jeder... */
+ if (fun=="file_size") return path;
+
+ /* Die Service-Verzeichnisse darf jeder lesen */
+ if (s>3 && strs[1]=="service") return path;
+
+ //Weise duerfen in /p/ schreiben, also auch lesen. (Zesstra, 04.11.06)
+ if (lev>=ELDER_LVL) return path;
+
+ /* wer hier schreiben darf, darf natuerlich auf jeden Fall lesen */
+ //Anmerkung v. Zesstra: das prueft nur, ob jemand in ACCESS_RIGHTS
+ //steht, nicht ob jemand (ggf. aus anderen Gruenden schreiben darf)
+ if (project_access(euid,strs[1])) return path;
+ //Alternativ kann man explizite Schreibrechte auch via access_rights.c
+ //vergeben. (und damit natuerlich auch Leserechte)
+ if (access_rights(strs,euid)>0) return path;
+
+ /* Objekte eines Projektes haben Lesezugriff auf ihr Projekt */
+ if (s>3 && (implode(strs[0..1], ".") == euid
+ || implode(strs[0..2], ".") == euid) ) return path;
+
+ /* Fall-Through */
+
+ case GUILDDIR:
+ /* "secure"-Verzeichnisse darf nur lesen, wer da auch schreiben darf */
+ if ( s > 3 && strs[<2] == "secure" && member( insecure, strs[1] ) < 0
+ && lev < ARCH_LVL && !access_rights(strs, euid) )
+ return 0;
+
+ /* Pruefen ob ein File existiert darf jeder... */
+ if (fun=="file_size") return path;
+
+ /* Fall-Through */
+
+ case SPELLBOOKDIR:
+ // Gildenpbjekte koennen hier lesen
+ if (lev==0 && euid[0..4]=="GUILD") return path;
+
+ // Mit Level <= 20 darf man hier nichts lesen
+ if ( lev<=WIZARD_LVL && sizeof(regexp( ({strs[<1]}), "\\.[och]" )) )
+ return 0;
+
+ return path;
+
+ case WIZARDDIR:
+ if (s==2) return path;
+ /* das eigene Verzeichniss darf man natuerlich immer lesen... */
+ if (strs[1]==euid && lev>=WIZARD_LVL) return path;
+
+ /* Pruefen ob ein File existiert darf jeder... */
+ if (fun=="file_size") return path;
+
+ /* fremde Verzeichnisse mit <= Level 20 noch nicht */
+ if (lev<=WIZARD_LVL) return 0;
+
+ /* wo man schreiben darf, darf man natuerlich auch lesen... */
+ if (lev>=ELDER_LVL) return path;
+
+ // kein Zugriff auf archivierten Code (wo z.B. secure/ drin sein
+ // koennen)
+ if (member(({"bz2","gz","zip"}), explode(strs[<1],".")[<1]) > -1)
+ return 0;
+
+ /* Files ohne Code sind nicht weiter interessant... */
+ if ( !sizeof(regexp( ({strs[<1]}), "\\.[och]" )) )
+ return path;
+
+ /* folgende Funktionen sind natuerlich voellig uninteressant */
+ if (member(({"get_dir", "restore_object"}), fun)!=-1)
+ return path;
+
+ /* Zugriff erlaubt, aber mitloggen */
+ write_file( READ_FILE, sprintf("%O %s %s: %s\n", fun, ctime()[4..],
+ euid, path) );
+ return path;
+
+ case DOMAINDIR:
+ /* Mit Level 15 darf man hier _nichts_ lesen */
+ if ( fun!="file_size" && lev<WIZARD_LVL &&
+ sizeof(regexp( ({strs[<1]}), "\\.[och]" )) ) return 0;
+
+ /* den Verzeichnisbaum von /d/ darf man natuerlich sonst lesen */
+ if (s<=2) return path;
+
+ /* eigenen Code darf man natuerlich auch lesen */
+ if (euid==strs[2] || euid==sprintf("d.%s.%s", strs[1], strs[2]))
+ return path;
+
+ /* Deputies haben ein gemeinsames Verzeichnis unter /d/erzmagier */
+ if (strs[1]=="erzmagier" && strs[2]=="polizei" && IS_DEPUTY(euid))
+ return path;
+
+ /* d/erzmagier darf man nur als Erzmagier lesen */
+ if (strs[1]=="erzmagier" && lev<ARCH_LVL) return 0;
+
+ /* einige Regionen haben std-verzeichnisse, die darf man natuerlich
+ auch mit Level 20 bereits komplett lesen! */
+ if (strs[2]=="std") return path;
+
+ /* "secure"-Verzeichnisse darf nur lesen, wer da auch schreiben darf */
+ if ( s > 4 && strs[<2] == "secure" && member( insecure, strs[2] ) < 0 ){
+ if ( lev < ELDER_LVL && !domain_master(euid, strs[1])
+ && !access_rights(strs, euid) )
+ return 0;
+ else
+ return path;
+ }
+
+ /* Dokus, Infos und .readme darf man immer lesen... */
+ if ( fun=="file_size" || !sizeof(regexp( ({strs[<1]}), "\\.[och]" )) )
+ return path;
+
+ /* Mit Level 20 darf man noch keinen fremden Code lesen! */
+ if (lev<=WIZARD_LVL) return 0;
+
+ /* Weise duerfen natuerlich alles weitere lesen */
+ if (lev>=ELDER_LVL) return path;
+
+ /* Regionsmagier in ihren Regionen natuerlich auch */
+ if (lev>=LORD_LVL && domain_master(euid, strs[1])) return path;
+
+ /* folgende Funktionen sind natuerlich voellig uninteressant */
+ if (member(({"get_dir", "restore_object"}), fun)!=-1)
+ return path;
+
+ /* Zugriff erlaubt, aber mitloggen */
+ write_file( READ_FILE, sprintf("%O %s %s: %s\n", fun, ctime()[4..],
+ euid, path) );
+ return path;
+ }
+ if (lev<ARCH_LVL) return 0;
+ return path;
+}
+
diff --git a/secure/master/guild.c b/secure/master/guild.c
new file mode 100644
index 0000000..9eaaa8b
--- /dev/null
+++ b/secure/master/guild.c
@@ -0,0 +1,62 @@
+// MorgenGrauen MUDlib
+//
+// master/guild.c -- Gilden und Gildenmagier
+//
+// $Id: guild.c 6142 2007-01-31 20:34:39Z Zesstra $
+
+/*
+ * Dies kann irgendwann auch hinsichtlich einer automatischen
+ * Rechtevergabe auf Gilden-Verzeichnisse erweitert werden.
+ * Bisher werden nur Gildenmagier verwaltet.
+ */
+
+#pragma strict_types
+
+#include "/secure/master.h"
+
+
+int guild_master(string user, string guild)
+{
+ string *guilds;
+ int i;
+
+ if (!find_userinfo(user)||
+ !pointerp(guilds=get_userinfo(user)[USER_GUILD-1]))
+ return 0;
+
+ return (member(guilds,guild) != -1);
+}
+
+int add_guild_master(string user, string guild)
+{
+ string *guilds;
+
+ if ((call_other(SIMUL_EFUN_FILE, "process_call") ||
+ call_other(SIMUL_EFUN_FILE, "secure_level") < GOD_LVL) ||
+ !find_userinfo(user))
+ return 0;
+
+ guilds=get_userinfo(user)[USER_GUILD-1];
+ if (!guilds)
+ set_guilds(user, ({ guild }) );
+ else {
+ guilds = guilds - ({guild}) + ({guild});
+ set_guilds(user, guilds);
+ }
+ return 1;
+}
+
+int remove_guild_master(string user, string guild)
+{
+ string *guilds;
+
+ if (!IS_GOD(geteuid(previous_object()))
+ ||!find_userinfo(user)
+ ||!(guilds=get_userinfo(user)[USER_GUILD-1])
+ || member(guilds,guild)==-1)
+ return 0;
+ guilds -= ({ guild });
+ set_guilds(user, guilds);
+ return 1;
+}
+
diff --git a/secure/master/misc.c b/secure/master/misc.c
new file mode 100644
index 0000000..02b0fcc
--- /dev/null
+++ b/secure/master/misc.c
@@ -0,0 +1,772 @@
+// MorgenGrauen MUDlib
+//
+// master/misc.c -- Diverses: (T)Banish, Projektverwaltung, Levelaufstieg, ...
+//
+// $Id: misc.c 9467 2016-02-19 19:48:24Z Zesstra $
+
+#pragma strict_types,rtt_checks
+
+#include "/sys/functionlist.h"
+#include "/sys/lpctypes.h"
+#include "/sys/object_info.h"
+#include "/sys/interactive_info.h"
+
+#include "/secure/master.h"
+#include "/mail/post.h"
+#include "/sys/thing/language.h"
+#include "/sys/thing/description.h"
+
+// Fuer CIDR-Notatio im sbanish
+#include "/secure/master/cidr.c"
+
+static mixed *banished;
+static mapping tbanished, sbanished;
+static string *deputies;
+
+// TODO: muss ggf. Fakeobjekt erzeugen+uebergeben, wenn sender kein object
+protected void send_channel_msg(string channel,mixed sendername,string msg)
+{
+ object sender;
+ if (objectp(sendername))
+ sender=sendername;
+ else
+ {
+ // wenn kein Objekt uebergeben, erstmal schauen, ob ein Spielerobject
+ // existiert... Wenn ja, das nehmen.
+ sender = call_sefun("find_player", sendername)
+ || call_sefun("find_netdead", sendername);
+ if (!objectp(sender))
+ {
+ // sonst faken wir eins. *seufz*
+ sender=clone_object("/p/daemon/namefake");
+ sender->SetProp(P_NAME, sendername);
+ sender->SetProp(P_ARTICLE,0);
+ // Dieses Objekt zerstoert sich nach 3s automatisch.
+ }
+ }
+ CHMASTER->send(channel, sender, msg);
+}
+
+static string *explode_files(string file) {
+ string data;
+ mixed *exploded;
+ int i;
+
+ data=read_file(file);
+ if (!data || !stringp(data) || data == "") return ({});
+ exploded = efun::explode(data,"\n");
+ for (i=sizeof(exploded);i--;)
+ if (!stringp(exploded[i]) || exploded[i]=="" || exploded[i][0]=='#')
+ exploded[i]=0;
+ exploded-=({0});
+ printf("%-30s: %3d Objekt%s\n",file,i=sizeof(exploded),(i==1?"":"e"));
+ return exploded;
+}
+
+void UpdateTBanish();
+
+mixed QueryBanished(string str){
+ int i;
+
+ if (!str) return 0;
+ if (!pointerp(banished)) return 0;
+ for (i=sizeof(banished)-1;i>=0;i--)
+ if (sizeof(regexp(({str}),"^"+banished[i][0]+"$")))
+ {
+ if (!banished[i][1] || banished[i][1]=="")
+ return "Dieser Name ist gesperrt.";
+ else
+ return banished[i][1];
+ }
+ return 0;
+}
+
+mixed QueryTBanished(string str) {
+ int i;
+
+ if (!str || !mappingp(tbanished) || !(i=tbanished[str]))
+ return 0;
+
+ if (i == -1 || i > time())
+ return sprintf("Es gibt schon einen Spieler diesen Namens.\n"
+ +"Allerdings kann er/sie erst am %s wieder ins Mud kommen.\n",
+ (i == -1 ? "St. Nimmerleinstag" :
+ call_sefun("dtime",i)[0..16]));
+
+// Ansonsten: die Zeit ist abgelaufen, Spieler darf wieder...
+ m_delete(tbanished, str);
+ UpdateTBanish();
+ return 0;
+}
+
+void ReloadBanishFile(){
+ int i, t;
+ string s1, s2, *s;
+
+ banished = efun::explode( read_file("/secure/BANISH") || "", "\n" );
+ banished = banished - ({""});
+ for ( i = sizeof(banished); i--; ){
+ s = efun::explode( banished[i], " " );
+ s1 = s[0];
+ s2 = implode( s[1..], " " );
+ banished[i] = ({ s1, s2 });
+ }
+
+ if ( !mappingp(tbanished) ){
+ tbanished = ([]);
+
+ s = efun::explode( read_file("/secure/TBANISH") || "", "\n" );
+ s -= ({""});
+
+ for ( i = sizeof(s); i--; ){
+ sscanf( s[i], "%s:%d", s1, t );
+ tbanished += ([ s1 : t ]);
+ }
+ }
+
+ if ( !mappingp(sbanished) ){
+ sbanished = m_allocate(3, 2);
+
+ s = efun::explode( read_file("/secure/SITEBANISH") || "", "\n" );
+ s -= ({""});
+
+ for ( i = sizeof(s); i--; ) {
+ int int_ip;
+ sscanf( s[i], "%s:%d:%s", s1, t, s2 );
+ int_ip = IPv4_addr2int(s1);
+ m_add(sbanished, int_ip, t, s2);
+ }
+ }
+}
+
+int IsDeputy(mixed name)
+{
+ if ( IS_ARCH(name) )
+ return 1;
+
+ if ( objectp(name) )
+ name = getuid(name);
+
+ if ( member( deputies || ({}), name ) >= 0 )
+ return 1;
+
+ return 0;
+}
+
+
+varargs void BanishName( string name, string reason, int force )
+{
+ string *names;
+ int i;
+
+ if ( PO != TO && call_sefun("secure_level") < LORD_LVL
+ && !IsDeputy( call_sefun("secure_euid") ) )
+ return;
+
+ if ( !stringp(name) || !sizeof(name) )
+ return;
+
+ name = lower_case(name);
+
+ if ( !reason || !stringp(reason) )
+ reason = "";
+
+ if ( QueryBanished(name) && lower_case(reason) != "loeschen" ){
+ write("Der Name ist schon gebannt.\n");
+ return;
+ }
+
+ if ( !force && file_size(SAVEPATH+name[0..0]+"/"+name+".o") > 0 ){
+ write("Es existiert bereits ein Spieler dieses Namens.\n");
+ return;
+ }
+
+/* if (!("/secure/login"->valid_name(name))) return;*/
+ if ( lower_case(reason) != "loeschen" ){
+ names = ({ name + " " + reason });
+
+ for ( i = sizeof(banished); i--; )
+ names += ({ banished[i][0] + " " + banished[i][1] });
+ }
+ else{
+ names = ({});
+
+ for ( i = sizeof(banished); i--; )
+ if ( banished[i][0] != name )
+ names += ({ banished[i][0] + " " + banished[i][1] });
+ }
+
+ names = sort_array( names, #'> );
+ rm("/secure/BANISH");
+ write_file( "/secure/BANISH", implode(names, "\n") );
+ write( "Okay, '"+capitalize(name)+"' ist jetzt "+
+ (lower_case(reason) == "loeschen" ? "nicht mehr " : "")+"gebanisht.\n" );
+ ReloadBanishFile();
+}
+
+void UpdateTBanish()
+{
+ int i;
+ string *names;
+
+ for (i=sizeof(names = sort_array(m_indices(tbanished), #'</*'*/))-1;i>=0;i--)
+ names[i] = sprintf("%s:%d", names[i], tbanished[names[i]]);
+
+ rm("/secure/TBANISH");
+ write_file("/secure/TBANISH", implode(names, "\n"));
+}
+
+void UpdateSBanish()
+{
+ int i;
+ mapping lines = m_allocate(sizeof(sbanished),0);
+
+ foreach(int ip, int tstamp, string user : sbanished) {
+ m_add(lines, sprintf("%s:%d:%s",
+ IPv4_int2addr(ip), tstamp, user));
+ }
+
+ write_file( "/secure/SITEBANISH",
+ implode( sort_array (m_indices(lines), #'<), "\n" ), 1);
+}
+
+int TBanishName(string name, int days)
+{
+ int t;
+
+ if ( (getuid(TI) != name) &&
+ !IsDeputy( call_sefun("secure_euid") ) )
+ return 0;
+
+ if (days && QueryTBanished(name)){
+ write("Der Name ist schon gebannt.\n");
+ return 0;
+ }
+
+ if (file_size(SAVEPATH+name[0..0]+"/"+name+".o")<=0){
+ write("Es existiert kein Spieler dieses Namens!\n");
+ return 0;
+ }
+
+ if (!days && member(tbanished, name))
+ m_delete(tbanished, name);
+ else {
+ if (!mappingp(tbanished))
+ tbanished = ([]);
+ if (days <= -1)
+ t = -1;
+ else
+ t = (time()/86400)*86400 + days*86400;
+ tbanished += ([ name : t ]);
+ }
+
+ UpdateTBanish();
+ return 1;
+}
+
+
+mixed QuerySBanished( string ip )
+{
+ int save_me, site;
+ string banished_meldung =
+ "\nSorry, von Deiner Adresse kamen ein paar Idioten, die "
+ "ausschliesslich\nAerger machen wollten. Deshalb haben wir "
+ "die Moeglichkeit,\neinfach neue Charaktere "
+ "anzulegen, kurzzeitig fuer diese Adresse gesperrt.\n\n"
+ "Falls Du bei uns spielen moechtest, schicke bitte eine Email "
+ "an\n\n mud@mg.mud.de\n\n"
+ "mit der Bitte, einen Charakter fuer Dich anzulegen.\n" ;
+
+ if ( !ip || !stringp(ip) || !mappingp(sbanished) || !sizeof(sbanished) )
+ return 0;
+
+ foreach(site, int tstamp: sbanished) {
+ if ( tstamp > 0 && tstamp < time() ) {
+ m_delete( sbanished, site );
+ save_me=1;
+ }
+ }
+ if (save_me)
+ UpdateSBanish();
+
+ if ( !sizeof(sbanished) )
+ return 0;
+
+ site = IPv4_addr2int(ip);
+ if (!site)
+ return 0;
+ // direkt drin?
+ if (member(sbanished, site))
+ return banished_meldung;
+ // oder Netz dieser IP gesperrt?
+ foreach(int locked_site : sbanished) {
+ if ((locked_site & site) == locked_site)
+ return banished_meldung;
+ }
+
+ return 0;
+}
+
+
+private int _sbanished_by( int key, string name )
+{
+ return sbanished[key, 1] == name;
+}
+
+
+mixed SiteBanish( string ip, int days )
+{
+ string *s, tmp, euid;
+ int t, level;
+
+ euid = call_sefun("secure_euid");
+ level = call_sefun("secure_level");
+
+ // Unter L26 gibt's gar nix.
+ if ( level <= DOMAINMEMBER_LVL )
+ return -1;
+
+ // Die Liste der gesperrten IPs anschauen darf jeder ab L26.
+ if ( !ip && !days )
+ return copy(sbanished);
+
+
+ if ( !stringp(ip) || !intp(days) )
+ return 0;
+
+ if ( days && QuerySBanished(ip) ){
+ write( "Diese Adresse ist schon gebannt.\n" );
+ return 0;
+ }
+
+ if ( !days ) {
+ int int_ip = IPv4_addr2int(ip);
+
+ if( member(sbanished, int_ip) ){
+ // Fremde Sitebanishs duerfen nur Deputies loeschen.
+ if ( sbanished[int_ip, 1] != euid && !IsDeputy(euid) )
+ return -1;
+
+ call_sefun("log_file", "ARCH/SBANISH",
+ sprintf( "%s: %s hebt die Sperrung der Adresse %s von %s "
+ + "auf.\n",
+ ctime(time()), capitalize(euid), ip,
+ capitalize(sbanished[int_ip, 1]) ) );
+
+ m_delete( sbanished, int_ip );
+ }
+ else
+ return 0;
+ }
+ else {
+ // Alles, was nicht Deputy ist, darf nur fuer einen Tag sbanishen.
+ if ( days != 1 && !IsDeputy(euid) )
+ return -1;
+
+ // Nur Deputies duerfen mehr als 10 Sperrungen vornehmen.
+ if ( sizeof(filter_indices(sbanished, #'_sbanished_by, euid)) >= 10
+ && !IsDeputy(euid) )
+ return -2;
+
+ int int_ip = IPv4_addr2int(ip);
+
+ if(!int_ip) {
+ write( "Ungueltige Adresse!\n" );
+ return 0;
+ }
+
+ // L26 duerfen exakt eine IP sperren, RMs ganze Class C-Subnetze
+ // und Deputies auch Class B-Subnetze.
+ int nsize=IPv4_net_size(ip);
+ if ( nsize > 1 && level < LORD_LVL
+ || nsize > 255 && !IsDeputy(euid) )
+ return -1;
+
+ if ( !mappingp(sbanished) )
+ sbanished = m_allocate(1, 2);
+
+ if ( days < 0 )
+ t = -1;
+ else
+ t = (time() / 86400) * 86400 + days * 86400;
+
+ m_add(sbanished, int_ip, t, euid);
+
+ call_sefun("log_file", "ARCH/SBANISH",
+ sprintf( "%s: %s sperrt die Adresse %s %s.\n",
+ ctime(time()), capitalize(euid),
+ ip,
+ days > 0 ? (days > 1 ? "fuer " + days + " Tage"
+ : "fuer einen Tag")
+ : "bis zum St. Nimmerleinstag" ) );
+ }
+
+ UpdateSBanish();
+ return 1;
+}
+
+static void CheckDeputyRights()
+{
+ object ob;
+ mixed *ginfo;
+
+ // Lese- und Schreibberechtigungen fuer die Rubrik 'polizei' setzen
+ call_other( "/secure/news", "???" );
+ ob = find_object("secure/news");
+ ginfo = ((mixed)ob->GetGroup("polizei"))[5..6];
+ ob->RemoveAllowed( "polizei", 0, ginfo[0], ginfo[1] );
+ ob->AddAllowed( "polizei", 0, deputies, deputies );
+ LoadDeputyFileList();
+}
+
+int ReloadDeputyFile()
+{
+ deputies = efun::explode( read_file("/secure/DEPUTIES") || "", "\n" );
+ deputies -= ({""});
+ deputies = map( deputies, #'lower_case/*'*/ );
+ call_out( "CheckDeputyRights", 2 );
+ return(1);
+}
+
+
+static int create_home(string owner, int level)
+{
+ string def_castle;
+ string dest, castle, wizard;
+ object player;
+ string *domains;
+ int i;
+
+ player = call_sefun("find_player",owner);
+ if (!player)
+ return -5;
+ domains=get_domain_homes(owner);
+ if (!sizeof(domains) && level >= DOMAINMEMBER_LVL)
+ {
+ tell_object(player,"Du gehoerst noch keiner Region an !\n");
+ return -6;
+ }
+ tell_object(player,"Du bist Mitarbeiter der folgenden Regionen:\n");
+ for (i=0;i<sizeof(domains);i++)
+ {
+ if (i) tell_object(player,", ");
+ tell_object(player,domains[i]);
+ }
+ tell_object(player,".\n");
+ update_wiz_level(owner, level);
+ wizard = "/players/" + owner;
+ castle = "/players/" + owner + "/workroom.c";
+ if (file_size(wizard) == -1) {
+ tell_object(player, "Verzeichnis " + wizard + " angelegt\n");
+ mkdir(wizard);
+ }
+ dest = object_name(environment(player));
+ def_castle = read_file("/std/def_workroom.c");
+ if (file_size(castle) > 0) {
+ tell_object(player, "Du HATTEST ja schon ein Arbeitszimmer !\n");
+ } else {
+ if (write_file(castle, def_castle))
+ {
+ tell_object(player, "Arbeitszimmer " + castle + " erzeugt.\n");
+ // Arbeitszimmer als Home setzen
+ player->SetProp(P_START_HOME,castle[0..<3]);
+ }
+ else
+ tell_object(player, "Arbeitszimmer konnte nicht erzeugt werden !\n");
+ }
+ return 1;
+}
+
+// Sendet dem befoerderten Magier eine Hilfemail zu.
+protected void SendWizardHelpMail(string name, int level) {
+
+ object pl=call_sefun("find_player",name);
+ if (!objectp(pl)) return;
+
+ string file=sprintf("%sinfo_ml%d", WIZ_HELP_MAIL_DIR, level);
+ // wenn kein Hilfetext fuer den Level da ist: raus
+ if (file_size(file) <= 0)
+ return;
+
+ string subject = read_file(file,1,1);
+ string text = call_sefun("replace_personal",
+ read_file(file,2), ({pl}));
+
+ mixed mail = ({"Merlin", "<Master>", name, 0, 0, subject,
+ call_sefun("dtime",time()),
+ MUDNAME+time(), text });
+ MAILDEMON->DeliverMail(mail, 0);
+}
+
+int allowed_advance_wizlevel(mixed ob)
+{
+ string what;
+
+ if (objectp(ob) && geteuid(ob)==ROOTID) return 1;
+
+ if (!stringp(ob))
+ what=efun::explode(object_name(ob),"#")[0];
+ else
+ what=ob;
+
+ if (what=="/secure/merlin") return 1;
+
+ return 0;
+}
+
+int advance_wizlevel(string name, int level)
+{
+ int answer;
+ mixed *user;
+
+ if (!allowed_advance_wizlevel(PO))
+ return -1;
+
+ if (level>80) return -2;
+
+ if (!find_userinfo(name)) return -3;
+
+ user=get_full_userinfo(name);
+
+ if (user[USER_LEVEL+1]>level) return -4;
+
+ if (user[USER_LEVEL+1]==level) return 1;
+
+ if (level>=10 && level<20)
+ {
+ update_wiz_level(name, level);
+ SendWizardHelpMail(name, level);
+ return 1;
+ }
+ if (level>=20 && user[USER_LEVEL+1]<21)
+ {
+ answer = create_home(name, level);
+ if ( answer > 0 ) {
+ answer = update_wiz_level(name, level);
+ SendWizardHelpMail(name, level);
+ }
+ return answer;
+ }
+
+ update_wiz_level(name, level);
+ SendWizardHelpMail(name, level);
+
+ return 1;
+}
+
+void restart_heart_beat(object heart_beat)
+{
+ if (heart_beat) heart_beat->_restart_beat();
+}
+
+int renew_player_object(mixed who)
+{
+ object newob;
+ object *obs, *obs2;
+ mixed err;
+ string ob_name;
+ object *armours, weapon;
+ object tp;
+ int i,active,j;
+
+ if (stringp(who))
+ {
+ who=call_sefun("find_player",who);
+ if (!who)
+ {
+ who=call_sefun("find_netdead",who);
+ if (!who)
+ return -1;
+ }
+ }
+ if (!objectp(who))
+ return -2;
+ if (!object_info(who, OI_ONCE_INTERACTIVE))
+ return -3;
+ if (who->QueryGuest())
+ {
+ printf("Can't renew guests!\n");
+ return -6;
+ }
+ active=interactive(who);
+ printf("OK, renewing %O\n",who);
+ seteuid(geteuid(who));
+ err=catch(newob=clone_object(query_player_object(getuid(who))); publish);
+ seteuid(getuid(TO));
+ if (err)
+ {
+ printf("%O\n",err);
+ return -4;
+ }
+ if (!newob)
+ return -5;
+ /* Ok, the object is here now ... lets go for it ... */
+ who->save_me(0);
+ /* SSL ip weiterreichen */
+ if( call_sefun("query_ip_number", who) != efun::interactive_info(who,II_IP_NUMBER) )
+ {
+ newob->set_realip( call_sefun("query_ip_number",who) );
+ }
+ efun::configure_object(previous_object(), OC_COMMANDS_ENABLED, 0);
+ efun::set_this_player(0);
+ armours=(object *)who->QueryProp(P_ARMOURS);
+ weapon=(object)who->QueryProp(P_WEAPON);
+
+ if ( previous_object() && object_name(previous_object()) == "/secure/merlin" )
+ send_channel_msg("Debug",
+ previous_object(),
+ sprintf("RENEWING: %O %O\n",newob,who));
+ else
+ send_channel_msg("Entwicklung",
+ previous_object(),
+ sprintf("RENEWING: %O %O\n",newob,who));
+
+ ob_name=explode(object_name(newob),"#")[0];
+ if (sizeof(ob_name)>11 && ob_name[0..11]=="/std/shells/")
+ ob_name=ob_name[11..];
+ ob_name=ob_name+":"+getuid((object)who);
+ if (active)
+ exec(newob,who);
+ if (active && (interactive(who)||!interactive(newob)))
+ {
+ send_channel_msg("Debug",previous_object(),
+ "ERROR: still active !\n");
+ newob->remove();
+ return 0;
+ }
+// newob->start_player(capitalize(getuid(who)),who->_query_my_ip());
+ funcall(
+ bind_lambda(
+ unbound_lambda( ({'x, 'y}),
+ ({#'call_other/*'*/,
+ newob,
+ "start_player",
+ 'x, 'y
+ })
+ ), who
+ ),
+ capitalize(getuid(who)), who->_query_my_ip() );
+
+ newob->move(environment(who),M_TPORT|M_NOCHECK|M_NO_SHOW|M_SILENT
+ |M_NO_ATTACK);
+ obs=all_inventory(who);
+ foreach(object tob: all_inventory(who)) {
+ if (!tob->QueryProp(P_AUTOLOADOBJ))
+ {
+ // kein Autoloader...
+ foreach(object ob: deep_inventory(tob))
+ {
+ // aber enthaltene Autoloader entsorgen...
+ if (ob->QueryProp(P_AUTOLOADOBJ))
+ {
+ catch(ob->remove();publish);
+ if (ob) destruct(ob);
+ }
+ }
+ // objekt ohne die AL bewegen.
+ catch(tob->move(newob,M_NOCHECK);publish);
+ }
+ else {
+ // Inhalt von Autoloadern retten.
+ // neue instanz des ALs im neuen Objekt.
+ object new_al_instance = present_clone(tob, newob);
+ foreach(object ob: deep_inventory(tob)) {
+ if (ob->QueryProp(P_AUTOLOADOBJ)) {
+ // autoloader in Autoloadern zerstoeren...
+ catch(ob->remove(1);publish);
+ if (ob) destruct(ob);
+ }
+ // alle nicht autoloader in die AL-Instanz im neuen Objekt oder
+ // notfalls ins Inv.
+ else {
+ if (objectp(new_al_instance))
+ catch(ob->move(new_al_instance, M_NOCHECK);publish);
+ else
+ catch(ob->move(newob, M_NOCHECK);publish);
+ }
+ }
+ // Autoloader zerstoeren. Wird nicht vom Spielerobjekt im remove()
+ // gemacht, wenn nicht NODROP.
+ catch(tob->remove(1);publish);
+ if (tob) destruct(tob);
+ }
+ }
+ who->remove();
+ if ( objectp(who) )
+ destruct(who);
+ rename_object(newob,ob_name);
+ newob->__reload_explore();
+ tp=this_player();
+ efun::set_this_player(newob);
+ if (objectp(weapon))
+ weapon->DoWield();
+ if (pointerp(armours))
+ for (i=sizeof(armours)-1;i>=0;i--)
+ if (objectp(armours[i]))
+ armours[i]->do_wear("alles");
+ efun::set_this_player(tp);
+ //Rueckgabewert noetig, weil Funktion vom Typ 'int'
+ return(1);
+}
+
+mixed __query_variable(object ob, string var)
+{
+ if (!PO || !IS_ARCH(geteuid(PO)) || !this_interactive() ||
+ !IS_ARCH(this_interactive()) || getuid(ob)==ROOTID )
+ {
+ write("Du bist kein EM oder Gott!\n");
+ return 0;
+ }
+ if (efun::object_info(ob, OI_ONCE_INTERACTIVE) && (PO!=ob) &&
+ (var=="history" || var=="hist2"))
+ send_channel_msg("Snoop", previous_object(),
+ sprintf("%s -> %s (history).",
+ capitalize(getuid(PO)),capitalize(getuid(ob))));
+
+ call_sefun("log_file", "ARCH/QV",
+ sprintf("%s: %O inquires var %s in %O\n",
+ ctime(time()),this_interactive(),var,ob) );
+ mixed res = variable_list(ob, RETURN_FUNCTION_NAME|RETURN_FUNCTION_FLAGS|
+ RETURN_FUNCTION_TYPE|RETURN_VARIABLE_VALUE);
+ int index = member(res,var);
+ if (index > -1)
+ {
+ return ({res[index],res[index+1],res[index+2],res[index+3]});
+ }
+ return 0;
+}
+
+void RestartBeats()
+{
+ int i,size,counter;
+ object ob;
+ mixed *list;
+ string file,obname,fun;
+
+ "/secure/simul_efun"->StopCallOut(0);
+ write("CALL_OUT-Restart in progress !\n");
+ filter(users(),#'tell_object,"CALL_OUT-Restart in progress !\n");
+ size=file_size("log/call_out_stop");
+ if (size<=0)
+ return;
+ file="";
+ counter=0;
+ while (counter<size)
+ {
+ file+=read_bytes("log/call_out_stop",counter,
+ (size-(counter+=40000)>0?counter:size));
+ }
+ list=explode(file,"__(CUT HERE)__\n");
+ list=list[<1..];
+ list=explode(list[0],"\n")-({""});
+ for (i=sizeof(list)-1;i>=0;i--)
+ if (sscanf(list[i],"%s \"%s\"",obname,fun)==2 && ob=find_object(obname))
+ {
+ write(sprintf("%O -> %s\n",ob,fun));
+ catch(ob->__restart(fun);publish);
+ }
+ write("CALL_OUT-Restart completed !\n");
+ filter(users(),#'tell_object,"CALL_OUT-Restart completed !\n");
+ rename("log/call_out_stop","log/call_out_stop.old");
+}
+
diff --git a/secure/master/network.c b/secure/master/network.c
new file mode 100644
index 0000000..e681f82
--- /dev/null
+++ b/secure/master/network.c
@@ -0,0 +1,431 @@
+// MorgenGrauen MUDlib
+//
+// master/network.c - UDP-Handling
+//
+// $Id: network.c 8934 2014-09-10 21:57:12Z Zesstra $
+
+#pragma strict_types
+
+#include "/secure/master.h"
+#define BBMASTER "/secure/bbmaster"
+
+/*
+#undef DEBUG
+#define DEBUG(x) if (call_sefun("find_player","zesstra")) \
+ tell_object(call_sefun("find_player","zesstra"),x);
+*/
+
+//ich will hieraus momentan kein Debug, ist zuviel. Zesstra
+
+#ifdef DEBUG
+#undef DEBUG
+#endif
+#define DEBUG(x)
+
+nosave int mails_last_hour;
+
+static string mail_normalize( string str )
+{
+ str = regreplace( str, "[^<]*<(.*)>[^>]*", "\\1", 0);
+ return regreplace( str, " *([^ ][^ ]*).*", "\\1", 0);
+}
+
+
+static string *mk_rec_list( string str )
+{
+ return map( explode( lower_case(str), "," ) - ({""}),
+ "mail_normalize", this_object() );
+}
+
+
+static int CheckPasswd( string name, string passwd ) {
+ mixed *uinf;
+
+ if (!stringp(passwd) || !sizeof(passwd))
+ return 0;
+ if ( sizeof(uinf = get_full_userinfo(name)) < 2 )
+ return 0;
+
+ string pwhash = uinf[USER_PASSWORD+1];
+ if (sizeof(pwhash) > 13) {
+ // MD5-Hash
+ passwd = md5_crypt(passwd, pwhash);
+ }
+ else if (sizeof(pwhash) > 2) {
+ // Crypt-Hash
+ passwd = crypt(passwd, pwhash[0..1]);
+ }
+ else return 0;
+
+ return (passwd == pwhash);
+}
+
+
+static void FtpAccess( string host, string message, int port )
+{
+ string *comp, reply, head;
+#if __EFUN_DEFINED__(send_udp)
+ comp = efun::explode( message, "\t" );
+#define FTP_ID 0
+#define FTP_SEQ 1
+#define FTP_TAG 2
+#define FTP_CMD 3
+#define FTP_ARG1 4
+#define FTP_ARG2 5
+#define FTP_ARG3 6
+
+ if ( sizeof(comp) <= FTP_CMD || lower_case(comp[FTP_TAG]) != "req" ){
+ log_file( "IMP_MSGS", "Host: " + host + ":" + port + " - '" +
+ message + "'\n" );
+ return;
+ }
+
+ reply = "INVALID";
+
+ head = sprintf( "%s\t%s\tRPLY\t%s\t",
+ comp[FTP_ID], comp[FTP_SEQ], comp[FTP_CMD] );
+
+ switch ( lower_case(comp[FTP_CMD]) ){
+ case "user":
+ if ( sizeof(comp) <= FTP_ARG1 )
+ break;
+
+ if ( IS_LEARNER(lower_case(comp[FTP_ARG1])) )
+ reply = "/players/" + lower_case(comp[FTP_ARG1]);
+ else
+ reply = "NONE";
+ break;
+
+ case "pass":
+ if ( sizeof(comp) <= FTP_ARG2 )
+ break;
+
+ comp[FTP_ARG1] = lower_case(comp[FTP_ARG1]);
+
+ if ( IS_LEARNER(comp[FTP_ARG1]) &&
+ !"/secure/master"->QueryTBanished(comp[FTP_ARG1]) ){
+ if ( CheckPasswd( comp[FTP_ARG1], comp[FTP_ARG2] ) )
+ reply = "OK";
+ else {
+ if ( get_wiz_level( comp[FTP_ARG1] ) < ARCH_LVL )
+ log_file( "LOGINFAIL",
+ sprintf( "PASSWORD: (FTP) %s %s\n",
+ comp[FTP_ARG1],
+ ctime(time()) ) );
+ else
+ log_file( "ARCH/LOGINFAIL",
+ sprintf( "PASSWORD: (FTP) %s %s\n",
+ comp[FTP_ARG1],
+ ctime(time()) ) );
+ }
+ }
+ else
+ reply = "FAIL";
+ break;
+
+ case "read":
+DEBUG("-FtpAccess----\nHost:"+host+"Message:\n"+message+"\n--------------\n");
+ if ( sizeof(comp) <= FTP_ARG2 )
+ break;
+
+ if ( comp[FTP_ARG2][0] == '/' &&
+ valid_read( comp[FTP_ARG2], lower_case(comp[FTP_ARG1]),
+ "read_file", 0 ) ){
+
+ BBMASTER->ftpbb( lower_case(comp[FTP_ARG1]),
+ "read " + comp[FTP_ARG2] + "\n" );
+ reply = "OK";
+ }
+ else
+ reply = "FAIL";
+ break;
+
+ case "writ":
+DEBUG("-FtpAccess----\nHost:"+host+"Message:\n"+message+"\n--------------\n");
+ if ( sizeof(comp) <= FTP_ARG2 )
+ break;
+
+ if ( comp[FTP_ARG2][0] == '/' &&
+ valid_write( comp[FTP_ARG2], lower_case(comp[FTP_ARG1]),
+ "write_file", 0 ) ){
+
+ BBMASTER->ftpbb( lower_case(comp[FTP_ARG1]),
+ "write " + comp[FTP_ARG2] + "\n" );
+ reply = "OK";
+ }
+ else
+ reply = "FAIL";
+ break;
+
+ case "list":
+DEBUG("-FtpAccess----\nHost:"+host+"Message:\n"+message+"\n--------------\n");
+ if ( sizeof(comp) <= FTP_ARG2 )
+ break;
+
+ if ( comp[FTP_ARG2][0] == '/' &&
+ valid_read( comp[FTP_ARG2], lower_case(comp[FTP_ARG1]),
+ "read_file", 0 ) )
+ reply = "OK";
+ else
+ reply = "FAIL";
+ break;
+
+ default:
+DEBUG("-FtpAccess----\nHost:"+host+"Message:\n"+message+"\n--------------\n");
+ log_file( "IMP_MSGS", "Host: " + host + ":" + port + " - '" +
+ message + "'\n" );
+ break;
+ }
+
+ send_udp( host, port, head+reply );
+#endif
+}
+
+
+static int doReadMail( string file )
+{
+ string str, *lines, *parts, *tmp;
+ mixed *message;
+ int i, j;
+
+ if ( (i = file_size(file)) > 50000 || i < 5 ){
+ rm(file);
+ DEBUG( "Mail size invalid\n" );
+ return -1;
+ }
+
+ if ( !(str = read_bytes( file, 0, 50000 )) )
+ return -1;
+
+ if ( !(j = sizeof(lines = explode( str, "\n" ))) )
+ return -2;
+
+ i = 0;
+
+ while ( i < j && lines[i] != "" )
+ i++;
+
+ if ( i == j )
+ return -2;
+
+ DEBUG( sprintf( "Have %d headerlines:\n", i ) );
+
+ message= allocate(9);
+ message[MSG_CC] = ({});
+ message[MSG_BCC] = ({});
+ message[MSG_BODY] = implode( lines[i..], "\n" );
+
+ for ( j = 0; j < i; j++ ){
+ parts = explode( lines[j], ":" );
+
+ if ( sizeof(parts) > 1 ){
+ str = lower_case(parts[0]);
+ parts[0] = implode( parts[1..], ":" );
+
+ switch (str){
+ case "subject":
+ message[MSG_SUBJECT] = parts[0];
+ break;
+
+ case "from":
+ DEBUG( "Found from\n" );
+ DEBUG( sprintf( "PARTS[0]=%s\n", parts[0] ) );
+ message[MSG_FROM] = mail_normalize(parts[0]);
+ message[MSG_SENDER] = parts[0];
+ DEBUG( sprintf( "FROM: %s\nSENDER: %s\n",
+ message[MSG_FROM],
+ message[MSG_SENDER] ) );
+ break;
+
+ case "cc":
+ DEBUG( sprintf("FOUND CC: %O\n", parts[0]) );
+ message[MSG_CC] += mk_rec_list(parts[0]);
+ break;
+
+ case "bcc":
+ DEBUG( sprintf("FOUND BCC: %O\n", parts[0]) );
+ message[MSG_BCC] += mk_rec_list(parts[0]);
+ break;
+
+ case "to":
+ DEBUG( sprintf("FOUND TO: %O\n", parts[0]) );
+ tmp = mk_rec_list(parts[0]);
+
+ if ( !message[MSG_RECIPIENT] )
+ message[MSG_RECIPIENT] = tmp[0];
+
+ message[MSG_CC] += tmp;
+ break;
+
+ // Das MUD-TO: wird vom Perlskript als erste Headerzeile eingefuegt
+ case "mud-to":
+ DEBUG( sprintf("FOUND MUD-TO: %O\n", parts[0]) );
+ message[MSG_RECIPIENT] = mail_normalize(lower_case(parts[0]));
+ break;
+ }
+ }
+ }
+
+ // Eigentlichen Empfaenger aus CC: loeschen
+ message[MSG_CC] -= ({ message[MSG_RECIPIENT],
+ message[MSG_RECIPIENT]+"@mg.mud.de",
+ message[MSG_RECIPIENT]+"@morgengrauen.mud.de" });
+
+
+ DEBUG( sprintf( "TO: %O\n", message[MSG_RECIPIENT] ) );
+ DEBUG( sprintf( "CC: %O\n", message[MSG_CC] ) );
+ DEBUG( sprintf( "BCC: %O\n", message[MSG_BCC] ) );
+
+ if ( !stringp(message[MSG_FROM]) )
+ return -2;
+
+ if ( !stringp(message[MSG_RECIPIENT]) ){
+ str = explode( file, "/" )[<1];
+ i = 0;
+ j = sizeof(str);
+
+ while ( i < j && str[i] <= '9' && str[i] >= '0' )
+ i++;
+
+ if ( i >= j )
+ return -2;
+
+ message[MSG_RECIPIENT] = str[i..];
+ DEBUG( sprintf( "EMERGENCY RECIPIENT=%s\n", str[i..] ) );
+ }
+
+ rm(file);
+
+ // Da vom Master besser nichts von ausserhalb /secure #include't wird,
+ // direkt die '5'. Normalerweise hiesse der Aufruf:
+ // DeliverMail( message, NO_USER_ALIASES|NO_CARBON_COPIES );
+ "/secure/mailer"->DeliverMail( message, 5 );
+ return 1;
+}
+
+
+public void mailread()
+{
+ string *files;
+ int i;
+
+ DEBUG( "mailread called\n" );
+
+ if ( mails_last_hour >= MAX_MAILS_PER_HOUR )
+ return;
+
+ files = (get_dir( "/mail/inbound/*" )||({})) - ({ "..", "." });
+ i = sizeof(files);
+
+ while ( i-- && mails_last_hour < MAX_MAILS_PER_HOUR ){
+ DEBUG( "FOUND FILE \"" + files[i] + "\" ...\n" );
+ mails_last_hour++;
+
+ if ( doReadMail( "/mail/inbound/" + files[i]) < -1 ){
+ mixed message;
+
+ message = allocate(9);
+ mails_last_hour++;
+ rename( "/mail/inbound/" + files[i],
+ "/secure/ARCH/MAIL/" + files[i] );
+ message[MSG_SENDER] = geteuid();
+ message[MSG_FROM] = getuid();
+ message[MSG_SUBJECT] = "Fehlerhafte Mail: /secure/ARCH/MAIL/" +
+ files[i];
+ message[MSG_RECIPIENT] = "mud@mg.mud.de";
+ message[MSG_BODY] = "Bitte Ueberpruefen!\n";
+ // NO_SYSTEM_ALIASES|NO_USER_ALIASES == 3
+ "/secure/mailer"->DeliverMail( message, 3 );
+ }
+ }
+}
+
+
+static void udp_query( string query, string host, int port )
+{
+#if __EFUN_DEFINED__(send_udp)
+ string *mess;
+ mixed *data;
+ int i, j;
+
+ mess = explode( query, " " );
+
+ switch ( mess[1] ){
+ case "wholist":
+ case "who":
+ data = (string *)"/obj/werliste"->QueryWhoListe();
+ break;
+
+ case "uptime":
+ data = ({ call_sefun("uptime") });
+ break;
+
+ case "finger":
+ if ( sizeof(mess) < 3 )
+ data = ({ "Error: Wen soll ich fingern ?" });
+ else
+ data = explode( (string)"p/daemon/finger"->
+ finger_single( lower_case(mess[2]), 0 ), "\n" );
+ break;
+
+ case "mailread":
+ data = ({ "Okay" });
+ mailread();
+ break;
+
+ default:
+ data = ({ "Error: unknown request " + mess[1] + "\n" });
+ }
+
+
+ send_udp( host, port, sprintf( "%s 0 %d", mess[0], sizeof(data) ) );
+
+ for ( i = 0, j = sizeof(data); i < j; i++ )
+ send_udp( host, port, sprintf( "%s %d %s", mess[0], i+1, data[i] ) );
+#endif
+}
+
+#define UDP_DEBUG(x)
+//#define UDP_DEBUG(x) (write_file("/log/ARCH/udp.log",(x)))
+
+void receive_udp(string host, string message, int port)
+{
+ mixed *tmp;
+ UDP_DEBUG(sprintf("%s %s:%d: %s\n",strftime(),host,port,message));
+
+ if (message[0..6]=="EXTREQ:"
+ || message[0..5]=="IPNAME"
+ || message[0..3]=="AUTH"
+ ) {
+ return;
+ }
+
+ if( message[0..8]=="IPLOOKUP\n" ) {
+ "/p/daemon/iplookup"->update( message );
+ return;
+ }
+
+ if( message[0..9]=="DNSLOOKUP\n" ) {
+ "/p/daemon/dnslookup"->update( message );
+ return;
+ }
+
+ if (message[0..4]=="NFTPD") {
+#if __HOST_NAME__==MUDHOST
+ if (host!=FTPD_IP) {
+ DEBUG(sprintf("INVALID HOST: %s\n",host));
+ return;
+ }
+#endif
+ FtpAccess(host,message,port);
+ return;
+ }
+
+ if (message[0..9]=="udp_query:") {
+ return udp_query(message[10..],host,port);
+ }
+
+ "secure/inetd"->_receive_udp(host, message);
+}
+
+
diff --git a/secure/master/players_deny.c b/secure/master/players_deny.c
new file mode 100644
index 0000000..105704c
--- /dev/null
+++ b/secure/master/players_deny.c
@@ -0,0 +1,151 @@
+// MorgenGrauen MUDlib
+//
+// master.c -- master object
+//
+// $Id: master.c 7041 2008-10-13 18:18:27Z Zesstra $
+#pragma strict_types
+#pragma no_clone
+#pragma no_shadow
+#pragma verbose_errors
+#pragma combine_strings
+#pragma pedantic
+#pragma range_check
+#pragma warn_deprecated
+
+#define WHITELIST "/secure/ARCH/players_deny_whitelist.o"
+#define TARGETLIST "/secure/ARCH/players_deny_targets.o"
+#define WHITELISTDUMP "/secure/ARCH/players_deny_whitelist.dump"
+#define TARGETLISTDUMP "/secure/ARCH/players_deny_targets.dump"
+
+//#define PLDENY_LEARNMODE
+
+#include "/secure/wizlevels.h"
+#include "/secure/master.h"
+
+/* Diese Objekte duerfen aus /players/ includieren/erben */
+nosave private mapping whitelist;
+/* Diese Dinge werden von Objektn in whitelist aus /players/ inkludiert/geerbt. */
+nosave private mapping targets;
+
+private mapping ParseList(string list) {
+ mixed data;
+
+ if (!stringp(list)) return ([:0 ]);
+ if (!stringp(data = read_file(list))) return ([:0 ]);
+ data = explode(data, "\n") || ({});
+
+ return mkmapping(data);
+}
+
+private void ParseWhiteList() {
+ DEBUG("Parsing Whitelist\n");
+ whitelist=ParseList(WHITELISTDUMP);
+}
+private void ParseTargetList() {
+ DEBUG("Parsing Targetlist\n");
+ targets=ParseList(TARGETLISTDUMP);
+}
+
+private void DumpList(mapping list, string file) {
+ if (!stringp(file) || !mappingp(list)
+ || !sizeof(file) || !sizeof(list))
+ return;
+
+ write_file(file, implode(m_indices(list),"\n"), 1);
+}
+
+private void DumpWhiteList() {
+ DEBUG("Dumping Whitelist\n");
+ DumpList(whitelist, WHITELISTDUMP);
+}
+private void DumpTargetList() {
+ DEBUG("Dumping Targetlist\n");
+ DumpList(targets, TARGETLISTDUMP);
+}
+
+public void DumpPLDenyLists() {
+ if ( extern_call() && call_sefun("secure_level") < ARCH_LVL )
+ return;
+ DEBUG("Dumping PLDenylists\n");
+ limited(#'DumpWhiteList);
+ limited(#'DumpTargetList);
+}
+
+public void SavePLDenyLists() {
+ if ( extern_call() && call_sefun("secure_level") < ARCH_LVL )
+ return;
+ DEBUG("Saving PLDenyLists\n");
+ write_file(WHITELIST,
+ save_value(whitelist), 1);
+ write_file(TARGETLIST,
+ save_value(targets), 1);
+}
+
+public void LoadPLDenylists() {
+ mixed tmp;
+
+ if ( extern_call() && call_sefun("secure_level") < ARCH_LVL )
+ return;
+
+ write("Loading PLDenylists\n");
+
+ if (stringp(tmp=read_file(WHITELIST))) {
+ // savefile exists
+ whitelist = restore_value(tmp);
+ }
+ else {
+ limited(#'ParseWhiteList);
+ }
+ if (stringp(tmp=read_file(TARGETLIST))) {
+ targets = restore_value(tmp);
+ }
+ else {
+ limited(#'ParseTargetList);
+ }
+}
+
+mixed include_file(string file, string compiled_file, int sys_include) {
+
+ if (sys_include) return 0;
+ // Loggen, wenn Files ausserhalb /players/ Kram aus /players/
+ // inkludieren/erben.
+ if (strstr(file, "/players/") == 0
+ && (strstr(compiled_file,"/players/") == -1
+ && !member(whitelist, compiled_file) )) {
+#ifdef PLDENY_LEARNMODE
+ DEBUG("include_file(): Whitelisting: "+compiled_file+"\n");
+ m_add(whitelist, compiled_file);
+ m_add(targets, file);
+ call_sefun("log_file", "PLAYERSWHITELIST",
+ sprintf("%s (inkludiert %s)\n",compiled_file, file),
+ 1000000);
+#else
+ // verweigern.
+ return -1;
+#endif
+ }
+ return 0;
+}
+
+mixed inherit_file(string file, string compiled_file) {
+
+ // Loggen, wenn Files ausserhalb /players/ Kram aus /players/
+ // inkludieren/erben.
+ if (strstr(file, "/players/") == 0
+ && (strstr(compiled_file,"/players/") == -1
+ && !member(whitelist, compiled_file) )) {
+#ifdef PLDENY_LEARNMODE
+ DEBUG("include_file(): Whitelisting: "+compiled_file+"\n");
+ m_add(whitelist, compiled_file);
+ m_add(targets, file);
+ call_sefun("log_file", "PLAYERSWHITELIST",
+ sprintf("%s (erbt %s)\n",compiled_file, file),
+ 1000000);
+#else
+ // verweigern.
+ return -1;
+#endif
+ }
+ return 0;
+}
+
diff --git a/secure/master/userinfo.c b/secure/master/userinfo.c
new file mode 100644
index 0000000..5d8f4ff
--- /dev/null
+++ b/secure/master/userinfo.c
@@ -0,0 +1,1119 @@
+// MorgenGrauen MUDlib
+//
+// master/userinfo.c -- Cache mit Spielerinfos
+//
+// $Id: userinfo.c 9467 2016-02-19 19:48:24Z Zesstra $
+
+#pragma strict_types
+
+#include "/secure/master.h"
+#include "/sys/files.h"
+#include "/sys/object_info.h"
+
+// Makro aus wizlevels.h ersetzen, da secure_level ne sefun ist. *seufz*
+#undef ARCH_SECURITY
+#define ARCH_SECURITY (call_sefun("secure_level") >= ARCH_LVL)
+
+private string* ExpandUIDAlias(string alias, int rec);
+
+nosave mapping userlist;
+string name, password;
+string ektips;
+string fptips;
+int level;
+string shell;
+int creation_date;
+string ep, ek, mq;
+string *domains,*guilds, *uidstotakecare;
+nosave string* lateplayers;
+// hier wird ein Mapping von UID-zu-Magier-Zuordnungen gespeichert. Als Keys
+// werden UIDs verwendet, der Value ist jeweils ein Array mit den magiern, die
+// dafuer zustaendig sind.
+nosave private mapping userids = ([]);
+// Ein Cache fuer UID-Aliase, damit ExpandUIDAlias() nicht staendig ganze
+// Verzeichnisse einlesen muss, sondern der Master das nur im Reset machen
+// muss.
+nosave private mapping uidaliase = ([]);
+
+#ifdef _PUREFTPD_
+// Liste von FTP-berechtigten Usern
+nosave private mapping ftpuser = ([]);
+
+public void read_ftp_users() {
+ string str = read_file("/"SECUREDIR"/ARCH/pureftpd.passwd") || "";
+ string *data = explode(str, "\n");
+ if (!sizeof(data)) return;
+ ftpuser = m_allocate(sizeof(data), 1);
+ foreach(str : data) {
+ string *tmp=explode(str, ":");
+ if(sizeof(tmp) >= 2)
+ m_add(ftpuser, tmp[0], tmp[1]);
+ }
+}
+
+public int write_ftp_users() {
+ string *record = allocate(18,"");
+ mapping tmp = m_allocate(sizeof(ftpuser));
+ // set UID/GID of system user
+ record[2] = "1000";
+ record[3] = "1000";
+ foreach(string u, string pwhash : ftpuser) {
+ record[0] = u; // UID
+ record[1] = pwhash;
+ record[5] = "/home/mud/mudlib/"WIZARDDIR"/" + u + "/./";
+ m_add(tmp, implode(record, ":"));
+ }
+ write_file("/"SECUREDIR"/ARCH/pureftpd.passwd", implode(m_indices(tmp),"\n"),1);
+ return sizeof(tmp);
+}
+
+// geloeschte Magier oder Magier, die 2 Wochen nicht da waren, expiren
+public void expire_ftp_user() {
+ foreach(string u : ftpuser) {
+ mixed uinfo = get_full_userinfo(u);
+ if (!uinfo) {
+ m_delete(ftpuser,u);
+ continue;
+ }
+ int zeit = call_sefun("file_time",
+ "/"SECURESAVEPATH + u[0..0] + "/" + u + ".o");
+ if (zeit < time()-1209600) {
+ m_delete(ftpuser,u);
+ continue;
+ }
+ }
+ call_out(#'write_ftp_users, 2);
+}
+
+#endif // _PUREFTPD_
+
+// ********** oeffentliche Funktionen ***********************
+
+//Verantwortliche Magier fuer eine UID ausgeben
+public varargs string* QueryWizardsForUID(string uid, int recursive) {
+
+ if (!stringp(uid) || !sizeof(uid))
+ return(({}));
+
+ string *tolookup=({uid}); //diese spaeter in userids nachgucken.
+ string *wizards=({});
+ // Schauen, was automatisch ermittelt werden kann.
+ string *parts=explode(uid,".");
+ switch(sizeof(parts)) {
+ case 3:
+ // z.B. d.region.magier und p.service.magier
+ if (find_userinfo(parts[2]))
+ //Magier existiert, reinschreiben.
+ wizards+=({parts[2]});
+ if (parts[0]=="d")
+ // d.region noch nachgucken (RMs)
+ tolookup=({implode(parts[0..1],".")});
+ break;
+ //case 2:
+ // GUILD.gilde, p.project, d.region
+ // koennen nur in userids nachgeguckt werden (s.u. tolookup)
+ // muessen da als GUILD.*, d.* und p.* drinstehen!
+ case 1:
+ // kein Punkt drin. Entweder Magier-ID oder spezielle ID
+ // (letztere wird unten noch per tolookup nachgeguckt)
+ if (find_userinfo(parts[0]))
+ wizards+=({parts[0]});
+ break;
+ }
+ // jetzt in userids nachschlagen
+ foreach(uid: tolookup) {
+ if (member(userids,uid))
+ wizards+=userids[uid];
+ }
+ // so. Nun kann es aber noch sein, dass in userids sowas wie
+ // "d.wald.atamur":({atamur}) und "atamur":({"rumata"}) drinsteht, also
+ // ein Magier sozusagen fuer Kram eines anderen verantwortlich ist. D.h.
+ // nochmal durch QueryWizardsForUID() schicken (das ist dann allerdings nicht
+ // weiter rekursiv).
+ if (!recursive) {
+ foreach(uid: wizards) {
+ //es ist moeglich, in der Schleife wizards zu vergroessern, ohne dass
+ //es Einfluss auf die Schleife hat.
+ wizards += QueryWizardsForUID(uid, 1) - ({uid});
+ }
+ }
+ return(m_indices(mkmapping(wizards)));
+}
+
+//das Define ist nicht sonderlich elegant, aber ich kann hier nicht das
+//newskills.h hier includen (1. zu viel, 2. ists nen Sicherheitsproblem)
+#define P_GUILD_DEFAULT_SPELLBOOK "guild_sb"
+// dito fuer den Gildenmaster
+#define GUILDMASTER "/secure/gildenmaster"
+
+// Den Alias-Cache loeschen (normalerweise einmal am Tag)
+public void ResetUIDAliase() {
+ // RM+ duerfen den Cache loeschen (wenn sie z.B. nen neues Verzeichnis
+ // angelegt haben.)
+ if (extern_call()
+ && call_sefun("secure_level") < LORD_LVL)
+ return;
+
+ uidaliase=([]);
+}
+
+// expandiert einige 'Aliase' in ein Array von allen UIDs, fuer die sie
+// stehen. Bsp: "region" -> d.region.* + region + d.region,
+// "zauberer" -> GUILD.zauberer, "p.service" -> p.service.*
+// Nutzt Eintrag aus dem uidalias-Cache, sofern vorhanden.
+public varargs string* QueryUIDAlias(string alias, int rec) {
+ string *uids;
+ if (!stringp(alias) || !sizeof(alias))
+ return ({});
+ // Wen im cache, gehts schnell.
+ if (member(uidaliase, alias))
+ uids = uidaliase[alias];
+ else
+ uids = ExpandUIDAlias(alias, rec);
+
+ if (extern_call())
+ return copy(uids);
+
+ return(uids);
+}
+
+// Fuer welche UIDs ist ein Magier verantwortlich? (Ist er RM,
+// Gildenmagier, in welchen Regionen hat er ein Verzeichnis, etc.)
+// recursive != 0 bedeutet, dass der Aufruf indirekt aus einem laufenden
+// QueryUIDsForWizard() erfolgt. In dem Fall soll QueryUIDAlias() keine
+// weitere Rekursion durchfuehren. Wird einfach durchgereicht.
+public varargs string* QueryUIDsForWizard(string wizuid, int recursive) {
+ string *uids, *tmp, *uidstoadd;
+ int i;
+
+ if (!stringp(wizuid) || !sizeof(wizuid) || !IS_LEARNER(wizuid))
+ return(({}));
+
+ if (!find_userinfo(wizuid))
+ return(({}));
+
+ uidstoadd=({}); //diese werden hinterher in userids gespeichert.
+
+ // als erstes die ratebaren UIDs. ;-)
+ uids=({wizuid});
+ // Regionen ermitteln, wo wizuid ein Verzeichnis hat und entsprechende
+ // UIDs anhaengen:
+ foreach(string region: get_domain_homes(wizuid)) {
+ uids+=({ sprintf(DOMAINDIR".%s.%s",region,wizuid) });
+ }
+ // Verzeichnis in /p/service?
+ if (file_size(PROJECTDIR"/service/"+wizuid) == FSIZE_DIR)
+ uids+=({PROJECTDIR".service."+wizuid});
+
+ // Gildenchef?
+ if (pointerp(userlist[wizuid,USER_GUILD])) {
+ foreach(string gilde: userlist[wizuid,USER_GUILD]) {
+ uidstoadd += QueryUIDAlias(gilde);
+ }
+ }
+ // Regionsmagier?
+ if (pointerp(userlist[wizuid,USER_DOMAIN])) {
+ foreach(string domain: userlist[wizuid,USER_DOMAIN]) {
+ //generelle Pseudo-UID 'd.region' und 'region' fuer geloeschte
+ //Magier dieser Region vormerken, um sie hinterher fuers
+ //Reverse-Lookup ins uid-Mapping zu schreiben.
+ string *pseudo=({DOMAINDIR"."+domain, domain});
+ uidstoadd += pseudo;
+ // Rest macht QueryUIDAlias, dabei aber die von der Funktion
+ // ebenfalls gelieferten Pseudo-UIDs wieder abziehen.
+ uids += QueryUIDAlias(domain) - pseudo;
+ }
+ }
+ // jetzt noch nachgucken, fuer welche UIDs dieser Magier explizit noch
+ // zustaendig ist.
+ if (pointerp(userlist[wizuid,USER_UIDS_TO_TAKE_CARE])) {
+ // dies koennte etwas a la "region" oder "anderermagier" sein, d.h. dieser
+ // Magier ist fuer alle UIDs von 'andermagier' auch zustaendig. Daher muss
+ // jedes davon durch QueryUIDAlias() (was im Falle von Spielern wiederum
+ // QueryUIDsForWizard() ruft, aber die Rekursion im Falle von Spielern ist
+ // auf 1 begrenzt).
+ foreach(string uid2: userlist[wizuid,USER_UIDS_TO_TAKE_CARE]) {
+ uidstoadd += QueryUIDAlias(uid2, recursive);
+ }
+ }
+
+ // so, in uidstoadd stehen UIDs drin, die nicht Magiername selber,
+ // d.region.magier oder p.service.magier sind und bei welchen folglich das
+ // Mapping UIDs-nach-Magier nur mit einer Liste moeglich ist. In die
+ // werden die uids nun eingetragen. (z.B. d.region)
+ if (sizeof(uidstoadd)) {
+ // genug Platz in userids? Sonst welche rauswerfen. :-/
+ // (besser als bug) TODO: Auf 10k begrenzen -> max Arraygroesse!
+ if ( sizeof(userids)+(i=sizeof(uidstoadd))
+ >= __MAX_MAPPING_KEYS__) {
+ foreach(string tmpuid: m_indices(userids)[0..i])
+ m_delete(userids,tmpuid);
+ }
+ foreach(string tmpuid: uidstoadd) {
+ if (member(userids,tmpuid)) {
+ //User dem Array hinzufuegen, wenn noch nicht drin.
+ if (member(userids[tmpuid],wizuid)==-1)
+ userids[tmpuid]=userids[tmpuid]+({wizuid});
+ }
+ //sonst neuen Eintragen hinzufuegen
+ else
+ m_add(userids,tmpuid,({wizuid}));
+ }
+ } // Ende spez. uids speichern
+
+ return(uids+uidstoadd);
+}
+
+//Einen Magier als verantwortlich fuer eine bestimmte UID eintragen
+public string* AddWizardForUID(string uid, string wizuid) {
+ if (!stringp(wizuid) || !sizeof(wizuid)
+ || !IS_LEARNER(wizuid))
+ return(({}));
+
+ //Zugriff nur als EM oder jemand, der fuer die UID zustaendig ist.
+ if ( !ARCH_SECURITY
+ && member(
+ QueryUIDsForWizard(call_sefun("secure_euid")),
+ uid) == -1)
+ return(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
+
+ if (!pointerp(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]))
+ //Array neu anlegen
+ userlist[wizuid,USER_UIDS_TO_TAKE_CARE]=({uid});
+ else {
+ //Ein Array schon vorhanden
+ if (member(userlist[wizuid,USER_UIDS_TO_TAKE_CARE],uid)==-1)
+ //uid nicht drin
+ userlist[wizuid,USER_UIDS_TO_TAKE_CARE]=
+ userlist[wizuid,USER_UIDS_TO_TAKE_CARE]+({uid});
+ }
+ save_userinfo(wizuid);
+ // aus dem UID-Alias-Cache werfen
+ m_delete(uidaliase, wizuid);
+ // Aufruf, um userids und uidaliase zu aktualisieren
+ QueryUIDsForWizard(wizuid);
+ return(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
+}
+
+// Einen Magier wieder austragen, wenn er nicht mehr zustaendig ist.
+public string* RemoveWizardFromUID(string uid, string wizuid) {
+ if (!stringp(wizuid) || !sizeof(wizuid)
+ || !find_userinfo(wizuid))
+ return(({}));
+
+ //Zugriff nur als EM oder jemand, der fuer die UID zustaendig ist.
+ if ( !ARCH_SECURITY
+ && member(
+ QueryUIDsForWizard(call_sefun("secure_euid")),
+ uid)==-1)
+ return copy(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
+
+ // jetzt muss diese wizuid aus allen UIDs in userids geloescht werden, die
+ // sie bisher enthalten. Hierzu sollte QueryUIDAlias die potentiell
+ // drinstehenden UIDs liefern.
+ foreach(string tuid: QueryUIDAlias(wizuid,0)) {
+ if (member(userids, tuid) &&
+ member(userids[tuid],wizuid)!=-1 )
+ userids[tuid] -= ({wizuid});
+ }
+ // wenn es eine UID war, fuer die der Magier explizit zustaendig war,
+ // entsprechend loeschen. Sonst ist hier Ende.
+ if (!pointerp(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]))
+ return ({});
+ if (member(userlist[wizuid,USER_UIDS_TO_TAKE_CARE],uid)==-1)
+ return copy(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
+
+ // Jetzt aus userlist loeschen.
+ userlist[wizuid,USER_UIDS_TO_TAKE_CARE] -= ({uid});
+ save_userinfo(wizuid);
+ // und userids/uidaliase aktualisieren.
+ QueryUIDsForWizard(wizuid);
+ return copy(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
+}
+
+//entfernt einen user aus dem usercache
+void RemoveFromCache(string user) {
+ m_delete(userlist,user);
+}
+
+//loescht den gesamten Usercache
+int clear_cache() {
+ userlist=m_allocate(0,widthof(userlist));
+ update_late_players();
+ return 1;
+}
+
+// Loescht UID->Magier Lookuptabelle. Sollte nicht gerufen werden, wenn man
+// nicht genau weiss, was man damit kaputtmacht.
+// als Nebeneffekt wird clear_cache() gerufen.
+int ResetUIDCache() {
+ // da diese Funktion auch das UID<->Magier-Lookup loescht, darf das nicht
+ // jeder rufen.
+ if (extern_call() &&
+ call_sefun("secure_level") < ELDER_LVL)
+ return -1;
+ userids=([]);
+ clear_cache();
+ return 1;
+}
+
+//verstorbene Spieler auflisten
+string list_late_players()
+{
+ string ret;
+ int size,i;
+
+ ret= "************************************\n";
+ ret+="* Verstorbene Spieler *\n";
+ ret+="************************************\n";
+
+ if(!lateplayers || (size=sizeof(lateplayers))==0)
+ {
+ return ret;
+ }
+
+ for(i=0;i<size;i++)
+ {
+ ret+=lateplayers[i]+"\n";
+ }
+
+ return ret;
+}
+
+// ist der Spieler gestorben?
+int check_late_player(string str)
+{
+ if(!lateplayers || !str || str=="")
+ {
+ return 0;
+ }
+
+ if(member(lateplayers,str)!=-1)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+// alle Eintraege im usercache ausgeben
+public mixed *show_cache() {
+ return m_indices(userlist);
+}
+
+// gibt es so einen User? Anfrage ausserdem immer in den Cache eintragen
+public int find_userinfo(string user) {
+ string file;
+ int i;
+ if (!stringp(user) || !sizeof(user)
+ || member(user,' ')!=-1 || member(user,':')!=-1)
+ return 0;
+ if (!member(userlist,user)) {
+ //erstmal schauen, ob wir mit einem neuen Eintrag nicht die max.
+ //Mapping-Groessen ueberschreiten, wenn ja, einen Eintrag loeschen
+ //BTW: widthof()+1 ist richtig so.
+ //BTW2: Ich hoffe, die max. Arraygroesse ist immer gross genug, sollte
+ //sie aber sein bei ner Mappingbreite von 10.
+ // BTW3: Dieses Rausloeschen von einem Eintrag bei Erreichen der Grenze
+ // erhoeht die benoetigten Ticks fuer diese Funktion um das 5-6fache und
+ // die noetige Zeit um das 70fache. Daher wird der Cache bei Erreichen
+ // der Grenze jetzt einfach geloescht.
+ if ( ( (i=sizeof(userlist)+1) >= __MAX_MAPPING_KEYS__)
+ || (( i * (widthof(userlist)+1)) >
+ __MAX_MAPPING_SIZE__))
+ //m_delete(userlist,m_indices(userlist)[0]);
+ userlist=m_allocate(1,widthof(userlist));
+
+ // Usersavefile finden
+ if ((file=secure_savefile(user))=="") {
+ // User gibt es nicht, aber Anfrage cachen
+ userlist+=([user: "NP"; -1; ({}); "LOCKED"; -1; time(); ""; ""; "";
+ ({});"";""; 0 ]);
+ return 0;
+ }
+ password="";
+ ep="";
+ ek="";
+ mq="";
+ guilds=({});
+ ektips="";
+ fptips="";
+ uidstotakecare=0;
+ if (!restore_object(file)) return 0;
+ userlist+=([user: password; level; domains; shell; creation_date;
+ time();ep; ek; mq; guilds; ektips; fptips; uidstotakecare]);
+ // die speziellen UIDs, fuer die dieser Magier zustaendig ist, ermitten
+ // und gleichzeitig im entsprechenden Mapping fuers Reverse-Loopup
+ // speichern.
+ if (level >= LEARNER_LVL)
+ QueryUIDsForWizard(user);
+ }
+ userlist[user,USER_TOUCH]=time();
+ if (userlist[user,USER_LEVEL]==-1) return 0;
+ return 1;
+}
+
+// Daten aus der Userlist fuer diesen User liefern. Macht ggf. find_userinfo()
+public mixed *get_userinfo(string user) {
+ if(!user||user=="")
+ return 0;
+ user=explode(user,".")[0];
+ if (!member(userlist,user) && !find_userinfo(user))
+ return 0;
+
+ if (userlist[user,USER_LEVEL]==-1) return 0;
+
+ return({user,"##"+user,userlist[user,USER_LEVEL],
+ userlist[user,USER_DOMAIN], userlist[user,USER_OBJECT],
+ userlist[user,USER_CREATION_DATE], 0, 0,
+ userlist[user,USER_GUILD],
+ userlist[user,USER_EKTIPS],userlist[user,USER_FPTIPS],
+ userlist[user,USER_UIDS_TO_TAKE_CARE]});
+}
+
+// liefert das Objekt des users aus der Userlist zurueck.
+public string query_player_object( string name )
+{
+ mixed *userentry;
+ if( !find_userinfo(name) ) return "";
+ return userlist[name,USER_OBJECT];
+}
+
+// Passwort ok?
+public int good_password( string str, string name )
+{
+ string rts;
+ int i, n;
+
+ if ( str[0] == '!' ){
+ tell_object( this_player() || this_object(), "Ein Ausrufungszeichen "
+ "('!') als erster Buchstabe des Passwortes wuerde zu\n"
+ "Problemen fuehren. Such Dir bitte ein anderes Passwort "
+ "aus.\n" );
+ return 0;
+ }
+
+ if ( sizeof(str) < 6 ){
+ tell_object( this_player() || this_object(),
+ "Das Passwort muss wenigstens 6 Zeichen lang sein.\n" );
+ return 0;
+ }
+
+ str = lower_case(str);
+ rts = "";
+
+ // Zahlen/Sonderzeichen am Anfang oder Ende des Passwortes
+ // (z.B. "merlin99") sind nicht wirklich einfallsreich.
+ while ( sizeof(str) && (str[0] > 'z' || str[0] < 'a') ){
+ rts += str[0..0];
+ str = str[1..];
+ }
+
+ while ( sizeof(str) && (str[<1] > 'z' || str[<1] < 'a') ){
+ rts += str[<1..];
+ str = str[0..<2];
+ }
+
+ // Anzahl unterschiedlicher Zeichen merken, die herausgeschnitten wurden
+ n = sizeof( mkmapping(efun::explode( rts, "" )) );
+ rts = "";
+
+ for ( i = sizeof(str); i--; )
+ rts += str[i..i];
+
+ // Eigener Name als Passwort bzw. eigener Name rueckwaerts
+ if ( str == lower_case(name) || rts == lower_case(name) ||
+ // Name eines anderen Mudders (Erstie?)
+ secure_savefile(str) != "" || secure_savefile(rts) != "" ||
+ // Name von der Banish-Liste
+ QueryBanished(str) || QueryBanished(rts) ||
+ // Name eines vorhandenen NPC o.ae.
+ call_sefun("find_living", str ) ||
+ call_sefun("find_living", rts ) ||
+ // Zuwenig verschiedene Zeichen
+ sizeof( mkmapping(efun::explode( str, "" )) ) + n < 4 ){
+ tell_object( this_player() || this_object(),
+ "Das Passwort waere zu einfach zu erraten. Nimm bitte "
+ "ein anderes.\n" );
+ return 0;
+ }
+
+ return 1;
+}
+
+// User-ID fuer ein File ermitteln.
+public string get_wiz_name(string file) {
+ return creator_file(file);
+}
+
+// Wizlevel aus dem Userinfo-Cache holen
+public int get_wiz_level(string user) {
+ if (user && find_userinfo(user))
+ return userlist[user,USER_LEVEL];
+ //return 0 if no user given (return type needed)
+ return(0);
+}
+
+// Wizlevel fuer eine UID ermitteln.
+public int query_wiz_level( mixed player )
+{
+ if ( objectp(player) && efun::object_info(player, OI_ONCE_INTERACTIVE) )
+ return get_wiz_level( getuid(player) );
+ else {
+ // erstmal UID ermitteln, falls Objekt
+ //if (objectp(player))
+ // player=getuid(player);
+ if ( stringp(player) ) {
+ if( player[0..1]==DOMAINDIR"." ) return 25;
+ if( player[0..5]==GUILDID"." )
+ return WIZLVLS[GUILDID];
+ if( player[0..1]==PROJECTDIR"." ) return 21;
+ // die alte Loesung mit || verhaelt sich falsch, wenn ein UID ne
+ // spezielle ist, der Level 0 zugeordnet wurde und es einen
+ // Spieler mit diesem namen gibt.
+ if (member(WIZLVLS,player))
+ return(WIZLVLS[player]);
+ return get_wiz_level(player);
+ }
+ }
+ return 0;
+}
+
+// Savefile aus /secure/save* zurueckliefern
+public string secure_savefile(string name)
+{
+ if(!name||name=="")
+ return "";
+ name=explode(name,".")[0];
+ if (file_size(SECURESAVEPATH+name[0..0]+"/"+name+".o")>=0)
+ return SECURESAVEPATH+name[0..0]+"/"+name;
+
+ return "";
+}
+
+// *************** 'halb-oeffentliche Funktionen *********************
+#ifdef _PUREFTPD_
+// FTP-Berechtigung fuer Magier
+int update_ftp_access(string user, int state) {
+ // wenn nicht EM+ oder ROOT, darf nur fuer this_interactive geaendert
+ // werden.
+ if (getuid(PO) != ROOTID && extern_call()
+ && !ARCH_SECURITY) {
+ if (this_interactive())
+ user = getuid(this_interactive());
+ else
+ user = 0;
+ }
+ if (!user || !sizeof(user))
+ return -1;
+
+ if (!find_userinfo(user))
+ return 0;
+
+ // Passwort muss manuell vom Magier neu gesetzt werden, da es keine
+ // Moeglichkeit gibt, an das aktuelle im Klartext heranzukommen.
+ if (state) {
+ if (!member(ftpuser,user))
+ m_add(ftpuser, user, "*");
+ }
+ else
+ m_delete(ftpuser, user);
+
+ call_out(#'write_ftp_users, 4);
+ return state;
+}
+#endif
+
+// Spieler ein neues Wizlevel verpassen.
+int update_wiz_level(string user,int lev) {
+ object ob;
+
+ if (getuid(PO) != ROOTID && extern_call()) return 0;
+ if (!find_userinfo(user)) return 0;
+ userlist[user,USER_LEVEL] = lev;
+ save_userinfo(user);
+ return 1;
+}
+
+// neue Forscherpunkte fuer den User abspeichern.
+int update_ep(string user,string ep_neu) {
+ if (getuid(PO) != ROOTID) return 0;
+ if (!find_userinfo(user)) return 0;
+ userlist[user,USER_EP] = ep_neu;
+ save_userinfo(user);
+ return 1;
+}
+
+// neue Erstkills fuer den User abspeichern.
+int update_ek(string user,string ek_neu) {
+ if (getuid(PO) != ROOTID) return 0;
+ if (!find_userinfo(user)) return 0;
+ userlist[user,USER_EK] = ek_neu;
+ save_userinfo(user);
+ return 1;
+}
+
+// Miniquestdaten speichern.
+int update_mq(string user,string mq_neu) {
+ if (getuid(PO) != ROOTID) return 0;
+ if (!find_userinfo(user)) return 0;
+ userlist[user,USER_MQ] = mq_neu;
+ save_userinfo(user);
+ return 1;
+}
+
+// Erstkillpunkt-Tips speichern.
+int update_ektips(string user,string ek_neu) {
+ if (getuid(PO) != ROOTID) return 0;
+ if (!find_userinfo(user)) return 0;
+ userlist[user,USER_EKTIPS] = ek_neu;
+ save_userinfo(user);
+ return 1;
+}
+
+// Forscherpunkttips abspeichern.
+int update_fptips(string user,string fp_neu) {
+ if (getuid(PO) != ROOTID) return 0;
+ if (!find_userinfo(user)) return 0;
+ userlist[user,USER_FPTIPS] = fp_neu;
+ save_userinfo(user);
+ return 1;
+}
+
+// forscherpunkte abfragen.
+string query_ep(string user) {
+ if (getuid(PO) != ROOTID) return 0;
+ if (!find_userinfo(user)) return 0;
+ return userlist[user,USER_EP];
+}
+
+// Erstkills abfragen
+string query_ek(string user) {
+ if (getuid(PO) != ROOTID) return 0;
+ if (!find_userinfo(user)) return 0;
+ return userlist[user,USER_EK];
+}
+
+// Miniquests abfragen.
+string query_mq(string user) {
+ if (getuid(PO) != ROOTID) return 0;
+ if (!find_userinfo(user)) return 0;
+ return userlist[user,USER_MQ];
+}
+
+// EK-Tips abfragen.
+string query_ektips(string user) {
+ if (getuid(PO) != ROOTID) return 0;
+ if (!find_userinfo(user)) return 0;
+ return userlist[user,USER_EKTIPS];
+}
+
+// FP-Tips abfragen.
+string query_fptips(string user) {
+ if (getuid(PO) != ROOTID) return 0;
+ if (!find_userinfo(user)) return 0;
+ return userlist[user,USER_FPTIPS];
+}
+
+#define PLAYERSHELLS ({"/std/shells/darkelf", "/std/shells/dwarf", \
+ "/std/shells/elf", "/std/shells/feline", "/std/shells/hobbit", \
+ "/std/shells/human" })
+
+// Aendert die Shells eines Users.
+int set_player_object( string user, string objectname )
+{
+ mixed *path;
+ string prev;
+
+ // nur EM und ROOT duerfen die Shell eines Charakters aendern
+ if ( !ARCH_SECURITY &&
+ (!previous_object() || getuid(previous_object()) != ROOTID) ) {
+ return -1;
+ }
+
+ if ( objectname == "" )
+ objectname = 0;
+
+ if ( !stringp(user) || user == "" )
+ return -6;
+
+ if ( !stringp(objectname) ){
+ if ( !find_userinfo(user) )
+ return -4;
+
+ userlist[user, USER_OBJECT] = 0;
+ save_userinfo(user);
+ return 1;
+ }
+
+ if ( catch(load_object(objectname);publish) ) {
+ write( "Fehler in " + objectname + "!\n" );
+ return -2;
+ }
+
+ objectname = _get_path( objectname, 0 );
+ path = (efun::explode( objectname, "/" ) - ({ "", 0 }));
+
+ if ( sizeof(path) < 3 || path[0] != "std" || path[1] != "shells" )
+ return -3;
+
+ if ( !find_userinfo(user) )
+ return -4;
+
+ prev = userlist[user, USER_OBJECT];
+ userlist[user, USER_OBJECT] = objectname;
+ save_userinfo(user);
+
+ // Loggen, falls die Aenderung nicht von Login beim Anlegen des Chars
+ // erfolgt.
+ if (load_name(this_interactive()) != "/secure/login"
+ || prev != "") {
+ if (prev == "") prev ="<keine>";
+ call_sefun("log_file", "ARCH/SHELL_AENDERUNGEN",
+ sprintf( "%s: %O aendert die Shell von %s von %s auf %s (PO: %O)\n",
+ strftime("%Y%m%d-%H%M%S"),
+ this_interactive(), capitalize(user), prev, objectname,
+ previous_object()) );
+ }
+
+ return 1;
+}
+
+// Passwort aktualisieren.
+int update_password( string old, string new )
+{
+ string user;
+
+ // nanu, kein user?
+ if ( !find_userinfo(user = getuid(PO)) )
+ return 0;
+
+ // wenn das neue PW unterschiedlich ist, schauen, ob das neue PW ok ist.
+ if ( old != new && !good_password( new, user ) )
+ return 0;
+
+ string pwhash = userlist[user, USER_PASSWORD];
+ string oldpwhash;
+ if (sizeof(pwhash) > 13) {
+ // MD5-Hash
+ oldpwhash = md5_crypt(old, pwhash);
+ }
+ else if (sizeof(pwhash) > 2) {
+ // Crypt-Hash
+ oldpwhash = crypt(old, pwhash[0..1]);
+ }
+
+ // wenn es einen PW-hash gibt, also ein PW gesetzt wird, muss der Hash von
+ // old mit dem pwhash uebereinstimmen. Leerer Hash oder gar kein Hash
+ // erlaubt jedes beliebige PW.
+ if ( stringp(pwhash) && sizeof(pwhash) && pwhash != oldpwhash)
+ return 0;
+ // an dieser Stelle stimmt 'old' wohl mit dem gesetzten Passwort ueberein.
+ // Wenn allerdings die Funktion mit old==new aufgerufen wurde, wird hier
+ // nur 1 zurueckgeben und sonst an sich nix gemacht. Kann nicht weiter
+ // oben schon gemacht werden, die Shells dies als PW-Pruefung nutzen.
+ // *seufz*
+ if (old == new) return 1;
+
+ // dann mal neu setzen
+ userlist[ user, USER_PASSWORD ] = md5_crypt( new, 0 );
+ save_userinfo(user);
+#ifdef _PUREFTPD_
+ // Bedauerlicherweise versteht pureftpd unser md5_crypt nicht. :-(
+ if (member(ftpuser,user)) {
+ ftpuser[user] = crypt(new);
+ if (find_call_out(#'write_ftp_users) == -1)
+ call_out(#'write_ftp_users,4);
+ }
+#endif
+ return 1;
+}
+
+// Spieler loeschen.
+int delete_player(string passwd, string real_name)
+{
+ int wlevel;
+ string part_filename;
+
+ if (!PO || PO!=TP || PO!=TI || real_name != getuid(PO) ||
+ !find_userinfo(real_name))
+ return 0;
+ mixed erstie=(mixed)this_interactive()->QueryProp(P_SECOND);
+ password = userlist[real_name,USER_PASSWORD];
+ wlevel = get_wiz_level(real_name);
+ if (!update_password(passwd, passwd)) return 0;
+
+ // Spielpausen aufheben (sonst kann man als Spieler nen Namen sperren).
+ TBanishName(real_name, 0);
+
+ part_filename="/"+real_name[0..0]+"/"+real_name+".o";
+ rm("/"SECUREDIR"/save"+part_filename);
+ rm("/"LIBSAVEDIR"/"+part_filename);
+ rm("/"MAILDIR"/"+part_filename);
+
+ m_delete(userlist,real_name);
+
+ if (wlevel >= LEARNER_LVL)
+ TO->BanishName(real_name, "So hiess mal ein Magier hier");
+ else if (wlevel >= SEER_LVL)
+ TO->BanishName(real_name, "So hiess mal ein Seher hier");
+
+#ifdef _PUREFTPD_
+ if (member(ftpuser,real_name)) {
+ m_delete(ftpuser,real_name);
+ call_out(#'write_ftp_users,4);
+ }
+#endif
+
+ call_sefun("log_file", "USERDELETE",
+ sprintf("%s: %s %s(%s)\n",
+ ctime(time()),real_name,
+ (stringp(erstie)?sprintf("[Erstie: %s] ",erstie):""),
+ call_sefun("query_ip_number",TI)));
+
+ return 1;
+}
+
+// ermittelt alle existierenden Spieler, die ein Savefile in /secure/save
+// haben.
+// ([ "a": ({...namen...}), "b": ({...namen...}), ... ])
+public mapping get_all_players() {
+ string *dirs=({"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o",
+ "p","q","r","s","t","u","v","w","x","y","z"});
+ mapping allplayer=([]);
+ string *tmp;
+ foreach(string dir: dirs) {
+ tmp=get_dir("/secure/save/"+dir+"/*")
+ - ({".","..",".KEEP",".svn"});
+ allplayer[dir] = map(tmp,function string (string fn)
+ { return explode(fn,".")[0]; } );
+ }
+ return allplayer;
+}
+
+// *************** interne Funktionen ********************************
+protected void create()
+{
+ userlist=m_allocate(0,12);
+ update_late_players();
+#ifdef _PUREFTPD_
+ read_ftp_users();
+#endif
+}
+
+//verstorbene Spieler einlesen
+void update_late_players() {
+ string read;
+ string *tmp;
+
+ lateplayers=0;
+
+ read=read_file("/"SECUREDIR"/LATE_PLAYERS");
+ if(!read || read=="")
+ {
+ return;
+ }
+
+ tmp=explode(read,"\n");
+ if(!sizeof(tmp))
+ {
+ return;
+ }
+
+ lateplayers=tmp;
+}
+
+
+// Daten aus der Userlist inkl. Passworthash zurueckliefern. Macht ggf.
+// find_userinfo(). Nur masterintern aufrufbar.
+protected mixed *get_full_userinfo(string user) {
+ if(!user||user=="")
+ return 0;
+ user=explode(user,".")[0];
+ if (!member(userlist,user) && !find_userinfo(user))
+ return 0;
+
+ return({user,userlist[user,USER_PASSWORD],userlist[user,USER_LEVEL],
+ userlist[user,USER_DOMAIN], userlist[user,USER_OBJECT],
+ userlist[user,USER_CREATION_DATE], 0, 0, userlist[user,USER_GUILD],
+ userlist[user,USER_EKTIPS],userlist[user,USER_FPTIPS],
+ userlist[user,USER_UIDS_TO_TAKE_CARE]});
+}
+
+// Userdaten aus der Userlist im Savefile in /secure/save/ speichern.
+protected void save_userinfo(string user) {
+ if(!user||user=="")
+ return;
+ user=explode(user,".")[0];
+ if (!member(userlist,user)) return;
+ name = user;
+ level = userlist[name,USER_LEVEL];
+ domains = userlist[name,USER_DOMAIN];
+ shell = userlist[name,USER_OBJECT];
+ password = userlist[name,USER_PASSWORD];
+ creation_date = userlist[name,USER_CREATION_DATE];
+ if (!creation_date) creation_date = -1;
+ ep = userlist[name,USER_EP];
+ if (!ep) ep="";
+ ek = userlist[name,USER_EK];
+ if (!ek) ek="";
+ mq = userlist[name,USER_MQ];
+ if (!mq) mq="";
+ guilds = userlist[name,USER_GUILD];
+ ektips=userlist[name,USER_EKTIPS];
+ if(!ektips) ektips="";
+ fptips=userlist[name,USER_FPTIPS];
+ if(!fptips) fptips="";
+ uidstotakecare=userlist[name,USER_UIDS_TO_TAKE_CARE];
+
+ if (save_object(SECURESAVEPATH+name[0..0]+"/"+name) != 0) {
+ // autsch. Buggen damit dieser moeglichst schnell auffaellt, dass hier
+ // Savefiles in /secure/save/ nicht geschrieben wurden.
+ raise_error(sprintf(
+ "Savefile %O konnte nicht erstellt werden!\n",
+ SECURESAVEPATH+name[0..0]+"/"+name));
+ }
+}
+
+// In welchen Regionen ist der Spieler Regionsmagier?
+protected void set_domains(string player, string *domains)
+{
+ // wenn der Magier jetzt Domains nicht mehr hat, muessen die aus 'userids'
+ // entfernt werden, das uebernimmt RemoveWizardFromUID().
+ if (pointerp(userlist[player, USER_DOMAIN])) {
+ string *removeduids=
+ ((string*)userlist[player, USER_DOMAIN] | domains) - domains;
+ foreach(string uid: removeduids)
+ RemoveWizardFromUID(uid, player);
+ }
+ // gecachtes Alias fuer player loeschen
+ m_delete(uidaliase,player);
+ userlist[player, USER_DOMAIN]=domains;
+ save_userinfo(player);
+ // UID-zu-Magier-Mapping aktualisieren
+ QueryUIDsForWizard(player);
+}
+
+// In welche Gilden ist der Spieler Gildenmagier?
+protected void set_guilds(string player, string *guilds)
+{
+ // wenn der Magier jetzt Gilden nicht mehr hat, muessen die aus 'userids'
+ // entfernt werden, das uebernimmt RemoveWizardFromUID().
+ if (pointerp(userlist[player, USER_GUILD])) {
+ string *removeduids=
+ ((string*)userlist[player, USER_GUILD] | guilds) - guilds;
+ foreach(string uid: removeduids)
+ RemoveWizardFromUID(uid, player);
+ }
+ // gecachtes Alias fuer player loeschen
+ m_delete(uidaliase,player);
+ userlist[player, USER_GUILD]=guilds;
+ save_userinfo(player);
+ // UID-zu-Magier-Mapping aktualisieren
+ QueryUIDsForWizard(player);
+}
+
+// Userinfo-Cache expiren...
+protected void _cleanup_uinfo()
+{
+ foreach(string user: userlist) {
+ if ((time() - userlist[user,USER_TOUCH]) > 1800
+ && !call_sefun("find_player",user))
+ m_delete(userlist,user);
+ }
+}
+
+// expandiert einige 'Aliase' in ein Array von allen UIDs, fuer die sie
+// stehen. Bsp: "region" -> d.region.* + region + d.region,
+// "zauberer" -> GUILD.zauberer, "p.service" -> p.service.* oder auch
+// "magier" -> QueryUIDsForWizard().
+// Das erfolgt ueber Lookup der entsprechenden Verzeichnisse auf der PLatte.
+// Da zusaetzlich auch noch jede Menge find_userinfo() dazu kommen, ist das
+// recht aufwaendig. Damit das ganze nicht jedesmal erfolgen muss, wird das
+// Ergebnis gecacht und QueryUIDAlias() nutzt den Cache.
+private string* ExpandUIDAlias(string alias, int rec) {
+
+ string *uids=({});
+
+ // Regionsname?
+ if (file_size("/"DOMAINDIR"/"+alias) == FSIZE_DIR) {
+ //generelle Pseudo-UID 'd.region' und 'region' fuer geloeschte
+ //Magier dieser Region
+ uids += ({DOMAINDIR"."+alias, alias});
+ //alle Magier-Verzeichnisse ermitteln:
+ string tmpdir="/"DOMAINDIR"/"+alias+"/";
+ foreach(string dir: (get_dir(tmpdir+"*") || ({}))
+ - ({".","..",".svn"})) {
+ // schauen, obs nen (sichtbares) Verzeichnis ist und ob der Magier
+ // existiert. Letzteres aber nur, falls die Rekursionstiefe min. 100
+ // ist, da beim Nachschauen eines Magiers mit (mehreren)
+ // RM-Posten, der in der Region RMs aus anderen Regionen hat, leicht
+ // Rekursionstiefen von 20 (*4) auftreten koennen, wenn noch gar
+ // keine UIDs im Cache sind (find_userinfo() ruft indirekt diese
+ // Funktion).
+ if (dir[0]!='.'
+ && file_size(tmpdir+dir) == FSIZE_DIR
+#if __MAX_RECURSION__ > 99
+ && find_userinfo(dir)
+#endif
+ )
+ uids += ({DOMAINDIR"."+alias+"."+dir});
+ }
+ }
+ // Gildenname?
+ else if (GUILDMASTER->ValidGuild(alias)) {
+ uids += ({GUILDID"."+alias});
+ //hat die Gilde ein Projektverzeichnis?
+ if (file_size("/"PROJECTDIR"/"+alias) == FSIZE_DIR) {
+ uids += ({PROJECTDIR"."+alias});
+ }
+ // jetzt haben dummerweise die Spellbooks meist nicht den gleichen
+ // Namen wie die Gilde. D.h. die Gilde muss nun noch nach dem
+ // Namen des Spellbooks gefragt werden, weil dessen UID nicht
+ // unbedingt gleich dem der Gilde ist. *seufz*
+ string spbook;
+ object guild;
+ catch(guild=load_object(GUILDDIR"/"+alias));
+ if (objectp(guild)
+ && (sizeof(spbook=(string)
+ guild->QueryProp(P_GUILD_DEFAULT_SPELLBOOK)))
+ && spbook!=alias)
+ uids += ({GUILDID"."+spbook});
+ }
+ // Spieler/Magier-UID?
+ else if (find_userinfo(alias)) {
+ // wenn rec > 0, wird eine Spieler-UID als Alias aufgefasst und zu seinen
+ // UIDs expandiert. Hierbei erfolgt aber nur eine Rekursion.
+ if (!rec) uids = QueryUIDsForWizard(alias, 1);
+ else uids = ({alias});
+ }
+ // Projektkrams? -> alle Subdirs von /p/ ausser /p/service selber.
+ else if (alias==PROJECTDIR) {
+ foreach(string dir: (get_dir("/"PROJECTDIR"/*") || ({}))
+ - ({".","..",".svn","service"})) {
+ if (dir[0]!='.' &&
+ file_size("/"PROJECTDIR"/"+dir) == FSIZE_DIR)
+ uids += ({PROJECTDIR"."+dir});
+ }
+ }
+ // p.service? -> Alle Subdirs von /p/service.
+ else if (alias==PROJECTDIR".service") {
+ foreach(string dir: (get_dir("/"PROJECTDIR"/service/*") || ({}))
+ - ({".","..",".svn"})) {
+ if (dir[0]!='.' &&
+ file_size("/"PROJECTDIR"/service/"+dir) == FSIZE_DIR)
+ uids += ({PROJECTDIR".service."+dir});
+ }
+ }
+ // wenn nix zutrifft -> unexpandiert zurueckgeben
+ else
+ uids = ({alias});
+
+ // im Cache vermerken
+ if (sizeof(uidaliase) >= __MAX_MAPPING_KEYS__)
+ uidaliase=m_allocate(1);
+ // auch vermerken, wenn keine UIDs ermittelt wurden.
+ uidaliase += ([alias: uids]);
+ return uids;
+}
+