Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/p/daemon/channeld.c b/p/daemon/channeld.c
new file mode 100644
index 0000000..1331d57
--- /dev/null
+++ b/p/daemon/channeld.c
@@ -0,0 +1,647 @@
+// channeld.c
+//
+// $Id: channeld.c 9138 2015-02-03 21:46:56Z Zesstra $
+//
+
+#pragma strong_types
+#pragma no_shadow // keine Shadowing...
+#pragma no_clone
+#pragma no_inherit
+#pragma save_types
+
+#include <sys_debug.h>
+#include <lpctypes.h>
+#include <wizlevels.h>
+
+#include <properties.h>
+#include <config.h>
+#include <language.h>
+
+#define NEED_PROTOTYPES
+#include "channel.h"
+
+#define CMNAME  "<MasteR>"
+#define CHANNEL_SAVE    "/p/daemon/save/channeld"
+
+#define MEMORY "/secure/memory"
+
+// channels - contains the simple channel information
+//            ([ channelname : ({ I_MEMBER, I_ACCESS, I_INFO, I_MASTER })...])
+private nosave mapping lowerch;
+private nosave mapping channels;
+private nosave mapping channelH;
+private nosave mapping stats;
+
+private mapping channelC;
+private mapping channelB;
+
+private int save_me_soon;
+
+// BEGIN OF THE CHANNEL MASTER ADMINISTRATIVE PART
+
+#define RECV    0
+#define SEND    1
+#define FLAG    2
+
+// Channel flags
+// Levelbeschraenkungen gegen Magierlevel (query_wiz_level) pruefen, nicht
+// P_LEVEL.
+#define F_WIZARD 1
+// Keine Gaeste. ;-)
+#define F_NOGUEST 2
+
+private nosave mapping admin = m_allocate(0, 3);
+
+int check(string ch, object pl, string cmd)
+{
+  int level;
+  
+  if((admin[ch, FLAG] & F_NOGUEST) && pl->QueryGuest()) return 0;
+
+  if((admin[ch, FLAG] & F_WIZARD) && query_wiz_level(pl) < SEER_LVL) return 0;
+  level = (admin[ch, FLAG] & F_WIZARD
+           ? query_wiz_level(pl)
+           : pl->QueryProp(P_LEVEL));
+
+  switch(cmd)
+  {
+  case C_FIND:
+  case C_LIST:
+  case C_JOIN:
+    if(admin[ch, RECV] == -1) return 0;
+    if(admin[ch, RECV] <= level) return 1;
+    break;
+  case C_SEND:
+    if(admin[ch, SEND] == -1) return 0;
+    if(admin[ch, SEND] <= level) return 1;
+    break;
+  case C_LEAVE:
+    return 1;
+  default: break;
+  }
+  return(0);
+}
+
+private int CountUser(mapping l)
+{
+  mapping n;
+  n = ([]);
+  walk_mapping(l, lambda(({'i/*'*/, 'a/*'*/, 'n/*'*/}),
+                         ({#'+=/*'*/, 'n/*'*/,
+                              ({#'mkmapping/*'*/,
+                                   ({#'[/*'*/, 'a/*'*/, 0})})})),
+               &n);
+  return sizeof(n);
+}
+
+private void banned(string n, mixed cmds, string res)
+{
+  res += sprintf("%s [%s], ", capitalize(n), implode(cmds, ","));
+}
+
+private mapping Tcmd = ([]);
+#define TIMEOUT (time() - 60)
+
+void ChannelMessage(mixed msg)
+{
+  string ret, mesg;
+  mixed lag;
+  int max, rekord;
+  string tmp;
+  if(msg[1] == this_object() || !stringp(msg[2]) ||
+     msg[0] != CMNAME || previous_object() != this_object()) return;
+
+
+  mesg = lower_case(msg[2]);
+
+  if(!strstr("hilfe", mesg) && sizeof(mesg) <= 5)
+    ret = "Folgende Kommandos gibt es: hilfe, lag, up[time], statistik, bann";
+  else
+  if(!strstr("lag", mesg) && sizeof(mesg) <= 3)
+  {
+    if(Tcmd["lag"] > TIMEOUT) return;
+    Tcmd["lag"] = time();
+    lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
+    ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
+                       "%.1f%%/20s, %.1f%%/2s",
+                  lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
+    call_out(#'send/*'*/, 2, CMNAME, this_object(), ret);
+    ret = query_load_average();
+  } else
+  if(!strstr("uptime", mesg) && sizeof(mesg) <= 6)
+  {
+    if(Tcmd["uptime"] > TIMEOUT) return;
+    Tcmd["uptime"] = time();
+    if(file_size("/etc/maxusers") <= 0) {
+      ret = "Diese Information liegt nicht vor.";
+    } else {
+      sscanf(read_file("/etc/maxusers"), "%d %s", max, tmp);
+      sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, tmp);
+      ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
+                  "eingeloggt; das Maximum lag heute bei %d und der Rekord "
+                  "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
+    }
+  } else
+  if(!strstr("statistik", mesg) && sizeof(mesg) <= 9)
+  {
+    if(Tcmd["statistik"] > TIMEOUT) return;
+    Tcmd["statistik"] = time();
+    ret = sprintf(
+    "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv.\n"
+    "Der %s wurde das letzte mal am %s von %s neu gestartet.\n"
+    "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.\n",
+    sizeof(channels), CountUser(channels), CMNAME,
+    dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
+  } else
+  if(!strstr(mesg, "bann"))
+  {
+    string pl, cmd;
+    if(mesg == "bann")
+      if(sizeof(channelB))
+      {
+        ret = "";
+        walk_mapping(channelB, #'banned/*'*/, &ret);
+        ret = "Fuer folgende Spieler besteht ein Bann: " + ret;
+      } else ret = "Zur Zeit ist kein Bann aktiv.";
+    else
+    {
+      if(sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
+         IS_DEPUTY(msg[1]))
+      {
+#     define CMDS ({C_FIND, C_LIST, C_JOIN, C_LEAVE, C_SEND, C_NEW})
+        pl = lower_case(pl); cmd = lower_case(cmd);
+        if(member(CMDS, cmd) != -1)
+        {
+          if(!pointerp(channelB[pl]))
+            channelB[pl] = ({});
+          if(member(channelB[pl], cmd) != -1)
+            channelB[pl] -= ({ cmd });
+          else
+            channelB[pl] += ({ cmd });
+          ret = "Fuer '"+capitalize(pl)+"' besteht "
+            + (sizeof(channelB[pl]) ?
+               "folgender Bann: "+implode(channelB[pl], ", ") :
+               "kein Bann mehr.");
+          if(!sizeof(channelB[pl]))
+            channelB = m_copy_delete(channelB, pl);
+          save_object(CHANNEL_SAVE);
+        }
+        else ret = "Das Kommando '"+cmd+"' ist unbekannt. Erlaubte Kommandos: "
+               + implode(CMDS, ", ");
+      }
+      else
+      {
+        if(!IS_ARCH(msg[1])) return;
+        else ret = "Syntax: bann <name> <kommando>";
+      }
+    }
+  }
+  else if(mesg == "lust")
+  {
+    mixed t, up;
+    if(Tcmd["lag"] > TIMEOUT ||
+       Tcmd["statistik"] > TIMEOUT ||
+       Tcmd["uptime"] > TIMEOUT) return;
+    Tcmd["lag"] = time();
+    Tcmd["statistik"] = time();
+    Tcmd["uptime"] = time();
+    lag = "/p/daemon/lag-o-daemon"->read_lag_data();
+
+    sscanf(read_file("/etc/maxusers"), "%d %s", max, tmp);
+    sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, tmp);
+
+    t=time()-last_reboot_time();
+    up="";
+    if(t >= 86400)
+      up += sprintf("%dT", t/86400);
+    if(t >= 3600)
+      up += sprintf("%dh", (t=t%86400)/3600);
+    if(t > 60)
+      up += sprintf("%dm", (t=t%3600)/60);
+    up += sprintf("%ds", t%60);
+
+    ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
+                  lag[1], lag[2], up, sizeof(users()), max, rekord, 
+                  sizeof(channels), CountUser(channels));
+  } else return;
+
+  call_out(#'send/*'*/, 2, CMNAME, this_object(), ret);
+}
+
+// setup() -- set up a channel and register it
+//            arguments are stored in the following order:
+//            ({ channel name,
+//               receive level, send level,
+//               flags,
+//               description,
+//               master obj
+//            })
+private void setup(mixed c)
+{
+  closure cl;
+  object m;
+  string d;
+  d = "- Keine Beschreibung -";
+  m = this_object();
+  if(sizeof(c) && sizeof(c[0]) > 1 && c[0][0] == '\\')
+    c[0] = c[0][1..];
+
+  switch(sizeof(c))
+  {
+  case 6:
+    if(!stringp(c[5]) || !sizeof(c[5]) 
+        || (catch(m=load_object(c[5]);publish) || !objectp(m) ))
+      m = this_object();
+  case 5: d = stringp(c[4]) || closurep(c[4]) ? c[4] : d;
+  case 4: admin[c[0], FLAG] = to_int(c[3]);
+  case 3: admin[c[0], SEND] = to_int(c[2]);
+  case 2: admin[c[0], RECV] = to_int(c[1]);
+    break;
+  case 0:
+  default:
+    return;
+  }
+  switch(new(c[0], m, d))
+  {
+  case E_ACCESS_DENIED:
+    log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
+                           dtime(time()), c[0], m));
+    break;
+  default:
+    break;
+  }
+  return;
+}
+
+void initialize()
+{
+  mixed tmp;
+  tmp = read_file(object_name(this_object())+".init");
+  tmp = regexp(old_explode(tmp, "\n"), "^[^#]");
+  tmp = map(tmp, #'regexplode/*'*/, "[^:][^:]*$|[ \\t]*:[ \\t]*");
+  tmp = map(tmp, #'regexp/*'*/, "^[^: \\t]");
+  map(tmp, #'setup/*'*/);
+}
+
+// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
+
+void create()
+{
+  seteuid(getuid());
+  restore_object(CHANNEL_SAVE);
+  if(!channelC) channelC = ([]);
+  if(!channelB) channelB = ([]);
+  channels = ([]);
+
+  /* Die Channel-History wird nicht nur lokal sondern auch noch im Memory 
+     gespeichert, dadurch bleibt sie auch ueber ein Reload erhalten. 
+     Der folgende Code versucht, den Zeiger aus dem Memory zu holen. Falls
+     das nicht moeglich ist, wird ein neuer erzeugt und gegebenenfalls im
+     Memory abgelegt. */
+
+  // Hab ich die noetigen Rechte im Memory?
+  if (call_other(MEMORY,"HaveRights")) {
+    // Objektpointer laden
+    channelH = (mixed) call_other(MEMORY,"Load","History");
+
+    // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger, dann
+    if (!mappingp(channelH)){
+      // Zeiger erzeugen
+      channelH = ([]);
+      // und in den Memory schreiben
+      call_other(MEMORY,"Save","History",channelH);
+    }
+  } else {
+    // Keine Rechte im Memory, dann wird mit einem lokalen Zeiger gearbeitet.
+    channelH = ([]);
+  }
+
+  stats = (["time": time(),
+            "boot": capitalize(getuid(previous_object())||"<Unbekannt>")]);
+  new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
+  initialize();
+  map_objects(efun::users(), "RegisterChannels");
+  this_object()->send(CMNAME, this_object(),
+                      sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
+                              sizeof(channels),
+                              CountUser(channels)));
+}
+
+// reset() and cache_to() - Cache Timeout, remove timed out cached channels
+// SEE: new, send
+private int cache_to(string key, mapping m, int t)
+{
+  if(!pointerp(m[key]) || m[key][2] + 43200 > t) return 1;
+  return(0);
+}
+
+varargs void reset(int nonstd)
+{
+  channelC = filter_indices(channelC, #'cache_to/*'*/, channelC, time());
+  if (save_me_soon)
+  {
+    save_me_soon=0;
+    save_object(CHANNEL_SAVE);
+  }
+}
+
+// name() - define the name of this object.
+string name() { return CMNAME; }
+string Name() { return CMNAME; }
+
+// access() - check access by looking for the right argument types and
+//            calling access closures respectively
+// SEE: new, join, leave, send, list, users
+// Note: <pl> is usually an object, only the master supplies a string during
+//       runtime error handling.
+varargs private int access(mixed ch, mixed pl, string cmd, string txt)
+{
+  mixed co, m;
+
+  if(!stringp(ch) || !sizeof(ch = lower_case(ch)) || !channels[ch])
+    return 0;
+  if(!channels[ch][I_ACCESS]||!previous_object(1)||!extern_call()||
+     previous_object(1)==this_object()||
+     (stringp(channels[ch][I_MASTER])&&
+      previous_object(1)==find_object(channels[ch][I_MASTER]))||
+     getuid(previous_object(1)) == ROOTID)
+    return 2;
+  if(!objectp(pl) || 
+     ((previous_object(1)!=pl) &&(previous_object(1)!=this_object()))) 
+     return 0;
+   if(pointerp(channelB[getuid(pl)]) &&
+     member(channelB[getuid(pl)], cmd) != -1)
+    return 0;
+   if(stringp(channels[ch][I_MASTER]) &&
+     (!(m = find_object(channels[ch][I_MASTER])) ||
+      (!to_object(channels[ch][I_ACCESS]) ||
+       get_type_info(channels[ch][I_ACCESS])[1])))
+  {
+    string err;
+    if(!objectp(m)) err = catch(load_object(channels[ch][I_MASTER]);publish);
+    if(!err &&
+       ((!to_object(channels[ch][I_ACCESS]) ||
+         get_type_info(channels[ch][I_ACCESS])[1]) &&
+        !closurep(channels[ch][I_ACCESS] =
+                  symbol_function("check",
+                                  find_object(channels[ch][I_MASTER])))))
+    {
+      log_file("CHANNEL", sprintf("[%s] %O -> %O\n",
+                                  dtime(time()), channels[ch][I_MASTER],
+                                  err));
+      channels = m_copy_delete(channels, ch);
+      return 0;
+    }
+    this_object()->join(ch, find_object(channels[ch][I_MASTER]));
+  }
+  if(closurep(channels[ch][I_ACCESS]))
+      return funcall(channels[ch][I_ACCESS],
+                     channels[ch][I_NAME], pl, cmd, &txt);
+}
+
+// new() - create a new channel
+//         a channel with name 'ch' is created, the player is the master
+//         info may contain a string which describes the channel or a closure
+//         to display up-to-date information, check may contain a closure
+//         called when a join/leave/send/list/users message is received
+// SEE: access
+
+#define IGNORE  "^/xx"
+
+varargs int new(string ch, object pl, mixed info)
+{
+  mixed pls;
+
+  if(!objectp(pl) || !stringp(ch) || !sizeof(ch) || channels[lower_case(ch)] ||
+     (pl == this_object() && extern_call()) ||
+     sizeof(channels) >= MAX_CHANNELS ||
+     sizeof(regexp(({ object_name(pl) }), IGNORE)) ||
+     (pointerp(channelB[getuid(pl)]) &&
+      member(channelB[getuid(pl)], C_NEW) != -1))
+    return E_ACCESS_DENIED;
+
+  if(!info) {
+    if(channelC[lower_case(ch)]) {
+      ch = channelC[lower_case(ch)][0];
+      info = channelC[lower_case(ch)][1];
+    }
+    else return E_ACCESS_DENIED;
+  }
+  else channelC[lower_case(ch)] = ({ ch, info, time() });
+
+  pls = ({ pl });
+
+  channels[lower_case(ch)] = ({ pls,
+                                symbol_function("check", pl) ||
+                                #'check/*'*/, info,
+                                (!living(pl) &&
+                                 !clonep(pl) &&
+                                 pl != this_object()
+                                 ? object_name(pl)
+                                 : pl),
+                                 ch,
+                             });
+
+  // ChannelH fuer einen Kanal nur dann initialisieren, wenn es sie noch nich gibt.
+  if ( !pointerp(channelH[lower_case(ch)]) )
+         channelH[lower_case(ch)] = ({});
+
+  if(pl != this_object())
+    log_file("CHANNEL.new", sprintf("[%s] %O: %O %O\n",
+                                    dtime(time()), ch, pl, info));
+  if(!pl->QueryProp(P_INVIS))
+    this_object()->send(CMNAME, pl,
+                        "laesst die Ebene '"+ch+"' entstehen.", MSG_EMOTE);
+  stats["new"]++;
+ 
+  save_me_soon=1;
+  return(0);
+}
+
+// join() - join a channel
+//          this function checks whether the player 'pl' is allowed to join
+//          the channel 'ch' and add if successful, one cannot join a channel
+//          twice
+// SEE: leave, access
+int join(string ch, object pl)
+{
+  if(!funcall(#'access,&ch, pl, C_JOIN)) return E_ACCESS_DENIED;
+  if(member(channels[ch][I_MEMBER], pl) != -1) return E_ALREADY_JOINED;
+  channels[ch][I_MEMBER] += ({ pl });
+  return(0);
+}
+
+// leave() - leave a channel
+//           the access check in this function is just there for completeness
+//           one should always be allowed to leave a channel.
+//           if there are no players left on the channel it will vanish, unless
+//           its master is this object.
+// SEE: join, access
+int leave(string ch, object pl)
+{
+  int pos;
+  if(!funcall(#'access,&ch, pl, C_LEAVE)) return E_ACCESS_DENIED;
+  channels[ch][I_MEMBER] -= ({0}); // kaputte Objekte erstmal raus
+  if((pos = member(channels[ch][I_MEMBER], pl)) == -1) return E_NOT_MEMBER;
+  if(pl == channels[ch][I_MASTER] && sizeof(channels[ch][I_MEMBER]) > 1)
+  {
+    channels[ch][I_MASTER] = channels[ch][I_MEMBER][1];
+    if(!pl->QueryProp(P_INVIS))
+      this_object()->send(ch, pl, "uebergibt die Ebene an "
+                          +channels[ch][I_MASTER]->name(WEN)+".", MSG_EMOTE);
+  }
+  channels[ch][I_MEMBER][pos..pos] = ({ });
+
+
+  if(!sizeof(channels[ch][I_MEMBER]) &&
+     !stringp(channels[ch][I_MASTER]))
+  {
+    // delete the channel that has no members
+    if(!pl->QueryProp(P_INVIS))
+      this_object()->send(CMNAME, pl,
+                          "verlaesst als "
+                          +(pl->QueryProp(P_GENDER) == 1 ? "Letzter" :
+                            "Letzte")
+                          +" die Ebene '"
+                          +channels[ch][I_NAME]
+                          +"', worauf diese sich in einem Blitz oktarinen "
+                          +"Lichts aufloest.", MSG_EMOTE);
+    channelC[lower_case(ch)] = ({ channels[ch][I_NAME],
+                                  channels[ch][I_INFO], time() });
+    m_delete(channels, lower_case(ch));
+
+    // Wird ein Channel entfernt, wird auch seine History geloescht
+    channelH = m_copy_delete(channelH, lower_case(ch));
+
+    stats["dispose"]++;
+    save_me_soon=1;
+  }
+  return(0);
+}
+
+// send() - send a message to all recipients of the specified channel 'ch'
+//          checks if 'pl' is allowed to send a message and sends if success-
+//          ful a message with type 'type'
+//          'pl' must be an object, the message is attributed to it. e.g.
+//            ignore checks use it. It can be != previous_object()
+// SEE: access, ch.h
+varargs int send(string ch, object pl, string msg, int type)
+{
+  int a;
+
+  if(!(a = funcall(#'access,&ch, pl, C_SEND, &msg))) return E_ACCESS_DENIED;
+  if(a < 2 && member(channels[ch][I_MEMBER], pl) == -1) return E_NOT_MEMBER;
+  if(!msg || !stringp(msg) || !sizeof(msg)) return E_EMPTY_MESSAGE;
+  map_objects(channels[ch][I_MEMBER],
+              "ChannelMessage", ({ channels[ch][I_NAME], pl, msg, type }));
+  if(sizeof(channelH[ch]) > MAX_HIST_SIZE)
+    channelH[ch] = channelH[ch][1..];
+  channelH[ch] += ({ ({ channels[ch][I_NAME],
+                        (stringp(pl)
+                         ? pl
+                         : (pl->QueryProp(P_INVIS)
+                            ? "/("+capitalize(getuid(pl))+")$" : "")
+                         + (pl->Name(WER, 2) || "<Unbekannt>")),
+                         msg+" <"+strftime("%a, %H:%M:%S")+">\n",
+                         type }) });
+  return(0);
+}
+
+// list() - list all channels, that are at least receivable by 'pl'
+//          returns a mapping,
+// SEE: access, channels
+private void clean(string n, mixed a) { a[0] -= ({ 0 }); }
+mixed list(object pl)
+{
+  mapping chs;
+  
+  chs = filter_indices(channels, #'access/*'*/, pl, C_LIST);
+  walk_mapping(chs, #'clean/*'*/);
+  if(!sizeof(chs)) return E_ACCESS_DENIED;
+  return deep_copy(chs);
+}
+
+// find() - find a channel by its name (may be partial)
+//          returns an array for multiple results and 0 for no matching name
+// SEE: access
+mixed find(string ch, object pl)
+{
+  mixed chs, s;
+  if(stringp(ch)) ch = lower_case(ch);
+  if( !sizeof(regexp(({ch}),"^[<>a-z0-9#-]*$")) ) return 0; // RUM
+  if(!sizeof(chs = regexp(m_indices(channels), "^"+ch+"$")))
+    chs = regexp(m_indices(channels), "^"+ch);
+  if((s = sizeof(chs)) > 1)
+    if(sizeof(chs = filter(chs, #'access/*'*/, pl, C_FIND)) == 1)
+      return channels[chs[0]][I_NAME];
+    else return chs;
+  return ((s && funcall(#'access,chs[0], pl, C_FIND)) ? channels[chs[0]][I_NAME] : 0);
+}
+
+// history() - get the history of a channel
+// SEE: access
+mixed history(string ch, object pl)
+{
+  if(!funcall(#'access,&ch, pl, C_JOIN))
+    return E_ACCESS_DENIED;
+  return deep_copy(channelH[ch]);
+}
+
+// remove - remove a channel (wird aus der Shell aufgerufen)
+// SEE: new
+mixed remove(string ch, object pl)
+{
+  mixed members;
+
+  if(previous_object() != this_object())
+    if(!stringp(ch) ||
+       pl != this_player() || this_player() != this_interactive() ||
+       this_interactive() != previous_object() ||
+       !IS_ARCH(this_interactive()))
+      return E_ACCESS_DENIED;
+
+  if(channels[lower_case(ch)]) {
+    channels[lower_case(ch)][I_MEMBER] =
+        filter_objects(channels[lower_case(ch)][I_MEMBER],
+                       "QueryProp", P_CHANNELS);
+    map(channels[lower_case(ch)][I_MEMBER],
+        lambda(({'u/*'*/}), ({#'call_other/*'*/, 'u, /*'*/
+                                   "SetProp", P_CHANNELS,
+                                   ({#'-/*'*/,
+                                          ({#'call_other/*'*/, 'u, /*'*/
+                                                 "QueryProp", P_CHANNELS}),
+                                          '({ lower_case(ch) })/*'*/,})
+                                   })));
+    channels = m_copy_delete(channels, lower_case(ch));
+
+    // Wird ein Channel entfernt, wird auch seine History geloescht
+    if( pointerp(channelH[lower_case(ch)] ))
+     channelH = m_copy_delete(channelH, lower_case(ch));
+
+    stats["dispose"]++;
+  }
+  if(!channelC[lower_case(ch)])
+    return E_ACCESS_DENIED;
+  channelC = m_copy_delete(channelC, lower_case(ch));
+  save_me_soon=1;
+  return(0);
+}
+
+// Wird aus der Shell aufgerufen 
+mixed clear_history(string ch)
+{
+  mixed members;
+  // Sicherheitsabfragen
+  if(previous_object() != this_object())
+    if(!stringp(ch) ||
+       this_player() != this_interactive() ||
+       this_interactive() != previous_object() ||
+       !IS_ARCH(this_interactive()))
+      return E_ACCESS_DENIED;
+
+  // History des Channels loeschen
+  if( pointerp(channelH[lower_case(ch)] ))
+    channelH[lower_case(ch)]=({});
+
+  return 0;
+}