1
0
Fork 0
mirror of git://git.psyced.org/git/psyced synced 2024-08-15 03:25:10 +00:00

let the past begone in cvs land. welcome to igit igit!

This commit is contained in:
PSYC 2009-01-26 20:21:29 +01:00
commit 4e601cf1c7
509 changed files with 77963 additions and 0 deletions

633
world/net/jabber/active.c Normal file
View file

@ -0,0 +1,633 @@
// $Id: active.c,v 1.395 2008/04/01 09:38:26 lynx Exp $ // vim:syntax=lpc:ts=8
#include <net.h>
#include <sys/time.h>
#include <url.h>
// a jabber thing which actively connects something
#define NO_INHERIT
#include "jabber.h"
#undef NO_INHERIT
#ifdef ERQ_WITHOUT_SRV
# define hostname host // hostname contains the name before SRV resolution
#endif
inherit NET_PATH "jabber/mixin_render";
// a derivate of circuit which knows JABBER
inherit NET_PATH "circuit";
inherit NET_PATH "name";
#if 0 // apparently unused
// virtual inherit of textc.c thus output.c isnt really working
#define NO_INHERIT
#include <text.h>
#undef NO_INHERIT
#endif
#include "interserver.c" // interserver stuff
volatile mixed gateways;
volatile mixed *dialback_queue;
volatile string streamid;
volatile float streamversion;
volatile int authenticated;
volatile int ready; // finished with sasl, starttls and such
volatile int dialback_outgoing;
tls_logon(); // prototype
#ifdef NOT_EXPERIMENTAL // not strictly necessary, but more spec conformant
quit() {
emit("</stream:stream>");
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\n%O: shutting down, quit called\n", ME); )
#endif
remove_interactive(ME); // not XEP-0190 conformant, but shouldn't
// matter on an outgoing-only socket
//destruct(ME);
}
#endif
sGateway(gw, ho, id) {
// TODO: ho is obsolete
P2(("%O: setting %O as gateway for %O, id %O\n", ME, gw, ho, id))
unless (gateways) gateways = ([ ]);
gateways[id] = gw;
return ME; // set myself as active for this gateway
}
removeGateway(gw, id) {
if (gateways[id] == gw) {
m_delete(gateways, id);
}
}
start_dialback() {
string source_host, key;
source_host = NAMEPREP(JABBER_HOST);
key = DIALBACK_KEY(streamid, hostname, source_host);
P3(("%O: starting dialback from %O to %O\n", ME, source_host, hostname))
dialback_outgoing = 1;
emit(sprintf("<db:result to='%s' from='%s'>%s</db:result>",
hostname, source_host, key));
}
process_dialback_queue() {
mixed *t;
ready = 1;
if (pointerp(dialback_queue))
foreach (t : dialback_queue)
render(t[1], t[2], t[3], t[0]);
dialback_queue = ({ });
}
handle_starttls(XMLNode node) {
if (node["@xmlns"] != NS_XMPP "xmpp-tls") {
P0(("%O: expecting xmpp-tls proceed or failure, got %O:%O\n",
ME, node[Tag], node["@xmlns"]))
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\n%O: expecting xmpp-tls proceed or failure, got %O:%O\t%O", ME, node[Tag], node["@xmlns"], ctime()); )
#endif
nodeHandler = #'jabberMsg;
return jabberMsg(node);
}
nodeHandler = #'jabberMsg;
if (node[Tag] == "proceed") {
tls_init_connection(ME, #'tls_logon);
} else { // treat everything else as a failure
P0(("%O got a tls failure\n", ME))
}
}
handle_stream_features(XMLNode node) {
unless (node[Tag] == "stream:features") {
// this should not happen!
P0(("%O: expecting stream:features, got %O\n", ME, node[Tag]))
nodeHandler = #'jabberMsg;
// TODO
// be careful, usually there are not many nodes which an active
// may get, mostly errors, so it might not be a good idea
// to do dialback if this happens
start_dialback();
process_dialback_queue();
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\nprocessing dialback queue, expecting stream features but not found" ); )
#endif
return jabberMsg(node);
}
unless (streamversion >= 1.0) {
P0(("%O unexpected stream:features with stream:version %O\n",
ME, streamversion))
return;
}
nodeHandler = #'jabberMsg;
#ifdef WANT_S2S_TLS
// should only happen if stream version is >= "1.0"
if (node["/starttls"] && tls_available()
&& !tls_query_connection_state(ME)
// dont starttls to hosts that are known to have
// invalid tls certificates
// && !config(XMPP + hostname, "_tls_invalid")
){
// may use tls unless we already do so
emit("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
nodeHandler = #'handle_starttls;
return;
}
#else
if (node["/starttls"]
&& node["/starttls"]["/required"]) {
P0(("%O requires <starttls/> but we can't provide that\n", ME))
connect_failure("_encrypt_necessary", "Encryption required but unable to provide that");
return;
}
#endif
#ifdef WANT_S2S_SASL
// possibly authenticated via sasl?
if (authenticated && !node["/switch"]) {
P2(("%O running Q after getting stream:features and having "
"authenticated via sasl\n", ME))
process_dialback_queue();
return runQ();
}
if (node["/mechanisms"]
&& node["/mechanisms"]["/mechanism"]){
XMLNode tx;
mixed mechs = ([ ]);
// get the mechanismsm
tx = node["/mechanisms"]["/mechanism"];
if (nodelistp(tx))
foreach (XMLNode tx2 : tx)
mechs[tx2[Cdata]] = 1;
else
mechs[tx[Cdata]] = 1;
// choose a mechanism
P2(("available SASL mechanisms: %O\n", mechs))
#ifndef _flag_disable_authentication_external_XMPP
if (mechs["EXTERNAL"]) {
// TODO we should check that the name in our
// certificate is equal to JABBER_HOST
// but so should the other side!
emit("<auth mechanism='EXTERNAL' "
"xmlns='" NS_XMPP "xmpp-sasl'>" +
encode_base64(JABBER_HOST)
+ "</auth>");
return;
} else
#endif
if (mechs["DIGEST-MD5"]
&& config(XMPP + hostname, "_secret_shared")) {
PT(("jabber/active requesting to do digest md5\n"))
emit("<auth mechanism='DIGEST-MD5' "
"xmlns='" NS_XMPP "xmpp-sasl>" +
encode_base64(JABBER_HOST) +
"</auth>");
return;
}
}
#endif
#ifdef SWITCH2PSYC
else if (node["/switch"]) { // should check scheme
PT(("upgrading %O from XMPP to PSYC.\n", ME))
emit("<switching xmlns='http://switch.psyced.org'>"
"<scheme>psyc</scheme>"
"</switching>");
return;
}
#endif
// send dialback request (step 4) here if nothing else
// is done
if (dialback_outgoing == 0 && qSize(me)) {
start_dialback();
}
process_dialback_queue();
}
disconnected(remainder) {
P2(("active %O disconnected\n", ME))
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\n%O disc\t%O", ME, ctime()); )
#endif
authenticated = 0;
ready = 0;
if (dialback_outgoing != 0) {
// just saw that happen twice with jabber.org
// should we trigger a reconnect then?
P0(("%O got disconnected with dialback outgoing != 0\n", ME))
// TODO: reconnect koennte sinnvoll sein
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\n%O disconnected with dialback outgoing != 0\t%O", ME, ctime()); )
#endif
dialback_outgoing = 0;
}
// nothing else happening here? no reconnect?
// TODO: what about the dialback Q if any?
::disconnected(remainder);
return flags & TCP_PENDING_DISCONNECT;
}
// we love multiple inheritance.. rock'n'roll!
static int logon(int failure) {
// TODO: it seems that it is bad to call this from tls_logon
// for a second time
// 1. Originating Server establishes TCP connection to Receiving Server.
if (NET_PATH "circuit"::logon(failure)) {
set_combine_charset(COMBINE_CHARSET);
#ifdef INPUT_NO_TELNET
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE | INPUT_NO_TELNET);
#else
enable_telnet(0, ME);
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE);
#endif
sTextPath(0, "en", "jabber");
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\n%O logon\t%O", ME, ctime()); )
#endif
/* 2. Originating Server sends a stream header to Receiving Server */
emit("<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "
"xmlns='jabber:server' xmlns:db='jabber:server:dialback' "
"to='" + hostname + "' "
"from='" + NAMEPREP(JABBER_HOST) + "' "
"xml:lang='en' "
"version='1.0'>");
#ifdef NOT_EXPERIMENTAL // not strictly necessary, but more spec conformant
} else if (!qSize(me)) { // no retry for dialback-only
P0(("notify gateways %O of failure\n", gateways))
if (sizeof(dialback_queue) > 1) {
P0(("tell fippo that sizeof(dialback queue) was > 1\n"))
}
foreach(string id, mixed gw : gateways) {
if (objectp(gw)) {
gw->remote_connection_failed();
}
}
dialback_queue = 0;
destruct(ME);
#endif
}
unless (gateways) {
gateways = ([ ]);
}
return 1;
}
#ifdef WANT_S2S_TLS
tls_logon(result) {
if (result < 0) {
PT(("%O tls_logon %d\n", ME, result))
connect_failure("_encrypt", "Problems setting up an encrypted circuit");
} else if (result == 0) {
// we need to check the certificate
// selfsigned certs are ok, but we need dialback then
//
// if the cert is ok, we can set authenticated to 1
// to skip dialback
mixed cert = tls_certificate(ME, 0);
P3(("active::certinfo %O\n", cert))
if (mappingp(cert)) {
unless (certificate_check_jabbername(hostname, cert)) {
#ifdef _flag_report_bogus_certificates
monitor_report("_error_invalid_certificate_identity",
sprintf("%O presented a certificate that "
"contains %O/%O",
hostname, cert["2.5.4.3"],
cert["2.5.29.17:1.3.6.1.5.5.7.8.5"]));
#else
P1(("TLS: %s presented a certificate with unexpected identity.\n", hostname))
P2(("%O\n", cert))
#endif
#ifdef _flag_log_bogus_certificates
log_file("CERTS", S("%O %O %O id?\n", ME, hostname, cert));
#endif
#if 0 //def _flag_reject_bogus_certificates
QUIT
return 1;
#endif
}
else if (cert[0] != 0) {
#ifdef _flag_report_bogus_certificates
monitor_report("_error_untrusted_certificate",
sprintf("%O certificate could not be verified",
hostname));
#else
P1(("TLS: %s presented untrusted certificate.\n", hostname))
P2(("%O\n", cert))
#endif
#ifdef _flag_log_bogus_certificates
log_file("CERTS", S("%O %O %O\n", ME, hostname, cert));
#endif
#if 0 //def _flag_reject_bogus_certificates
// QUIT is wrong...
QUIT
return 1;
#endif
}
} else {
P0(("%O: no tls_certificate() for %O\n", ME, hostname))
}
logon(0);
} else {
// should not happen, if it does the driver is doing sth wrong
PT(("%O tls_logon %d - MUST NOT HAPPEN\n", ME, result))
}
}
#endif
// this one is completly specific to active.c
jabberMsg(XMLNode node) {
/* this will be rarely used if we do dialback
* due to active part beeing virtually write-only
*/
P2(("%O jabber/active got %O\n", ME, node[Tag]))
object o;
string t;
switch (node[Tag]) {
case "db:result":
/* 10: Receiving Server informs Originating Server of the result
* we are originating server and are informed of the result
*/
dialback_outgoing = 0;
if (node["@type"] == "valid") {
#ifdef LOG_XMPP_AUTH
D0( log_file("XMPP_AUTH", "\n%O auth dialback", ME); )
#endif
authenticated = 1;
runQ();
} else {
// else: queue failed
P0(("%O something gone wrong in active db:result: %O\n", ME, node))
authenticated = 0;
/* Note: At this point, the connection has either been
* validated via a type='valid', or reported as invalid.
* If the connection is invalid, then the Receiving
* Server MUST terminate both the XML stream and the
* underlying TCP connection.
*/
emit("</stream:stream>");
remove_interactive(ME);
connect_failure("_dialback", "dialback gone wrong");
}
break;
case "db:verify": // receiving step 9
// t = NAMEPREP(node["@to"]) + ";" + node["@id"];
t = node["@id"];
o = gateways[t];
if (objectp(o)) {
o -> verify_connection(node["@to"],
node["@from"],
node["@type"]);
// probably we can delete this...
m_delete(gateways, t);
#ifdef NOT_EXPERIMENTAL // not strictly necessary, but more spec conformant
} else if (member(gateways, t)) {
P0(("%O found gateway for %O, but it is not an object: %O\n",
ME, t, o))
m_delete(gateways, t);
#endif
} else {
// wir haben keinen Gateway zu der ID...
// was machen wir? loggen und ignorieren!
P0(("%O could not find %O in %O\n", ME,
t, gateways));
}
break;
#ifdef WANT_S2S_SASL
case "success":
if (node["@xmlns"] == NS_XMPP "xmpp-sasl") {
# ifdef LOG_XMPP_AUTH
D0( log_file("XMPP_AUTH", "\n%O auth SASL", ME); )
# endif
// TODO: for digest-md5 we should check rspauth
emit("<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "
"xmlns='jabber:server' xmlns:db='jabber:server:dialback' "
"to='" + hostname + "' "
"from='" + NAMEPREP(JABBER_HOST) + "' "
"xml:lang='en' "
"version='1.0'>");
authenticated = 1;
}
break;
case "challenge":
PT(("%O got a sasl challenge\n", ME))
if (node["@xmlns"] == NS_XMPP "xmpp-sasl") {
unless(t = node[Cdata]) {
// none given
} else unless (t = to_string(decode_base64(t))) {
// base64 decode error?
} else {
// this one is shared across all those digest md5's
mixed data;
string secret;
string response;
PT(("decoded challenge: %O\n", t))
data = sasl_parse(t);
PT(("extracted %O\n", data))
data["username"] = JABBER_HOST;
secret = config(XMPP + hostname, "_secret_shared");
unless(secret) {
// mh... this is a problem!
// we only started doing this if we have a secret,
// so this cant be empty
}
data["cnonce"] = RANDHEXSTRING;
data["nc"] = "00000001";
data["digest-uri"] = "xmpp/" JABBER_HOST;
response = sasl_calculate_digestMD5(data, secret, 0);
// ok, the username is our hostname
// note: qop must not be quoted, as we are 'client'
t = "username=\"" JABBER_HOST "\","
"realm=\"" + data["realm"] + "\","
"nonce=\"" + data["nonce"] + "\","
"cnonce=\"" + data["cnonce"] + "\","
"nc=" + data["nc"] + ",qop=auth,"
"digest-uri=\"" + data["digest-uri"] + "\","
"response=" + response + ",charset=utf-8";
PT(("%O sent rspauth %O\n", ME, response))
emit("<response xmlns='" NS_XMPP "xmpp-sasl'>"
+ encode_base64(t) +
"</response>");
}
}
break;
case "failure":
// the other side has to close the stream
monitor_report("_error_invalid_authentication_XMPP", sprintf("%O got a failure with xml namespace %O\n", ME, node["@xmlns"]));
connect_failure("_invalid_authentication", "counterpart did not like our authorization");
break;
#endif
case "stream:error":
if (ME) remove_interactive(ME);
authenticated = 0;
if (node["/connection-timeout"]) {
// this is normal, unless we had something to say,
// but we are an event-oriented system, so this
// "if" should never happen. then again, i've seen
// plenty of should-never-happens to happen, so..
// to put it in brian wilson words, god only knows ;)
if (qSize(me))
connect_failure("_timeout", "counterpart sent timeout");
} else if (node["/not-authorized"]) {
connect_failure("_illegal_source",
"counterpart claims we are not authorized");
} else if (node["/system-shutdown"]) {
connect_failure("_unavailable_restart",
"counterpart is doing a system shutdown");
} else if (node["/host-unknown"]) {
// remote side runs a jabber server, but does not
// accept hostname
connect_failure("_host_unknown",
"counterpart does not recognize hostname");
} else {
P0(("%O stream error, other side said %O\n", ME, node))
connect_failure("_unknown",
"counterpart stopped transaction for unknown reason");
}
break;
#ifdef SWITCH2PSYC
case "switched":
# ifdef QUEUE_WITH_SCHEME
o = ("psyc:" + host) -> circuit();
# else
o = ("psyc:" + host) -> circuit(0, 0, 0, 0, host); // whoami
# endif
P1(("%O switched to %O for %O\n", ME, o, host))
exec(o, ME);
o -> logon();
destruct(ME);
return;
#endif
default:
P0(("%O: unexpected %O:%O\n", ME, node["@tag"],
node["@xmlns"]))
break;
}
}
// prototype
int msg(string source, string mc, string data,
mapping vars, int showingLog, string target) {
mixed t;
string template, output;
P2(("%O jabber/active:msg(%O,%O..)\n", ME, source, mc))
/* TODO: use the language, i.e. sTextPath if _language is not
* the current language
* sTextPath is quite expensive (string object lookup),
* but as we're parsing xml anyway...
*/
vars = copy(vars);
#ifdef DEFLANG
unless(vars["_language"]) vars["_language"] = DEFLANG;
#else
unless(vars["_language"]) vars["_language"] = "en";
#endif
/* another note:
* instead of queuing the msg()-calls we could simply queue
* the output from emit (use the new net/outputb ?)
* this avoids bugs with destructed objects
*/
#ifdef PREFIXES
// completely skip these methods
if (abbrev("_prefix", mc)) return 1;
#endif
#if 0 // !EXPERIMENTAL
/* currently, we want _status_person_absent
* this may change...
*/
else if (abbrev("_status_person_absent", mc)) return 1;
#endif
switch (mc){
case "_message_echo_private":
return 1;
}
// desperate hack
unless (vars["_INTERNAL_mood_jabber"])
vars["_INTERNAL_mood_jabber"] = "neutral";
// TODO: only _dialback_request_verify sollte hier betroffen sein
if (abbrev("_dialback", mc)) {
unless (interactive()) connect(); // ?
if (ready) {
render(mc, data, vars, source);
} else {
unless (dialback_queue) dialback_queue = ({ });
dialback_queue += ({ ({ source, mc, data, vars, target }) });
}
return 1;
}
// this should be part of render()/w()
// but it has to happen before enqueuing (otherwise we had
// problems with destructed objects)
// "late enqueu" could be a solution to that problem
determine_sourcejid(source, vars);
determine_targetjid(target, vars);
if (vars["_place"]) vars["_place"] = mkjid(vars["_place"]);
// component needs to set authenticated as needed
unless (authenticated) {
if (interactive() && ready && dialback_outgoing == 0) {
start_dialback();
}
#if JABBER_HOST == SERVER_HOST
// we can only do this if mkjid patches the psyc host into jabber host
// but that is terrifically complicated and requires parsing of things
// we already knew.. so let's simply enqueue with object id and reject
// if the object got lost in the meantime
vars["_source"] = UNIFORM(source);
// hmm.. actually no.. since we also set _INTERNAL_source_jabber
// this variable should never get used for anything anyway.. so
// leaving it out should be completely harmless
#else
// is this a bad idea? not sure.. we'll see..
// if i don't have this here, a "tell lynX where it happened" will
// be triggered from here -- best to never use JABBER_HOST really ;)
vars["_source"] = source;
// behaviour has changed.. so maybe we don't need this any longer TODO
#endif
return enqueue(source, mc, data, vars, showingLog, target);
}
return ::msg(source, mc, data, vars, showingLog, target);
}
open_stream(XMLNode node)
{
mixed *t;
string key;
P2(("%O active for %O.\n", ME, hostname))
D1( unless (interactive(ME))
PP(("%O open_stream: should be interactive!\n", ME));
)
streamid = node["@id"];
// actually, this is incorrect, als both parts of the version
// are to be treated as separte ints ($4.4.1)
// but as long as we only have 0 and 1.0...
streamversion = to_float(node["@version"]);
// note: flushing this Q should wait until stream features is completed
if (streamversion < 1.0) {
process_dialback_queue();
if (qSize(me))
start_dialback();
} else {
// wait for stream:features
nodeHandler = #'handle_stream_features;
}
return;
}

