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:
commit
4e601cf1c7
509 changed files with 77963 additions and 0 deletions
633
world/net/jabber/active.c
Normal file
633
world/net/jabber/active.c
Normal 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
475
world/net/jabber/common.c
Normal 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] + ">"; // + 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;
|
||||
}
|
192
world/net/jabber/component.c
Normal file
192
world/net/jabber/component.c
Normal 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
160
world/net/jabber/disco.c
Normal 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
559
world/net/jabber/gateway.c
Normal 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);
|
||||
}
|
48
world/net/jabber/interserver.c
Normal file
48
world/net/jabber/interserver.c
Normal 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
111
world/net/jabber/jabber.h
Normal 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
|
933
world/net/jabber/mixin_parse.c
Normal file
933
world/net/jabber/mixin_parse.c
Normal 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;
|
||||
}
|
566
world/net/jabber/mixin_render.c
Normal file
566
world/net/jabber/mixin_render.c
Normal 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
118
world/net/jabber/parse.c
Normal 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
|
||||
}
|
68
world/net/jabber/remotemuc.c
Normal file
68
world/net/jabber/remotemuc.c
Normal 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
504
world/net/jabber/server.c
Normal 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
1250
world/net/jabber/user.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue