1
0
Fork 0
mirror of git://git.psyced.org/git/psyced synced 2024-08-15 03:25:10 +00:00
psyced/world/net/user.c
2015-09-29 15:39:10 +02:00

2117 lines
67 KiB
C

// $Id: user.c,v 1.547 2008/12/22 11:11:36 lynx Exp $ // vim:syntax=lpc
//
// common subclass for PSYC clients, whatever the user interface
// local debug messages - turn them on by using psyclpc -DDuser=<level>
#ifdef Duser
# undef DEBUG
# define DEBUG Duser
#endif
#include <net.h>
#include <person.h>
#include <psyc.h>
#include <uniform.h>
inherit NET_PATH "person";
inherit NET_PATH "common";
inherit NET_PATH "sockets";
#include <text.h>
//virtual inherit NET_PATH "output";
#define NO_INHERIT
#include <user.h>
#undef NO_INHERIT
volatile mixed query;
volatile mapping tags;
volatile int showEcho;
volatile mixed beQuiet;
// my nickspace. used by psyctext(). could be passed as closure, but then
// it wouldn't be available for *any* psyctext call in user objects.
uni2nick(source, vars) {
P2(("uni2nick(%O) in %O\n", source, ME))
#ifdef USE_AUTOALIAS
string ni = raliases[source];
if (ni) return ni;
if (member(raliases, source)) return UNIFORM(source); // marked dead
// ok let's do an autoalias then
string ln;
// currently only net/irc employs objects as psyctext sources
// we should either use them more aggressively or abolish them
// and have mixed case uniforms instead
if (objectp(source)) ni = source->qName();
else {
mixed u = parse_uniform(source);
// unfortunately UNick already comes lowercased..
// we should start using mixed case uniforms..
unless (u && ni = u[UNick]) {
// source is probably a server root
raliases[source] = 0; // mark dead
return source;
}
}
ln = lower_case(ni);
string al = aliases[ln];
if (al) {
if (UNIFORM(source) == UNIFORM(al)) {
// just the object of a known uniform or vice versa
raliases[source] = ni;
P2(("uni2nick: re-aliased %O to %O\n", source, al))
return ni;
}
raliases[source] = 0; // mark dead
P2(("uni2nick: already belongs to %O\n", al))
w("_failure_unavailable_alias", 0,
([ "_uniform_new": source,
"_uniform_old": al,
"_nick_old": ni ]) );
return UNIFORM(source);
}
aliases[ln] = source;
raliases[source] = ni;
P2(("uni2nick: autoaliased %O to %O\n", source, ni))
w("_notice_add_alias_temporary", 0,
([ "_uniform_entity": source,
"_nick_entity": ni ]) );
return ni;
#else
// this enables local nickspace, but it fails on local uniforms,
// it fails when local nick collides with alias, so either you make
// it super complicated here, or you keep it super stupid.
//if (objectp(n)) return n->qName();
# ifdef ALIASES
return raliases[lower_case(source)] || source;
# else
return source;
# endif
#endif
}
// jabber needs no private echo but plenty of public echo..
// net/jabber takes care of that, we just give it echo here.
// in psyc echoes are essential, so they are on.
// on irc it's better to turn them on, but you first need a
// client like irssi. for telnets and webchats you usually
// want echoes, but you can turn them off
#define ASSIGN_SHOWECHO(setting, scheme) switch(scheme) { \
case "psyc": \
case "jabber": \
showEcho = 1; \
break; \
case "irc": \
showEcho = setting == "on"; \
break; \
default: \
showEcho = setting != "off"; \
}
#include "usercmd.i"
sName(a) {
// currently this sName gets called more than once
//ASSERT("user:sName called again", !MYNICK, MYNICK)
// i don't know if this should get cleaned up TODO
unless (places) {
places = ([ ]);
tags = ([ ]); // this doesn't need an extra if, does it?
cmdchar = '/';
}
return ::sName(a);
}
qHasCurrentPlace() {
return 1;
}
qResource() {
return v("scheme");
}
#ifdef HTTP_PATH
makeToken() {
string token = sprintf("%x", random(time() ^ 98987));
P2(("%O's new %O and old %O token\n", ME, token, v("token")))
vSet("token", token);
return token;
}
validToken(token) {
P2(("token %O == given %O?\n", v("token"), token))
if (is_localhost(query_ip_number())) return 1;
if (v("token") && token != v("token")) return 0;
return 1;
}
volatile mapping descvars;
#ifndef DEFAULT_FILE_STYLE_EXAMINE
//# define DEFAULT_FILE_STYLE_EXAMINE "http://www.psyced.org/examine.css"
# define DEFAULT_FILE_STYLE_EXAMINE "/static/examine.css"
#endif
htDescription(anonymous, query, headers, qs, variant, vars) {
string k, t, page, foto, buttons, profile, nick, type;
#ifndef HTTP_PATH
int doTalk = 1;
#else
int doTalk = 0;
P3(("htDescription in %O for %O showing %O or %O\n", ME, qs, vars, descvars))
if (query) sTextPath(query["layout"] || v("layout"),
query["lang"] || v("language"), "html");
else sTextPath(v("layout"), v("language"), "html");
#endif
unless (vars) {
unless (descvars) return 0;
// vars = descvars; descvars = 0;
vars = copy(descvars); // will be modded later
// right now we do not htquote the descvars.
// seen how much creativity not quoting html can
// produce we should probably at least allow friends
// to send us html code in profiles.. whatcha think?
// then again, maybe only certain fields should be
// htmlized, otherwise psyc profiles end up heavily
// polluted by html code.. extreme solution: let
// every var have an _HTML trailed variant?!?
// or go the other way: accept html code in psyc
// profiles, but remove it when outputting to plain
// devices? that's a smart thing to say. (buy the record!)
}
// store this before we delete some vars
//doTalk = vars["_language"] || vars["_color"];
// adventurous guess.. what if a user provides this? hmm
// well it looks funny, that's all
if (stringp(nick = vars["_nick_place"])) {
type = "_place";
// used by exa() in _PAGES_start_description
vars["_nick"] = nick;
} else {
type = "_person";
nick = vars["_nick"];
}
// we could also try _identification here, or even apply inheritance
if (vars["_source"]) profile = ppl[ vars["_source"] ];
if (!profile && stringp(nick)) profile = ppl[ lower_case(nick) ];
page = listDescription(vars, 0, variant
? "<a href=\"javascript:exa('%s')\">%s</a>"
: "<a href=\"%s\">%s</a>"); // direct psyc: links!
if (anonymous) {
// anonymous profile inspection from the web
// we are rendering ourselves for somebody else
// not the other way around
} else if (type == "_place") {
// new meaning of _context used here.. ay ay ay
// we should only show one of these
buttons = T("_HTML_examine_context_enter", "")
+ T("_HTML_examine_context_leave", "");
} else {
string display = "_normal";
switch (profile && profile[PPL_DISPLAY]) {
case PPL_DISPLAY_NONE:
// this, of course, needs to happen before we use
// the "doTalk" variable
doTalk = 0;
display = "_none";
break;
case PPL_DISPLAY_SMALL:
display = "_reduce";
break;
case PPL_DISPLAY_BIG:
display = "_magnify";
break;
}
vars["_display_none"] = "";
vars["_display_reduce"] = "";
vars["_display_magnify"] = "";
vars["_display_normal"] = "";
vars["_display"+display] = "selected";
if (profile && profile[PPL_NOTIFY] >= PPL_NOTIFY_PENDING) {
buttons = T("_HTML_examine_friend_delete", "");
if (doTalk) buttons += T("_HTML_examine_talk", "");
} else {
buttons = T("_HTML_examine_friend_new", "");
if (doTalk && !FILTERED(vars["_source"]))
buttons += T("_HTML_examine_talk", "");
}
#ifdef TELEPHONY_SECRET
buttons += T("_HTML_examine_button_call", "");
#endif
buttons += T("_HTML_examine_display", "");
}
#ifndef _flag_enable_profile_table
page = T("_list_description_on", "")
+ page
+ T("_list_description_off", "");
#else
page = T("_list_description_on_table", "")
+ page
+ T("_list_description_off_table", "");
#endif
if (buttons) page = T("_HTML_examine_buttons_start", "")
+ buttons + T("_HTML_examine_buttons_end", "") + page;
if (t = vars["_uniform_photo"] || vars["_uniform_photo_small"]) {
foto = "<img class=\"Peup\" src=\""+ t +"\">";
if (vars["_page_photo"])
foto = "<a target=\"_blank\" href=\""
+ vars["_page_photo"] +"\">"+ foto +"</a>";
// foto = "<span class=\"Peup\">"+ foto +"</span>\n";
} else foto = "";
if (variant) page = "<link rel=\"stylesheet\" href=\""+
(vars["_uniform_style"] || DEFAULT_FILE_STYLE_EXAMINE)
+"\">\n"+ T("_PAGES_start_description"+ variant, "") + page
+ T("_PAGES_end_description"+ variant, "");
else {
// we send only the "body" of the page to psyc clients.
// psyczilla doesn't really need a form,
// but the formatting goes wrong if there isn't one
page = foto +"\n<form class='Pef'>\n"+ page +"</form>";
}
P4(("EXAMINE psyctext %O with %O\n", page, vars))
// <form target=cout_" CHATNAME " name=f" HTFORMOPTS "
#ifdef HTTP_PATH
sTextPath(v("layout"), v("language"), v("scheme"));
#endif
// <input type=hidden name=user value=\""+qName()+"\">\n\
// <input type=hidden name=token value=\""+v("token")+"\">\n\
// <input type=hidden name=lang value=\""+v("language")+"\">\n\
//
# ifdef _flag_enable_module_microblogging
string htchannels;
mapping channels;
if (mappingp(vars["_channels"])) channels = vars["_channels"];
else channels = parse_json(vars["_channels"]);
if (channels) {
P3((">>> channels: %O\n", channels))
htchannels =
"<script type='text/javascript'>\n"
"function selectTab(parent, id) {"
"var tabs = document.getElementById(parent);"
"var tab = tabs.firstChild;"
"while (tab) {"
"tab.className = (tab.id == parent+'-'+id) ? 'selected' : '';"
"tab = tab.nextSibling;"
"}"
"}\n"
"function switchTab(id) {"
"selectTab('tabs', id);"
"selectTab('tab-contents', id);"
"}\n"
"</script>\n";
string tabs = "";
string contents = "";
int first = 1;
object threads = clone_object(NET_PATH "place/threads");
foreach (string channel, array(mapping) entries : channels) {
unless (sizeof(entries) > 0) continue;
tabs +=
"<a id='tabs-" + channel + "' onclick=\"switchTab('" + channel + "')\" "
+ (first ? "class='selected'" : "") + ">#" + channel + "</a>\n";
contents +=
"<div id='tab-contents-" + channel + "' "
+ (first ? "class='selected'" : "") + ">" + threads->htmlEntries(entries, 1, 1, !first, channel, anonymous ? "" : vars["_identification"] + "#" + channel, vars["_profile_url"] + "/" + channel) + "</div>\n";
first = 0;
}
if (first) htchannels = "";
else htchannels +=
"<div id='tabs' class='tabs'>\n" + tabs + "</div>\n"
"<div id='tab-contents' class='tab-contents'>\n" + contents + "</div>\n";
}
# endif
return psyctext(page, vars + ([
"_FORM_start" : "\
<form class=\"Pef\" name=\"Pef\" action=\"\">\n\
<input type=\"hidden\" name=\"cmd\" value=\"\">\n\
<input type=\"hidden\" name=\"dest\" value=\"\">\n",
"_QUERY_STRING" : qs || "",
"_HTML_photo" : foto,
"_nick_me" : MYNICK,
"_FORM_end" : "</form>\n",
])
)
# ifdef _flag_enable_module_microblogging
+ htchannels
# endif
;
}
#endif
// leavePlace: extracted from _echo_place_leave handling so we can
// do it without depending on the place's answer
//
// "impatient leave" is an apparently necessary strategy:
// _ when servers crash, the UNI would not reissue a join request because
// it still sees the context in its places. this also helps with remote MUCs.
// _ the /cycle feature of some IRC clients expects no delays between direct
// subsequent PART and JOIN commands, but if psyced hasn't received leave
// confirmation yet, it used to simply ignore the JOIN.
//
// both issues could probably also be solved with glue around
// "polite leave", but it's a lot more complicated and maybe it's
// the wrong strategy anyhow.
//
protected leavePlace(where) {
P2(("%O leavePlace's %O\n", ME, where))
if (place == where) {
// current place.. psyced specific thing
place = 0;
// vDel("place"); // wrong! dann kommen die
// leute nich mehr in ihren
// letzten raum rein!
}
// leave the context slave, so we immediately stop getting
// the context's traffic even before it knows about us leaving
deregister_context(ME, where);
// after this step any castmsg from 'where' will be considered spam.
// luckily we're not in the context slave anymore.
m_delete(places, where);
}
/** PSYC-conformant message receiving function
**
** after doing basic PSYC, ppl and lastlog processing in
** person:msg() we're here to do the output..
*/
msg(source, mc, data, mapping vars, showingLog) {
string family;
mixed pal, t, variant;
int glyph;
#ifdef USE_THE_NICK
string nick, nick2;
#endif
P3(("%O user:msg(%O,%O,%O,%O)\n", ME, source, mc, data, vars))
// context checking since ip paranoia is applied to context
// not source whenever context is available..
//
// this should probably also apply for objectp sources since
// one day even they might be spoofed by a smart context..
// but right now sources are not resolved to objects
//
// also.. this belongs into person.c if it werent for the
// rplaces ... hmmm!
// Note: with context mcast we dont need this any more as we
// will only get valid contexts
if (vars["_context"]) {
if (showingLog) {
// sometimes lastlog comes with a _context which i
// need to rename here as an indicator for the final
// renderer that this is a retransmission
vars["_INTERNAL_context"] = vars["_context"];
m_delete(vars, "_context");
// unless (source) source = vars["_source_relay"];
#if DEBUG > 0
} else if (!objectp(vars["_context"]) &&
!places[vars["_context"]] &&
// !psyc_object(vars["_context"]) &&
!abbrev("_notice_place", mc)) { // just _notice_place? TODO
string ctx, chan;
// monitor_report("_warning_abuse_invalid_context",
// S("%O: Invalid context %O in %O from %O (%O)",
// ME, vars["_context"], mc, source, data));
if ((sscanf(vars["_context"], "%s#%s", ctx, chan) == 2)
&& places[ctx]) {
P2(("got valid place context %O with channel %O\n",
ctx, chan))
// a friendcast or status update
} else if (member(ppl, ctx) &&
ppl[ctx][PPL_NOTIFY] >= PPL_NOTIFY_FRIEND) {
P2(("got friendcast from %O with channel %O\n",
ctx, chan))
} else {
P1(("Invalid context %O from source %O:\n\
\t%s(%O) with %O ... stack %O ... places %O\n",
ctx, source, to_string(mc), data, vars,
caller_stack(1), places))
return 0;
}
#endif
}
unless (source) source = vars["_source_relay"] || vars["_context"];
}
// before? after?
unless (source) source = vars["_INTERNAL_source"];
// call the psyc handler in person.c
// it returns some extra info concerning display
t = source; // keep the real source before entity.c puts the UNI into it
// and here we call person::msg() without ensuring that local sources
// are objectp. this may cause problems. shouldn't we have all the
// location resolution happen BEFORE we let person act on things?
// this looks like a big TODO here.
// <fippo>: if _INTERNAL_target_resource is set, calling person::msg
// is INAPPROPRIATE. This is the current terminology of
// net/jabber, but we will have to do something similar in net/psyc
//
// Rationale: if this variable is set, we dont want the uni to
// handle this.
#ifdef PRO_PATH
unless (variant = ::msg(&source, &mc, &data, &vars, showingLog))
return 0;
if (member(vars, "_authenticated")) variant = "_important";
#else
unless (::msg(&source, &mc, &data, &vars, showingLog)) {
P3(("%O ::msg no display for %O's %O\n", ME, source, mc))
return 0;
}
variant = "";
#endif
P4(("after p:msg(%O,%O,%O,%O) -> %O\n", source,mc,data,vars, variant))
// D2(unless (showingLog) D(S("user:msg(%O,%O,%O..) -> %O\n",
// source, mc, data, variant));)
#ifdef USE_THE_NICK
nick = vars["_nick"];
#endif
#ifdef ALIASES
# ifdef BRAIN
unless (mappingp(v("aliases"))) vSet("aliases", ([]));
# endif
#endif
if (stringp(t)
#ifdef SLAVE
// we trust user if he got relayed from local room object
&&! objectp(vars["_context"])
#endif
#ifndef UNSAFE_LASTLOG
// evil scary rewrite to support changed lastlog behaviour ,)
// TODO:: watch for _source_relay||(source resp. t)
&&! (stringp(source)
&& abbrev(SERVER_UNIFORM +"~", source))
#endif
) {
#ifdef PRO_PATH
if (data && index(data, '\n') != -1)
data = replace(data, "\n", " ");
#endif
#ifdef USE_THE_NICK
if (t != source) {
if (objectp(source)) {
# ifdef ALIASES
string t2;
if (t2 = raliases[nick = source->qNameLower()]) {
nick = t2;
} else if (aliases[lower_case(nick)]) {
nick = t;
}
# else
nick = source->qName();
# endif
} else {
# ifdef ALIASES
nick = raliases[source] || source;
# else
nick = source;
# endif
}
# ifndef TRUST_PSYC_HACK
# ifdef ALIASES
} else if (raliases[source]) {
nick = raliases[source];
# endif
} else {
// short term solution (nick manager to follow)
//if (nick) nick = "("+ source + ") "+ nick;
// middle term solution
if (nick && nick != source) {
// this ensures we always escape the
// original nick, not an already escaped one
// other solution would probably be not
// to send the same vars to all users..
// eeeeh.. probably the better solution
unless (vars["_nick_verbatim"])
vars["_nick_verbatim"] = nick;
vars["_INTERNAL_nick_plain"] = nick;
// sometimes we get here with source being
// a context - that of course looks very
// stupid - how does it happen? TODO
// beQuiet is -1 exactly with those schemes
// where this nick patching habit is completely
// inappropriate. we may want to change the
// var name.
if (beQuiet != -1)
nick = psyctext(
T("_MISC_identification_remote",
"«[_source]» [_nick]"), ([
"_source":source,
"_nick":vars["_nick_verbatim"] ]));
}
else nick = stringp(source) ? source : to_string(source);
# if 0 //DEBUG > 1
// checking for validity of messages should
// happen at psyc-parsing level.. maybe? YEEEES!!
// and.. vars["_context"] is zero when showing log!
// member might help.
if (abbrev("_message", mc)) {
D("patching remote mc\n");
mc = member(vars, "_context") ?
"_message_public" : "_message_private";
}
// this just wont work.. it even breaks
// remote _message_echo_private's
# endif
# endif
}
nick2 = nick;
#endif // USE_THE_NICK
// belongs into person.c?
if (mc == "_message") mc = vars["_context"] ?
"_message_public" : "_message_private";
P3(("%O got msg(%O,%O,%O,%O)\n", ME,source,mc,data,vars))
}
#ifdef USE_THE_NICK
else if (nick && nick != MYNICK) {
# ifdef ALIASES
string tn;
// ein kleines bisschen hilflosigkeit (&&! showingLog)
//
// why are summoned_persons incarnated as users, not net/person??
// any reason? don't want to think over it, but it kinda sucks for
// aliases.. says tobij. lynX adds: well in fact with some clean
// up summoned persons could be reverted to NOT be users again.
// so please go ahead! TODO
if (raliases[tn = lower_case(nick)] &&! showingLog) {
nick2 = nick = raliases[tn];
} else if (aliases[tn] &&! showingLog) {
nick2 = nick = UNIFORM(source);
} else
# endif
nick2 = vars["_nick_stylish"] || vars["_nick_local"] || nick;
} else nick2 = nick = MYNICK;
P3(("q/n/n2: %O,%O,%O\n", MYNICK,nick,nick2))
#endif // USE_THE_NICK
t = vars && vars["_context"] || source;
#ifdef SANDBOX
unless (objectp(t) && stringp(geteuid(t)) && geteuid(t)[0] == '/') {
#else // duh. we cannot combine this, ldmud complains, might be a preprocessor
// bug or just my very own stupidity.
unless (objectp(t)) {
#endif
string rnick;
array(mixed) u;
#ifdef SANDBOX
if (objectp(t)) {
rnick = object_name(t);
rnick = rnick[rmember(rnick, '/')+1..];
if (!vars["_nick_place"]
|| stricmp(rnick, vars["_nick_place"])) {
// TODO: we shouldn't raise_error here, as we shouldn't
// allow unprivileged places to annoy users.
// what to do? enforce rnick? drop msg?
raise_error(sprintf("INVALID _nick_place in msg by %O(%O)\n",
t, rnick));
}
} else
#endif
if (stringp(t) && (rnick = (u = parse_uniform(t, 1))[UResource])
&& strlen(rnick)
&& rnick[0] == '@') {
string hnick = rnick[1..];
rnick = vars["_nick_place"];
if (!rnick || stricmp(hnick, rnick)) {
rnick = hnick;
}
vars["_nick_place"] = u[URoot] + "/@" + rnick;
}
}
// verrry similar code in psyctext()
// didn't we want to eliminate these?
// also we have the textdb with inheritance, so we could deal with
// these things in a proper way!
t = vars["_time_place"];
if (!t && showingLog) {
// _time_log is just for psyc clients: since we had to
// delete _time_INTERNAL from the psyc out, we need a
// variable for the case when this _really_ is a /log output
unless (t = vars["_time_log"]) {
if (t = vars["_time_INTERNAL"]) {
//m_delete(vars, "_time_INTERNAL");
vars["_time_log"] = t;
} else {
// sort-of impossible condition
P0(("%O got showingLog w/out _time in %O\n",
ME, vars))
}
}
P3(("%O time %O data %O from %O\n", ME, t, data, vars))
}
PSYC_TRY(mc) {
#ifndef ALPHA
case "_jabber_iq_error": // DONT reply
P2(("%O got %O", ME, mc))
break;
// TODO: add the xmlns to unsupported method format
// technisch gesehen sollten wir auch die resource
// wieder zurueckgeben an die das gesendet wurde...
// aber kriegen wir das hier?
case "_jabber_iq_get":
case "_jabber_iq_set":
sendmsg(source, "_error_unsupported_method", 0,
([ "_tag_reply" : vars["_tag"] ]));
break;
#endif
case "_jabber":
P1(("%O got %O", ME, mc))
break;
case "_notice_composing_media":
if (v("scheme") != "psyc") return 1;
break;
case "_message_video":
case "_message_audio":
// not being displayed to users other than psyc clients
if (v("scheme") != "psyc") return 1;
data = ""; // temporary for @welcome history
break;
case "_message_private_question":
variant = "_ask" + variant;
m_delete(vars, "_action");
// fall thru
case "_message": // sollte schon vorher abgefangen worden sein
mc = "_message_private";
// fall thru
case "_message_private":
// question recognition takes place on sender side
// for public talk, so it should also for private talk..
#ifdef USE_THE_NICK
#if 0
# ifdef BRAIN
P2(( "this shouldn't happen - the brain aliases bug\n" ))
unless (v("aliases")) vSet("aliases", ([]));
// for some reason v("aliases") can be undefined here
// but it never happens on my test server
# endif
if (objectp(source)) {
if (v("aliases")[nick]) { // nick collision detected!
nick = psyc_name(source);
} else {
nick = raliases[lower_case(nick)] || nick2;
// patchen in den vars.. vorsicht!
vars["_nick_long"] = vars["_nick"];
}
} else if (stringp(source)) {
nick = raliases[source] || nick2;
} else {
nick = nick2;
}
#else
nick = nick2;
#endif
#endif
#ifdef QUESTION_RECOGNITION_ON_RECEPTION
if (variant == "" && data && index(data, '?') != -1) {
// mc += "_ask";
variant = "_ask";
m_delete(vars, "_action");
break;
}
#endif
break;
case "_message_echo_private_question":
variant = "_ask" + variant;
m_delete(vars, "_action");
mc = mc[..<10];
case "_message_echo":
case "_message_echo_private":
unless (showingLog || showEcho) return 1;
#if 0 //def CAN_WE_DARE_THIS // why do we have this block at all!?
// this is the wrong way and the wrong place to create _source_relay
pal = vars["_nick_target"];
if (pal) {
if (wAction(mc, data, vars, source, variant, nick))
return 1;
#ifdef QUESTION_RECOGNITION_ON_RECEPTION
if (variant == "" && index(data, '?') != -1) {
w(mc+"_ask", data,
([ "_nick_target" : pal,
"_time_log" : vars["_time_log"],
"_source_relay" : source ]));
return 1;
}
#endif
// should have been "vars + ([" as below for public?
w(mc, data, ([ "_nick_target" : pal,
"_time_log" : vars["_time_log"],
"_source_relay" : source ]));
return 1;
}
return 0; // dont walk into _message_public if !pal
#else
break;
#endif
case "_message_public_question":
#ifndef NO_PUBLIC_QUESTIONS
variant = "_ask" + variant;
m_delete(vars, "_action");
#endif
mc = "_message_public";
// fall thru
case "_message_public":
// could theoretically move into the if, but the devil etc.
//unless (ONLINE) vInc("new");
if (source != ME) {
if (qHasCurrentPlace()) {
mixed room;
room = vars["_context"] || vars["_INTERNAL_context"];
if (room && room != place) { // && place ..not!
vSet("otherplace", room);
// rplaces will normally do the job
room = places[room] ||
(objectp(room) ? room->qName()
: to_string(room));
w((data ? "_message_public_other":
"_message_public_other_action")+variant,
data, vars + ([
#ifdef USE_THE_NICK
"_nick": nick2,
#endif
"_nick_place": room ]) );
return 1;
}
}
#ifdef USE_THE_NICK
nick = nick2;
#endif
break;
}
if (vars["_nick_local"] && MYLOWERNICK != lower_case(vars["_nick_local"])) {
// should we have special templates for this?
//mc = "_message_echo_public_masquerade";
#ifdef USE_THE_NICK
nick = vars["_nick_local"];
#endif
// or should we rather make sure that all clients
// learn to detect echo themselves, since future
// multicast routing may not give us the possibility
// to patch methods on the way to the client
// like we do here:
} else mc = "_message_echo_public";
// fall through for echoes coming from rooms
case "_message_echo_public":
unless (showingLog || showEcho || vars["_INTERNAL_force_echo"])
return 1;
break;
case "_message_announcement":
case "_message_behaviour_warning":
case "_message_behaviour_punishment":
case "_message_behaviour":
case "_message_twitter":
unless (stringp(data)) variant = "_default";
else variant = "";
#ifdef USE_THE_NICK
nick = nick2;
// fmt = nick ? "%s announces: %s\n"
// : "*** Announcement: %s ***\n";
#endif
break;
case "_request_message_public_question":
case "_request_message":
mc = "_request_message_public";
case "_request_message_public":
break;
case "_request_description":
// if (source == ME) return 1;
return 1;
case "_status_description":
case "_status_description_person":
case "_status_description_place":
P4(("x-ret %O\n", vars))
if (vars["_tag_reply"]) {
sscanf(vars["_tag_reply"], "%s %s", variant, t);
//PT(("format %O tag %O\n", variant, t))
descvars = vars;
descvars["_source"] = source;
switch (variant) {
default:
// client doesn't want HTML
if (sizeof(v("locations")[0])) sendmsg(m_indices(v("locations")[0])[0],
mc, data, ([
"_tag_reply": t,
"_source_relay": source,
"_uniform_style": vars["_uniform_style"]
]) + vars);
return 1;
case "_HTML":
// client wants HTML
if (sizeof(v("locations")[0])) sendmsg(m_indices(v("locations")[0])[0],
mc+"_HTML", htDescription(), ([
"_type_data": "text/html",
"_tag_reply": t,
"_source_relay": source,
"_uniform_style": vars["_uniform_style"]
]));
return 1;
case "_surf":
// we're doing a /surf, so stop here
return 1;
case "0":
case 0:
// fall thru to manual examine
}
}
// internal transformation of a single msg into a list of msgs
// hmmm.. well, that's how psyc to client happens here
mc = "_list"+ mc[7..];
w(mc+"_on", data, ([ // used by irc whois..
"_source_relay": source, "_tag_reply": t,
"_nick" : vars["_nick"] || vars["_nick_place"],
"_name_public" : vars["_name_public"] || "",
"_action_motto" : vars["_action_motto"] || "",
"_description_motto" : vars["_description_motto"] || ""
]));
listDescription(vars, 1);
w(mc+"_off", 0, ([
"_source_relay": source, "_tag_reply": t,
"_nick" : vars["_nick"] ]));
return 1;
case "_status_place_members_none_automatic":
if (beQuiet != -1) {
PT(("%O skipping %O from %O\n", ME, mc, source))
return 1;
}
break;
case "_status_place_members_automatic":
if (beQuiet != -1) {
PT(("%O skipping %O from %O\n", ME, mc, source))
return 1;
}
// fall thru
case "_status_place_members":
#ifdef PARANOID
//unless (pointerp(vars["_list_members"])) break;
// t = implode(renderMembers(vars["_list_members"],
// vars["_list_members_nicks"], objectp(source)), ", ");
if (stringp(t = vars["_list_members"])) { // _tab
vars["_list_members"] = ({ t });
} else unless (pointerp(vars["_list_members"])) break;
if (stringp(t = vars["_list_members_nicks"])) {
vars["_list_members_nicks"] = ({ t });
} // no else break here by intention, renderMembers() won't be
// scared by that.
#endif
// same code in _echo_place_enter etc.
// renderMembers is also useful for psyc clients
vars["_list_members_nicks"] =
renderMembers(vars["_list_members"],
vars["_list_members_nicks"], objectp(source));
break;
// if (v("verbatimuniform") == "on")
// t = replace(t, "@", "%");
// w(mc, "In [_nick_place]: [_list_members_nicks]", ([
// "_nick_place" : vars["_nick_place"],
// "_list_members_nicks" : t ]) );
// irc-thang: w(mc + "_end", 0, vars);
// return 1;
#ifdef TELEPHONY_SECRET
# include <sys/tls.h>
case "_notice_answer_call_click":
case "_notice_answer_call":
t = vars["_time_expire"];
if (to_int(t) < time()) {
P1(("Expired phone call for %O from %O: %O\n",
ME, source, t))
return 1; // call expired
}
string t2, t3;
t2 = "?thats=me&user="+ MYNICK +"&expiry="+ t +"&jack="+
(t3 = hmac(TLS_HASH_SHA256, TELEPHONY_SECRET,
"I"+ t +":"+ MYNICK));
P1(("hmac/call %O for %O\n", t3, "I"+t+":"+MYNICK))
t3 = NET_PATH "http/call"->make_session(MYNICK, to_int(t), t3);
w("_notice_answer_call_link", 0, ([
"_page_call": (
#ifdef __TLS__
(tls_available() && HTTPS_URL) ||
#endif
HTTP_URL) + NET_PATH +"http/call?thats=me&sid=" +t3,
# ifdef TELEPHONY_SERVER
"_uniform_call": TELEPHONY_SERVER + t2,
# endif
]));
return 1;
#endif
case "_notice_received_email":
case "_notice_email_received":
if (stringp(vars["_origin"])) vars["_origin"] =
decode_embedded_charset(vars["_origin"]);
if (stringp(vars["_subject"])) vars["_subject"] =
decode_embedded_charset(vars["_subject"]);
break;
case "_notice_presence_here_quiet":
mc = mc[..<7]; // _quiet is just internal
// humm.. not since i started using it for CACHE_PRESENCE
// problem? should i use _request_friend_present instead?
// fall thru
case "_notice_presence":
case "_status_presence":
break;
case "_notice_person_absent_netburp":
if (vars["_context"] != place) return 1;
break;
case "_failure_unsuccessful_delivery":
case "_failure_network_connect_invalid_port":
// is this the right place to do this? i have seen a recursion where
// person.c was never asked for opinion, so i'm putting this into user.c
#if 0 //def ALPHA
string loc;
foreach (t, loc : v("locations")) {
if (member(loc, vars["_source_relay"])) {
P1(("%O in %O talking to its %O location at %O.",
mc, ME, t, loc))
sLocation(t, 0);
}
}
#else
foreach (string type, mapping locs : v("locations")) {
if (member(locs, vars["_source_relay"])) {
P0(("%O got %O, deleting %O from %O\n", ME, mc, vars["_source_relay"], v("locations")))
m_delete(locs, vars["_source_relay"]);
unless (sizeof(locs)) m_delete(v("locations"), type);
}
}
P4(("locations left: %O\n", v("locations")))
#endif
// fall thru - not strictly necessary but adds a feature
case "_failure_redirect_permanent":
// we currently have no implementation for permanent changes
case "_failure_redirect_temporary":
case "_failure_redirect":
// current policy: we trust local senders to do this
if (objectp(source) && vars["_source_redirect"]) {
PT(("%O REDIRECTED TO %O\n", ME,
vars["_source_redirect"]))
// how did we get here without a vars["_method_relay"]?
mc = vars["_method_relay"];
// intended for _enter and _leave, but right now
// only places send this, so it's ok to simplify
if (mc && abbrev("_request", mc)) {
// but we use placeRequest here, because we
// need a _tag
placeRequest(vars["_source_redirect"],
mc, 0, 1);
} // else.. we'll see
return 1;
}
break;
case "_echo_place_enter_INTERNAL_CHECK": // only do the check and dont write anything
pal = 3093;
// _unicast comes from MUCs and carries a faked _context
// that's why it has to be a _notice even if it's an _echo
// maybe psycmatch() could solve this issue instead of abbrev()
case "_echo_place_enter_unicast":
case "_notice_place_enter_unicast_INTERNAL_ECHO": // MUC join
case "_echo_place_enter_automatic":
case "_echo_place_enter_automatic_subscription":
case "_echo_place_enter_subscribe": // /sub place differs from auto-
// joining a subscribed place after
// logon
variant = 2933; // do not make it the current place
case "_echo_place_enter": // = /c <channel>
case "_echo_place_enter_other": // = /c ??
case "_echo_place_enter_login": // primary join, but used to be quiet..
if (beQuiet != -1 && vars["_context"] != place) {
if (beQuiet == source) pal = 3093;
else beQuiet = source;
}
case "_echo_place_enter_follow": // = /f
case "_echo_place_enter_home": // = /h
case "_echo_place_enter_join": // = /j <channel>
P3(("%O %O %O\n", ME, mc, vars))
# ifndef TAGS_ONLY
if (ME == source // || psyc_object(source) == ME
// weil bei remotes _source_relay der string von ME ist..
// TODO: die Policy dass die Raeume das echo nach dem
// castmsg direkt an uns sendet sollte den Code
// hier vereinfachen
// u.u. auch deshalb, weil wir unterschiedliche
// cases machen koennen fuer _notice und _echo
// || vars["_nick_verbatim"] == MYNICK
|| ME == vars["_source_relay"]
|| ME == psyc_object(vars["_source_relay"])) {
# else
if (objectp(source) || tags[vars["_tag_reply"]]
|| tags[vars["_tag"]] // BUGGY PLACES
) {
# endif
mixed p;
p = vars["_context"] || source;
t = vars["_nick_place"];
# if 0 // DEBUG_FLAGS & 0x02 // abgesehen davon wurde der auch bei D0...
// dieser unless leuchtet mir nicht mehr ein..
// warum sollte context oder source jemals ME sein?
unless (t || p == ME) {
log_file("USER", "%O < broken (%O,%s,%O,%O)\n",
ME, source, mc, data, vars);
return;
}
# endif
if (variant != 2933) { // primary join operation
place = p;
vSet("place", objectp(place) ? place->qName() : place);
P3(("%O goes to %O (%O)\n", ME,
place, v("place")))
} else {
P3(("%O just joins %O (%O)\n", ME, p, t))
}
#ifdef ENTER_MEMBERS
// net/jabber does some funky things that do
// not fit in here.
if (variant != 2933) switch(v("scheme")) {
case "jabber":
break;
case "irc":
case "psyc":
// this is a nice service the UNI provides
if (sizeof(vars["_list_members"])) { // _tab
vars["_list_members_nicks"] =
renderMembers(vars["_list_members"],
vars["_list_members_nicks"], objectp(source));
}
break;
default:
// this part belongs into the client emulator
// instead (excluding the renderMembers)
if (sizeof(vars["_list_members"])) { // _tab
vars["_list_members_nicks"] =
renderMembers(vars["_list_members"],
vars["_list_members_nicks"], objectp(source));
w("_status_place_members", 0, vars);
} else if (vars["_amount_members"])
w("_status_place_members_amount", 0, vars);
else
w("_status_place_members_none", 0, vars);
}
#endif
// so much semantics in here.. shouldnt this be in person.c ?
// wasn't user.c:msg just meant to be about display? TODO
// incoming context isnt always objectp()
// even for local contexts
// then again.. as of now it should
//rplaces[psyc_name(p)] = t;
unless (places[p]) {
places[p] = t;
// D(S("p, t: %O, %O\n", p, t));
} else {
// we have already joined this
return 1;
}
if (stringp(source)) {
P3(("%O joins mcast group for %O\n", ME, source))
register_context(ME, source);
}
variant = "";
P4(("enter: places = %O after %s(%O) from %O\n",
places, mc, vars, source))
} else {
// untagged or wrong tag
P0(("%O got untagged %s from %O: %O\n", ME, mc, source, vars))
// eh... da stimmt was net
return -1;
}
if (pal == 3093) return 1;
pal = 0;
break;
case "_notice_place_enter_automatic":
case "_notice_place_enter_automatic_subscription":
P3(("auto-enter(%O,%O,%O,%O) for %O in %O\n", source,
mc, data, vars, ME, place))
case "_notice_place_enter_subscribe":
case "_notice_place_enter": // = /c <channel>
case "_notice_place_enter_other": // = /c
case "_notice_place_enter_login": // primary join, but used to be quiet..
// _unicast comes from MUCs and carries a faked _context
case "_notice_place_enter_unicast":
//case "_notice_place_enter_quiet":
if (beQuiet != -1 && vars["_context"] != place) {
if (beQuiet == source) pal = 3093;
else beQuiet = source;
}
case "_notice_place_enter_follow": // = /f
case "_notice_place_enter_home": // = /h
case "_notice_place_enter_join": // = /j <channel>
P2(("msg.enter(%O,%O,%O,%O) for %O in %O\n", source,
mc, data, vars, ME, place))
// der neue parse.i liefert source als object
if (pal == 3093) return 1;
vSet("otherplace", vars["_context"] || source);
variant = "";
pal = 0;
break;
case "_notice_place_leave_automatic":
case "_notice_place_leave_automatic_subscription":
P2(("auto-leave(%O,%O,%O,%O) for %O in %O\n",
source, mc, data, vars, ME, place))
case "_notice_place_leave_logout":
case "_notice_place_leave_netburp":
case "_notice_place_leave_disconnect":
// _unicast comes from MUCs and carries a faked _context
case "_notice_place_leave_unicast":
if (beQuiet != -1 && vars["_context"] != place) {
if (beQuiet == source) pal = 3093;
else beQuiet = source;
}
// fall thru
case "_echo_place_leave_follow":
case "_echo_place_leave_home":
case "_echo_place_leave_invalid":
case "_echo_place_leave_netburp":
case "_echo_place_leave_other":
case "_echo_place_leave_reload":
case "_echo_place_leave_reload_server":
case "_echo_place_leave_subscribe":
case "_echo_place_leave_banned":
// stellt sich die frage, ob _notice und _echo wirklich
// beide existieren sollten, nur weil es ein unicast ist.
// _notice kann man doch auch unicasten, und man würde sich
// diese dämliche verdoppelung sparen.
case "_notice_place_leave_follow":
case "_notice_place_leave_home":
case "_notice_place_leave_invalid":
case "_notice_place_leave_other":
case "_notice_place_leave_reload":
case "_notice_place_leave_reload_server":
case "_notice_place_leave_subscribe":
case "_notice_place_leave_banned":
variant = 0; // no double variants
// fall further
case "_echo_place_leave":
case "_notice_place_leave":
// P1(("vars %O != %O(%O)\n", vars,place,v("place")))
#if 0
if ((ME == source || (member(rplaces, source) && nick ==
MYNICK)) && (t = vars["_nick_place"])) {
#else
if (ME == source && member(places, t = vars["_context"])
|| member(places, t = source)) {
#endif
if (abbrev("_notice_place_leave", mc)
&&! abbrev("_notice_place_leave_reload", mc))
mc[0..6] = "_echo";
#if 0
P2(("%O %s's %O\n", ME, mc, t))
m_delete(places, t); // places -= ([ t ]);
P4(("%O %O: place %O, t %O, context %O\n", ME, mc,
place, t, vars["_context"]))
// t should be more accurate
if (place == (vars["_context"] || source)) {
place = 0;
// vDel("place"); // wrong! dann kommen die
// leute nich mehr in ihren
// letzten raum rein!
// leave the context slave
// BUG!! this does not belong within this if
deregister_context(ME, vars["_context"] || source);
// t should be more accurate
}
#else
leavePlace(t);
#endif
}
if (pal == 3093) return 1;
pal = 0;
break;
case "_notice_list_feature_server":
case "_notice_list_feature":
case "_notice_list_item":
case "_notice_list":
case "_notice":
case "_status_place_topic_official":
case "_status_place_topic":
case "_status_place":
case "_status":
break; // optimization: avoid frequent unnecessary slicing
PSYC_SLICE_AND_REPEAT
}
#if 0 // reserved for the future when aliases are also affecting places
if (v("aliases")[nick])) {
if (objectp(source)) { // this should alway be true
// as /alias doesn't allow aliases to
// contain ":", but you can never know.
// nick collision!
nick = psyc_name(source);
}
}
#endif
#ifdef USE_THE_NICK
// imho macht das hier probleme bei _echo_place_enter
// da ist der raum die source, es ist kein _context dabei
// vars["_nick"] ist der nick des joinenden (==qName())
// u:msg _nick "fippo" is changed into
// "«psyc://goodadvice.pages.de/@your-community» fippo"
// IMHO this stuff should be hacked into an _INTERNAL var,
// not a real var - fippo
D2( if (vars["_nick"] != nick)
D(S("u:msg _nick %O is changed into %O\n", vars["_nick"], nick)); )
#ifdef ALPHA
// DANGEROUS CHANGE! I added this line to make sure
// remote UNI is shown on private messages. let's see
// if it breaks anything --- 2003-05-09
//
// for irssi w/ no-irssi-echo-chatting a backup comes in handy.
// --- 2005-01-07
//
// tg has found out that this little line keeps masquerade from
// breaking for ircers: they would see object path otherwise
unless (vars["_nick_verbatim"]) vars["_nick_verbatim"] = vars["_nick"];
#endif
vars["_nick"] = nick;
// this at least works also for HTTP
#endif
if (! wAction(mc, data, vars, source, variant
#ifdef USE_THE_NICK
, nick
#endif
)) {
if (!data && vars["_action_possessive"])
w(mc+"_action_possessive", data, vars, source, showingLog);
else
w(variant ? mc+variant : mc, data, vars, source, showingLog);
}
return 1;
}
// pr() has replaced p(), P() and print() in most cases.
// it uses the PSYC "message code" (actually a PSYC method)
// for classification of output going to the user
// (a bit like lastlog_level in ircII, remember?)
// but all of this should become w() now, since sprintf isnt flexible enuff
//
pr(mc, fmt, a,b,c,d,e,f,g,h,i,j,k) {
P2(("user:pr(%O,%O,%O,%O..)\n", mc,fmt,a,b))
if (mc) {
#ifdef PRO_PATH
mapping di = printStyle(mc);
if (di["_method"]) mc = di["_method"];
#endif
unless (fmt = T(mc, fmt)) return;
#ifdef PRO_PATH
if (di["_prefix"]) fmt = di["_prefix"]+fmt;
#endif
}
return emit(sprintf(fmt, a,b,c,d,e,f,g,h,i,j,k));
}
// the next generation comes along: w() expects
// a properly psyc-formatted string in data where it can replace
// the vars into and then output it. the text database can provide
// a "localized" format string using psyc-formatting, too.
//
w(string mc, string data, mapping vars, mixed source, int showingLog) {
string template, output, type, loc;
mapping locs;
mapping di = printStyle(mc); // display infos
int t;
unless (vars) vars = ([]);
else t = vars["_time_log"] || vars["_time_place"];
// would be nicer to have _time_log in /log rather than showingLog
if (!t && showingLog) t = vars["_time_INTERNAL"];
if (t && intp(t)) di["_prefix"] = time_or_date(t);
#if 0
template = T(di["_method"] || mc, 0);
#else
template = T(di["_method"] || mc, "");
#endif
P3(("%O user:w(%O,%O,%O,%O) - %O\n", ME,mc,data,vars,source, template))
#ifndef _flag_enable_alternate_location_forward
// why did it say.. if (mc != lastmc && ...
if (mappingp(v("locations")) && sizeof(v("locations"))) {
string nudata = data;
// this little thing enables languages for psyc clients etc.
// but we really need to get rid of the special cases in the
// protocol syntax
if (template && strlen(template)
&& !abbrev("_message", mc)
&& !abbrev("_request", mc))
nudata = template;
//PT(("%O user:w(%O,%O..%O) - %O\n", ME,mc,data,source, template))
P4(("%O user:w locations %O\n", ME, v("locations")))
foreach (type, locs : v("locations")) {
// check uniformness of location here?
// no! no broken location should have made it into the
// mapping at this point. if you need to check it, check
// it in sLocation()
#if DEBUG > 0
if (!sizeof(locs)) {
// oh.. happens on beta?
P1(("%O late deletion of a %O zero location - should never happen\n", ME, type))
//m_delete(v("locations"), type);
sLocation(type, 0);
continue;
}
#endif
// allow for intp(type) then we can have several
// see-all clients, not just [0]. needs support from person.c
if (intp(type) || strstr(mc, type) != -1) {
// we dont need no rendered data! who needs that anyway?
// so this whole thing probably belongs
// back into person:msg()
// P3(("PSYCW: %s -> %O == %O? (%O)\n", mc, loc, source, vars))
// do we want to see stupid echoes of our stuff?
// if (source == loc) continue;
// this is the place to generate _source_relay for clients
//
// this causes /history to not provide the true originator
// of a message, as the room providing history already IS a
// _source_relay. so do we really need a hierarchy of relays?
// who's going to vouch for them? :)
//
if (source) {
#if 0 //ndef EXPERIMENTAL
// _source_relay already contains context submitter
if (member(vars, "_source_relay")) {
if (source == vars["_source_relay"]) {
// shouldn't happen:
m_delete(vars, "_source_relay_relay");
} else {
vars["_source_relay_relay"] =
vars["_source_relay"];
vars["_source_relay"] = source;
}
} else {
vars["_source_relay"] = source;
// shouldn't happen:
m_delete(vars, "_source_relay_relay");
}
#else
// whoever gave us _source_relay was trustworthy
unless (member(vars, "_source_relay")) {
vars["_source_relay"] = source;
}
#endif
}
#ifdef LPC3
// i have no idea how a _message_private can trigger this
// code but it does. member(vars, "_context") should not
// return true in that case. very strange!! therefore
// i add "&& vars["_nick_place"]" ... sigh!
if (member(vars, "_context") &&! vars["_context"]
&& vars["_nick_place"]) {
// the context was an object, but got lost
// during ldmud's lastlog persistence. let's
// reconstruct it!
vars["_INTERNAL_context"] = SERVER_UNIFORM;
// the place nick shouldn't be missing, but
// sometimes it does. let's figure out why
if (stringp(vars["_nick_place"]))
vars["_INTERNAL_context"] += "@"+
lower_case(vars["_nick_place"]);
else {
P1(("%O encountered context reconstruction from nickless context (%O, %O, %O)\n", ME, mc, nudata, vars))
}
}
#else
# echo No LPC? Wow. Good luck!
#endif
foreach (loc : locs) {
#ifdef _flag_enable_circuit_proxy_multiplexing
// this is necessary when a single proxy is emulating
// several clients for several users. to figure out
// which context stuff is forwarded to which client
// we need to add this _target_forward. this is not the
// way psyc should operate in the long term. psyc clients
// should be integrated into the context distribution
// tree themselves, thus the proxy would manage a cslave
// for them instead of accepting forwards from each UNI
vars["_target_forward"] = loc;
// maybe this can be avoided when no _context is set...?
P3(("%O user:w forwarding %O to %O\n", ME, mc, vars["_target_forward"]))
#endif
sendmsg(loc, mc, nudata, vars);
// PT(("PSYCW: %s -> %O (%O)\n", mc, loc, vars))
#if DEBUG > 1
log_file("PSYCW", "%s(%O) %O » %O\n", mc, type,
nudata, type && loc);
#endif
}
}
}
}
#endif // !_flag_enable_alternate_location_forward
unless (interactive(ME)) return 1;
// who is that output for anyway?
output = psyctext(template, vars, data, source);
if (di["_prefix"]) {
if (vars["_postfix_time_log"])
output += " " + di["_prefix"];
else
output = di["_prefix"] +" "+ output;
}
if (output) {
if (template == "") {
// desperate security precaution until we have known
// methods. quite bad for english language users.
// the additional check for [_nick] is very desperate
// and should disappear sometime soon.. TODO
//
// we could aswell refine this by taking trustworthiness
// into consideration.. showing the localhost url for CVS
// notifications is just plain wrong
//
if (!stringp(source)|| (vars["_INTERNAL_trust"] >5
|| (vars["_nick"] && data && strlen(data)
&& strstr(data, "[_nick]") != -1)))
output += "\n";
else {
//PT(("notemp %O %O %O\n", source, vars, data))
output = "«"+source+"» "+ output +"\n";
}
}
if (output != "")
#ifdef RELAY
emit(output, source);
#else
emit(output);
#endif
}
D2(else PP(("user:w zero output in %O for %O (%O)\n", ME, source, mc));)
return 1;
}
wAction(mc, data, vars, source, variant, nick) {
P3(("wAction(%O,%O..%O,%O,%O)\n", mc, data, source, variant, nick))
ASSERT("sLocation", v("locations"), v("locations"))
if (vars["_action"]) {
mapping va;
ASSERT("wAction", stringp(vars["_action"])
&& strlen(vars["_action"]), vars["_action"])
if (nick) {
#ifdef VARS_IS_SACRED
// still holding on to shared vars. probably bad idea.
va = copy(vars);
#else
va = vars;
#endif
va["_nick"] = nick;
} else va = vars;
if (data && data != "")
w(mc+"_text_action"+variant, data, va, source);
else {
if (va["_nick_target"])
w(mc+"_action"+variant, 0, va, source);
else
w(mc+"_action"+variant,
#ifdef ALPHA
0,
#else
"[_nick] [_action].",
#endif
va, source);
}
return 1;
}
return 0;
}
#if DEBUG > 0
// coming from master/master.c
runtime_error(error, program, current_object, line, sprintfed) {
w("_failure_exception_runtime", sprintfed);
return 1;
}
#endif
logon() {
string t;
P2(("LOGON %O from %O\n", ME, query_ip_name() ))
// shouldn't this be qScheme() instead? little paranoid TODO
// but then we would have to move qScheme() from the server.c's
// into the common.c's .. well, we could do that some day
t = v("scheme");
ASSIGN_SHOWECHO(v("echo"), t)
if (t == "irc" || t == "psyc" || t == "jabber")
beQuiet = -1; // never turn off less interesting enter/leave echoes
// makeToken() isn't a good idea here, apparently
sTextPath(v("layout"), v("language"), t);
unless (legal_host(query_ip_number(), 0, 0, 0)) {
// this happens when people reconnect during the shutdown
// procedure.. and also when they are banned, but huh..
// that hardly ever happens :)
//
// w() needs to be called *after* sTextPath
w("_error_rejected_address",
"You are currently not permitted to connect here.");
return remove_interactive(ME);
// and the object will deteriorate when user gives up..
//
// with the #'quit call_out.. or was it meant to
// deteriorate differently?
}
#ifdef __TLS__
string evil;
if (tls_query_connection_state(ME) == 1) {
// evil TLS ciphers are no problem if the connection is being
// tunneled through SSH or Tor, so we shut up in that case.
if (probably_private(ME) < PRIVACY_REASONABLE &&
(evil = tls_bad_cipher(ME, t))) {
// Seems to affect only pidgin for linux prior to 2015
w("_warning_circuit_encryption_cipher", 0, ([ "_circuit_encryption_cipher": evil ]));
//return remove_interactive(ME);
} else {
unless (beQuiet) w("_status_circuit_encryption_cipher");
}
} else if (!probably_private(ME)) {
w("_warning_missing_circuit_encryption"
# ifdef _warning_missing_circuit_encryption
, _warning_missing_circuit_encryption
# endif
);
}
#endif
// cannot if (greeting) here this since jabber:iq:auth depends on this
// also greeting will only be defined after ::logon()
// (use another w() maybe?)
w("_notice_login", 0, ([ "_nick": MYNICK,
"_page_network": "http://www.psyc.eu/",
"_name_network": "PSYC" ]), ME );
while (remove_call_out(#'quit) != -1);
#ifndef _flag_disable_info_session
if (t == "irc" || t == "tn") w("_warning_usage_set_charset",
0, ([ "_charset": v("charset") || SYSTEM_CHARSET ]));
#endif
autojoin();
actchar = v("actioncharacter") || T("_MISC_character_action",":");
actchar = actchar == "off" ? 0 : actchar[0];
// this code appears twice
if (v("query")) w("_status_query_on",
"Dialogue with [_nick_target] continued.",
([ "_nick_target" : v("query") ])
);
#ifdef JABBER_PATH
// currently only used by net/jabber/user.c
unless(v("peoplegroups")) vSet("peoplegroups", ([]));
#endif
#ifdef _flag_log_hosts
vSet("ip", query_ip_number(ME) ||
(this_interactive() && query_ip_number(this_interactive())) );
#endif
::logon( query_ip_name(ME) ||
(this_interactive() && query_ip_name(this_interactive())) );
cmdchar = (v("commandcharacter") ||
T("_MISC_character_command", "/"))[0..0];
#ifndef _flag_disable_info_session
// greeting is only defined after ::logon has run
if (greeting) {
w("_warning_usage_set_language",
"Mittels \"/set language de\" kann zur deutschen Sprache gewechselt werden.");
if (cmdchar != "/") w("_warning_modified_command_character",
"Beware, \"[_command_character]\" is configured as your command character.",
([ "_command_character" : cmdchar ]) );
}
#endif
cmdchar = cmdchar[0];
}
// net/jabber/user filters this call, so this is never executed for
// jabber users. how sad, why miss out on all newscasts? we should
// refine this so at least newscasts are kept. or should we solve
// that by +enrol? that would be jabber style. TODO
// btw irc has it's own autojoin, which is a little different
autojoin() {
#ifndef _flag_disable_place_enter_automatic
string s;
object o;
# ifdef FORCE_PLACE
// per local policy, all users must join the default place
// a community-esq feature
vSet("place", DEFPLACE);
# endif
P2(("autojoin with %O %O places=%O\n", v("place"), place, places))
// see also http://about.psyc.eu/Client_coders#Room_members
// checking sizeof(places) is unsafe.. exiting with /bye
// doesn't always clean them all out (FIXME?) which means
// that a new login may fail on rejoining subscriptions!
// i fix this by enforcing the foreach on subscriptions
// but there may be better fixes on earth.. then again,
// this is how irc's autojoin() has been doing it all the
// time.. so we should rather refactor that? then again
// the IRC clients really seem to need it differently.
// --lynX 2015
//
#if 0
if (sizeof(places)) {
foreach (o, s : places) {
P3(("%O relinking %O\n", ME, o))
# ifdef FORCE_PLACE_RESET
// we could have a v("leaveonreconnect") instead of an ifdef for this
placeRequest(o,
# ifdef SPEC
"_request_context_leave"
# else
"_request_leave"
# endif
"_netburp", 0, 1);
# else
placeRequest(o,
# ifdef SPEC
"_request_context_enter"
# else
"_request_enter"
# endif
"_relink", 0, 1);
# endif
}
// }
}
else
#endif
{
# ifndef _flag_disable_place_default
unless (v("place"))
vSet("place", T("_MISC_defplace", DEFPLACE));
// re-entering your last place is unusual by irc
// habits, but since we don't want people to set up
// autojoins they may find this quite practical until
// they decide upon subscribe or not. the requested
// history is probably not necessary for ircers, but
// shouldn't harm either. jabberers never get here.
# ifndef _limit_amount_history_place_default
# define _limit_amount_history_place_default 5 // what about history glimpse?
// history glimpse is how much a room will
// allow (at max), this is how much the client
// requests. maybe this should go, _login kind of
// says it, too.
# endif
teleport(v("place"), "_login", 0, 0, ([ "_amount_history":
_limit_amount_history_place_default ]));
# endif
// subscriptions are stored in lowercase, warum auch immer
if (sizeof(v("subscriptions"))) {
// string t1;
P3(("places %O subscriptions %O\n",
places, v("subscriptions")))
// code to avoid "double join" in particular for irc
// t1 = lower_case(v("place") || "");
// used to also handle v("home") which was complete
// non-sense...
foreach (s in v("subscriptions"))
// if (s != t1)
{
P3(("enter: %O\n", s))
// teleport() weeds out all places we are in
// and avoids so called 'double joins' which
// are bad for IRC
teleport(s, "_automatic_subscription",
1, 1);
// so we don't use this stuff anymore:
//placeRequest(s,
// "_request_enter_automatic_subscription");
}
}
}
#endif // _flag_disable_place_enter_automatic
}
quit(immediate, variant) {
string s;
object o;
// this change has something to do with SANE_QUIT and fixing
// the double joins.. says tobij. just a micro-hint.
//if (immediate) return ::quit(immediate);
if (find_call_out(#'quit) != -1) return ::quit(immediate);
if (leaving++) {
D1( D("recursive quit() in net/user: intercepted\n"); )
return 0; // ignore potential errors from emit()
}
P2(("QUIT(%O,%O) in %O: %O\n", immediate, variant, ME, places))
unless (variant) variant = "_logout";
// if (v("subscriptions")) foreach (s in v("subscriptions"))
// placeRequest(s, "_request_leave_automatic_subscription", 1);
#ifdef SUBSCRIBE_PERMANENT
// this may even work, but irc clients go crazy at seeing double joins
// we should make this a feature of /detach instead, maybe?
int stayinalive = 0;
// so, SUBSCRIBE_PERMANENT won't work for now.
if (sizeof(v("subscriptions")) && widthof(v("subscriptions"))) {
foreach (o, s : places) {
P3(("Stay in %O, %O?\n", o, s))
if (v("subscriptions")[lower_case(s)] != SUBSCRIBE_PERMANENT)
placeRequest(o,
#ifdef SPEC
"_request_context_leave"
#else
"_request_leave"
#endif
+ variant, 1, 1);
else
stayinalive++;
}
} else
#endif
foreach (o, s : places)
placeRequest(o,
#ifdef SPEC
"_request_context_leave"
#else
"_request_leave"
#endif
+ variant, 1, 1);
leaving = 0; // sonst gehen die announce's nicht raus
//place = 0; // maybe? dunno.
return ::quit(immediate, variant);
}
link_disconnected() {
P3(("link_disconnected in %O from %O\nlocations: %O\n", ME, previous_object(), v("locations")))
linkDel(0, previous_object());
// if there are any catch-all connections left don't quit,
// just delete link
if (member(v("locations"), 0) && sizeof(v("locations")[0])) return;
// unless we have a legacy client, let's get outta here
unless (interactive(ME)) disconnected();
}
// driver calls us here to tell us we lost the connection
// if you don't like this default behaviour, override it
//
// we also call this manually from _request_unlink_disconnect
disconnected(remainder) {
P2(("disconnected(%O) in %O from %O\n",
remainder, ME, previous_object()))
// user did not detach his client properly. we'll make a wild guess
// at how many messages he may have missed - enough to make the user
// check the lastlog if that's not enough.
// FIXME: problem with jabber/user running into some bug when
// lastlog messages are shown.. which at this point of course
// creates a recursion - thus, eliminating the otherwise useful
// feature of replay-on-tcp-loss here.
if (find_call_out(#'quit) == -1 && v("scheme") != "jabber") {
// can't use "leaving" here because it only serves the
// purpose of detecting recursion within quit(), thus
// gets resetted before arriving here.
P1(("unexpected disconnect in %O\n", ME))
vInc("new", 7);
}
// actually - we could show all messages since last activity
// from user. TODO
#ifdef AVAILABILITY_OFFLINE
if (availability == AVAILABILITY_OFFLINE) {
P1(("i think i am already offline, so i won't quit (%O)\n", ME))
return;
}
#endif
if (find_call_out(#'quit) != -1) return;
// if (place) sendmsg(place, "_notice_place_leave_disconnect",
// "[_nick] disconnects from the twilight zone.",
// ([ "_nick" : MYNICK ]) );
#if 1
// disconnect is never a good way of quitting anyway
//call_out(#'quit, 10, 1);
// okay but why immediate here?
call_out(#'quit, 10, 0, "_disconnect");
#else
quit(0, "_disconnect");
#endif
return 0; // unexpected
}
#ifndef _flag_disable_module_presence
// usually called from logon, unless set to manual
announce(level, manual, verbose, text) {
::announce(level, manual, verbose, text);
unless (ONLINE) return;
// psyc://x-net.hu/~tg probably just ran into a bug:
// i forgot to put this if here.
if (verbose) return showFriends(1);
}
#endif
// belongs into person.c as it is being called from there..?
static showFriends(verbose) {
int i, idle, lnum, isaway;
string *list;
array(mixed) k;
mixed o, n, *fmt;
P3(("showFriends in %O: %O\n", ME, friends))
k = m_indices(friends);
unless(k && sizeof(k)) return;
fmt = allocate(2);
fmt[1] = T("_list_friends_present_each", "%s\n");
fmt[1] = (fmt[1] == "%s\n") ? 0 : fmt[1][0..<2];
fmt[0] = T("_list_friends_away_each", "%s\n");
fmt[0] = (fmt[0] == "%s\n") ? 0 : fmt[1][0..<2];
list = allocate(6, "");
list[2] = ([ ]); list[5] = ([ ]);
for(i=sizeof(k); i;) {
// there is a 'bug' inside which exposes idletimes of people you have offered
// friendship to - just check the lvl
o = k[--i];
#ifdef AVAILABILITY_NEARBY
isaway = friends[o, FRIEND_AVAILABILITY] < AVAILABILITY_NEARBY;
#else
isaway = 0;
#endif
list[isaway * 3 + 2][o] = friends[o, FRIEND_NICK];
if (objectp(o)) {
#ifdef ALIASES
string n2;
n = o->qNameLower();
if (n && !(n2 = raliases[n]) && aliases[n]) {
n2 = psyc_name(o);
}
if (n2) n = n2;
#else
unless(stringp(n = friends[o, FRIEND_NICK])) friends[o, FRIEND_NICK] = n = o->qName();
#endif
list[isaway * 3] += ", "+ n;
list[isaway * 3 + 1] += ", "+ (fmt[isaway] ? sprintf(fmt[isaway],n,n,n) : n);
// for once we leave out the CALC_IDLE_TIME.. ;)
if (verbose && idle = o->vQuery("aliveTime"))
list[isaway * 3 + 1] += " [" + timedelta(time() - idle) + "]";
} else {
// TODO: do handling of remote nicks here
// aliases + nickname here?
// can we do the aliases bit when *storing* the friends?
// unless (stringp(n = friends[o])) friends[o] =
n = to_string(o);
#ifdef ALIASES
n = raliases[lower_case(n)] || n;
#endif
list[isaway * 3] += ", "+ n;
list[isaway * 3 + 1] += ", "+ (fmt[isaway] ? sprintf(fmt[isaway],n,n,n) : n);
}
}
// if (list2 == "") list2 = list1;
if (strlen(list[1]) > 2)
w("_list_friends_present", "Friends online: [_friends].",
([ "_friends" : list[1][2..],
"_list_friends" : m_indices(list[2]), // _tab
"_list_friends_nicknames" : m_values(list[2]) ]) );
// man könnte ja die blöden textstrings dahinterschreiben
// wenn es nicht zuviele sind..
if (strlen(list[4]) > 2)
w("_list_friends_away", "Friends away: [_friends_away].",
([ "_friends_away" : list[4][2..],
"_list_friends_away" : m_indices(list[5]), // _tab
"_list_friends_nicknames_away" : m_values(list[5]) ]) );
// pr("_list_friends_present", "Friends online: %s\n", list2[2..]);
return list[0][2..];
}
listDescription(vars, eachout, nicklink) {
string k, na, va, buf, fmt, t;
buf = "";
P3(("%O listDescription(%O,%O,%O)\n", ME, vars, eachout, nicklink))
#ifndef _flag_enable_profile_table
// html version with more intelligent tables:
// <tr id="[_key]"><td class="ldpek">[_name_key]</td><td class="ldpev">[_value]</td></tr>
fmt = T("_list_description_each_item", "[_key]: [_value]\n");
#else
// maybe someday we'll use psyctext() instead, but for now this
// is sooo much faster. too bad the %22 trick doesn't work for utf8
fmt = T("_list_description_each", "%22s: %s\n");
#endif
if (vars["_identification_alias"]) {
m_delete(vars, "_identification_scheme_XMPP");
//m_delete(vars, "_identification_scheme_SIP");
//m_delete(vars, "_identification_scheme_IRC");
}
if (vars["_host_name"] &&! boss(ME)) {
m_delete(vars, "_host_IP");
}
#if 1 // fix and remove this.. TODO
if (vars[0]) {
P1(("Encountered vars[0] containing %O\n", vars[0]))
vDel(vars, 0);
}
#endif
foreach (k : sort_array(m_indices(vars), #'>)) {
va = vars[k];
switch(k) {
case "_color":
if (v("scheme") == "applet") break;
// else fall thru and filter
case "_nick":
case "_nick_stylish":
case "_uniform_style":
case "_uniform_photo_small":
case "_INTERNAL_type_image_photo":
va = 0;
break;
case "_uniform_photo":
P4(("photo is %O\n", va))
if (nicklink) va = 0;
break;
case "_language":
if (va == v("language")) va = 0;
break;
case "_protocol_agent":
if (va == v("scheme")) va = 0;
break;
case "_agent_design":
if (va == v("layout")) va = 0;
break;
case "_action_motto":
va = vars["_nick"] +" "+ vars[k];
k = "_motto";
break;
case "_list_groups": // _tab
#if 0
if (nicklink) {
int i;
string n, u;
string s = SERVER_UNIFORM;
va = copy(va);
for (i=sizeof(va)-1; i>=0; --i) {
n = va[i];
#if 1
// TODO? this doesn't handle jids but i am
// also not sure if jids belong here anyway
u = is_formal(n) ? n : s +"@"+ n;
va[i] = sprintf(nicklink, u, n, n);
#else
va[i] = sprintf(nicklink, n, n, n);
#endif
}
}
va = implode(va, ", ");
k = "_groups";
break;
#endif
// fall thru, since (currently) all entries are formal
// the special case for ~ will never happen
case "_list_friends": // _tab
if (nicklink) {
int i;
string n, u;
string s = SERVER_UNIFORM;
va = copy(va);
for (i=sizeof(va)-1; i>=0; --i) {
n = va[i];
#if 1
// TODO? this doesn't handle jids but i am
// also not sure if jids belong here anyway
u = is_formal(n) ? n : s +"~"+ n;
va[i] = sprintf(nicklink, u, n, n);
#else
va[i] = sprintf(nicklink, n, n, n);
#endif
}
}
va = implode(va, ", ");
//k = "_friends";
k = k[sizeof("_list")..]; // _tab
break;
case "_time_idle":
case "_time_age":
if (intp(va)) va = timedelta(va);
break;
case "_INTERNAL_image_photo":
t = T("_list_description_image", "");
// this scheme cannot display this
if (t == "") { va = 0; break; }
va = psyctext(t, ([
"_image_base64": va,
"_type_image": vars["_INTERNAL_type_image_photo"]
]));
k = "_image_photo";
break;
//default:
}
if (stringp(va)) {
na = T("_TEXT"+k, "");
unless (na) {
P1(("Description: No _TEXT for %O\n", k))
} else if (na != "") {
if (eachout) w("_list_description"+k,
// sprintf("%22s: [%s]",na,k),
sprintf("%22s: %s",na,va),
([ k: va, // "_text"+k: na
]));
else
#ifndef _flag_enable_profile_table
buf += psyctext(fmt, ([ "_key": k,
"_name_key": na, "_value": va ]) );
#else
buf += sprintf(fmt, na, va);
#endif
}
// else.. no output encoding
}
}
return buf;
}
// no longer in use
qPlace() { if (place) return v("place"); }
qPublicInfo(showguests, showplace, showinvisibles) {
mapping m;
int newb, t;
if (!ONLINE) return 3; // gets called when an offline user
// has been summoned by /tell
newb = IS_NEWBIE;
if (!showguests && newb) return 2;
if (!showinvisibles && (v("guest") || v("visibility") == "off"))
return 1;
CALC_IDLE_TIME(t);
m = ([ "nick" : MYNICK, "lowerNick" : MYLOWERNICK,
"me" : v("me"), "age" : v("age"),
"registered" : !newb,
// actual aliveTime is for /lu usage only.. not for public output
"idleTime" : t, "aliveTime" : v("aliveTime"),
"name": v("publicname") || v("longname"),
"operator" : boss(ME) > 9,
]);
#ifdef SHOW_REMOTE_PLACES_IN_PEOPLE_LIST
if (showplace) m["place"] = objectp(place)
? place->qPublicName() : place;
#else
if (showplace && objectp(place)) m["place"] = place->qPublicName();
#endif
#if 0 // __EFUN_DEFINED__(tls_query_connection_state)
if (interactive(ME) && tls_query_connection_state()) m["encrypted"] = 1;
#endif
return m;
}