475
world/net/jabber/common.c Normal file
View file

@ -0,0 +1,475 @@
// $Id: common.c,v 1.270 2008/04/11 10:27:24 fippo Exp $ // vim:syntax=lpc:ts=8
#define NO_INHERIT
#include "jabber.h"
#undef NO_INHERIT
#include <net.h>
#include <text.h>
//virtual inherit NET_PATH "output";
#include <url.h>
#ifdef __psyclpc__
# if __VERSION_MICRO__ > 4
// since this file is in the psyced distribution we can't
// be sure we _really_ have RE_UTF8 unless we check the
// driver version.. sigh
# include <sys/regexp.h>
# endif
#endif
jabberMsg();
inherit NET_PATH "xml/common";
volatile string buffer = "";
closure jid_has_node_cl = (: int t, t2;
t = index($1, '@');
if (t == -1) return 0;
t2 = index($1, '/');
if (t2 == -1 || t2 > t) return 1;
return 0; :);
qScheme() { return "xmpp"; } // habber.. chabber.. xabber?
// wie schreibt man das wie alvaro es spricht?
// all objects should call this for unwelcome hosts. TODO
// i wonder if gateway.c does inherit net/connect though. prolly not.
block() {
STREAM_ERROR("policy-violation", "This is way beyond imagination.")
// cant we :: that?
destruct(ME);
return 0;
//return ::block();
}
int emit(string message) {
#if __EFUN_DEFINED__(convert_charset) && SYSTEM_CHARSET != "UTF-8"
// apparently render() does this for us
// iconv(message, SYSTEM_CHARSET, "UTF-8");
#endif
#ifdef RE_UTF8
string t, err;
// according to http://www.w3.org/TR/xml/#charsets
// remove illegal unicode chars --// thx elmex
err = catch(t = regreplace(message, "[^\\x{9}\\x{A}\\x{D}\\x{20}-\\x{D7FF}\\x{E000}-\\x{FFFD}\\x{10000}-\\x{10FFFFFF}]+", "*", RE_GLOBAL | RE_UTF8); nolog);
if (err || t != message) {
// Info: Chars filtered to %O. Message was %O.
log_file("CHARS_XMPP", "[%s] %O %O %O\n", ctime(),
ME, err, message);
if (t) message = t;
// we get here when somebody has configured utf8 even though
// he is actually sending latin. would be nicer to figure this
// out at parsing time rather than at rendering time, and to
// generate an _error back to sender in that case. TODO
//monitor_report("_error_invalid_data_charset", ...what?);
P1(("catch! invalid chars going out to %O\n", ME))
return 0; // do not emit
}
#endif
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\n« %O\t%s", ME, message); )
#endif
return ::emit(message);
}
// this assumes the old ldmuddish charmode+combine-charset
// if we ever get a input_bytes it needs to be rewrittn
feed(a) {
int pos;
buffer += a;
while ((pos = strstr(buffer, ">") + 1) > 0){
if (strstr(buffer, "<") == -1) {
/* XML is a braindead spec
* > MAY be encoded
* 'this may be fixed in future versions'
* of the xmpp spec
*/
buffer = buffer[0..pos-2] + "&gt;"; // + buffer[pos..] <-- empty
continue;
}
#if __EFUN_DEFINED__(convert_charset) && SYSTEM_CHARSET != "UTF-8"
if (catch(a = convert_charset(buffer[0..pos - 1],
"UTF-8", SYSTEM_CHARSET); nolog)) {
P1(("catch! iconv %O in %O\n", a, ME))
//QUIT
a = buffer; // let's give it a try
}
xmlparse(a);
#else
xmlparse(buffer[0..pos - 1]);
#endif
buffer = buffer[pos..];
}
#ifdef INPUT_NO_TELNET
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE | INPUT_NO_TELNET);
#else
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE);
#endif
}
#define JABBER_PARSE
#define XML_ERROR(code, long) \
P0(("%O aborting XML parse: %s\n", ME, long)) \
STREAM_ERROR(code, "") \
remove_interactive(ME);
#include NET_PATH "xml/parse.c"
#undef JABBER_PARSE
jabberMsg(XMLNode node) {
P2(("common:jabberMsg (should not happen) %O\n", node[Tag]))
}
// TODO: move this to separate file and determine which parts can be ifdeffed
varargs string mkjid(mixed who, mixed vars, mixed ignore_context, string target, string jabberhost) {
/* Die große Aufgabe: wie quetschen wir die gehaltvolle
* Information aus dem vars-mapping gegeben durch
* _nick, nick_place, source && context in einen
* einzigen String der Form
* user@host/resource?
*/
string t, *u;
if (!who || who == "") return "";
unless (jabberhost) jabberhost = JABBER_HOST;
P3(("%O mkjid(%O, %O, %O, %O, %O)\n", ME, who, vars, ignore_context, target, jabberhost))
if (!ignore_context && vars && vars["_nick_place"]
&& vars["_context"]) {
// dies ist eine nachricht die multicastet wird.
// jabber-roomids: #room@jabber.host/nickname
if (objectp(vars["_context"])) {
t = PLACEPREFIX + NODEPREP(vars["_nick_place"]) +"@"+ NAMEPREP(jabberhost);
} else if (u = parse_uniform(vars["_context"])) {
if (u[UScheme] == "psyc")
t = PLACEPREFIX + NODEPREP(u[UResource][1..]) + "@" + NAMEPREP(u[UHost]);
else // here we presume we have a u@h or xmpp:
t = NODEPREP(u[UUser]) + "@" + NAMEPREP(u[UHost]);
} else {
P0(("%O mkjid should not happen 1\n"))
t = PLACEPREFIX + vars["_nick_place"] + "@impossible";
}
return t;
} else if (objectp(who)) {
// we could optimize here with a jabberInfo() that returns
// an array of ({ isplace, nickname, resource }) so we
// don't have to make guesses at t[0] and splices at t[1..] etc
//
t = who->psycName();
unless (t) return NAMEPREP(jabberhost); // ?
if (t[0] == '@'){
t = PLACEPREFIX + NODEPREP(t[1..]) +"@"+ NAMEPREP(jabberhost);
// jabber-roomids: #room@host/nickname
// it seems that those are not case-sensitive
// we could probably use clash nick here,
// but it's difficult
} else {
string r;
// jabber-user: nick@host/resource (letztere optional)
t = NODEPREP(t[1..]) +"@"+ NAMEPREP(jabberhost);
// this call_other sucks for several reasons (other
// than being a call other):
// TODO: pass resource in vars if and only if
// needed
// TODO: mkjid needs a MAJOR rewrite
}
return t;
} else unless (stringp(who)) {
P1(("%O unknown type for mkjid(%O, %O, %O, %O, %O)\n", ME, who, vars, ignore_context, target, jabberhost))
//return NAMEPREP(jabberhost); --- maybe better?
return "";
} else if (u = parse_uniform(who, 1)) {
// jabber-userjids: nick@host/resource (letztere optional)
t = u[UResource];
unless (strlen(t)) t = 0;
// this _SHOULD_ recognize its own host.. then again, we
// simply avoid sending local sources as uniforms, please!
unless (t) {
if (u[UUser])
return NODEPREP(u[UUser]) + "@" + NAMEPREP(u[UHost]);
else
return NAMEPREP(u[UHost]);
}
// this almost works... but it seems the resource is
// case sensitive ???
// er... what is that for?
// ah... used for example when doing /version xmpp:host
if (u[UScheme] == "xmpp" || !u[UScheme]) { // no scheme = pure jid
t = RESOURCEPREP(t);
if (u[UUser])
return NODEPREP(u[UUser]) +"@"+ NAMEPREP(u[UHost]) +"/"+ t;
else
return NAMEPREP(u[UHost]) + "/" + t;
}
// here we wildly presume this is a psyc: uniform
if (t[0] == '@') {
t = PLACEPREFIX + NODEPREP(t[1..]) + "@" + NAMEPREP(u[UHost]);
return t;
} else
return NODEPREP(t[1..]) +"@"+ NAMEPREP(u[UHost]);
}
// argument already _is_ a jid..
// .. no wait i let parse_uniform handle that
// if (index(who, '@') > 0) return who; // oops, PREPping is missing
// argument is just a local username
return NODEPREP(who) +"@"+ NAMEPREP(jabberhost);
}
void determine_sourcejid(mixed source, mapping vars) {
mixed t;
unless (vars["_INTERNAL_source_jabber_bare"]) {
vars["_INTERNAL_source_jabber_bare"] = mkjid(source, vars);
P4(("determine_sourcejid: %O\n", vars["_INTERNAL_source_jabber_bare"]))
}
unless (vars["_INTERNAL_source_jabber"]) {
// append the resource to the bare jid
vars["_INTERNAL_source_jabber"] = vars["_INTERNAL_source_jabber_bare"];
if (vars["_nick_place"] && (t = vars["_nick_local"] || vars["_nick"])) {
vars["_INTERNAL_source_jabber"] += "/" + RESOURCEPREP(t);
}
else if (vars["_INTERNAL_source_resource"])
vars["_INTERNAL_source_jabber"] += "/" + RESOURCEPREP(vars["_INTERNAL_source_resource"]);
}
}
void determine_targetjid(mixed target, mapping vars) {
string full;
unless(vars["_INTERNAL_target_jabber_bare"]) {
int d;
full = mkjid(target, vars, 1);
// f*** uni2unl logic
if ((d = strstr(full, "/")) != -1)
vars["_INTERNAL_target_jabber_bare"] = full[..(d-1)];
else
vars["_INTERNAL_target_jabber_bare"] = full;
P4(("determine_targetjid: %O\n", vars["_INTERNAL_target_jabber_bare"]))
}
unless (vars["_INTERNAL_target_jabber"]) {
// append resource to the bare jid
vars["_INTERNAL_target_jabber"] = vars["_INTERNAL_target_jabber_bare"];
if (strstr(full, "/") != -1)
vars["_INTERNAL_target_jabber"] = full; // just to be sure
else if (vars["_INTERNAL_target_resource"])
vars["_INTERNAL_target_jabber"] += "/" + vars["_INTERNAL_target_resource"];
}
}
internalError() {
STREAM_ERROR("internal-server-error", "something gone wrong internally")
remove_interactive(ME);
}
render(string mc, string data, mapping vars, mixed source) {
string template, output;
unless(vars["_tag"]) vars["_tag"] = ""; // dont display [_tag]
template = T(mc, "");
if (!strlen(template) || template[0] != '<') {
// generation of default psyc messages
output = psyctext(template, vars, data, source);
if (!stringp(output) || output=="")
return P2(("jabber:w() inherited no output\n"));
output = "<message to='"+ vars["_INTERNAL_target_jabber"]
+"' from='"+ vars["_INTERNAL_source_jabber"] +"' type='"
+ (ISPLACEMSG(vars["_INTERNAL_source_jabber"]) && vars["_nick"] ?
"groupchat" : "chat")
+"'><body>"+ chomp(xmlquote(output)) +"</body></message>";
#if DEBUG > 1
// most of these message we are happy with, so we don't need this log
log_file("XMPP_TODO", "%O %s %s\n", ME, mc, output);
#endif
} else {
if (stringp(data)) data = xmlquote(data);
else if (vars["_action"])
data = "/me " + xmlquote(vars["_action"]);
output = psyctext(template, vars, data, source);
if (!stringp(output) || output=="")
return P2(("jabber:w() no output\n"));
}
#if __EFUN_DEFINED__(convert_charset) && SYSTEM_CHARSET != "UTF-8"
if (catch(output = convert_charset(output,
SYSTEM_CHARSET, "UTF-8"); nolog)) {
sendmsg(source, "_failure_unsuccessful_conversion_charset",
"Could not convert your message to UTF-8 for XMPP delivery.",
vars);
P1(("catch! iconv %O from %O in %O\n", output,
SYSTEM_CHARSET, ME))
return 0;
}
#endif
emit(output);
return 1;
}
/* TODO:
* it could be useful to have an error condition mapping (jep-0086)
* to check both old-style and xmpp-style errors
* at least two uses of that in gateway.c
*/
xmpp_error(node, xmpperror) {
unless (mappingp(node)) {
// psyced.org logs claim.. this does happen!?
P0(("%O encountered funny xmpp_error %O %O\n",
ME, node, xmpperror))
return 1;
}
if (node["/" + xmpperror]) return 1;
// shared_memory()? doesn't matter, switch is fine too. not worth changing
switch(xmpperror) {
case "bad-request":
return node["@code"] == "400";
case "conflict":
return node["@code"] == "409";
case "feature-not-implemented":
return node["@code"] == "501";
case "forbidden":
return node["@code"] == "403";
case "gone":
return node["@code"] == "302";
case "internal-server-error":
return node["@code"] == "500";
case "item-not-found":
return node["@code"] == "404";
case "jid-malformed":
return node["@code"] == "400";
case "not-acceptable":
return node["@code"] == "406";
case "not-allowed":
return node["@code"] == "405";
case "not-authorized":
return node["@code"] == "401";
case "payment-required":
return node["@code"] == "402";
case "recipient-unavailable":
return node["@code"] == "404";
case "redirect":
return node["@code"] == "302";
case "registration-required":
return node["@code"] == "407";
case "remote-server-not-found":
return node["@code"] == "404";
case "remote-server-timeout":
return node["@code"] == "504";
case "resource-constraint":
return node["@code"] == "500";
case "service-unavailable":
return node["@code"] == "503";
case "subscription-required":
return node["@code"] == "407";
case "undefined-condition":
return node["@code"] == "500";
case "unexpected-request":
return node["@code"] == "400";
}
return 0;
}
#ifdef WANT_S2S_TLS
certificate_check_jabbername(name, cert) {
mixed t;
/* this does not support wildcards if there is more than one
* id-on-xmppAddr/CN
* API Note: name MUST be an utf8 string
*/
name = NAMEPREP(name);
unless(cert && mappingp(cert)) return 0;
if ((t = cert["2.5.29.17:1.3.6.1.5.5.7.8.5"])) { // id-on-xmppAddr
PT(("id-on-xmppAddr %O found\n", t))
# ifdef LOG_XMPP_AUTH
D0( log_file("XMPP_AUTH", "\n%O try SASL external with id-on-xmppAddr", ME); )
# endif
if (pointerp(t)) {
if (member(t, name)) return 1;
foreach(string cn : t) {
if (NAMEPREP(cn) == name) return 1;
}
return 0;
}
else if (name == NAMEPREP(t))
return 1;
}
if ((t = cert["2.5.29.17:dNSName"])) { // dNSName, wildcard allowed
if (pointerp(t)) {
foreach(string t2 : t) {
if (strlen(t2) > 2 && t2[0] == '*' && t2[1] == '.')
if trail(NAMEPREP(t2[2..]), name)
return 1;
if (name == NAMEPREP(t2))
return 1;
}
} else {
if (strlen(t) > 2 && t[0] == '*' && t[1] == '.') {
return trail(NAMEPREP(t[2..]), name);
}
if (name == NAMEPREP(t))
return 1;
}
}
if ((t = cert["2.5.4.3"])) { // common name
string idn;
# ifdef LOG_XMPP_AUTH
D0( log_file("XMPP_AUTH", "\n%O try SASL external with CN", ME); )
# endif
if (pointerp(t)) { // does that happen?!
if (member(t, name)) return 1;
foreach(string cn : t) {
idn = NAMEPREP(idna_to_unicode(cn));
if (idn == name) return 1;
}
return 0;
}
#ifdef __IDNA__
idn = NAMEPREP(idna_to_unicode(t));
#else
idn = NAMEPREP(t);
#endif
if (strlen(idn) > 2 && idn[0] == '*' && idn[1] == '.')
return trail(idn[2..], name);
if (idn == name)
return 1;
}
return 0;
}
#endif
/* get first child of a node
* used for <iq/>
*/
getfirstchild(node) {
mixed res;
foreach(mixed key, mixed val : node) {
unless(stringp(key) && key[0] == '/') continue;
# if DEBUG > 1
if (res) {
P0(("%O encountered iq get with more than one child!\n%O\n",
ME, node))
}
# endif
if (node["@type"] != "error" && key != "error") {
res = val;
# if DEBUG < 2
break;
# endif
}
}
return res;
}
/* get child with specific xmlns
*/
getchild(node, child, ns) {
foreach(mixed key, mixed val : node) {
if (key == "/" + child) {
if (nodelistp(val)) {
foreach(mixed h : val)
if (h["@xmlns"] == ns)
return h;
} else if (val["@xmlns"] == ns) {
return val;
}
break;
}
}
return 0;
}

