psyced/world/net/irc/user.c

1247 lines
40 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// vim:foldmethod=marker:syntax=lpc:noexpandtab
// $Id: user.c,v 1.565 2008/10/01 10:59:47 lynx Exp $
//
// server-side handler for a logged in IRC client
//
#include "irc.h"
// ctcp support etc
inherit IRC_PATH "decode";
//volatile string msa, msare, cc, bc, uc;
#include "user.h"
#include "error.h"
#include "reply.h"
#include "person.h"
#include "psyc.h"
#include "uniform.h"
#include "hack.i"
volatile int isService;
#ifdef IRC_ANNOYANCE
volatile int isGenervt;
#endif
qCharset() { return v("charset"); }
qHasCurrentPlace() { return 0; }
parse(a) {
next_input_to(#'parse);
::parse(a);
}
msg(source, mc, data, mapping vars, showingLog) {
string t;
int special;
mixed a, res;
P4(("irc:msg (%O,%s,%O,%O)\n", source, mc, data, vars))
P2(("irc:msg (%O,%s,%O..)\n", source, mc, data))
if (abbrev("_notice_place_enter", mc) && source == ME) {
// it seems there is a bug here if the remote room is reloaded
// and someone else from local server joins
// source == ME should be false then
P0(("%O ignoring myself joining via _notice\n", ME)) // monitor report?
return;
}
//#if DEBUG > 0 // searching for recursions
if (vars["_INTERNAL_recursion_counter_" + MYNICK]) {
// ok.. no one seems to care to fix this bug so i will
// stop it jamming up the debug output
P1(("recursion mc %O in %O from %O\n", mc, ME, source))
// seen 2007-09-24 very often:
// recursion mc "_request_ping" in net/irc/user#nisa from net/irc/user#nisa
// probably an autopinging client..
// here it gets its own log, so we can slowly examine when it
// happens and if it is indeed a bug or problem
log_file("IRC_RECURSION", "[%s] %O (%O, %O, %O, %O)\n", ctime(),
ME, source, mc, data, vars);
}
vars["_INTERNAL_recursion_counter_" + MYNICK]++;
//#endif
res = ::msg(&source, mc, data, &vars, showingLog);
/* diese lösung ist eigentlich falsch.. wir müssen das annehmen
* fremder templates generell bürokratisieren: origin wird
* davorgepappt, so wie es bei telnet der fall ist. dann isses
* auch egal wenn da ein : oder # vorn ist. oder? denk..
* vielleicht ist es so gar nicht so schlecht:
*/
if (!objectp(source) && !objectp(vars["_context"])
&& stringp(data) && strlen(data) &&
(data[0] == ':' || data[0] == '#') && (!abbrev("_message", mc)
|| T(mc, "") == "")) {
monitor_report("_warning_abuse_invalid_data_IRC",
S("%O received %O from %O\n", ME, data, source));
data = " "+data;
}
// this is partly a hack for a more generic 512-byte-length problem
// but we need a #366 end of names...
// maybe call ::msg and then return writing the 366
#ifdef ENTER_MEMBERS //{{{
if (mc == "_status_place_members")
return _status_place_members(source, mc, data, vars);
else
#endif //}}}
if (mc == "_message_announcement") {
// what about not checking for _context and using ::msg() or even
// w()?
foreach (mixed room, string np : places) {
w(mc, data, vars +
([ "_context" : room,
"_nick_place" : np ]), source);
// wait a minute? this is faking a multicast here? JEEZ!! TODO
}
}
// das müsste das longname und coolname problem lösen
// und vars soll ja angeblich eine kopie sein..
// m_delete(vars, "_nick_local");
return res;
}
static int namreply(mapping vars) {
mixed u;
// TODO: control == silent ist eigentlich nicht richtig,
// control == keine join/part waere richtiger
// && vars["_control"] != "silent"
if (pointerp(u = vars["_list_members_nicks"]) && sizeof(u) > 1) {
// PARANOID check missing but no longer necessary
//if (pointerp(vars["_list_members"]))
// UNI is supposed to do this.
// u = implode(renderMembers(vars["_list_members"],
// vars["_list_members_nicks"]), " ");
u = implode(u, " ");
if (!strlen(u)) u = MYNICK; // paranoid
// if (strlen(u)) u += " " + MYNICK;
// else u = MYNICK;
# ifdef _flag_encode_uniforms_IRC
u = uniform2irc(u);
# endif
} else {
// empty, quiet or overcrowded place.
// some IRC clients require ANY info, so we give them ANY
u = MYNICK;
// normal behaviour, when _amount is sent instead of _list
// w/o is american for without ;)
P3(("Empty or anonymous channel: %O w/o _list_members\n",
vars["_nick_place"]))
}
// this code is all weirdly redundant with net/user.c
// doing renderMembers itself in some cases, in some not -
// it is working sort-of, but next time it breaks it needs
// a reorg across all schemes
render("_status_place_members", 0, ([
"_nick_place" : vars["_nick_place"],
"_members" : u,
"_nick_me" : MYNICK ]) );
render("_status_place_members_end", 0, vars);
return 1;
}
static int _status_place_members(mixed source, string mc,
mixed data, mapping vars) {
mixed u = "";
P4(("irc:_status_place_members got %O\n", vars))
if (vars["_tag_reply"] == "W") {
mapping uv, v2;
string n;
/*
u = renderMembers(vars["_list_members"],
vars["_list_members_nicks"],
objectp(source));
PT(("renderMembers %O vs %O\n", u, vars["_list_members"]))
*/
// the room could be empty
if (pointerp(vars["_list_members"])) // _tab
foreach (u : vars["_list_members"]) {
if (objectp(u)) {
uv = u -> qPublicInfo(1, 0, 1);
unless (mappingp(uv)) continue;
#ifdef ALIASES
n = raliases[uv["lowerNick"]] || uv["nick"];
#else
n = uv["nick"];
#endif
v2 = vars + ([
"_nick" : n,
"_nick_login" : uv["nick"],
"_identification_host" : SERVER_HOST,
// "_IRC_away" : time() - uv["aliveTime"] > 30*60 + 60*60 ? "G" : "H",
"_IRC_away" : uv["idleTime"] > 30*60 + 60*60 ? "G" : "H",
"_IRC_hops" : "0",
"_IRC_identified" :
// 123456 = 34h, 1234567 = 14 days
uv["age"] > 123456 ? "~" :
uv["registered"] ? "" : "!",
"_IRC_operator" : uv["operator"] ?
"*" : "",
"_name" : uv["name"] || "",
"_identification" : psyc_name(u),
// "_description_action" : uv["me"] || "",
]);
} else {
if (stringp(u)) u = parse_uniform(u);
unless (pointerp(u)) {
P0(("%O waaargh.. broken parse_uniform in %O\n",
ME, vars["_list_members"])) // _tab
continue;
}
#ifdef ALIASES
n = raliases[lower_case(u[UString])] || u[UString];
#else
n = u[UString];
#endif
v2 = vars + ([
# ifdef _flag_encode_uniforms_IRC
"_nick" : uniform2irc(n),
# else
"_nick" : n,
# endif
"_nick_login" : u[UResource],
"_identification_host" : u[UHost],
"_IRC_away" : "H",
"_IRC_hops" : "1",
"_IRC_identified" : "",
"_IRC_operator" : "",
"_name" : "",
"_identification" : u[UString],
// "_description_action" : "",
]);
}
w(mc + "_each", 0, v2);
}
w(mc + "_end_verbose", 0, vars);
return 1;
} else return namreply(vars);
}
w(string mc, string data, mapping vars, mixed source) {
string nick, nick_place, nick2, family;
mapping di; // = printStyle(mc); // display infos
mixed t, u = "";
int glyph;
P3(("%O: irc:w(%O, %O, %O, %O) %O\n", ME, mc, data, 0, source, vars))
#ifndef GHOST
// should it be..?
//unless (ONLINE) return;
unless (interactive(ME)) return;
#endif
#ifdef VARS_IS_SACRED //{{{
// "VARS_IS_SACRED" bedeutet dass *kein* copy gemacht wurde und
// man deshalb hier paranoid sein muss. der normalfall ist, dass
// der raum uns ne kopie gibt.. ist auch gut so, denn der irc code
// versaut total die history der räume! einmal ein ircer
// drin gehabt und aus vars["_time_place"] sind _prefix'e geworden
// und _source_hack's sind im raum gespeichert und mehr so unsinn.
if (mappingp(vars)) vars = copy(vars);
else vars = ([]);
// hmm.. wie die _time_place's aus der room history rausfallen
// konnten ist mir ein rätsel.. aber ich habs mit eigenen augen
// gesehen.. ich kopiers sogar nach /ve/data/damaged-rendezvous.o
// ah.. der neue foreach code im place ist schuld
#else //}}}
unless (mappingp(vars)) vars = ([]);
#endif
if (trail("_important", mc)) {
mc = mc[..<11];
#ifdef PRO_PATH
} else if (trail("_magnify", mc)) {
mc = mc[..<9];
} else if (trail("_reduce", mc)) {
mc = mc[..<8];
#endif
// render() still doesn't handle unexpected notices like _unicast from MUC..
// so we don't get to see the nice leave messages. the inheritance mechanism
// isn't working in the order that is necessary here. freaky issue.
#ifndef SAFE_IRC
} else if (abbrev("_notice_place_enter", mc)) {
mc = "_notice_place_enter";
} else if(abbrev("_notice_place_leave", mc)) {
mc = "_notice_place_leave";
#endif
}
// currently only this _control is in use and 'tempofficial'
if (vars["_control"] == "_silent") {
#ifndef SAFE_IRC
if (mc == "_notice_place_enter")
#else
if (abbrev("_notice_place_enter", mc))
#endif
{
if (v("entersilent") != "off") return 7;
}
// war das hier nicht vorher da, wo es den kram besser abfing?!
// lynX ist schuld!
else if (abbrev("_echo_place_enter", mc)) {
// maybe the smarter way to do this would be to send entersilent
// out from placeRequest and also make members and status depend
// on it. as now we can make irc join newsrooms with
// /set entersilent off
// but still not know who's there. TODO
if (v("entersilent") != "off") return 7;
mc = "_echo_place_enter_join";
}
}
// do we need a special case for _echo_place_leave_logout ?
else if (abbrev("_echo_place_leave", mc))
mc[0..4] = "_notice"; // impractical distinction
if (v("visiblespeakaction") == "on"
&& !vars["_action"]
&& (mc == "_message_public"
|| mc == "_message_echo_public"))
return ::wAction(mc, data, vars
+ ([ "_action" : T("_TEXT_action_says", 0) ]),
source, "", vars["_nick"]);
#ifdef OLD_LOCAL_NICK_PLAIN_TEXTDB_HACK //{{{
else if (vars["_nick_local"]) { // less work
if (mc == "_message_echo_public_action"
&& (t = vars["_INTERNAL_nick_plain"])) {
vars["_nick"] = t;
// template = T(mc, "");
} else {
#ifdef PRO_PATH
sTextPath(0, v("language"), "ircgate");
#else
sTextPath(0, v("language"), "plain");
#endif
// template = T(mc, 0);
sTextPath(0, v("language"), v("scheme"));
}
}
#else //}}}
else if (vars["_nick_local"] &&
vars["_nick_local"] == vars["_nick"])
vars["_nick"] = vars["_INTERNAL_nick_plain"] || vars["_nick_verbatim"];
#endif
// if (vars["_nick_local"]) mc += "_masquerade"; // more work
di = printStyle(mc); // display infos
if ((mc == "_message_public"
|| mc == "_message_echo_public")
&& !vars["_action"]
&& v("visiblespeakaction") == "on")
return ::wAction(mc, data, vars
+ ([ "_action" :
T("_TEXT_action_says", 0) ]),
source, "", vars["_nick"]);
if (!prefix && di["_prefix"]) prefix = di["_prefix"];
vars["_prefix"] = prefix || "";
D2(
if (vars["_nick_me"] && source)
D(S("COLLISION: msg arrived in irc:w had _nick_me with "
"value: %O\n", vars["_nick_me"]));
)
#if !defined(PRO_PATH) && defined(ALIASES)
// heldensaga thinks nickspaces are perfect only when you can
// give away your own nickname. i think that is unnecessary geek pride.
// so be it!
vars["_nick_me"] = aliases[MYLOWERNICK]
? SERVER_UNIFORM +"~"+ MYNICK
: MYNICK;
#else
vars["_nick_me"] = MYNICK;
#endif
if (vars["_nick"] && places[source] && vars["_time_place"]) {
P3(("%O glaubt dem _relay von %O jetz mal, gell?\n",
ME, source))
if (stringp(source) && !vars["_source_relay"]
&& abbrev("psyc://", source)) {
source = source[7..index(source[7..], '/')] + "~"
+ (vars["_INTERNAL_nick_plain"] || vars["_nick"]);
} else
source = vars["_source_relay"];
}
if (objectp(source) || source == 0 // TODO:: look out for mysterious
// source == 0 msgs.
// those might be simple w()s... hm.
#ifndef UNSAFE_LASTLOG
|| abbrev(SERVER_UNIFORM +"~", source)
#endif
) {
#ifdef GHOST //{{{
// in S2S mode we are not supposed to deliver nick!user@host
// thus we use plain nicks or plain uniforms
vars["_source_hack"] = vars["_INTERNAL_nick_plain"] || vars["_nick"];
#else //}}}
# if 0 // OLD // according to elmex "should never happen" happened...
if (vars["_nick"]) {
vars["_source_hack"] =
(vars["_INTERNAL_nick_plain"] || vars["_nick"])
+ "!"+ (vars["_nick_long"] || vars["_INTERNAL_nick_plain"]
|| vars["_nick"])
+"@" SERVER_HOST;
} else // should never happen
vars["_source_hack"] = to_string(source);
# else // EXPERIMENTAL
nick2 = vars["_INTERNAL_nick_plain"] || vars["_nick"];
vars["_source_hack"] = nick2 ? nick2
+"!"+ (vars["_nick_long"] || vars["_INTERNAL_nick_plain"]
|| vars["_nick"]) +"@" SERVER_HOST
: to_string(source); // should never happen
# endif
} else if (abbrev("_echo_place_enter", mc)) {
vars["_source_hack"] = MYNICK + "!" + MYNICK + "@" SERVER_HOST;
#endif
} else {
#ifdef GHOST //{{{
// in S2S mode we are not supposed to deliver nick!user@host
// thus we use plain nicks or plain uniforms
vars["_source_hack"] = source;
#else //}}}
u = parse_uniform(source);
unless (u) {
// this happens when a user@host notation gets here..
// we could handle that, but how did it happen?
P0(("%O got a w() from %O which is neither object nor uniform!?\n%O %O\n", ME, source, data, vars))
# if DEBUG > 1
raise_error("hey sammy, i got a jid on irc\n");
# endif
return;
}
# ifdef ALIASES
if (raliases[source]) {
nick2 = raliases[source];
vars["_source_hack"] = nick2 + ((u[UUser] ||
(u[UResource] && u[UResource][0]))
? "!"+ UName(u) +"@"+ u[UHost]
: "!"+ (vars["_nick_long"] || vars["_INTERNAL_nick_plain"]
|| vars["_nick"])
+"@alias.undefined");
}
unless (nick2) {
# endif
switch (u[UScheme]) {
case "psyc":
if (u[UUser] || (u[UResource] && strlen(u[UResource])
&& u[UResource][0] == '~')) {
string tmp = UName(u);
vars["_source_hack"] = u[UScheme] + "://"
+ u[UHostPort] +"/~"+ tmp +"!"+ tmp +"@"
+ u[UHostPort];
P4(("w:psyc _source_hack %O\n", vars["_source_hack"]))
} else {
vars["_source_hack"] = uniform2irc(source)
+"!*@"+ (u[UHostPort] || "host.undefined");
// '*' nicer than 'undefined' and never a nick
// this happens a lot more often then we thought :(
// like for _request_version from server_unl
D1( unless(u[UHostPort]) PP(("irc/user %O source %O results in %O for %O\n", ME, source, vars["_source_hack"], mc)); )
}
break;
case "jabber":
case "xmpp":
if (vars["_location"] && v("fulljid") == "on") {
// TODO: the resource may contain characters indigestible
// to irc, e.g. whitespace
/*
if (vars["_INTERNAL_identification"]
&& v("fulljid") != "on") {
*/
P3(("changing source from %O to %O\n", source, vars["_location"]))
source = vars["_location"];
}
vars["_source_hack"] = uniform2irc(source)
+ "!" + u[UUserAtHost];
#ifdef JABBER_PATH // MUC support..
// unfortunately, net/entity has rendered _nick unusable
if (vars["_nick_place"] && vars["_INTERNAL_source_resource"] && member(vars["_INTERNAL_source_resource"], ' ') != -1){
// xmpp: is here in cleartext.. TODO maybe?
vars["_source_hack"] = "xmpp:"+
uniform2irc(u[UUserAtHost]);
t = replace(vars["_INTERNAL_source_resource"], " ", "%20");
vars["_source_hack"] += "/" + t + "!" + u[UUserAtHost];
PT(("irc/user: XMPP source hack %O for %O\n", vars["_source_hack"], ME))
}
#endif
break;
default:
vars["_source_hack"] = source + "!*@*";
}
# ifdef ALIASES
}
# endif
#endif
}
# ifndef NO_CTCP_PRESENCE
if ((t = vars["_degree_availability"]) && strlen(mc) > 16
&& mc[7..15] == "_presence") {
if (v("ctcppresence")) {
int l;
string output;
output =":"+ vars["_source_hack"] +" NOTICE "+ MYNICK +" :";
l = strlen(output);
output += "#PRESENCE "+ t +" "+ vars["_degree_mood"]
+" :"+ vars["_description_presence"] +"#\n";
output[l] = 0x01;
output[<2] = 0x01;
emit(output);
return 1;
# ifdef IRC_FRIENDCHANNEL //{{{
} else {
# ifdef IRC_FRIENDCHANNEL_HEREAWAY
string old = vars["_degree_availability_old"];
if (old <= AVAILABILITY_VACATION) {
if (t >= AVAILABILITY_NEARBY)
emit(":"+ vars["_source_hack"] +" JOIN :&HERE\n");
else if (t >= AVAILABILITY_AWAY)
emit(":"+ vars["_source_hack"] +" JOIN :&AWAY\n");
} else if (old < AVAILABILITY_NEARBY) {
if (t >= AVAILABILITY_NEARBY) {
emit(":"+ vars["_source_hack"] +" PART :&AWAY\n");
emit(":"+ vars["_source_hack"] +" JOIN :&HERE\n");
} else if (t < AVAILABILITY_AWAY)
emit(":"+ vars["_source_hack"] +" PART :&AWAY\n");
} else {
if (t < AVAILABILITY_NEARBY)
emit(":"+ vars["_source_hack"] +" PART :&HERE\n");
if (t >= AVAILABILITY_AWAY)
emit(":"+ vars["_source_hack"] +" JOIN :&AWAY\n");
}
# else
// ... MODE #whateverTesting +v xni3
if (t >= AVAILABILITY_NEARBY)
emit(":"+ SERVER_HOST +" MODE & +o-v "+ vars["_nick"] +"\n");
else if (t >= AVAILABILITY_VACATION)
emit(":"+ SERVER_HOST +" MODE & +v-o "+ vars["_nick"] +"\n");
else
emit(":"+ SERVER_HOST +" MODE & -v-o "+ vars["_nick"] +"\n");
# endif
# endif //}}}
}
} else
# endif
P2(("irc/user:w(%O,%O,..,%O)\n", mc, data, source))
t = 0;
PSYC_TRY(mc) {
#ifdef IRC_FRIENDCHANNEL //{{{
case "_list_friends_offline": // _tab
t = " "; // fall thru
case "_list_friends_away": // _tab
unless (t) t = " +"; // fall thru
case "_list_friends_present": // _tab
unless (t) t = " @"; // fall thru
if (pointerp(vars["_list_friends_nicknames"])) { // _tab
# ifdef IRC_FRIENDCHANNEL_HEREAWAY
u = implode(vars["_list_friends_nicknames"], " ");
# else
u = implode(vars["_list_friends_nicknames"], t);
# endif
// u = strlen(u) ? (MYNICK+" "+u) : MYNICK;
# ifdef _flag_encode_uniforms_IRC
u = uniform2irc(u);
# endif
// just a trick to do the #ifdef in textdb, should we
// opt for IRC_FRIENDCHANNEL forever, we can remove
// _channel again, from both textdb and here
render(
# ifdef IRC_FRIENDCHANNEL_HEREAWAY
mc+"_channel"
# else
"_list_friends_channel" // _tab
# endif
, 0, ([ "_friends": u,
"_nick_me" : MYNICK ]) );
} else {
P1(("%O irc/user:w() got %O without friends list in %O\n",
ME, mc, vars))
}
# ifdef IRC_FRIENDCHANNEL_HEREAWAY
reply(RPL_ENDOFNAMES, (mc == "_list_friends_away"
? "&AWAY": "&HERE") +" :End of Buddylist.");
# else
// bad!
if (mc == "_list_friends_away")
reply(RPL_ENDOFNAMES, "& :End of Buddylist.");
# endif
return 1;
#endif //}}}
case "_status_place_topic":
// traditional IRC topic message without author
render(mc +"_only", 0, vars);
// extra semi-official '333' code containing author and time
render(mc +"_author", 0, vars);
return 1;
case "_status_place_members_automatic":
mc = "_status_place_members"; // fall thru
case "_status_place_members":
unless (vars["_members"])
#ifndef ENTER_MEMBERS
return _status_place_members(source, mc, data, vars);
#else
return 1;
#endif
// recursive call from _status_place_members()..
// yeah yeah don't ask
//PT(("irc:w(%O,%O,%O)\n", mc,data,vars))
break;
case "_request_attention":
case "_request_attention_wake":
vars["_beep"] = " ";
break;
case "_echo_place_enter":
case "_echo_place":
case "_echo":
case "_list_user_description_end":
case "_list_user_description":
case "_list_user":
case "_list":
case "_message_public":
case "_message":
case "_notice_session_end":
case "_notice_session":
case "_notice_place_leave":
case "_notice_place":
case "_notice_logon_last":
case "_notice_logon":
case "_notice_login":
case "_notice":
case "_request_version":
case "_request":
case "_status_log_none":
case "_status_log":
case "_status_place_members_end_verbose":
case "_status_place_members_end":
case "_status_place_members_each":
case "_status_place":
case "_status_person_present_action":
case "_status_person_present":
case "_status_person":
case "_status":
case "_warning_place_duty_owner":
case "_warning_place_duty":
case "_warning_place":
case "_warning_server_shutdown_temporary":
case "_warning_server_shutdown":
case "_warning_server":
case "_warning_usage_set_charset":
case "_warning_usage_set":
case "_warning_usage":
case "_warning":
// PT(("%O wegoptimiert in %O\n", mc, ME))
break; // optimization, avoid slicing for nothing
PSYC_SLICE_AND_REPEAT
}
render(mc, data, vars, source);
PSYC_TRY(mc) {
#ifndef GHOST
// cannot print it in irc::quit() as the last thing outputted w/ SANE_QUIT
case "_notice_session_end":
emit("ERROR :Closing Link: "+MYNICK+" (Don't worry, this is the "
"normal erratic way of IRC to close a connection)\n");
break;
#endif
#ifdef ALIASES
case "_echo_alias_added":
// man könnte das auch einfach so machen.. oder als
// sprintf().. und nur einen emit absetzen statt zwei.
// psyctext ist an dieser stelle in der tat ohne vorteil
// aber auch kein performancefaktor.. also egal
emit(":"+ vars["_alias"] +" NICK "+
SERVER_UNIFORM +"~"+ vars["_alias"] + "\n");
emit(psyctext(":[_nick_old] NICK [_nick_new]",
([ "_nick_old" : aliases[lower_case(vars["_address"])]
? SERVER_UNIFORM +"~"+
vars["_address"]
: uniform2irc(vars["_address"]),
"_nick_new" : vars["_alias"] ])) + "\n");
break;
case "_echo_alias_removed":
emit(psyctext(":[_nick_old] NICK [_nick_new]",
([ "_nick_old" : vars["_alias"],
"_nick_new" : aliases[lower_case(vars["_address"])]
? SERVER_UNIFORM +"~"+ vars["_address"]
: uniform2irc(vars["_address"])
])) + "\n");
emit(psyctext(":[_nick_old] NICK [_nick_new]",
([ "_nick_old" : SERVER_UNIFORM +"~"+ vars["_alias"],
"_nick_new" : vars["_alias"] ])) + "\n");
break;
#endif
#ifdef ENTER_MEMBERS //{{{
// now obsolete since net/user does the rendering of _list_members
// and converts it to _status_members* w()
case "_echo_place_enter":
namreply(vars);
break;
#endif //}}}
case "_message_public":
case "_message":
case "_notice_place_leave":
case "_notice_place_enter":
case "_notice_place":
case "_notice_logon_last":
case "_notice_logon":
case "_notice_login":
case "_notice":
case "_request_version":
case "_request":
case "_status_log_none":
case "_status_log":
case "_status_person_present_action":
case "_status_person_present":
case "_status_person":
case "_status":
case "_warning_server_shutdown_temporary":
case "_warning_server_shutdown":
case "_warning_server":
case "_warning_usage_set_charset":
case "_warning_usage_set":
case "_warning_usage":
case "_warning":
break; // optimization, avoid slicing for nothing
PSYC_SLICE_AND_REPEAT
}
return 1;
}
// somehow we lost visiblespeakaction == no handling... duh.
// but handling it here seems to be more adequate than doing it in msg()
wAction(mc, data, vars, source, variant, nick) {
// maybe this does the same job after all
// with that variant == "_ask" check, it does.
if (data && data != "" &&
#ifdef VISIBLESPEAKACTION_BY_DEFAULT
v("visiblespeakaction") == "off"
#else
!v("visiblespeakaction")
#endif
) {
if (variant == "_ask") {
return w(mc, data, vars, source);
}
return 0;
}
return ::wAction(mc, data, vars, source, variant, nick);
}
#ifndef _limit_amount_history_place_default
# define _limit_amount_history_place_default 5
#endif
// irc has it's own autojoin, which is a little different from others
autojoin() {
#if !defined(_flag_disable_place_enter_automatic) && !defined(GHOST) // too tricky for now
mixed t, t2;
string s;
if (isService) return -1;
# ifndef GAMMA
unless (v("place"))
vSet("place", T("_MISC_defplace", DEFPLACE));
# endif
// subscriptions are stored in lowercase, warum auch immer
if (sizeof(v("subscriptions")))
foreach (s in v("subscriptions")) {
// call_out(#'placeRequest, delay++, s, "_request_enter", //_automatic_subscription
placeRequest(s,
# ifdef SPEC
"_request_context_enter"
# else
"_request_enter"
# endif
, // _automatic_subscription
0, 0, ([ "_amount_history" : _limit_amount_history_place_default ]));
} else {
# ifdef GAMMA
unless (v("place"))
vSet("place", T("_MISC_defplace", DEFPLACE));
# endif
# ifndef _flag_disable_place_default
// call_out(#'placeRequest, delay++, v("place"), ...
placeRequest(v("place"),
# ifdef SPEC
"_request_context_enter"
# else
"_request_enter"
# endif
"_login", 0, 0,
([ "_amount_history" : _limit_amount_history_place_default ]));
# endif
}
# ifdef IRC_FRIENDCHANNEL //{{{
# ifdef IRC_FRIENDCHANNEL_HEREAWAY
emit(":"+ MYNICK +" JOIN :&HERE\n");
emit(":"+ MYNICK +" JOIN :&AWAY\n");
# else
emit(":"+ MYNICK +" JOIN :&\n");
# endif
# endif //}}}
#endif // GHOST || _flag_disable_place_enter_automatic
}
logon() {
mixed rc;
decodeInit();
// the only robot/service we are currently supporting:
// and we dont like those proxyscanners either
isService = abbrev("tunix", MYNICK);
vSet("scheme", "irc");
vDel("layout");
// vDel("agent"); -- either you start a ctcp to find it out
// or we prefer to have the old info
vDel("query"); // server-side query would drive most ircers crazy
#if 0
// what's wrong with doing this.. here?
// it's redundant, as it happens again in ::logon
sTextPath(0, v("language"), v("scheme"));
// it's necessary for _request_user_amount to work
// let's see if we can simply postpone that to after ::logon
#endif
//
// this helps handle the /set visiblespeakaction setting if this
// define has changed
#ifdef VISIBLESPEAKACTION_BY_DEFAULT
if (v("visiblespeakaction") == "mixed") vDel("visiblespeakaction");
#else
if (v("visiblespeakaction") == "off") vDel("visiblespeakaction");
#endif
#ifndef GHOST
set_buffer_size(32768); // enlarge buffer size
set_prompt("");
next_input_to(#'parse);
# ifdef RPL_ISUPPORT
// see also http://www.irc.org/tech_docs/005.html
# ifndef MAX_UNIFORM_LEN
# define MAX_UNIFORM_LEN 256 // anything goes. too dangerous to put nothing.
# endif
//
// PSYC for the fact that we natively speak a certain PSYC protocol
// version. UNIFORMS for our extension that nicknames may be
// full-fledged uniforms starting with a scheme. in fact we could list
// all schemes we have on this server but even then, schemes are
// dynamically extendable. then we have a list of psyc commands which
// may be seen as seperate modules and could in a fantastic world be
// considered as custom irc extensions. finally the regular 005
// settings as defined out there in the messed up world. to add all
// these "custom" setting names is primarily a suggestion from Nei,
// but i guess it is indeed appropriate to make it clear how very much
// different we are from a regular irc server.
//
reply(RPL_ISUPPORT, "PSYC=.99 ALIAS AVAILABILITY FRIEND HISTORY MOOD SHOUT SSET STATUS SUBSCRIBE THREAD TRUST PREFIX= CHANTYPES=# CHANMODES= NICKLEN="+ (string)MAX_UNIFORM_LEN +" CHANNELLEN="+ (string)MAX_UNIFORM_LEN +" CASEMAPPING=ascii TOPICLEN=4404 KICKLEN=4404 AWAYLEN=4404 MAXTARGETS=1 CHARSET="+ (v("charset")||SYSTEM_CHARSET) +" NETWORK=PSYC CTCP=PRESENCE,TS UNIFORMS=psyc,xmpp :are supported by this server");
//
// MAXCHANNELS vs CHANLIMIT - we currently only have a limit on subs
// STD? what the hell is STD?
// RFC2812? should we say that?
// FNC: forced nick changes. we may want this one day.
// TOPICLEN=4404 KICKLEN=4404 AWAYLEN=4404 or just ignore
// MAXNICKLEN?
// neue befehle: IGNORE vs SILENCE? SHOW? MASQUERADE?
//
// PSYC as network name is not a #define, since any psyc server is
// a gateway to the complete PSYC. introducing network names here is
// misleading and not useful thing to do. that's why it is statically
// PSYC, nothing more or less.
//
// nei suggests: ALIAS SUBSCRIBE FRIEND SET SSET SILENCE CHANTYPES=# PREFIX= CHANMODES= CMDCHAR=+ ACTIONCHAR=: EINOTIFY=notify LANGUAGE=en CHARSET_PAYLOAD=utf-8 CHARSET=utf-8 NETWORK=psyc UNIFORM_NICK UNIFORM_CHAN
// SILENCE: ach und ich weiss nicht ob /quote silence bzw /silence den psyced befehl silence aufruft, aber imo sollte er das aus verwirrungs-vermeidungs-gruenden nicht tun. silence im irc ist serverseitiges ignore.
# endif
# ifndef BETA
lusers();
# endif
motd();
rc = ::logon();
// the following things happen after logon, because the textdb isn't
// available earlier. if this order of things is not acceptable, then
// we have to run sTextPath twice (see above)
# ifdef BETA
# ifndef _flag_disable_query_server
sendmsg("/", "_request_user_amount", 0, ([]));
// reply.h says RPL_LUSERME is mandatory.. huh.. FIXME?
// #255 [_nick_me] :I have 4404 clients and 4404 servers
# endif
# endif
# ifndef _flag_disable_request_version_IRC
// since we cannot relay tagged version requests to the client
// easily, we request the version number once at starting time.
// any other protocol finds it completely normal to exchange
// version strings, only IRC makes it terribly complicated and
// even political. oh of course, that's because on irc the server
// admin isn't necessarily a person of your trusting.
w("_request_version", 0, 0, SERVER_UNIFORM);
# endif
#endif
return rc;
}
ircMsg(from, c, args, text, all) {
mixed n, t, t2;
// if (isService)
// log_file("IRCSERV", "[%s %s] %s\n", query_ip_number(), MYNICK, all);
switch(c) {
#if 0
// wird von ircII falsch verarbeitet.. also lieber
// help ausgeben.
case "nick":
reply(ERR_NICKNAMEINUSE, args + " :Nickchanges are not supported on "
"this server. They never will be.");
//emit(":"+SERVER_HOST+" 432 " +v("name")+" # :Nick changes are not permitted on this server yet.\r\n");
return 1;
#endif
case "privmsg":
privmsg(args, text, 1);
return 1;
case "msg": // very old-school irc syntax
privmsg(0, args || text, 1);
return 1;
case "notice":
case "n":
privmsg(args, text, 0);
return 1;
case "who":
// denkbare argumentsyntax:
// WHO #fasel
// WHO nickname
// WHO *.fi
// manche irc-clients (GUI) senden who #raum,
// oder sonstigen WHO kram automatisiert..
// das filtern wir dann mal, weil wir noch kein
// korrektes WHOREPLY zurückgeben.
// sobald wir das haben kann man's freigeben.....
if (args && strlen(args)>1) {
if (channel2place(args)) {
whores(args, 1);
return 1;
}
}
// in any case: end of who
// it is okay to ignore perverted requests like *.fi
// use google for that
w("_list_user_description_end");
return 1;
case "names":
// denkbare argumentsyntax NAMES #bla,#fase
if (args && strlen(args)>1) {
map(explode(args, ","), #'whores);
return 1;
}
people();
w("_list_places_end"); // end of names
return;
case "ison":
// ha! ich hab ne neue idee:
// so lange damit nerven bis sie's abschalten
// aber nur wenn sie sich gereggt haben
// <fippo>: schlechte idee, du nervst damit unsere huebschen
// userinnen
#ifdef IRC_ANNOYANCE
if (!IS_NEWBIE && !isGenervt++)
reply(ERR_UNKNOWNCOMMAND,
//c+" :PSYC does not support historic poll-based notify"
c+" :Please clean out your notify list for this server. It is of no use on PSYC.");
#else
// faking everyone to be on.
// some clients need that to be gui-usable at all
reply(RPL_ISON, args);
// would be better to check our state for presence
#endif
return 1;
case "mode":
#ifndef GHOST
// "don't mode me" is a software design rule - you should
// not send people into cryptic non-obvious modes.
// reply(ERR_UNKNOWNMODE, args+" :Don't mode me (No support for IRC legacy, use PSYC features instead)");
// die clients moden soviel sie lust haben.. und machen stress
// wenn man es ignoriert.. oder?
unless (stringp(args)) t = "";
else unless (sscanf(args, "%s %s", t, t2)) t = args;
if (strlen(t) && channel2place(t)) {
unless (t2) {
reply(RPL_CHANNELMODEIS, t+" +");
} else for (int i = 0; i < strlen(t2); i++) {
switch (t2[i]) {
case '+':
case '-':
break;
case 'b':
reply(RPL_ENDOFBANLIST,
t + " :No traditional banlist on " SERVER_VERSION);
// " :End of new KONTEXT-AWAY banlist"
break;
case ' ':
return; // skip argument list
default:
#ifdef _flag_strict_IRC_mode
reply(RPL_CHANNELMODEUNKNOWN, t2[i..i] + " :"
"is unknown mode char to me.");
#endif
}
}
# ifdef IRC_FRIENDCHANNEL
} else if (t[0] == '&') {
reply(RPL_CHANNELMODEIS, t+" +m");
# endif
} else
//emit(MYNICK+" MODE "+MYNICK+" :+\n");
reply(RPL_UMODEIS, "+");
#endif
return 1;
case "topic":
unless (args && strlen(args)) return;
P4(("IRC topic %O %O\n", args, text))
unless (t = channel2place(args)) return;
t = find_place(t) || t;
// The topic for channel <channel> is returned if there is
// no <topic> given. If the <topic> parameter is an empty
// string, the topic for that channel will be removed.
unless (text) //wrong:// && strlen(text)
sendmsg(t, "_query_topic", 0, ([ "_nick" : MYNICK ]));
else
sendmsg(t, "_request_set_topic", 0, ([
"_topic" : text,
"_nick" : MYNICK
]));
return 1;
case "user":
if (sscanf(args, "%s %s %s", n, t, t2)) {
vSet("publiclogin", n);
vSet("publichost", t);
}
// publicname is sent as a 'nicer' full name to jabber
// users. if it contains a typical IRCNAME they will freak
// out, especially unaccostumed newbies on iChat, G-Talk etc.
// iChat even shows 'publicname' for every line of chat!
// so let's make a specific field for IRCNAME instead.
// chat cultures just don't want to get merged sometimes....
vSet("mottotext", text);
return 1;
case "invite":
if (sscanf(args, "%s #%s", n, t) && strlen(t)) {
t2 = find_place(t);
if (!t2 || !places[t2]) {
PT(("irc: invite %s into %s = %O.\n", n, t, t2))
w("_error_necessary_membership",
0, ([ "_nick_place": t ]));
return 1;
}
vSet("place", t);
place = t2;
} else {
#if 0
// atypical: allow for invite using current place.
n = args;
// runs the risk of you inviting the person to the
// wrong channel. too risky. let's go traditional:
#else
reply(ERR_NEEDMOREPARAMS,
"INVITE :Not enough parameters");
return 1;
#endif
}
if (invite(n)) reply(RPL_INVITING, args);
// else: ERR_USERONCHANNEL or ERR_CHANOPRIVSNEEDED otherwise?
return 1;
case "join":
case "part":
// bug in some clients like.. uh.. ircg
unless (args) args = text;
// make sure there _is_ something. this ignores requests to
// 'JOIN #' but also 'JOIN 0' which i don't see why it
// should be a useful thing to implement. should anyone
// ask we'll add a +panic command to usercmd.i and have it
// called from here... ;) we should in any case not
// support the completely absurd /join #0,0 syntax
unless (args && strlen(args)>1) return 1;
// PART: the text var contains the parting reason message.
// we happily ignore that as it is bad protocol design. ;)
// JOIN: we should only be using the first arg as the second arg
// would be the key of a +k channel (since we don't have
// them, we never see that). formally it can even be a
// comma list of keys matching the comma list of chans.
// that's seriously awful and a good reason not to support
// MODE +k ;)
// request() calls teleport() which in the case of places we
// have already joined quietly returns and does nothing.
// this is the same behaviour that ircd(8) provides, even if
// it is not clearly specified in RFC2812. anyway, this should
// be the clean way to solve all our issues with autojoining
// and reconnecting irc clients
foreach(t : explode(args, ","))
request(ME, c == "part"? "_leave": "_enter_join",
([ "_group": channel2place(t) || t,
"_flag": "_quiet",
]));
return 1;
case "away":
// irc-semantik ist anders als in psyc
unless (text) text = args; // just in case a user forgot the :
if (!stringp(text) || !strlen(text)) text = 0;
return parsecmd(text ? "ircgone "+text : "irchere");
case "identify":
// some script providing us with nickserv credentials
// we don't support such kinky stuff
return 1;
case "cs":
case "chanserv":
// instead of emulating "mode" one could implement chanserv
// compatible commands in net/place/standard.c -- hmmm
case "statserv":
case "bs":
case "botserv":
case "hs":
case "helpserv":
case "os":
case "operserv":
case "ms":
case "memoserv":
case "nws":
case "newsserv":
// these don't really work, but we could make them..
case "ns":
case "nickserv":
case "psyc":
return parsecmd(text || args);
#if 1
case "dialog":
case "m":
case "more":
case "message":
case "r":
case "reply":
case "q":
case "query":
case "t":
case "talk":
case "tell":
case "g":
case "gr":
case "greet":
// diese befehle sind für irc nicht sinnvoll, egal wie.
// sollten aber hübscher abfangbar sein, one happy day
reply(ERR_UNKNOWNCOMMAND, c+" :Command not useful here");
// hmm.. per +r und +q kann man den mist immernoch
// einschalten.. dies ist also genau genommen der falsche
// ort das abzuhandeln.. TODO
return 1;
#endif
default:
if (::ircMsg(from, c, args, text, all)) return 1;
// mud-compatible debug tool
// just type 'hello to say hello
D1(if (all[0] == '\'') return ::input(all[1..]);)
// fall
}
// most commands can be handled by usercmd:cmd()
// that's not ultimately efficient as of now,
// but better than code duplication
if (text && args) return parsecmd(c+" "+args+" "+text);
if (text || args) return parsecmd(c+" "+(text || args));
return parsecmd(c);
// but the argument passing needs some tweaking..
}
static whores(chan, flag) {
chan = channel2place(chan);
if (chan) chan = find_place(chan);
unless (chan) return;
P4(("irc:whores(%O, %O)\n", chan, flag? "W": "N"))
sendmsg(chan, "_request_members", 0, ([ "_nick" : MYNICK,
"_tag": flag? "W": "N" ]));
return 1;
}
static privmsg(args, text, req) {
string person,room;
mixed t;
unless (stringp(text) && strlen(text)) return;
#ifdef GAMMA
// fippoism typing indicator.. but shouldn't it *do* something
// after detecting this CTCP-like "typing" flag hack?
if (strlen(text) > 1 && text[<1] == 0x0f && text[<2] == 0x0f) {
text = text[..<2];
}
#endif
if (index(args, ',') > 0) {
w("_failure_unsupported_targets_multiple",
"We do not allow sending to several recipients at once. Why did your client ignore our MAXTARGETS=1 directive?");
return 0;
}
if (room = channel2place(args)) {
if (!place || !v("place") || stricmp(room, v("place"))) {
if (t = find_place(room)) {
place = t;
// this makes positive use of the psyc notion
// of a "current" room even if irc doesn't support
// that protocolwise..
vSet("place", room);
} else { // should the caller print this on privmsg() == 0?
w("_error_illegal_name_place", "Room name contains illegal "
"characters."); // uh. might be a lie
return 0;
}
}
} else {
if (lower_case(args) == "nickserv") return parsecmd(text);
person = args;
}
unless (text = decode(text, person, req)) return;
// unless (args) speak(text);
// else if (room = channel2place(args)) speak(text, 0, room);
// else tell(args, text);
if (!req && person) // NOTICE <nick>
tell(person, text, 0, 0, "_message_private_annotate");
// NOTICE <channel> treated as normal room chat
else return input(text, person);
}
version(text, person, req) {
mixed target;
#ifdef ALIASES
mixed t = aliases[lower_case(person)] || person;
#else
# define t person
#endif
target = find_person(t) || t;
P4(("user:version() in %O from %O\n", ME, target))
if (text &&! req &&! v("agent")) vSet("agent", text);
return ::version(text, target, req, MYNICK);
#ifdef t
# undef t
#endif
}
ctcp(code, text, person, req) {
mixed target;
#ifdef ALIASES
mixed t = aliases[lower_case(person)] || person;
#else
# define t person
#endif
target = find_person(t) || t;
return ::ctcp(code, text, target, req, MYNICK);
#ifdef t
# undef t
#endif
}