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(&region, 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;
+}
+