View file

@ -0,0 +1,192 @@
// $Id: component.c,v 1.68 2008/03/11 15:13:58 lynx Exp $ // vim:syntax=lpc
//
// this implements a passive listener component
#define NO_INHERIT
#include "jabber.h"
#undef NO_INHERIT
#include <url.h>
inherit NET_PATH "xml/common";
inherit NET_PATH "jabber/mixin_parse";
inherit NET_PATH "jabber/mixin_render";
// in theory - or future - scheme plan will be implemented by
// a gateway/imtransport which inherits this file
#define SCHEME_PLAN
volatile protected mapping config;
volatile string streamid;
volatile string componentname;
volatile int authenticated;
logon(arg) {
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\n%O logon\t%O", ME, ctime()); )
#endif
#ifdef INPUT_NO_TELNET
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE | INPUT_NO_TELNET);
#else
enable_telnet(0, ME);
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE);
#endif
if (this_interactive()) set_prompt("");
set_combine_charset(COMBINE_CHARSET);
sTextPath(0, "en", "jabber");
}
reboot(reason, restart, pass) {
// close the stream according to XEP 0190
if (interactive(ME)) {
flags |= TCP_PENDING_DISCONNECT;
emit("</stream:stream>");
}
}
onHandshake() {
#ifdef SCHEME_PLAN
register_scheme(config["_scheme"]);
#else
register_target(XMPP + componentname);
#endif
}
#ifdef SCHEME_PLAN
jabberMsg(XMLNode node) {
mixed *su = parse_uniform(XMPP + node["@from"]);
string origin = config["_scheme"] + ":";
unless(su) {
P0(("%O could not find uniform source in %O\n", ME, node))
return;
}
if (su[UUser])
origin += su[UUser];
return ::jabberMsg(node, origin, su);
}
#endif
#ifdef SCHEME_PLAN
determine_targetjid(target, vars) {
mixed *tu;
unless(vars["_INTERNAL_target_jabber_bare"]) {
unless(target)
vars["_INTERNAL_target_jabber_bare"] = componentname;
else {
tu = parse_uniform(target);
if (stringp(tu[UBody]) && strlen(tu[UBody]))
vars["_INTERNAL_target_jabber_bare"] = tu[UBody] + "@" + componentname;
else
vars["_INTERNAL_target_jabber_bare"] = componentname;
}
}
unless (vars["_INTERNAL_target_jabber"]) {
vars["_INTERNAL_target_jabber"] = vars["_INTERNAL_target_jabber_bare"];
}
}
#endif
waitfor_handshake(XMLNode node) {
switch (node[Tag]) {
case "handshake":
if (node[Cdata] == sha1(streamid + config["_secret"])) {
PT(("%O component auth succeded as %O\n", ME, componentname))
nodeHandler = #'jabberMsg;
authenticated = 1;
emit("<handshake/>");
onHandshake();
} else {
monitor_report("_error_invalid_password",
sprintf("%O tried to link to %O using a wrong password",
query_ip_name(), componentname));
STREAM_ERROR("not-authorized", "");
remove_interactive(ME);
}
break;
default:
P0(("%O got %O while waiting for handshake\n", ME, node))
break;
}
}
int msg(string source, string mc, string data,
mapping vars, int showingLog, string target) {
#ifdef DEFLANG
unless(vars["_language"]) vars["_language"] = DEFLANG;
#else
unless(vars["_language"]) vars["_language"] = "en";
#endif
#ifdef PREFIXES
if (abbrev("_prefix", mc)) return 1;
#endif
#ifndef EXPERIMENTAL // TODO: decide if this is good or bad
else if (abbrev("_status_person_absent", mc)) {
PT(("Intercepted absent from %O to %O\n", mc, source, ME))
return 1;
}
#endif
switch (mc){
case "_message_echo_private":
return 1;
}
// copied from active
unless (vars["_INTERNAL_mood_jabber"])
vars["_INTERNAL_mood_jabber"] = "neutral";
determine_sourcejid(source, vars);
determine_targetjid(target, vars);
if (vars["_place"]) vars["_place"] = mkjid(vars["_place"]);
unless (vars["_INTERNAL_target_jabber"]) return 1;
return ::msg(source, mc, data, vars, showingLog, target);
}
disconnected(remainder) {
/* unregistering scheme / target is a bad idea, as the target hostname
* does not resolve (in cases where we call it 'icq'), so we're going
* to enqueue until the component connects again
*/
authenticated = 0;
return 0; // unexpected
}
open_stream(node) {
string packet;
if (node["@xmlns"] != "jabber:component:accept") {
remove_interactive(ME); // disconnect
}
object o = find_object(CONFIG_PATH "config");
if (o) config = o->qConfig(node["@to"]);
P2(("\n%O loads config %O\n", ME, config))
streamid = RANDHEXSTRING;
packet = sprintf("<?xml version='1.0' encoding='UTF-8' ?>"
"<stream:stream "
"xmlns='%s' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"xml:lang='en' id='%s' ", node["@xmlns"], streamid);
if (node["@to"]) {
packet += "from='" + node["@to"] + "' ";
} else {
packet += "from='" JABBER_HOST "' ";
}
if (!config) {
/* reply with a stream error */
monitor_report("_error_unknown_host",
sprintf("%O tried to link to %O but we dont have a config for this",
ME, node["@to"]));
STREAM_ERROR("host-unknown", "")
remove_interactive(ME);
}
packet += "version='1.0'>";
emit(packet);
nodeHandler = #'waitfor_handshake;
componentname = node["@to"];
return;
}

160
world/net/jabber/disco.c Normal file
View file

@ -0,0 +1,160 @@
// $Id: disco.c,v 1.41 2008/01/05 13:44:38 lynx Exp $ // vim:syntax=lpc
//
// this gets included by user.c and gateway.c. you can distinguish this
// by ifdeffing USER_PROGRAM. it may be renamed into disco.i one day.
//
//#include <net.h>
//#include "jabber.h"
#ifdef USER_PROGRAM
# define SEND(TARGET, MC, DATA, VARS, SOURCE) w(MC, DATA, VARS, SOURCE)
#else
# define SEND(TARGET, MC, DATA, VARS, SOURCE) sendmsg(TARGET, MC, DATA, VARS, SOURCE)
#endif
/* the WHOLE disco stuff in here should be solved by psycig
* messages instead of dirty call_other
*/
disco_info_root(vars) {
string featurelist;
featurelist = "<feature var='http://jabber.org/protocol/muc'/>"
#ifndef REGISTERED_USERS_ONLY
"<feature var='jabber:iq:register'/>"
#endif
#ifndef VOLATILE
"<feature var='msgoffline'/>"
#endif
#ifndef ERQ_WITHOUT_SRV
"<feature var='dnssrv'/>"
#endif
#ifdef __IDNA__
"<feature var='stringprep'/>"
#endif
#ifdef WANT_S2S_SASL
"<feature var='" NS_XMPP "xmpp-sasl#s2s'/>"
#endif
"<feature var='jabber:iq:last'/>"
"<feature var='jabber:iq:version'/>"
"<feature var='jabber:iq:time'/>"
"<feature var='vcard-temp'/>"
"<feature var='" NS_XMPP "xmpp-sasl#c2s'/>";
#if __EFUN_DEFINED__(tls_available)
if (tls_available()) {
featurelist += "<feature var='" NS_XMPP "xmpp-tls#c2s'/>"
"<feature var='" NS_XMPP "xmpp-tls#s2s'/>";
#if HAS_PORT(JABBERS_PORT, JABBER_PATH)
featurelist += "<feature var='sslc2s'/>";
#endif
}
#endif
vars["_list_features"] = featurelist;
}
disco_info_place(name, vars) {
vars["_nick_place"] = name;
vars["_list_features"] = "<feature var='http://jabber.org/protocol/muc'/>";
// potential features are listed on jabber.org/registrar/disco-features.htm
// useful for us:
// muc_membersonly, muc_nonanonymous, muc_open,
// muc_persistent, muc_public, muc_temporary,
// muc_unmoderated,
// muc_unsecure (does not have a password!)
// we should query the room about these
// object o = find_place(name);
}
disco_info_person(name, vars) {
string featurelist;
/* we could query the user for certain information... */
vars["_nick"] = name; // publicname?
featurelist = "<feature var='vcard-temp'/>"
// http://www.jabber.org/jeps/jep-0096.html -- File Transfer
// is handled by the new iq innerxml trick. probably more
// JEPs can be implemented this way. some may actually already
// work now and we only need to add their <feature> here
// I am not sure if the server should answer here...
// this probably needs to be done by the jabber client
// (which can receive disco's also)
// how should oob() ever get to do its work if it doesn't say oob here?
// oob is proxied to client
#if 0
"<feature var='jabber:iq:oob'/>"
"<feature var='jabber:x:oob'/>"
#endif
// "<feature var='http://jabber.org/protocol/si/profile/file-transfer'/>"
;
vars["_list_features"] = featurelist;
}
disco_items_root(vars) {
// TODO:
// here we should use the list of chatrooms from
// CONFIG_PATH "places.h"
#ifdef PUBLIC_PLACES
// see also: library advertised_places()
#endif
// TODO: is it safe to use JABBER_HOST here?
vars["_list_item"] = "<item name='Chatrooms' jid='" JABBER_HOST "'/>";
}
disco_items_place(name, vars) {
// unused currently
vars["_list_item"] = "";
}
disco_items_person(name, vars) {
// possibly used in that pubsub thingie
vars["_list_item"] = "";
}
#if 0 // you loose if you dont use transparency :-)
// http://www.jabber.org/jeps/jep-0066.html -- Out of Band Data
//
// der job ist es den mist durchzureichen, leider auch nicht trivial
// this might work, haven't used a client who uses it. the templates
// to render it back into jabber aren't done yet.
oob(XMLNode node, mapping vars) {
string source, target;
mixed *u;
unless (vars) vars = ([ ]);
vars["_INTERNAL_target_jabber"] = target = node["@to"];
vars["_INTERNAL_source_jabber"] = source = node["@from"];
vars["_nick"] = source; // too lazy to parse now
if (node["/query"]) {
vars["_uniform"] = node["/query"]["@url"];
vars["_description"] = node["/query"]["@desc"];
}
switch(node["@type"]) {
case "set":
SEND(XMPP + target, "_notice_available_uniform",
"[_nick] points you to [_uniform]: [_description].",
vars, XMPP + source);
break;
case "result":
// jabber only provides the iq-id here. no filenames.
SEND(XMPP + target, "_notice_accepted",
"[_nick] has accepted something.",
vars, XMPP + source);
break;
case "error":
switch(node["/error"]["@type"]) {
case "cancel":
SEND(XMPP + target,
"_error_rejected_delivery_uniform",
"[_nick] has not accepted [_uniform].",
vars, XMPP + source);
break;
default:
SEND(XMPP + target,
"_failure_unsuccessful_delivery_uniform",
"[_nick] has not received [_uniform].",
vars, XMPP + source);
}
break;
}
}
#endif

559
world/net/jabber/gateway.c Normal file
View file

@ -0,0 +1,559 @@
// $Id: gateway.c,v 1.451 2008/03/29 16:07:30 fippo Exp $ // vim:syntax=lpc
/*
* jabber/gateway
* listens on jabber interserver port for incoming connections
*
*/
#define NO_INHERIT
#include "jabber.h" // inherits net/jabber/common
#undef NO_INHERIT
#include "url.h"
#include "services.h"
#define NO_INHERIT
#include "server.h" // does not inherit net/server
#undef NO_INHERIT
//virtual inherit NET_PATH "output";
inherit NET_PATH "trust";
inherit NET_PATH "jabber/mixin_parse";
//inherit NET_PATH "storage";
volatile object active; // currently unused, but you could send w() to it
#include "interserver.c" // interserver stuff
/* major pre-mammas-birthday-todo:
* replace usage of sendmsg() by usage of tell/placerequest
* sendmsg should not be used directly!
*/
/* some notes about _error_unknown_name_user:
* those should probably be _error_unknown_name if source of the error
* does not have a node identifier (for example a bare server jid)
*/
volatile string host; // about time to remember which host we are talking to
volatile string tag;
volatile string streamid;
volatile string resource;
volatile mapping certinfo;
v(a, b) {
PT(("%O v(%O, %O)\n", ME, a, b))
return "";
}
load(ho, po) { host = ho; return ME; }
// this is not called by driver. this is called by call_out.
quit() {
flags |= TCP_PENDING_DISCONNECT;
remove_interactive(ME);
}
disconnected(remainder) {
// TODO: handle remainder
P2(( "gateway %O disconnected\n", ME ))
if (objectp(active)) active -> removeGateway(streamid);
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\n%O disc\t%O", ME, ctime()); )
#endif
destruct(ME);
// expected or unexpected disconnect? flags shall tell
return flags & TCP_PENDING_DISCONNECT;
}
// this is called by active if it was created only for the
// purpose of doing dialback and the attempt failed
remote_connection_failed() {
STREAM_ERROR("remote-connection-failed", "")
QUIT
}
qScheme() { return "jabberserver"; }
void create() {
unless (clonep()) return;
// not multilingual yet - TODO
sTextPath(0, "en", "jabber");
// we could switch them into variables anytime
// cmdchar = '/'; // to remain in style with /me
// actchar = ':'; // let's try some mudlike emote for jabber
::create();
}
#ifdef WANT_S2S_TLS
tls_logon(result) {
P2(("%O tls_logon(%d)\n", ME, result))
if (result < 0) {
QUIT
}
else if (result == 0) {
certinfo = tls_certificate(ME, 0);
} else {
P0(("tls_logon with result > 0?!?!\n"))
// should not happen
}
}
#endif
logon(a) {
P4(("logon(%O) beim jabber:gateway\n", a))
call_out(#'quit, TIME_LOGIN_IDLE);
set_combine_charset(COMBINE_CHARSET);
#ifdef INPUT_NO_TELNET
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE | INPUT_NO_TELNET);
#else
enable_telnet(0, ME);
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE);
#endif
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\n%O logon\t%O", ME, ctime()); )
#endif
// return ::logon(a);
}
verify_connection(string to, string from, string type) {
P2(("verify connection from %s to %s type %s\n", to, from, type))
/* 10. Receiving server informs originating server of the result */
emit(sprintf("<db:result from='%s' to='%s' type='%s'/>",
to, from, type));
if (type != "valid") {
emit("</stream:stream>");
P2(("quitting invalid stream\n"))
QUIT
} else {
#ifdef LOG_XMPP_AUTH
D0( log_file("XMPP_AUTH", "\n%O has authenticated %O via dialback", ME, from); )
#endif
sAuthenticated(from);
while (remove_call_out(#'quit) != -1);
}
}
#if 0
volatile mapping proper_addressing = ([
"stream:error" : 1,
"auth" : 1,
"response" : 1,
#ifdef SWITCH2PSYC
"switching" : 1,
#endif
"starttls" : 1
]);
#endif
jabberMsg(XMLNode node) {
mixed *su, *tu;
string source, target;
object o;
mixed t, t2;
target = node["@to"];
source = node["@from"];
origin = XMPP + source;
/* TODO: we should reset origin when we're done here
*/
#ifdef XMPPERIMENTAL
/* note for world domination:
* if !target target is the cslave
* we need to reflect this somehow in sendmsg...
* or we can handle it as a special case everywhere
*/
if (node[Tag] == "presence" && !target) {
string mc;
su = parse_uniform(origin);
unless(su) return 0;
o = find_context(su[UUserAtHost]);
PT(("find_context(%O) -> %O\n", su[UUserAtHost], o))
unless (o) return 1;
if (node["@type"] == "probe") {
mc = "_request_status_person";
data = "";
} else {
mc = "_notice_presence_here"; // does not handle away, etc
data = "[_nick] is available.";
//vars["_INTERNAL_mood_jabber"] = "neutral";
}
o -> castmsg(origin, mc, data, ([ "_nick" : su[UUser] ]));
return 1;
}
#endif
/* error conditions as in XMPP CORE 4.7.3 first */
// todo.. use proper_addressing but this combination of && and || is not
// very nice.. you can't expect people to know which operator is stronger
if (! (source && target
|| node[Tag] == "stream:error"
|| node[Tag] == "auth"
|| node[Tag] == "response"
#ifdef SWITCH2PSYC
|| node[Tag] == "switching"
#endif
|| node[Tag] == "starttls") ) {
// apparently we are the only jabber server to complain about it
if (node[Tag] == "stream:features") {
P3(("(warn) %O got buggy stream:features: %O\n", ME, node))
return;
}
monitor_report("_error_syntax_missing_element_jabber",
S("%O got jabber %O type %O from %O to %O", ME, node[Tag],
node["@type"], source, target));
PT(("node %O\n", node))
STREAM_ERROR("improper-addressing", "")
remove_interactive(ME);
return;
}
P2(("%O jabberMsg(%O from %O to %O)\n", ME, node[Tag], source, target))
/* handling for stream features, dialback, etc pp */
switch (node[Tag]) {
#ifdef SWITCH2PSYC
case "switching":
// tested manually using:
// <?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:server' to='localhost' xmlns:db='jabber:server:dialback' version='1.0'><switching xmlns='http://switch.psyced.org'><scheme>psyc</scheme></switching>
emit("<switched xmlns='http://switch.psyced.org'/>");
PT(("received 'switching'. authhosts %O\n", authhosts))
o = ("S:psyc:" + host) -> load();
P1(("%O switching to %O for %O\n", ME, o, host))
exec(o, ME);
o -> logon();
o -> sAuthHosts(authhosts);
destruct(ME);
return;
#endif
case "db:result":
target = NAMEPREP(target);
source = NAMEPREP(source);
origin = XMPP + source;
/* this is receiving step 4
* what we do now is
* 8. receiving server sends authorative server
* a request for verification of a key
*/
// if we dont know the host, complain
// put NAMEPREP(JABBER_HOST) into the localhost mapping pleeeease
//if (target != NAMEPREP(JABBER_HOST)) {
unless (is_localhost(lower_case(target))) {
monitor_report("_error_unknown_host",
sprintf("%O sent us a dialback packet believing we would be %O",
source, target));
STREAM_ERROR("host-unknown", "")
remove_interactive(ME);
return;
}
sendmsg(origin,
"_dialback_request_verify", 0,
([ "_INTERNAL_target_jabber" : source,
"_INTERNAL_source_jabber" : NAMEPREP(JABBER_HOST),
"_dialback_key" : node[Cdata],
"_tag" : streamid
])
);
unless (o = find_target_handler(NAMEPREP(origin))) {
// sendmsg should have created it!
P0(("%O could not find target handler for %O "
"after sendmsg\n", ME, origin))
return;
}
active = o -> sGateway(ME, target, streamid);
return;
case "db:verify":
target = NAMEPREP(target);
source = NAMEPREP(source);
// pretty much everywhere else, where XMPP is prepended, either
// the UString of su[] or tu[] should have been used..
origin = XMPP + source;
/* check to and from */
/* verify dialback key */
o = find_target_handler(origin);
unless (o) {
mixed *u = parse_uniform(origin);
// probably this has been destructed due to gone wrong
P0(("ERROR: could not find_target_handler for %O in db:verify\n",
origin))
// fatal error, we have not called that host
// probably thats what invalid-from is for
#ifdef QUEUE_WITH_SCHEME
o = ("C:"+origin)-> circuit(u[UHost], u[UPort]
|| JABBER_S2S_SERVICE, 0, "xmpp-server", origin);
#else
o = ("C:"+origin)-> circuit(u[UHost], u[UPort]
|| JABBER_S2S_SERVICE, 0, "xmpp-server", u[UHostPort]);
#endif
register_target(origin, o);
}
// TODO: das hier gehoert noch eins weiter unten rein
// oder einfach ein stueckl tiefer
active = o -> sGateway(ME, target, streamid);
unless (node["@type"]){
int valid;
/* we were calling this server, this packet is step 8
* and we are doing step 9
*/
/* if we dont recognize target (currently: == JABBER_HOST)
* then croak with a host-unknown and commit suicide
*/
// same as above...
unless (is_localhost(lower_case(target))) {
STREAM_ERROR("host-unknown", "")
QUIT
}
valid = node[Cdata] == DIALBACK_KEY(node["@id"], source,
target);
emit(sprintf("<db:verify from='%s' to='%s' type='%s' id='%s'/>",
target, source,
valid ? "valid" : "invalid",
node["@id"]));
} else {
P0(("received db:verify without type on an incoming connection\n"))
}
return;
case "starttls":
#ifdef WANT_S2S_TLS
if (tls_available()) {
emit("<proceed xmlns='" NS_XMPP "xmpp-tls'/>");
tls_init_connection(ME, #'tls_logon);
return;
}
#endif
emit("<failure xmlns='" NS_XMPP "xmpp-tls'/>");
return;
case "stream:error":
if (node["/connection-timeout"]) {
/* ignore it */
} else if (node["/system-shutdown"]) {
P1(("%O: counterpart is doing a system shutdown", ME))
/* ignore it */
} else {
P0(("stream error in %O: %O\n", ME, node))
}
return;
#ifdef WANT_S2S_SASL
case "auth":
t = to_string(decode_base64(node[Cdata]));
switch (node["@mechanism"]) {
case "EXTERNAL":
if (tls_query_connection_state(ME) == 1
&& mappingp(certinfo)
&& certinfo[0] == 0) {
/*
* order of checks should be first against
* id-on-xmppAddr field and only if that is
* unused match against common name
*/
int success = 0;
success = certificate_check_jabbername(t, certinfo);
if (success) {
emit("<success xmlns='" NS_XMPP "xmpp-sasl'/>");
P2(("successful sasl external authentication with "
"%O\n", t))
sAuthenticated(t);
# ifdef LOG_XMPP_AUTH
D0( log_file("XMPP_AUTH", "\n%O has authenticated %O via SASL external", ME, t); )
# endif
} else {
SASL_ERROR("not-authorized")
QUIT
}
} else {
// we did not offer it definetly
SASL_ERROR("invalid-mechanism")
QUIT
}
break;
case "DIGEST-MD5":
PT(("jabber/gateway got a request to do digest md5\n"))
// if the other side thinks, that is has a shared
// secret with us... well, THEY tried
if (config(XMPP + t, "_secret_shared")) {
emit("<challenge xmlns='" NS_XMPP "xmpp-sasl'>" +
encode_base64(sprintf("realm=\"%s\",nonce=\"%s\","
"qop=\"auth\",charset=utf-8,"
"algorithm=md5-sess",
JABBER_HOST, RANDHEXSTRING)
) + "</challenge>");
} else {
// kind of 'unknown username'
SASL_ERROR("not-authorized")
QUIT
}
break;
default:
SASL_ERROR("invalid-mechanism")
QUIT
break;
}
return;
case "response":
P2(("%O got SASL response\n", ME))
if ((t2 = node[Cdata])
&& (t = to_string(decode_base64(t2)))) {
// this one is very similar to the stuff in active.c
string secret;
mixed data;
data = sasl_parse(t);
P2(("extracted: %O\n", data))
secret = config(XMPP + data["username"], "_secret_shared");
unless(secret) {
// tell the host that we dont share a secret with them
// currently this happens as not-authorized
}
if (data["response"] == sasl_calculate_digestMD5(data, secret, 0)) {
emit("<success xmlns='" NS_XMPP "xmpp-sasl'>"
+ encode_base64("rspauth=" + sasl_calculate_digestMD5(data, secret, 1)) + "</success>");
# ifdef LOG_XMPP_AUTH
D0( log_file("XMPP_AUTH", "\n%O has authenticated %O via SASL digest md5", ME, data["username"]); )
# endif
sAuthenticated(data["username"]);
} else {
SASL_ERROR("not-authorized")
QUIT
}
}
return;
#endif
}
su = parse_uniform(origin);
unless (su) {
// this is not jid-malformed, rather invalid-from
P0(("%O: source jid malformed? %O\n", ME, source))
STREAM_ERROR("invalid-from", "I could not parse that from");
QUIT // too hard?
}
/* security policy
* DROP unless authenticated
* see XMPP-Core §4.3
*/
unless (qAuthenticated(su[UHost])) {
PT(("%O dropping %O: su[UHost] %O\n",
ME, node[Tag], su[UHost]))
STREAM_ERROR("invalid-from", "")
QUIT // too hard?
}
tu = parse_uniform(XMPP + target);
unless (tu) {
// we should croak with an jid-malformed error and commit suicide
// jabberd responds with a invalid-from, but that doesnt seem
// to be the right semantic
// but... well, for now
P0(("gateway: target jid malformed? %O in %O\n", target, ME))
STREAM_ERROR("invalid-from", "could not parse that to");
QUIT // too hard?
}
return ::jabberMsg(node, origin, su, tu);
}
open_stream(XMLNode node) {
string packet;
float version;
// make a loooong random string and hash it not to expose our random numbers
streamid = sha1(RANDHEXSTRING + RANDHEXSTRING + RANDHEXSTRING + RANDHEXSTRING);
version = to_float(node["@version"]);
packet = sprintf("<?xml version='1.0' encoding='UTF-8' ?>"
"<stream:stream "
"xmlns='%s' "
"xmlns:db='jabber:server:dialback' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"xml:lang='en' id='%s' ", node["@xmlns"], streamid);
if (node["@to"]) {
packet += "from='" + node["@to"] + "' ";
} else {
packet += "from='" JABBER_HOST "' ";
}
if (node["@to"] && !(is_localhost(lower_case(node["@to"])))) {
emit(packet + ">");
STREAM_ERROR("host-unknown", "")
QUIT
return;
}
if (node["@xmlns"] != "jabber:server") {
/* If the namespace name is incorrect, then Receiving Server
* MUST generate an <invalid-namespace/> stream error condition
* and terminate both the XML stream and the underlying TCP connection.
*/
STREAM_ERROR("invalid-namespace", "")
QUIT
return;
}
/* if stream version is >= "1.0" reply with stream version
* attribute and add a stream:feature tag
*/
if (version >= 1) {
packet += "version='1.0'>";
emit(packet);
// this sends one stanza per tcp packet
packet = "<stream:features>";
#ifdef WANT_S2S_TLS
if (tls_available()) {
if (tls_query_connection_state(ME) == 0) {
packet += "<starttls xmlns='" NS_XMPP "xmpp-tls'/>";
} else unless (mappingp(authhosts)) {
# ifdef WANT_S2S_SASL
packet += "<mechanisms xmlns='" NS_XMPP "xmpp-sasl'>";
// let the other side decide if it knows a shared secret
// with us
// if it it has, it will use it with digest-md5
# if __VERSION_MINOR__ > 3 || __VERSION_MICRO__ > 610
if (node["@from"]
&& config(XMPP + node["@from"],
"_secret_shared")) {
packet += "<mechanism>DIGEST-MD5</mechanism>";
}
# endif
// if the other side did present a client certificate
// and we have verified it as X509_V_OK (0)
// we offer SASL external (authentication via name
// presented in x509 certificate
P3(("gateway::certinfo %O\n", certinfo))
if (mappingp(certinfo) && certinfo[0] == 0) {
// if from attribute is present we only offer
// sasl external if we know that it will succeed
// later on
if (node["@from"] &&
certificate_check_jabbername(node["@from"],
certinfo)) {
packet += "<mechanism>EXTERNAL</mechanism>";
}
}
packet += "</mechanisms>";
# endif
}
}
#endif
#ifdef SWITCH2PSYC
packet += "<switch xmlns='http://switch.psyced.org'>"
"<scheme>psyc</scheme>"
"</switch>";
#endif
packet += "</stream:features>";
} else {
packet += ">";
}
emit(packet);
}
/* watch out, this is used by the user objects cmd() parser!
* and that must not happen as we are using unidirectional sockets
* this is not really a bug, we just dont handle those yet...
* the strategy will be to do a sendmsg() to origin
*/
w(string mc, string data, mapping vars, mixed source) {
P2(("%O using w() for %O, unimplemented... mc %O, source %O\n",
ME, origin, mc, source))
unless (vars) vars = ([ ]);
vars["_INTERNAL_source_jabber"] = objectp(source) ? mkjid(source) : JABBER_HOST;
sendmsg(origin, mc, data, vars);
}

View file

@ -0,0 +1,48 @@
// $Id: interserver.c,v 1.12 2008/03/11 15:13:58 lynx Exp $ vim:syntax=lpc
//
// common things for interserver jabber.. included or maybe later inherited by
// active.c and gateway.c. i am sure fippo will find some more nice things to
// extrapolate into here.. :)
volatile int flags = 0;
reboot(reason, restart, pass) {
if (pass == 0) {
if (interactive(ME)) {
flags |= TCP_PENDING_DISCONNECT;
STREAM_ERROR("system-shutdown", (restart ? "Server restart: "
: "Server shutdown: ")+ reason);
// shutdown order pretty much ensures no other data will be
// sent over this socket after this message. but it isn't
// really enforced.. TODO?
}
} else
destruct(ME);
}
int clean_up(int refcount) {
if (interactive(ME)) {
// closing the socket without asking the other side will raise
// the chances of losing packets. we're not playing that game,
// and i'm afraid every jabber server in the world needs to be
// fixed if jabber is one day trying to become a reliable thing. -lynX
#if 0
// that's the 0190 incompliant way to do it
STREAM_ERROR("connection-timeout", "just reconnect if you like")
remove_interactive(ME);
#else
// this instead will initiate a clean shutdown of the connection
// and is therefore the correct way to timeout a connection.
PT(("%O cleaning up: closing stream\n", ME))
// close the stream according to XEP 0190
emit("</stream:stream>");
// flag says the stream is in closing phase and nothing may be
// delivered on it. but we aren't enforcing this. TODO!
flags |= TCP_PENDING_DISCONNECT;
#endif
} else if (clonep(ME)) {
PT(("%O cleaning up: self destruct\n", ME))
destruct(ME);
}
return 0;
}

111
world/net/jabber/jabber.h Normal file
View file

@ -0,0 +1,111 @@
// $Id: jabber.h,v 1.74 2008/03/30 10:27:49 lynx Exp $ // vim:syntax=lpc
//
// REMINDER:
// there are plenty of calls to lower_case in the code, that is because
// jabber requires all hostnames to be lowercased, or otherwise the
// protocol won't work! TODO: optimize this
//
#ifndef _JABBER_H
#define _JABBER_H
#define JABBER_TRANSPARENCY // switching this off improves parser
// performance but kills file transfers
#include <net.h>
#include <xml.h>
#include <sys/time.h>
#if !__EFUN_DEFINED__(tls_available)
# undef WANT_S2S_TLS
#endif
#ifndef NO_INHERIT
// should this be virtual?
virtual inherit JABBER_PATH "common";
#endif
#define COMBINE_CHARSET XML_CHARSET
//"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 </=\"'?.:@-"
// combining > errors
// eigentlich schon ein fall für textdb
// siehe auch MISC/jabber/conference.fmt
#define PLACE "place"
#define XMPP "xmpp:"
#define IQ_OFF "</query></iq>"
#define NS_XMPP "urn:ietf:params:xml:ns:"
#define IMPLODE_XML(list, tag) pointerp(list) ? tag + implode(list, "</" + tag[1..] + tag) + "</" + tag[1..] : tag[..<2] + "/>"
#define JABBERTIME(gm) sprintf("%d%02d%02dT%02d:%02d:%02d", gm[TM_YEAR], gm[TM_MON] + 1, gm[TM_MDAY], gm[TM_HOUR], gm[TM_MIN], gm[TM_SEC])
#define xbuddylist v("peoplegroups")
// usage: STREAM_ERROR("system-shutdown", "bye!")
// TODO: would be nice to see
#define STREAM_ERROR(condition, textual) emit("<stream:error><" condition " "\
"xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"\
"<text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>" +(textual)+ \
"</text></stream:error></stream:stream>");
#define SASL_ERROR(condition) emit("<failure "\
"xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"\
"<" + condition + "/>"\
"</failure>"\
"</stream:stream>");
#ifndef PREFERRED_HASH
# define PREFERRED_HASH 1 // sha1 is usually available
#endif
#ifdef SYSTEM_SECRET
# if __EFUN_DEFINED__(hmac)
# define DIALBACK_KEY(id, source, target) hmac(PREFERRED_HASH, hash(PREFERRED_HASH, SYSTEM_SECRET), target + " " + source + " " + id)
# else
# define DIALBACK_KEY(id, source, target) sha1(sha1(SYSTEM_SECRET) + " " + target + " " + source + " " + id)
# endif
#else
// ssotd is already a hash and therefore, the length is sufficient
# if __EFUN_DEFINED__(hmac)
# define DIALBACK_KEY(id, source, target) hmac(PREFERRED_HASH, server_secret_of_the_day(), target + " " + source + " " + id)
# else
# define DIALBACK_KEY(id, source, target) sha1(server_secret_of_the_day() + " " + target + " " + source + " " + id)
# endif
#endif
#ifndef JABBER_HOST
# define JABBER_HOST SERVER_HOST
#endif
// this is not ready for is_localhost
#define is_localhost(a) (a) == JABBER_HOST
#define JABSOURCE "_INTERNAL_source_jabber"
#define JABTARGET "_INTERNAL_target_jabber"
#define JID_HAS_RESOURCE(x) (index(x, '/') != -1)
#define JID_HAS_NODE(x) apply(jid_has_node_cl, x)
#ifdef _flag_provide_places_only
# define PLACEPREFIX ""
# define ISPLACEMSG(x) 1
# define FIND_OBJECT(x) find_place(x)
# define PREFIXFREE(x) (x)
#else
// changing the place prefix requires changing it also in the output of
// uniforms. depending on the character you choose you may also have to
// implement URI (un)escaping first.
# define PLACEPREFIX "*"
# define FIND_OBJECT(x) (x[0] == '*' ? find_place(x[1..]) : summon_person(x) )
# define ISPLACEMSG(x) (x && x[0] == '*')
# define PREFIXFREE(x) (x[1..])
#endif
// using channels is funny.. but xmpp: doesn't define channels really.
// isn't it fine if we just use xmpp: resources in _context to achieve this?
//efine MUCSUC_SEP "#_"
#define MUCSUC_SEP "/"
#endif // _JABBER_H

View file

@ -0,0 +1,933 @@
#include <net.h> // vim:set syntax=lpc
#include "jabber.h"
#include <url.h>
#include "presence.h"
#include <time.h>
// necessary to implement a minimum set of commands for remote jabber users
// #undef USER_PROGRAM
// #undef MYNICK
// #define MYNICK <yournicknamevariable>
inherit NET_PATH "name";
volatile string origin;
volatile string nickplace;
volatile mixed place;
// shared_memory()
volatile mapping jabber2avail, avail2mc;
// we can leave out the variables if these are static anyway
#define cmdchar '/' // to remain in style with /me
#define actchar ':' // let's try some mudlike emote for jabber
#define NICKPLACE nickplace
#ifndef T
# define T(MC, TEXT) TEXT // do not use textdb
#endif
#include NET_PATH "usercmd.i"
void create() {
jabber2avail = shared_memory("jabber2avail");
avail2mc = shared_memory("avail2mc");
}
jabberMsg(XMLNode node, mixed origin, mixed *su, array(mixed) tu) {
string target, source;
string mc, data;
string body;
int isplacemsg;
mapping vars;
object o;
XMLNode helper;
mixed t;
target = node["@to"];
source = node["@from"];
unless(origin)
origin = XMPP + source;
#define MYORIGIN origin
// #define MYORIGIN XMPP + su[UUserAtHost]
unless(su) su = parse_uniform(origin);
#if 1 //def NOT_EXPERIMENTAL
origin = XMPP;
if (su[UUser]) {
origin += NODEPREP(su[UUser]) + "@";
}
origin += NAMEPREP(su[UHost]);
if (su[UResource]) {
origin += "/" + RESOURCEPREP(su[UResource]);
}
su = parse_uniform(origin);
#endif
if (node["/nick"] &&
node["/nick"]["@xmlns"] == "http://jabber.org/protocol/nick" &&
node["/nick"][Cdata]) {
sName(node["/nick"][Cdata]);
vars = ([ "_nick": MYNICK ]);
} else if (su[UUser]) {
sName(su[UUser]);
vars = ([ "_nick": MYNICK ]);
// if i turn on this line, stuff no longer arrives at local target
//if (su[UResource]) vars["_identification"] = origin;
// but.. this is the way it should work.. right?!
} else {
vars = ([ "_nick" : su[UString] ]);
}
#ifdef USE_THE_RESOURCE
if (su[UResource]) {
su[UResource] = RESOURCEPREP(su[UResource]);
// entity.c currently needs both _source_identification and
// _INTERNAL_identification to let it know the UNI is safe to use
// this is good because PSYC clients will get to see the
// _source_identification on the way out, too, while the fact
// the id can be trusted will be removed
vars["_INTERNAL_identification"] =
vars["_source_identification"] = XMPP + su[UUserAtHost];
vars["_INTERNAL_source_resource"] = su[UResource];
//vars["_location"] = origin;
P2(("UNI %O for UNR %O\n", vars["_source_identification"], source))
}
#endif
unless(tu) tu = parse_uniform(XMPP + target);
if (tu[UUser]) tu[UUser] = NODEPREP(tu[UUser]);
// TODO: probably we need nameprep here also
//
if (tu[UResource]) {
tu[UResource] = RESOURCEPREP(tu[UResource]);
vars["_INTERNAL_target_resource"] = tu[UResource];
}
isplacemsg = ISPLACEMSG(tu[UUser]);
/* I wonder if it makes sense to split this this into several functions,
* at least for IQ it would make sense, dito for presence
* also we should try to maximize shared code with jabber/user.c
*/
switch (node[Tag]) {
case "message":
D2( if (isplacemsg) D("place"); )
P2(("message %O from %O to %O\n",
node["@type"], origin, target))
#if 0
// this check is completly insufficient and doesn't work anyway...
if (node["/x"] && nodelistp(node["/x"])) // jabber:x:oob
vars["_uniform"] = node["/x"]["/url"];
#endif
switch (node["@type"]) {
case "error":
// watch out, do not create circular error messages
unless (o = summon_person(tu[UUser])) return;
if (node["/error"]) {
// _nick_target? why?
vars["_nick_target"] = origin;
if (xmpp_error(node["/error"],
"service-unavailable")) {
if (node["/text"]) {
vars["_text_XMPP"] = node["/text"][Cdata];
mc = "_failure_unavailable_service_talk_text";
data = "Talking to [_nick_target] is not possible: [_text_XMPP]";
} else {
mc = "_failure_unavailable_service_talk";
data = "Talking to [_nick_target] is not possible. You may have to establish friendship first.";
}
} else if (1) { // TODO: what was that error?
PT(("gateway TODO <error> in <message>: %O\n",
node["/error"]))
mc = "_error_unknown_name_user";
data = "Can't find [_nick_target].";
} else {
mc = "_jabber_message_error";
data = "[_nick] is sending you a jabber message error.";
// TODO: we can grab the error code / description
vars["_jabber_XML"] = innerxml;
}
sendmsg(o, mc, data, vars, origin);
}
break;
case "groupchat": // _message_public
if (node["/body"] && !pointerp(node["/body"]))
body = node["/body"][Cdata];
else {
body = 0;
P4(("no body in %O\n", node))
}
if (isplacemsg) {
// lots of these should be handled by placeRequest/input
// instead of sendmsg
// let usercmd know which room we are operating on..
unless (place = FIND_OBJECT(tu[UUser])) {
P0(("could not create place.. from %O to %O saying %O\n",
source, target, body))
break;
}
P2(("groupchat to %O\n", place))
// eg this should be a placeRequest("_set_topic", ...)
if (node["/subject"]
&& stringp(node["/subject"][Cdata])) {
#if 0
PT(("attempt by %O to change subject in %O lost: %O\n",
ME, place, node))
#else
vars["_topic"] = node["/subject"][Cdata];
sendmsg(place, "_request_set_topic", 0, vars, origin);
#endif
break;
}
PT(("input¹ %O\n", body))
if (stringp(body) && strlen(body)) {
if (body[0] == cmdchar) {
// '/ /usr' notation is a USER_PROGRAM feature
// so we have to redo it here
if (strlen(body) > 1 && body[1] == ' ') {
body = body[2..];
// fall thru
} else {
parsecmd(body[1..]);
return 1;
}
}
sendmsg(place, "_message_public", body,
vars, origin);
}
} else { // remote join stuff room message
o = summon_person(tu[UUser]);
// design decision: show them with full room nickname
if (su[UResource])
vars["_nick"] = su[UResource];
vars["_nick_place"] = vars["_INTERNAL_identification"] || origin;
#if __EFUN_DEFINED__(mktime)
if (helper = getchild(node, "x", "jabber:x:delay")) {
string fmt = helper["@stamp"];
int *time = allocate(TM_MAX);
int res;
// xep 0091 style CCYYMMDDThh:mm:ss
// 20080410T19:12:22
res = sscanf(fmt, "%4d%2d%2dT%2d:%2d:%2d",
time[TM_YEAR], time[TM_MON],
time[TM_MDAY], time[TM_HOUR],
time[TM_MIN], time[TM_SEC]);
if (res == 6 && (res = mktime(time)) != -1) {
vars["_time_place"] = res; //helper["@stamp"];
}
}
#endif
#ifdef MUCSUC
// now using channels for unicast context emulation
vars["_context"] = XMPP+ su[UUserAtHost]
+MUCSUC_SEP+ tu[UUser];
o = find_context(vars["_context"]);
if (!o) {
P0(("%O could not find the personal remotemuc for %O\n",
ME, vars["_context"]))
return;
}
P3(("xmpp castmsg %O\n", o))
#endif
if (!su[UResource] && node["/subject"]) {
/* a message from the room with subject (and in theory
* no body) is the topic
*/
vars["_topic"] = node["/subject"][Cdata];
#ifdef MUCSUC
o->castmsg(origin, "_status_place_topic", 0, vars);
//sendmsg(o, "_status_place_topic", 0, vars, origin);
} else {
o->castmsg(origin, "_message_public", body, vars);
//sendmsg(o, "_message_public", body, vars, origin);
#else
sendmsg(o, "_status_place_topic", 0, vars, origin);
} else {
sendmsg(o, "_message_public", body, vars, origin);
#endif
}
// innerxml pass-thru
// vars["_jabber_XML"] = innerxml;
// sendmsg(o, "_jabber_message_groupchat", 0, vars, origin);
}
break;
case 0: // _message_private which may have a subject
case "chat": // _message_private
default:
if (isplacemsg) {
#if 1 // STRICTLY UNFRIENDLY NON-FUN TREATMENT
sendmsg(XMPP + source, "_failure_unsupported_function_whisper",
"Routing private messages through groupchat managers is dangerous to your privacy and therefore disallowed. Please communicate with the person directly.",
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source ]),
ME);
#else // MAKE FUN OF ST00PID JABBER USERS VARIANT
// handle this by doing "flüstern" in room ;)
// <from> whispers to <to>: <cdata>
P0(("private message in place.. from %O to %O\n",
source, target))
// stimmt das? egal..
o = FIND_OBJECT(tu[UUser]);
vars["_nick_target"] = tu[UResource];
sendmsg(o, "_message_public_whisper",
// "[_nick] tries to whisper to [_nick_target] but it fails",
node[Cdata], vars, origin);
// cmd("/whisper", ...?)
#endif
} else if (!tu[UUser]) {
// stricmp is better than lower_case only when both sides
// have to be lowercased..
if (lower_case(tu[UResource]) == "echo") {
sendmsg(origin, "_message_private",
node["/body"][Cdata],
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source ]), ME);
} else if (node["/body"]
&& node["/body"][Cdata][0] != cmdchar) {
// monitor_report will log this to a file
// if no admin is listening
monitor_report("_request_message_administrative",
sprintf("%O wants to notify the administrators of %O", origin, node["/body"][Cdata]));
}
} else {
// no relaying allowed, so we ignore hostname
o = summon_person(tu[UUser]);
#ifdef EXPERIMENTAL
// xep 0085 typing notices - we even split active into a separate message
// for now. could be sent as a flag
if ((node[t="/composing"] || node[t="/active"] ||
node[t="/paused"] ||node[t="/inactive"] ||node[t="/gone"]) &&
node[t]["xmlns"] == "http://jabber.org/protocol/chatstates") {
// ...
sendmsg(o, "_notice_typing_" + t[1..], 0, vars);
}
#endif
// there are some messages which dont have a body
// we dont care about those
unless (node["/body"]) return;
ASSERT("Cdata", mappingp(node["/body"])
&& stringp(node["/body"][Cdata]), node)
body = node["/body"][Cdata];
if (strlen(body) && body[0] == cmdchar) {
body = body[1..];
if (abbrev("me ", body)) {
// doesn't cmd() handle this?
vars["_action"] = body[3..];
body = 0;
#ifdef USERCMD_IN_JABBER_CONVERSATION
} else {
// this doesn't take care of '/ /usr' notation!
parsecmd(body);
break;
#else
// fall thru
// the /bin/whatever will be treated as normal text
// so nusse is happy
#endif
}
}
if (helper = getchild(node, "x", "jabber:x:signed")) {
vars["_signature"] = helper[Cdata];
vars["_signature_encoding"] = "base64";
}
if (helper = getchild(node, "x", "jabber:x:encrypted")) {
vars["_data_openpgp"] = helper[Cdata];
// syntactical note: i would prefer to have this var
// called _data_openpgp:_encoding
vars["_encoding_data_openpgp"] = "base64";
mc = "_notice_private_encrypt_gpg";
// well... we need to put this stuff here and cant
// have it in the textdb...
// I would appreciate if we could do something like
// body = 0 and the fmt would be fetched from the
// textdb...
// also, this eludes the users language setting
// (this problem also occurs with presence
// notifications)
body = "openpgp encrypted message data follows\n"
"--- BEGIN OPENPGP BLOCK ---\n"
"[_data_openpgp]\n"
"--- END OPENPGP BLOCK ---";
};
// shouldn't we use /tell?
sendmsg(o, mc || "_message_private", body,
vars, origin);
}
; // break??
}
break;
case "presence":
if (!isplacemsg && getchild(node, "x", "http://jabber.org/protocol/muc#user")) {
isplacemsg = 2;
}
D2( if (isplacemsg) D("place"); )
P2(("presence %O from %O to %O\n",
node["@type"],
XMPP + source,
target))
// su = parse_uniform(XMPP + source);
// see also: XMPP-IM §2.2.1 Types of Presence
switch (node["@type"]) {
case "error":
// TODO:
// for now we ignore it at least
// so there wont be circular error messages
if (tu[UUser]) {
o = summon_person(tu[UUser]);
#ifndef EXPERIMENTAL
if (o && o->execute_callback(node["@id"], ({ vars["_INTERNAL_identification"], vars, node }))) return 1;
#else
// the following should catch errors - in theory, requires testing
if (o) {
int cb_ret;
mixed err;
err = catch(
cb_ret = o->execute_callback(node["@id"], ({ vars["_INTERNAL_identification"], vars, node }) )
);
if (err) {
P0(("%O caught error during callback execution: %O\n", ME, err))
}
if (err || cb_ret) {
return 1;
}
}
#endif
}
if (tu[UResource]) {
// innerxml
vars["_jabber_XML"] = innerxml;
//sendmsg(o, "_jabber_presence_error", 0, vars, origin);
P1(("%O presence error. innerxml proxy to %O please: %O\n",
ME, node["@to"], innerxml))
}
break;
case "subscribe": // _request_friendship
if (isplacemsg) {
// autojoins dont work that way - what are
// those clients (ichat, gaim) trying to do?
// what's the appropriate stanza error?
// btw, text elemnent in stanzas errors SHOULD
// NOT be displayed to the user (see rfc3920 §9.3)
P2(("%O encountered presence %O for place %O\n",
ME, node["@type"], tu[UUser]));
o = FIND_OBJECT(tu[UUser]);
if (o->qNewsfeed())
sendmsg(origin, "_notice_friendship_established", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_source_jabber_bare" : target,
"_INTERNAL_target_jabber" : source ]),
ME);
else
sendmsg(origin, "_error_unsupported_method_request_friendship", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source]),
ME);
return;
}
unless(tu[UResource]) {
o = summon_person(tu[UUser]);
if (su[UResource]) {
P0(("encountered _request_friendwith with resource from %O to %O\n", source, target))
// return;
}
sendmsg(o, "_request_friendship", 0, vars, MYORIGIN);
} else {
// not sure if that's valid.. so let's look out for it
P0(("%O Surprise! Encountered friendship w/out resource: %O\n",
ME, node))
}
break;
case "subscribed": // _notice_friendship_established
if (isplacemsg) {
P2(("%O encountered presence %O for place %O\n",
ME, node["@type"], tu[UUser]));
sendmsg(origin, "_error_unsupported_method_notice_friendship_established", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source]),
ME);
return;
}
unless(tu[UResource]) {
o = summon_person(tu[UUser]);
if (su[UResource]) {
P0(("encountered _notice_friendship_established with resource from %O to %O\n", source, target))
// return;
}
sendmsg(o, "_notice_friendship_established", 0, vars, MYORIGIN);
} else {
// not sure if that's valid
}
break;
case "unsubscribe": // _notice_friendship_removed
if (isplacemsg) {
// TODO: wouldn't it be better to use _jabber_presence_error
// here in conjunction with _jabber_XML?
//
// like for subscribe, this might be useful for newsfeed
// if place->qNewsfeed() schicke ein unsubscribed zurueck
o = FIND_OBJECT(tu[UUser]);
if (o->qNewsfeed())
sendmsg(origin, "_notice_friendship_established", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_source_jabber_bare" : target,
"_INTERNAL_target_jabber" : source ]),
ME);
else
sendmsg(origin, "_error_unsupported_method_notice_friendship_removed", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source]),
ME); // should it be tu[UString] instead? TODO
}
/*
* mh... this may be one-sided... but PSYC
* does not have one-sided subscription
* so... fall thru
*/
case "unsubscribed": // _notice_friendship_removed
if (isplacemsg) {
// ignore it
} else {
unless (o = summon_person(tu[UUser])) return;
vars["_possessive"] = "the";
if (su[UResource]) {
P0(("encountered _notice_friendship_removed with resource from %O to %O\n", source, target))
// return;
}
sendmsg(o, "_notice_friendship_removed", 0, vars, MYORIGIN);
}
break;
case "unavailable": // _notice_presence_absent / _notice_place_leave
if (isplacemsg == 1) {
o = FIND_OBJECT(tu[UUser]);
#ifndef DONT_REWRITE_NICKS
vars["_nick_local"] = tu[UResource]; // it's a matter of case
#endif
sendmsg(o,
#ifdef SPEC
"_request_context_leave"
#else
"_request_leave"
#endif
, 0, vars, origin);
} else if (isplacemsg == 2) { // remote join stuff
o = summon_person(tu[UUser]);
vars["_nick"] = su[UResource];
vars["_nick_place"] = vars["_INTERNAL_identification"] || origin;
#ifdef MUCSUC
vars["_context"] = XMPP+ su[UUserAtHost]
+MUCSUC_SEP+ tu[UUser];
#else
vars["_context"] = vars["_nick_place"];
#endif
if (o && o->execute_callback(node["@id"], ({ o, vars, node }))){
return 1;
}
#ifdef MUCSUC
o = find_context(XMPP+ su[UUserAtHost]
+MUCSUC_SEP+ tu[UUser]);
if (o)
o->castmsg(origin, "_notice_place_leave", 0, vars);
else {
P0(("%O could not find the personal remotemuc for %O bis\n",
ME, vars["_context"]))
}
#else
sendmsg(o, "_notice_place_leave_unicast", 0, vars, origin);
#endif
} else {
o = summon_person(tu[UUser]);
// http://www.psyc.eu/presence
vars["_degree_availability"] = AVAILABILITY_OFFLINE;
#ifdef CACHE_PRESENCE
persistent_presence(XMPP + su[UUserAtHost],
AVAILABILITY_OFFLINE);
#endif
vars["_description_presence"] =
(node["/status"] && node["/status"][Cdata]) ?
node["/status"][Cdata] : ""; // "Get psyced!";
vars["_INTERNAL_mood_jabber"] = "neutral";
sendmsg(o, "_notice_presence_absent", 0,
vars, origin);
#if 0 // packen wir das doch wieder zusammen...
} else {
// one more famous fippoesque else case.. let's fill it ;)
P0(("%O Surprise! Encountered absence with resource: %O\n",
ME, node))
// interessant... wir werden das wohl noch oefter sehen
// ich bin nicht sicher, ob das ein bug der gegenseite ist
#endif
}
break;
case "probe":
if (isplacemsg) {
// this is actually not an error with newsfeed
// being subscribable
P2(("%O encountered presence %O for place %O\n",
ME, node["@type"], tu[UUser]));
} else {
// probe SHOULD only be generated by server but gmail
// sends it from a generated resource string. also jabber.org
// let's clients send it occasionally
o = summon_person(tu[UUser]);
sendmsg(o, "_request_status_person", 0,
vars, origin);
// XMPP + su[UUserAtHost]);
// maybe we can fix gmail presence by passing the UNR
// instead of the UNI in source
}
break;
default: // this is bad!
P2(("jabber presence isplacemsg %O\n", isplacemsg))
if (isplacemsg == 1) {
// TODO: houston... there is no way to
// decide whether this is a join or a
// status change... so the current
// behaviour of the rooms will send member
// list and history on each status change...
#if 0
// was not that a good idea...
if (node["/status"]) {
P2(("skipping status change in place\n"))
return;
}
#endif
if (helper = getchild(node, "x", "http://jabber.org/protocol/muc")) {
if (helper["/password"])
vars["_password"] = helper["/password"][Cdata];
if (helper["/history"]) {
// FIXME: support for other modes
if (t = helper["/history"]["@maxstanzas"])
vars["_amount_history"] = t;
}
}
o = FIND_OBJECT(tu[UUser]);
// lets see, if it works with lower_case
// it seems clients dont care about the case...
// but at least normal muc components care about case
// did i mention that muc is silly?
// if (lower_case(vars["_nick"]) != lower_case(tu[UResource]))
// yes! this is a good use for stricmp! ;)
// YACK!!! this does not work as intended.
// lynx, fix it please!!!
// hm.. the definition of stricmp is inverted.. oops
#ifdef DONT_REWRITE_NICKS
if (stricmp(vars["_nick"], tu[UResource])) {
// as everything else is much too complicated:
sendmsg(XMPP + source, "_error_unavailable_nick_place", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source ]),
o);
return;
}
#else
vars["_nick_local"] = tu[UResource]; // it's a matter of case
#endif
// if (node["/show"]) {
// then it should be a availability change
// yet... are there possibly clients that try sending
// this upon the initial enter?
// -- yes, if they're in global away and try to join
// did I mention that muc is a silly protocol?
// }
P4(("_request_enter from %O to %O: %O\n", ME, o, vars))
// dont send me a memberlist if i am a member already
#ifndef HISTORY_AMOUNT
# define HISTORY_AMOUNT 5
#endif
unless(vars["_amount_history"])
vars["_amount_history"] = HISTORY_AMOUNT;
sendmsg(o,
#ifdef SPEC
"_request_context_enter"
#else
"_request_enter"
#endif
"_again", 0,
vars, origin);
} else if (isplacemsg == 2) { // remote join stuff
#ifdef MUCSUC
object ctx = find_context(XMPP+ su[UUserAtHost]
+MUCSUC_SEP+ tu[UUser]);
if (!ctx) {
P0(("%O could not find the remotemuc for %O tris\n",
ME, vars["_context"]))
return;
}
#endif
o = summon_person(tu[UUser]);
vars["_nick"] = su[UResource];
vars["_nick_place"] = vars["_INTERNAL_identification"] || origin;
#ifdef MUCSUC
if (ctx) // get memberlist from remote slave
vars["_list_members"] = vars["_list_members_nicks"] = ctx->qMembers() + ({ tu[UUser] });
if (o && o->execute_callback(node["@id"], ({ vars["_context"], vars, node }))) return 1;
m_delete(vars, "_list_members");
m_delete(vars, "_list_members_nicks");
if (o = ctx)
o->castmsg(origin, "_notice_place_enter", 0, vars);
else {
// this can happen when joining
PT(("%O could not find the remotemuc for %O (yet)\n",
ME, vars["_context"]))
}
# else
vars["_context"] = vars["_nick_place"];
if (o && o->execute_callback(node["@id"], ({ vars["_INTERNAL_identification"], vars + ([ "_list_members" : 0, "_list_members_nicks" : 0 ]), node }))) return 1;
// comes with a faked _context for logic in user.c
sendmsg(o, "_notice_place_enter_unicast", 0, vars, origin);
#endif
} else {
int isstatus;
/* see http://www.psyc.eu/presence */
// if the node contains a x element in the
// jabber:x:delay namespace this is a
// _status_presence_here
o = summon_person(tu[UUser]);
if (helper = getchild(node, "x", "jabber:x:delay")) {
isstatus = 1;
}
// if (!intp(isstatus)) {
// parse jabbertime and convert to timestamp
// we also know since when he has
// been available TODO
// }
vars["_description_presence"] =
(node["/status"] && node["/status"][Cdata]) ?
node["/status"][Cdata] : ""; // "Get psyced!";
vars["_degree_availability"] = jabber2avail[node["/show"]
&& node["/show"][Cdata]];
// this message is too verbose, let's put in into
// debug log so we can see it in relation to the
// bug we experienced before (does it still exist?)
// PV(("p-Show in %O, origin %O, isstatus %O, vars %O\n",
// ME, origin, isstatus, vars));
// the info hasn't proved useful :(
vars["_INTERNAL_quiet"] = 1;
vars["_INTERNAL_mood_jabber"] = "neutral";
sendmsg(o, (isstatus? "_status_presence": "_notice_presence")
+ (avail2mc[vars["_degree_availability"]] || "_here"), 0,
vars, origin);
#ifdef CACHE_PRESENCE
persistent_presence(XMPP + su[UUserAtHost],
vars["_degree_availability"]);
#endif
}
break;
}
break;
case "iq":
{
mixed firstchild = getfirstchild(node);
string xmlns = firstchild ? firstchild["@xmlns"] : 0;
// TODO: maybe this should be handled by several functions
// iq_get, iq_set, iq_result, iq_error
t = node["@type"];
if (t == "result" || t == "error") {
if (tu[UUser])
o = FIND_OBJECT(tu[UUser]);
if (o && o->execute_callback(node["@id"], ({ origin, vars, node })))
return 1;
vars["_tag_reply"] = node["@id"];
} else {
vars["_tag"] = node["@id"];
}
// mh... we don't get that child with a result if the
// entity that we have asked does not have a vCard
// cool protocol!
switch(xmlns) {
case "vcard-temp":
{
mixed mvars;
// innerxml note: only result is a possible candidate
switch (t) {
case "result":
// this should not happen any longer since _request_description is chained
P3(("vCard result from %O to %O\n", source, target))
// only do the work if we find a rcpt
unless (o = summon_person(tu[UUser])) return;
mvars = convert_profile(node["/vCard"], "jCard");
PT(("extracted from vCard: %O\n", mvars))
mvars["_nick"] = su[UUser] || origin;
sendmsg(o, "_status_description_person", 0, mvars, origin);
break;
case "get":
P3(("vCard request from %O to %O\n",
source, target))
// target must be a 'bare' jid, but hey... we dont
// care about those rules anyway
if (isplacemsg) return;
if (tu[UResource]) return;
unless (tu[UUser]) return;
o = summon_person(tu[UUser]);
unless (o) return; // TODO
sendmsg(o, "_request_description_vCard", 0, vars, origin);
break;
case "set":
// a remote entity trying to do a set? haha!
// just be gentle and ignore it
P0(("%O Surprise! Encountered vCard set: %O\n", ME, node))
break;
case "error":
// this should not happen any longer since _request_description is chained
if (node["/error"]) {
unless (o = summon_person(tu[UUser])) {
// watch out, do not create circular error messages
P0(("%O vCard error from %O to %O\n",
ME, source, target))
return;
}
vars["_nick_target"] = MYORIGIN; // should be origin probably
if (xmpp_error(node["/error"],
"service-unavailable")) {
mc = "_failure_unavailable_service_description";
} else {
mc = "_error_unknown_name_user";
}
sendmsg(o, mc, 0, vars, origin);
}
break;
}
break;
}
case "http://jabber.org/protocol/disco#info":
if (firstchild["@node"])
vars["_target_fragment"] = firstchild["@node"];
if (tu[UUser])
o = FIND_OBJECT(tu[UUser]);
else
o = "/" + (tu[UResource] || "");
switch(node["@type"]) {
case "get":
sendmsg(o, "_request_list_feature", 0, vars, origin);
break;
case "set": // doesnt make sense
case "result": // handled by callback usually
case "error": // dito
break;
}
break;
case "http://jabber.org/protocol/disco#items":
if (firstchild["@node"])
vars["_target_fragment"] = firstchild["@node"];
if (tu[UUser])
o = FIND_OBJECT(tu[UUser]);
else
o = "/" + (tu[UResource] || "");
switch(node["@type"]) {
case "get":
sendmsg(o, "_request_list_item", 0, vars, origin);
break;
case "set": // doesnt make sense
case "result": // handled by callback usually
case "error": // dito
break;
}
break;
case "jabber:iq:version":
switch(t) {
case "get":
if (tu[UUser])
o = FIND_OBJECT(tu[UUser]);
else
o = "/" + (tu[UResource] || "");
PT(("sending _request_version to %O\n", o))
sendmsg(o, "_request_version", 0, vars, origin);
break;
case "set":
// UHM???
P0(("encountered jabber:iq:version set\n"))
break;
case "result":
case "error":
P0(("got jabber:iq:version result/error without tag\n"))
break;
}
break;
case "jabber:iq:last":
switch(t) {
case "get":
if (isplacemsg || is_localhost(lower_case(target)))
o = "/" + (tu[UResource] || "");
else
o = summon_person(tu[UUser]);
sendmsg(o, "_request_description_time", 0, vars, origin);
break;
case "set":
break;
case "result":
o = summon_person(tu[UUser]);
vars["_time_idle"] = node["/query"]["@seconds"];
sendmsg(source, "_status_description_time", 0, vars, origin);
break;
case "error":
break;
}
break;
case "urn:xmpp:ping":
if (tu[UUser])
o = FIND_OBJECT(tu[UUser]);
else
o = "/" + (tu[UResource] || "");
switch(t) {
case "get":
case "set": // I dont know why xep 0199 uses set... its a request
sendmsg(o, "_request_ping", 0, vars, origin);
break;
break;
case "result": // caught by tagging
break;
case "error": // caught by tagging
break;
}
break;
default:
// isn't this dangerous now that we send a resource
if (tu[UResource]) {
vars["_jabber_XML"] = innerxml;
o = summon_person(tu[UUser]);
sendmsg(o, "_jabber_iq_"+ t,
"[_source] is sending you a jabber iq "+t, vars, origin);
} else {
switch(t) {
case "get":
case "set":
// see XMPP-IM §2.4
// (whereas we are rather recipient than router)
// send service-unavailable stanza error
sendmsg(origin, "_error_unsupported_method", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source,
"_tag_reply" : node["@id"] ]));
break;
case "result":
// usually we dont do requests where we dont
// understand the answer
// hence this is usually caught by TAGGING
P0(("%O iq result from %O to %O\n", ME, source, target))
break;
case "error":
// dont create circular error messages and hence: ignore
P0(("%O iq error from %O to %O\n", ME, source, target))
break;
default:
P0(("%O ignores unknown iq: %O\n", ME, t))
break;
}
}
break;
}
break;
}
default:
// mh... this might be interesting...
break;
}
return 1;
}

