blob: 1331d574b132309b471f9718bc9e62c5f033b047 [file] [log] [blame]
// 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;
}