psyced/world/net/jabber/common.c

508 lines
16 KiB
C

// $Id: common.c,v 1.276 2008/12/01 11:31:33 lynx 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 <uniform.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 = "";
volatile 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{FFFFF}]+", "*", 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
log_file("RAW_XMPP", "\n« %O\t%s", ME, message);
#endif
return ::emit(message);
}
// don't check message, use this only where you are 100% sure
// to be sending safe data
int emitraw(string message) {
#ifdef _flag_log_sockets_XMPP
log_file("RAW_XMPP", "\n« %O\t%s", ME, message);
#endif
return ::emit(message);
}
// this assumes the old ldmuddish charmode+combine-charset
// if we ever get a input_bytes it needs to be rewrittn
feed(a) {
int pos;
buffer += a;
while ((pos = strstr(buffer, ">") + 1) > 0){
if (strstr(buffer, "<") == -1) {
/* XML is a braindead spec
* > MAY be encoded
* 'this may be fixed in future versions'
* of the xmpp spec
*/
buffer = buffer[0..pos-2] + "&gt;"; // + buffer[pos..] <-- empty
continue;
}
#if __EFUN_DEFINED__(convert_charset) && SYSTEM_CHARSET != "UTF-8"
if (catch(a = convert_charset(buffer[0..pos - 1],
"UTF-8", SYSTEM_CHARSET); nolog)) {
P1(("catch! iconv %O in %O\n", a, ME))
//QUIT
a = buffer; // let's give it a try
}
xmlparse(a);
#else
xmlparse(buffer[0..pos - 1]);
#endif
buffer = buffer[pos..];
}
#ifdef INPUT_NO_TELNET
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE | INPUT_NO_TELNET);
#else
input_to(#'feed, INPUT_IGNORE_BANG | INPUT_CHARMODE);
#endif
}
#define JABBER_PARSE
#define XML_ERROR(code, long) \
P0(("%O aborting XML parse: %s\n", ME, long)) \
STREAM_ERROR(code, "") \
remove_interactive(ME);
#include NET_PATH "xml/parse.c"
#undef JABBER_PARSE
jabberMsg(XMLNode node) {
P2(("common:jabberMsg (should not happen) %O\n", node[Tag]))
}
// TODO: move this to separate file and determine which parts can be ifdeffed
varargs string mkjid(mixed who, mixed vars, mixed ignore_context, string target, string jabberhost) {
/* Die große Aufgabe: wie quetschen wir die gehaltvolle
* Information aus dem vars-mapping gegeben durch
* _nick, nick_place, source && context in einen
* einzigen String der Form
* user@host/resource?
*/
string t, *u;
if (!who || who == "") return "";
unless (jabberhost) jabberhost = _host_XMPP;
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[UNick]) + "@" + 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>"+
#ifdef NEW_LINE
xmlquote(output)
#else
// was: chomp after xmlquote.. but why?
xmlquote(chomp(output))
#endif
+"</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 {
// hack for a special case where status update contains <, >
// if this kind of problem recurrs, we should quote every
// single damn variable
if (vars["_description_presence"])
vars["_INTERNAL_XML_description_presence"] =
xmlquote(vars["_description_presence"]);
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 0
if (strstr(output, "r00t") >= 0) {
P0(("common:render(%O, %O, %O, %O) -> %O\n", mc,
data, vars, source, output))
}
#endif
}
#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;
}
// deprecated - use tls_check_service_identity from library/tls.c instead
// is this being used at all? -- no longer, but keep it around a little
// for backward compat
#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
*/
unless(name) return 0;
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) != -1) return 1;
foreach(string cn : t) {
if (NAMEPREP(cn) == name) return 1;
}
}
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) != -1) 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/>
* "first" is actually inaccurate, since there is no defined order in mappings,
* so we select the child, that is not an error
*/
getiqchild(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;
}