View file

@ -0,0 +1,566 @@
#include <net.h>
#include <url.h>
#include "jabber.h"
// just renderMembers
#include NET_PATH "members.i"
int msg(string source, string mc, string data,
mapping vars, int showingLog, string target) {
mixed t;
switch (mc){
case "_status_person_away":
case "_status_person_present":
case "_status_person_present_implied":
#if 0
if (strstr(vars["_INTERNAL_target_jabber"], "@") == -1) {
P3(("skipping status person* to %O\n", vars["_INTERNAL_target_jabber"]))
return 1;
}
#endif
// reply to a presence probe
if (member(vars, "_time_idle")) {
t = vars["_time_idle"];
if (stringp(t)) {
t = to_int(t);
PT(("_time_idle %O == %O, right?\n", vars["_time_idle"], t))
}
t = gmtime(time() - t);
vars["_INTERNAL_time_jabber"] = JABBERTIME(t);
} else {
return 1;
}
break;
case "_status_legacy_CTCP":
case "_request_legacy_CTCP":
// ignore these
return 1;
break;
case "_request_description": // wir sollten uns da auf eins einigen :)
case "_request_examine": // needs a tag also... probably all _request's do
mc = "_request_examine_vCard";
unless (vars["_tag"]) vars["_tag"] = RANDHEXSTRING;
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result") {
mixed mvars = convert_profile($3["/vCard"], "jCard");
return ({ $1, "_status_description_person", 0, mvars + $2 });
}
else {
string err_mc;
if (xmpp_error($3["/error"],
"service-unavailable")) {
err_mc = "_failure_unavailable_service_description";
} else {
err_mc = "_error_unknown_name_user";
}
return ({ $1, err_mc, "Received no description from [_nick].", $2 });
}
:));
break;
case "_request_version":
unless (vars["_tag"]) vars["_tag"] = RANDHEXSTRING;
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result") {
// string v = "unknown";
XMLNode helper = $3["/query"];
if (helper["/name"])
$2["_version_description"] = helper["/name"][Cdata];
else
$2["_version_description"] = "-";
if (helper["/version"])
$2["_version"] = helper["/version"][Cdata];
else
$2["_version"] = "-";
/*
if (helper["/os"]) {
v += " on " + (helper["/os"][Cdata] || "unknown OS");
} */
return ({ $1, "_status_version",
"Version: [_nick] is using \"[_version_description]\" ([_version]).",
$2 });
}
else
return ({ $1, "_failure_unavailable_version", "Received no version from [_nick].", $2 });
:));
break;
case "_request_user_amount":
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result" && $3["/query"]) {
XMLNode helper;
helper = $3["/query"];
if (helper["/stat"] && nodelistp(helper["/stat"]))
foreach(helper : helper["/stat"]) {
switch(helper["@name"]) {
case "users/total":
$2["_amount_users_registered"] = helper["@value"];
break;
case "users/online":
$2["_amount_users"] = helper["@value"];
break;
}
}
return ({ $1, "_status_user_amount",
"There are [_amount_users] people online, [_amount_users_registered] accounts on this server.",
$2 });
} else
return ({ $1, "_error_request_users_amount",
"[_nick] does not support querying users amount.",
$2 });
:));
break;
case "_request_list_feature":
if (vars["_node"]) mc += "_node";
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result" && $3["/query"]) {
// string variant;
XMLNode helper = $3["/query"];
if (helper["/identity"]) {
if (nodelistp(helper["/identity"])) {
// FIXME
} else {
$2["_identity"] = helper["/identity"]["@category"] + "/" + helper["/identity"]["@type"];
$2["_name"] = helper["/identity"]["@name"];
#if 0 // nice variable, but please do something with it sometime soon :)
switch(helper["/identity"]["@category"]) {
case "account":
variant = "_person";
break;
case "conference":
variant = "_place";
break;
case "headline":
variant = "_newsfeed";
break;
case "server":
variant = "_server";
break;
}
#endif
}
}
$2["_list_feature"] = ({ });
if (helper["/feature"] && nodelistp(helper["/feature"]))
foreach(helper : helper["/feature"]) {
$2["_list_feature"] += ({ helper["@var"] });
}
return ({ $1, "_notice_list_feature",
"[_nick] is a [_identity] called [_name] offering features [_list_feature].",
$2 });
} else
return ({ $1, "_error_request_list_feature",
"[_nick] does not support querying features.",
$2 });
:));
break;
case "_request_list_item":
if (vars["_node"]) mc += "_node";
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result" && $3["/query"]) {
XMLNode helper;
helper = $3["/query"];
if (helper["@node"]) $2["_node"] = helper["@node"];
$2["_list_item"] = ({ });
$2["_list_item_description"] = ({ });
$2["_list_item_node"] = ({ });
if (helper["/item"] && nodelistp(helper["/item"]))
foreach(helper : helper["/item"]) {
$2["_list_item"] += ({ XMPP + helper["@jid"] });
$2["_list_item_description"] += ({ helper["@name"] });
$2["_list_item_node"] += ({ helper["@node"] });
}
// die darstellung ist fuer telnetter suboptimal...
return ({ $1, "_notice_list_item",
"[_nick] has lots of items: [_list_item_description].",
$2 });
} else
return ({ $1, "_error_request_list_item",
"[_nick] does not support querying items.",
$2 });
:));
break;
case "_request_registration":
if (!vars["_username"]) {
mc = "_request_registration_query";
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result" && $3["/query"]) {
XMLNode helper;
helper = $3["/query"];
if (helper["/instructions"])
$2["_instructions"] = helper["/instructions"][Cdata];
else
$2["_instructions"] = "No instructions available.";
return ({ $1, "_notice_registration",
"[_nick] provides the following registration instructions: [_instructions]",
$2 });
} else
return ({ $1, "_error_query_registration",
"[_nick] does not support registration.",
$2 });
:));
} else {
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result") {
return ({ $1, "_status_registration",
"Your registration at [_nick] was successful.",
$2 });
} else
return ({ $1, "_error_query_registration",
"[_nick] does not support registration.",
$2 });
:));
}
break;
case "_request_ping":
source->chain_callback(vars["_tag"], (:
$2["_time_ping"] = vars["_time_ping"];
if ($3["@type"] == "result")
return ({ $1, "_echo_ping",
"[_nick] pongs you.", $2 });
else
return ({ $1, "_failure_unsupported_ping",
"[_nick] does not support ping.", $2 });
:));
break;
case "_request_description_time":
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result" && $3["/query"]) {
$2["_time_idle"] = $3["/query"]["@@seconds"];
return ({ $1, "_status_description_time",
"[_nick] has been alive about [_time_idle] ago.", $2 });
} else
return ({ $1, "_failure_unsupported_description_time",
"[_nick] does not support querying idle time.",
$2 });
:));
break;
case "_request_authentication":
// TODO: XEP 0070 says we should use <message/> when the recipient is a bare jid
// but I prefer the iq method
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result")
return ({ $1, "_notice_authentication", 0, $2 });
else
return ({ $1, "_error_invalid_authentication", 0, $2 });
:));
break;
case "_notice_list_feature":
case "_notice_list_feature_person":
case "_notice_list_feature_place":
case "_notice_list_feature_server":
case "_notice_list_feature_newsfeed":
mixed id2jabber = shared_memory("disco_identity");
mixed feat2jabber = shared_memory("disco_features");
vars["_identity"] = id2jabber[vars["_identity"]] || vars["_identity"];
vars["_list_feature"] = implode(map(vars["_list_feature"],
(: return "<feature var='" + feat2jabber[$1] + "'/>"; :)), "");
break;
case "_notice_list_item":
t = "";
// same stuff in user.c (what happened to code sharing?)
for (int i = 0; i < sizeof(vars["_list_item"]); i++) {
t += "<item name='" + xmlquote(vars["_list_item_description"][i]) + "'";
if (vars["_list_item"] && vars["_list_item"][i])
t += " jid='" + mkjid(vars["_list_item"][i]) + "'";
if (vars["_list_item_node"] && vars["_list_item_node"][i])
t += " node='" + vars["_list_item_node"][i] + "'";
t += "/>";
}
vars["_list_item"] = t;
break;
case "_notice_headline_news":
vars["_page_news"] = xmlquote(vars["_page_news"]);
break;
// case "_notice_received_email":
// vars["_subject"] = xmlquote(vars["_subject"]);
// vars["_origin"] = xmlquote(vars["_origin"]);
// break;
case "_status_place_description_news_rss":
vars["_link_news_rss"] = xmlquote(vars["_link_news_rss"]);
break;
case "_status_description_person":
mc = "_status_description_vCard";
vars["_INTERNAL_data_XML"] = convert_profile(vars, 0, "jCard");
break;
#ifndef ENTER_MEMBERS
case "_status_place_members":
string skip, clashnick, placejid;
array(string) rendered;
mixed u;
int i;
P2(("_status_place_members from %O to %O\n", source, target))
u = parse_uniform(target);
if (u) clashnick = u[UUser];
else clashnick = 0;
// PARANOID? noooo
rendered = ({array(string)})renderMembers(vars["_list_members"], vars["_list_members_nicks"], 1);
placejid = mkjid(source);
for(i = 0; i < sizeof(vars["_list_members"]); i++) {
// see JEP-0045 6.3.3 last paragraph
// probably their assumption is about a linear
// list where this is always sent to the last
// (ie newest member)
# ifdef USE_THE_RESOURCE
if (stringp(vars["_list_members"][i])
&& abbrev(vars["_list_members"][i], target))
# else
if (target == vars["_list_members"][i])
# endif
{
skip = vars["_list_members_nicks"][i];
continue;
}
// here we also have to do collision decection
// (local user with the same nick as remote user)
// and adjust rendered[i] accordingly
if (rendered[i] == clashnick) {
// this only happens with local users
rendered[i] = query_server_unl() +"~"+ clashnick;
}
render("_status_place_members_each", "", ([
"_INTERNAL_target_jabber": vars["_INTERNAL_target_jabber"],
"_INTERNAL_source_jabber": placejid + "/" + RESOURCEPREP(rendered[i]),
"_nick": rendered[i],
"_source_relay": mkjid(vars["_list_members"][i]),
"_duty": "none" ]), source);
}
vars["_INTERNAL_source_jabber"] += "/" + skip;
vars["_source_relay"] = mkjid(target);
vars["_duty"] = "none";
render("_status_place_members_self", "", vars, source);
return 1;
#endif
case "_status_presence_here":
case "_notice_presence_here":
// _notice_presence_here requires an additional jabberig
// messsage at least if this is the login announce
// TODO: this one is temporary
case "_notice_presence_here_quiet":
#ifdef XMPPERIMENTAL
emit("<presence from='" + vars["_INTERNAL_source_jabber"] + "'/>"
"<presence from='" + vars["_INTERNAL_source_jabber"] +
"' type='probe'/>");
return 1;
#else
// notiz: _request_status_person muss von der UNI erfolgen,
// nicht von der UNL, damit die Antwort auch wieder
// an diese geht
// und eigentlich muss das nur beim logon erfolgen...
P4(("%O _request_status_person to be done: %O,%O,%O,%O.. %O\n", ME, source,mc,data,target, vars))
//
// is this correct here, or do we need something smarter? -lynX
vars["_INTERNAL_target_jabber_bare"] = vars["_INTERNAL_target_jabber"];
// apparently we get the bare one here, anyway.. so we could
// throw out this special case of *_bare variable. then again,
// maybe i am wrong. how can i ensure? TODO -lynX
msg(source, "_request_status_person", "", vars, showingLog, target);
#endif
// TODO: we dont explicitly need this for CACHE PRESENCE
// on the other hand syncing with jabber is much harder O(n)
// than it is with psyc O(1)
break;
case "_message_public":
// TODO: this needs to be applied to questions & actions also
if (vars["_time_place"]) {
// see JEP-0045, 6.3.11 Discussion History
// and JEP-0091 Delayed Delivery
mc = "_message_public_history";
t = gmtime(vars["_time_place"]);
vars["_INTERNAL_time_place_jabber"] = JABBERTIME(t);
} else if (!vars["_context"]) {
mc = "_request_message_public";
}
break;
case "_request_execute":
// oh yes, this is the jabber way to do it!!!!111!
// taken from the xep 0045 "irc command mapping"
mixed args = explode(data, " ");
switch(args[0]) {
case "topic":
// <lynX> might as well convert to _request_do_topic but
// (a) we don't have that yet
// (b) might even share code with place/basic to do that
// as any place needs to be able to understand both
// spec'd commands (_do) and ad hoc commands (_execute)
mc = "_request_execute_topic";
data = ARGS(1);
break;
case "kick": // TODO: we could add a callback for this
mc = "_request_execute_kick";
vars["_nick_target"] = is_formal(args[1]) ? parse_uniform(args[1])[UResource] : args[1];
vars["_reason"] = ARGS(2);
break;
case "ban": // TODO: we could add a callback for this
// mh... we have a hard time finding out the real jid
// of the participant, so this is deactivated for now
return 0;
case "invite":
// TODO: we should do invite via the places!
return 0;
default:
return 0; // pushback
}
break;
default:
// reihenfolge bitte nach wahrscheinlichkeit der mc TODO ;)
if (abbrev("_message_private", mc)) {
// generate echo here as jabber does not provide echo (apart from MUC)
sendmsg(source, "_message_echo" + mc[8..], data, vars,
target);
} else if (abbrev("_notice_place_leave", mc)) {
/*
if (mc == "_notice_place_leave_invalid")
vars["_INTERNAL_source_jabber"] = vars["_INTERNAL_source_jabber_bare"] + "/" + vars["_nick_local"];
*/
mc = "_notice_place_leave"; // remove this if textdb inheritance works
} else if (abbrev("_notice_place_enter", mc)) {
vars["_source_relay"] = mkjid(source);
mc = "_notice_place_enter";
} else if (abbrev("_echo_place_enter", mc)) {
#if 0
vars["_source_relay"] = mkjid(target);
mc = "_echo_place_enter";
vars["_duty"] = "none";
#else
# ifdef ENTER_MEMBERS
if (vars["_list_members"]) {
string placejid = mkjid(source);
string *rendered;
// i wish we could leave the clash strategy to mkjid...
// PARANOID? noooo
rendered = ({array(string)})renderMembers(vars["_list_members"], vars["_list_members_nicks"], 1);
for(int i = 0; i < sizeof(vars["_list_members"]); i++) {
// here we also have to do collision decection
// (local user with the same nick as remote user)
// and adjust rendered[i] accordingly
if (rendered[i] == vars["_nick"]) {
// this only happens with local users
rendered[i] = query_server_unl() +"~"+ rendered[i];
}
render("_notice_place_enter", "",
([ "_INTERNAL_target_jabber": vars["_INTERNAL_target_jabber"],
"_INTERNAL_source_jabber" : placejid + "/" + RESOURCEPREP(rendered[i]),
"_nick" : rendered[i],
"_source_relay" : mkjid(vars["_list_members"][i]),
"_duty" : "none" ]),
source);
}
}
vars["_source_relay"] = mkjid(target);
vars["_duty"] = "none";
/*
vars["_INTERNAL_source_jabber"] = mkjid(source,
([ "_nick" : vars["_nick_local"] || vars["_nick"] ]),
0, 0, target),
*/
render("_echo_place_enter", "", vars, source);
# endif
return 1;
#endif
} else if (abbrev("_error_place_enter", mc)) {
mc = "_error_place_enter";
// can we render data string here?
data = psyctext("", vars, data, source);
// vielleicht sollte man hier das ganze nochmal als
// msg() hinterherschieben, irgendwie finden jabba-clients
// es nicht notwendig, den text zu zeigen...
// traurig eigentlich
} else if (abbrev("_notice_presence_away", mc)) {
// mh... for muve2muve it would be better to
// extend jabber with an 'automatic' flag
// but they could use psyc anyway :)
mc = "_notice_presence_away";
}
else if (abbrev("_request_enter", mc) /* remote jabber join */
|| abbrev("_request_context_enter", mc)) {
mc = "_request_enter";
#ifdef MUCSUC
t = XMPP+ vars["_INTERNAL_target_jabber_bare"]
+MUCSUC_SEP+ vars["_nick"];
P4(("MUCSUC/render1: %O of %O\n", t, 0 && vars))
set_context(clone_object(JABBER_PATH "remotemuc"), t);
#endif
source->chain_callback(vars["_tag"], (:
P3(("remote jabber part echo with %O!\n", $2))
// FIXME:
// if there is a 201 status code we need
// to send extra mumbo jumbo
$2["_tag_reply"] = $3["@id"];
$2["_nick_place"] = $2["_INTERNAL_identification"];
#ifdef MUCSUC
$2["_source_relay"] = source;
t = XMPP+ vars["_INTERNAL_target_jabber_bare"]
+MUCSUC_SEP+ vars["_nick"];
P4(("MUCSUC/render2: %O of %O\n", t, 0 && vars))
// echo place enter has not context (yet)
//$2["_context"] = vars["_INTERNAL_target_jabber_bare"] + ...
// m_delete($2, "_nick");
#else
t = $1;
#endif
if ($3["@type"] == "error") {
// FIXME: could remove context
return ({ t, "_failure_place_enter_XMPP",
"[_nick_place] could not be entered for jabberish reasons.",
$2 });
} else {
#ifdef MUCSUC
return ({ t, "_echo_place_enter", 0, $2 });
#else // ugly old code
// since we fake _context we also have to use _notice, not _echo
// can we send _echo_place_enter_unicast and instead
// make sure we don't provide a _context?
return ({ $1, "_notice_place_enter_unicast_INTERNAL_ECHO",
"[_nick] enters a unicast chatroom at [_nick_place].",
$2 });
#endif
}
:));
} else if (abbrev("_request_leave", mc)
|| abbrev("_request_context_leave", mc)) {
mc = "_request_leave";
source->chain_callback(vars["_tag"], (:
P3(("remote jabber part echo with %O!\n", $2))
$2["_tag_reply"] = $3["@id"];
$2["_nick_place"] = $2["_INTERNAL_identification"];
$2["_nick"] = $2["_INTERNAL_source_resource"];
#ifdef MUCSUC
// single-user channels on top of xmpp URI scheme.. funny
// whatever does the job.
$2["_context"] = XMPP+ vars["_INTERNAL_target_jabber_bare"]
// should i try $2["_nick"] instead?
+MUCSUC_SEP+ vars["_nick"];
P3(("MUCSUC/render3: %O\n", $2["_context"]))
#else
$2["_context"] = $2["_INTERNAL_identification"];
#endif
return ({ $1,
"_notice_place_leave",
"You leave [_nick_place].",
$2 });
:));
} /* remote jabber join */
else if (abbrev("_message_announcement", mc)) return 1;
else if (abbrev("_failure_redirect", mc)) {
if (vars["_tag_reply"]) { // wild guess that it is an iq then
mc = "_jabber_iq_error";
vars["_jabber_XML"] = "<error type='modify'><gone xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/><text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' xml:lang='en'>" + psyctext(data, vars) + "</text></error>";
}
}
}
render(mc, data, vars, source);
return 1;
}

