// $Id: server.c,v 1.159 2008/10/01 10:59:24 lynx Exp $ // vim:syntax=lpc:ts=8 #include "jabber.h" #include "server.h" // inherits net/server #include "person.h" // find_person #include "uniform.h" #include volatile string authtag; volatile string resource; volatile string streamid; volatile string pass; volatile string language; int reprompt; volatile string sasluser; volatile string saslchallenge; #ifdef __TLS__ volatile mixed certinfo; #endif qScheme() { return "jabber"; } // qName() { } // we need that in common.c - probably no more void create() { unless (clonep()) return; // not multilingual yet - TODO sTextPath(0, 0, "jabber"); } #ifdef __TLS__ // similar code in other files tls_logon(result) { if (result == 0) { # ifdef WANT_S2S_SASL /* hey wait.. this is not S2S here!? */ certinfo = tls_certificate(ME, 0); # endif P3(("%O tls_logon fetching certificate: %O\n", ME, certinfo)) # ifdef ERR_TLS_NOT_DETECTED } else if (result == ERR_TLS_NOT_DETECTED) { // no encryption. no problem. # endif } else if (result < 0) { P1(("%O TLS error %d: %O\n", ME, result, tls_error(result))) QUIT // don't fall back to plaintext instead } else { P0(("tls_logon with result > 0?!?!\n")) // should not happen } } #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" : authtag || "", "_nick" : nick, "_resource" : resource ]) ); emitraw(""); QUIT // should we really close the stream here? 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" : authtag || "" ]) ); } return 1; } logon(a) { #ifdef _flag_log_sockets_XMPP 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(authtag); user->sResource(resource); return ::userLogon(); } 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...); } 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 authtag = -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 = ""; // why an empty string? explanation needed emit("" ""+ nick +"@" SERVER_HOST "/"+ resource +"" ""); return 0; } else if (node["/session"]) { unless(user) return 0; // what then? emit(""); user -> vSet("language", language); return morph(); } switch (node["/query"]["@xmlns"]) { // old-school style.. for clients that don't like SASL, like kopete, jabbin case "jabber:iq:auth": authtag = id; if (node["@type"] == "get"){ // hello(nick) ? w("_query_password", 0, ([ "_nick": nick, "_tag_reply": authtag || "" ]), ""); } 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; #if defined(_flag_disable_unauthenticated_users_XMPP) || defined(_flag_disable_registration_XMPP) // super dirty.. this should all be in textdb packet = sprintf("" "" "" "" "You dont even need to register, " "this is psyced. Manual at http://help.pages.de" "", id); #endif emit(packet); } else if (node["@type"] == "set"){ string packet; packet = sprintf("" "", 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 } unless (user -> isNewbie()) { // already registered to someone else packet += "" "" ""; emit(packet); // elmex says, disconnect is wrong here: // QUIT // it should at least close the stream } else unless ((t = helper["/username"]) && t[Cdata] && (t = helper["/password"]) && t[Cdata]) { packet += "" "" ""; emit(packet); // QUIT } else { #if defined(_flag_disable_unauthenticated_users_XMPP) || defined(_flag_disable_registration_XMPP) // TODO: generate some error as above #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("", id)); #endif } user = 0; } } break; case "starttls": #if __EFUN_DEFINED__(tls_available) if (tls_available()) { emitraw(""); // 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 emitraw(""); destruct(ME); #endif break; case "auth": switch (node["@mechanism"]) { case "DIGEST-MD5": // TODO: time-based nonce saslchallenge = RANDHEXSTRING; emit("" + encode_base64(sprintf("realm=\"%s\",nonce=\"%s\"," "qop=\"auth\",charset=utf-8," "algorithm=md5-sess", SERVER_HOST, saslchallenge) ) + ""); 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]; emitraw(""); } 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]; emitraw(""); #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(certinfo) && certinfo[0] == 0 && certinfo["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 == certinfo["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(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; emitraw(""); } } # else SASL_ERROR("invalid-mechanism") QUIT # endif break; #endif #ifndef _flag_disable_unauthenticated_users_XMPP 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; emitraw(""); } 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("" + encode_base64("rspauth=" + result) + ""); } else { P0(("digest md5 failure: %O\n", creds)) sasluser = 0; SASL_ERROR("invalid-authzid") // why do we get here? QUIT } return 0; // ignored, but avoids a warning }); 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 */ P0(("jabber/server:jabberMsg default case\n")) } // return ::jabberMsg(from, cmd, args, data, all); return 0; } open_stream(XMLNode node) { string features; string header; P2(("%O open_stream from %O\n", ME, query_ip_name())) 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 = ""; #endif if (sasluser) { features += ""; features += ""; } else { features += "" #ifndef _flag_disable_authentication_digest_MD5 "DIGEST-MD5" #endif "PLAIN"; #ifndef _flag_disable_unauthenticated_users_XMPP // sasl anonymous "ANONYMOUS"; #endif // here it makes sense to use check_jabbername // but that is currently unused anyway #if __EFUN_DEFINED__(tls_available) if (tls_available() && tls_query_connection_state(ME) > 0 && mappingp(certinfo) && certinfo[0] == 0 // why do we use the old one here? && certificate_check_jabbername(0, certinfo)) { features += "EXTERNAL"; } #endif features += ""; features += ""; #ifndef _flag_disable_registration_XMPP features += ""; #endif } 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, certinfo) { // plan: prefer subjectAltName:id-on-xmppAddr, // but allow email (1.2.840.113549.1.9.1) // and subjectAltName:rfc822Name // FIXME: do something useful here... return 0; }