118
world/net/jabber/parse.c Normal file
View file

@ -0,0 +1,118 @@
#include <net.h>
#include <xml.h>
// dummy declarations
open_stream(node) { ; }
volatile string innerxml;
volatile closure nodeHandler;
protected int _xml_start, _xml_end, _xml_offset;
string buffer = "";
XMLNode _node;
volatile XMLNode *_nodestack = ({ });
void xml_onStartTag(string tag, mapping params) {
string t = "/"+ tag;
if (!_node && tag == "stream:stream") {
_node = new_XMLNode;
_node[Tag] = tag;
foreach(string key, string val : params)
_node["@" + key] = val;
open_stream(_node);
_node = 0;
buffer = buffer[expat_position() + expat_eventlen()..];
return;
}
if (_node) {
_nodestack += ({ _node });
if (!_node[t]) {
/* no child with that name */
_node[t] = new_XMLNode;
_node = _node[t];
} else {
if (!nodelistp(_node[t])) {
/* just a single node with that name, convert it
*/
_node[t] = ({ _node[t] });
}
_node[t] += ({ new_XMLNode });
_node = _node[t][<1];
}
} else {
_node = new_XMLNode;
_nodestack = ({ });
_xml_start = expat_position() + expat_eventlen();
_xml_offset = expat_position();
}
_node[Tag] = tag;
foreach(string key, string val : params)
_node["@" + key] = val;
}
void xml_onEndTag(string tag) {
if (!_node && tag == "stream:stream") {
// is there anything to be done here? like sending a closing
// ack? we did not do it before but... lynX wrote that stream
// closing handshake xep so he probably implemented it, too...
return;
}
if (sizeof(_nodestack) > 0) {
_node = _nodestack[<1];
_nodestack = _nodestack[..<2];
} else {
_xml_end = expat_position();
innerxml = buffer[_xml_start-_xml_offset..(_xml_end-_xml_offset-1)];
P2(("innerxml %O\n", innerxml))
funcall(nodeHandler, _node);
_node = 0;
buffer = "";
innerxml = 0;
}
}
void xml_onText(string data) {
if (!_node)
return; // whitespace usually
if (_node[Cdata])
_node[Cdata] += data;
else
_node[Cdata] = data;
}
feed(a) {
int d;
if (!_node && a == "\n") {
/* whitespace ping */
} else {
buffer += a;
d = expat_parse(a);
if (!d) {
mixed e = expat_last_error();
PT(("parse error while parsing %O:\n%O\n", a, e))
}
// TODO: error handling muss hier her
}
/*
#if __EFUN_DEFINED__(convert_charset) && SYSTEM_CHARSET != "UTF-8"
if (catch(a = convert_charset(buffer[0..pos - 1],
"UTF-8", SYSTEM_CHARSET); nolog)) {
P1(("catch! iconv %O in %O\n", a, ME))
//QUIT
a = buffer; // let's give it a try
}
xmlparse(a);
#else
xmlparse(buffer[0..pos - 1]);
#endif
*/
#ifdef INPUT_NO_TELNET
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE | INPUT_NO_TELNET);
#else
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE);
#endif
}

View file

@ -0,0 +1,68 @@
#include <net.h>
/* a cache for the remote muc
* yes, this is for a SINGLE person
* yes, that's horrible
*
* at least we get to implement a member list and
* even /history if we'd like to have that.. ;)
*/
// FIXME: should set "owner nick" and tag so
// we can detect when the remote room does not
// support tagging or when we are kicked
// and this could even do echo detection
// NOTE: this should work when using the
// isecho check
object owner;
mapping membercache = ([ ]);
void insert_member(mixed member, mixed origin, mixed data) {
owner = member;
}
void remove_member(mixed member, mixed origin) {
owner = 0;
membercache = 0;
destruct(ME);
}
castmsg(source, method, data, mapping vars) {
P3(("remotemuc proxying %O\n", method))
int isecho = (source == vars["_context"]);
if (isecho)
source = owner;
switch(method) {
case "_message_public":
break;
case "_notice_place_enter":
if (isecho) {
// can happen if room does not support tagging
P0(("%O joined via _notice_place_enter, this is strange\n", vars["_context"]))
} else if (membercache[source]) {
P4(("skipping notice place enter from person already in room\n"))
return;
} else
membercache[source] = vars["_nick"];
break;
case "_notice_place_leave":
if (isecho) {
// got kicked or room does not support tagging
P0(("%O left via _notice_place_leave, this is strange\n", vars["_context"]))
}
m_delete(membercache, source);
break;
}
if (!owner) {
P4(("remotemuc castmsg %O and there is no owner\n", method))
return;
}
owner -> msg(source, method, data, vars );
}
mapping qMembers() {
P4(("membercache is %O\n", membercache))
return m_indices(membercache);
}

504
world/net/jabber/server.c Normal file
View file

@ -0,0 +1,504 @@
// $Id: server.c,v 1.148 2008/03/11 15:13:58 lynx Exp $ // vim:syntax=lpc:ts=8
#include "jabber.h"
#include "server.h" // inherits net/server
#include "person.h" // find_person
#include "url.h"
volatile string tag;
volatile string resource;
volatile string streamid;
volatile string pass;
volatile string language;
int reprompt;
volatile string sasluser;
volatile string saslchallenge;
#ifdef __TLS__
volatile mixed cert;
#endif
qScheme() { return "jabber"; }
// qName() { } // we need that in common.c - probably no more
qTag() { return tag; }
void create() {
unless (clonep()) return;
// not multilingual yet - TODO
sTextPath(0, 0, "jabber");
}
#ifdef __TLS__
tls_logon(result) {
PT(("%O tls_logon(%d)\n", ME, result))
PT(("%O tls? %O\n", ME, tls_query_connection_state(ME)))
// we may write again
# ifdef WANT_S2S_SASL
cert = tls_certificate(ME, 0);
# endif
}
#endif
string *splitsasl(string data) {
string *result = ({ "" });
int *decoded;
int i, j;
decoded = decode_base64(data);
foreach(i : decoded) {
if (i == 0) {
j++;
result += ({ "" });
continue;
}
result[j] += sprintf("%c", i);
}
return result;
}
promptForPassword(user) {
P2(("promptForPassword with %O\n", user))
if (reprompt == 1 || pass) {
w("_error_invalid_password", "Invalid password.\n",
([ "_tag_reply" : qTag(), "_nick" : nick,
"_resource" : resource ]) );
write("</stream:stream>");
QUIT
return; // ?
}
P2(("nick %O, pass %O\n", nick, pass))
unless (pass) {
reprompt = 1;
w("_query_password", 0, //"Please provide your password.",
([ "_nick": nick, "_tag_reply" : qTag() ]) );
}
return 1;
}
logon(a) {
#ifdef _flag_log_sockets_XMPP
D0( log_file("RAW_XMPP", "\n%O logon\t%O", ME, ctime()); )
#endif
P3(("logon(%O) beim jabber:server.c\n", a))
set_combine_charset(COMBINE_CHARSET);
#ifdef INPUT_NO_TELNET
input_to(#'feed, input_to_settings =
INPUT_IGNORE_BANG | INPUT_CHARMODE | INPUT_NO_TELNET);
#else
enable_telnet(0, ME);
input_to(#'feed, input_to_settings =
INPUT_IGNORE_BANG | INPUT_CHARMODE);
#endif
return ::logon(a);
}
createUser(nick) {
return named_clone(JABBER_PATH "user", nick);
}
userLogon() {
user->sTag(tag);
user->sResource(resource);
return ::userLogon();
}
#ifdef EXPERIMENTAL
authChecked(result, varargs array(mixed) args) {
// a point where we could be sending our jabber:iq:auth reply
// instead of letting _notice_login do that
PT(("%O got authChecked %O, %O\n", ME, result, args))
return ::authChecked(result, args);
}
#endif
jabberMsg(XMLNode node) {
XMLNode helper;
string id;
mixed t;
id = node["@id"]; // tag?
switch (node[Tag]) {
case "iq":
if (node["/bind"]) {
// suppresses the jabber:iq:auth reply in the SASL case
tag = -1;
unless (sasluser) {
// not-allowed stanza error?
return 0;
}
helper = node["/bind"];
// see XMPP-core, 7. Resource binding
if (helper["/resource"])
resource = helper["/resource"][Cdata];
// assign a resource
if (!stringp(resource) || resource == "")
resource = "PSYC";
nick = sasluser;
sasluser = "";
emit(sprintf("<iq type='result' id='%s'>"
"<bind xmlns='" NS_XMPP "xmpp-bind'>"
"<jid>%s</jid>"
"</bind></iq>",
id, nick + "@" SERVER_HOST "/" + resource));
return 0;
} else if (node["/session"]) {
unless(user) return 0; // what then?
if (!stringp(id))
id = "";
emit(sprintf("<iq type='result' id='%s' from='%s'/>",
id, SERVER_HOST));
user -> vSet("language", language);
return morph();
}
switch (node["/query"]["@xmlns"]) {
// old-school style.. for clients that don't like SASL, like kopete
case "jabber:iq:auth":
tag = id;
if (node["@type"] == "get"){
// hello(nick) ?
w("_query_password", 0,
([ "_nick": nick, "_tag_reply": tag ]), "");
} else if (node["@type"] == "set") {
helper = node["/query"];
resource = helper["/resource"][Cdata];
nick = helper["/username"][Cdata];
if (mappingp(helper["/password"]) &&
(pass = helper["/password"][Cdata])) {
hello(nick, 0, pass);
} else if (mappingp(helper["/digest"]) &&
(pass = helper["/digest"][Cdata])) {
P3(("jabber:iq:auth/digest got %O\n", pass))
hello(nick, 0, pass, "sha1", streamid);
} else {
// TODO: write an error?
P0(("jabber:server iq auth set without pass or digest\n"))
}
}
return 1;
case "jabber:iq:register":
if (node["@type"] == "get"){
string packet;
#ifdef REGISTERED_USERS_ONLY
packet = sprintf("<iq type='result' id='%s'>"
"<query xmlns='jabber:iq:register'/>"
"<error code='501>No way!</error>" IQ_OFF,
id);
#else
packet = sprintf("<iq type='result' id='%s'>"
"<query xmlns='jabber:iq:register'>"
"<instructions>You dont even need to register, "
"this is psyced. Manual at http://help.pages.de</instructions>"
"<name/><email/><username/><password/>" IQ_OFF,
id);
#endif
emit(packet);
} else if (node["@type"] == "set"){
string packet;
packet = sprintf("<iq type='error' id='%s'>"
"<query xmlns='jabber:iq:register'>",
id);
unless ((helper = node["/query"])
&& (t = helper["/username"][Cdata])
&& (user = summon_person(t))) {
// internal server error
STREAM_ERROR("internal-server-error",
"Oh dear! Internal server error")
QUIT // too hard?
}
unless (user -> isNewbie()) {
// already registered to someone else
packet += "<error code='406' type='cancel'>"
"<conflict xmlns='" NS_XMPP "xmpp-stanzas'/>"
"</error>"
IQ_OFF;
emit(packet);
QUIT
} else unless ((t = helper["/username"]) &&
t[Cdata] &&
(t = helper["/password"]) &&
t[Cdata]) {
packet += "<error code='406' type='modify'>"
"<not-acceptable xmlns='" NS_XMPP "xmpp-stanzas'/>"
"</error>"
IQ_OFF;
emit(packet);
QUIT
} else {
user -> vSet("password", t[Cdata]);
if (t = helper["/email"]) {
user -> vSet("email", helper["/email"]);
}
// maybe immediate save is not really a good idea
// user -> save();
emit(sprintf("<iq type='result' id='%s'/>", id));
}
user = 0;
}
}
break;
case "starttls":
#if __EFUN_DEFINED__(tls_available)
if (tls_available()) {
emit("<proceed xmlns='" NS_XMPP "xmpp-tls'/>");
// we may not write until tls_logon is called!
tls_init_connection(ME, #'tls_logon);
} else {
P1(("%O received a 'starttls' but TLS isn't available.\n", ME))
}
#else
emit("<failure xmlns='" NS_XMPP "xmpp-tls'/>");
emit("</stream:stream>");
destruct(ME);
#endif
break;
case "auth":
switch (node["@mechanism"]) {
case "DIGEST-MD5":
// TODO: time-based nonce
saslchallenge = RANDHEXSTRING;
emit("<challenge xmlns='" NS_XMPP "xmpp-sasl'>" +
encode_base64(sprintf("realm=\"%s\",nonce=\"%s\","
"qop=\"auth\",charset=utf-8,"
"algorithm=md5-sess",
SERVER_HOST,
saslchallenge)
) + "</challenge>");
break;
case "PLAIN":
string *creds;
creds = splitsasl(node[Cdata]);
// check that creds[2] is valid for user creds[1]
// and in case of success:
if ((sizeof(creds) == 3)
&& (user = find_person(creds[1])
|| (user = createUser(creds[1])))
#ifdef ASYNC_AUTH
) {
mixed authCb = CLOSURE((int result), (mixed creds), (creds),
{
if (result) {
sasluser = creds[1];
emit("<success xmlns='" NS_XMPP "xmpp-sasl'/>");
} else {
sasluser = 0;
SASL_ERROR("temporary-auth-failure")
QUIT
}
return;
});
user -> checkPassword(creds[2], "plain", 0, 0, authCb);
#else
&& user -> checkPassword(creds[2], "plain")) {
sasluser = creds[1];
emit("<success xmlns='" NS_XMPP "xmpp-sasl'/>");
#endif
} else {
SASL_ERROR("invalid-mechanism")
QUIT
}
break;
#ifdef __TLS__
case "EXTERNAL":
# if 0
// TODO: basically this works but is untested due to
// lack of clients
// also, it uses email addresses instead of xmpp jids
// and i am not sure if I should check who signed the cert,
// as I probably want my users only to use certs created by me
unless (node[Cdata]) {
SASL_ERROR("incorrect-encoding")
QUIT
} else unless (mappingp(cert) && cert[0] == 0
&& cert["1.2.840.113549.1.9.1"]) {
SASL_ERROR("invalid-mechanism")
QUIT
} else {
string deco, ho, u;
// TODO: there could be a try-except block here with
// incorrect-encoding sasl error
deco = to_string(decode_base64(node[Cdata]));
// TODO: the right thingie could be a list!
unless (deco == cert["1.2.840.113549.1.9.1"]) {
// TODO: not sure about this one
SASL_ERROR("invalid-mechanism")
QUIT
}
// lets see if its one of our users
sscanf(deco, "%s@%s", u, ho);
unless (is_localhost(lower_case(ho))) {
// wrong host
SASL_ERROR("invalid-authzid")
QUIT
// TODO: consider legalized names
} else unless ( ///// legal_name(u)) {
SASL_ERROR("not-authorized")
QUIT
} else {
user = find_person(u) || createUser(u);
sasluser = u;
emit("<success xmlns='" NS_XMPP "xmpp-sasl'/>");
}
}
# else
SASL_ERROR("invalid-mechanism")
QUIT
# endif
break;
#endif
#ifndef REGISTERED_USERS_ONLY
case "ANONYMOUS":
unless(node[Cdata]) {
SASL_ERROR("incorrect-encoding")
QUIT // i suppose this was missing here --lynX
} else {
string u;
t = to_string(decode_base64(node[Cdata]));
u = legal_name(t);
unless(u) {
if (t) {
SASL_ERROR("not-authorized")
} else {
SASL_ERROR("invalid-authzid")
}
QUIT
}
PT(("wants to auth anonymously as %O\n", u))
unless (user = summon_person(u)) {
SASL_ERROR("temporary-auth-failure")
QUIT
}
unless (user -> isNewbie()) {
SASL_ERROR("not-authorized")
QUIT
}
sasluser = u;
emit("<success xmlns='" NS_XMPP "xmpp-sasl'/>");
}
break;
#endif
default:
SASL_ERROR("invalid-mechanism")
QUIT
break;
}
break;
case "response":
// this does not behave according to rfc3920 but rather the 3920bis
// version
if (node[Cdata] && (t = to_string(decode_base64(node[Cdata])))) {
mapping creds = sasl_parse(t);
mixed authCb = CLOSURE((int result), (mixed creds), (creds),
{
if (result) {
P3(("digest md5 success\n"))
sasluser = creds["username"];
P3(("result is %O\n", result))
emit("<success xmlns='" NS_XMPP "xmpp-sasl'>"
+ encode_base64("rspauth=" + result) +
"</success>");
} else {
PT(("digest md5 failure\n"))
sasluser = 0;
SASL_ERROR("invalid-authzid")
QUIT
}
});
user = find_person(creds["username"]) || createUser(creds["username"]);
user -> checkPassword(1, "digest-md5", 0, creds, authCb);
} else {
SASL_ERROR("not-authorized")
QUIT
}
break;
default:
/* angeblich werden andere Pakete in diesen Zustand gequeried
* aber das glaube ich nicht dass das wirklich so ist
* wie auch immer haetten wir sonst mittlerweile net/queue fuer
* diesen job
*/
P2(("jabber/server:jabberMsg default case\n"))
}
// return ::jabberMsg(from, cmd, args, data, all);
return 0;
}
open_stream(XMLNode node) {
string features;
string header;
P2(("jabber/server open_stream\n"))
streamid = RANDHEXSTRING;
unless(node["@xmlns"] == "jabber:client") {
// TODO: is this a fatal error?
}
switch (node["@xml:lang"]) {
case "DE": // what about DE_at etc?
case "de":
case "german":
case "German": // curious... isnt the xml:lang supposed to be 'de'?
language = "de";
break;
default:
if (!language) language = "en";
break;
}
features = "";
header = "<stream:stream "
"from='" SERVER_HOST "' "
"xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' ";
if (node["@version"] == "1.0") {
header += "version='1.0' ";
features = "<stream:features>";
#if __EFUN_DEFINED__(tls_available)
if (tls_available() && tls_query_connection_state(ME) == 0)
features += "<starttls xmlns='" NS_XMPP "xmpp-tls'/>";
#endif
if (sasluser) {
features += "<bind xmlns='" NS_XMPP "xmpp-bind'/>";
features += "<session xmlns='" NS_XMPP "xmpp-session'/>";
} else {
features += "<mechanisms xmlns='" NS_XMPP "xmpp-sasl'>"
#if __VERSION_MINOR__ > 3 || __VERSION_MICRO__ > 610
"<mechanism>DIGEST-MD5</mechanism>"
#endif
"<mechanism>PLAIN</mechanism>";
#ifndef REGISTERED_USERS_ONLY
// sasl anonymous
"<mechanism>ANONYMOUS</mechanism>";
#endif
#if __EFUN_DEFINED__(tls_available)
if (tls_available() && tls_query_connection_state(ME) > 0
&& mappingp(cert) && cert[0] == 0
&& certificate_check_jabbername(0, cert)) {
features += "<mechanism>EXTERNAL</mechanism>";
}
#endif
features += "</mechanisms>";
features += "<auth xmlns='http://jabber.org/features/iq-auth'/>";
#ifndef REGISTERED_USERS_ONLY
features += "<register xmlns='http://jabber.org/features/iq-register'/>";
#endif
}
features += "</stream:features>";
}
header += "id='" + streamid + "'>";
emit(header + features);
}
// overrides certificate_check_jabbername from common.c with a function
// that is approproate for authenticating users
certificate_check_jabbername(name, cert) {
// plan: prefer subjectAltName:id-on-xmppAddr,
// but allow email (1.2.840.113549.1.9.1)
// and subjectAltName:rfc822Name
return 0;
}

1250
world/net/jabber/user.c Normal file

File diff suppressed because it is too large Load diff