mirror of
git://git.psyced.org/git/psyced
synced 2024-08-15 03:25:10 +00:00
514 lines
14 KiB
C
514 lines
14 KiB
C
// $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 <sys/tls.h>
|
|
|
|
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("</stream:stream>");
|
|
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("<iq type='result' id='"+ id +"'>"
|
|
"<bind xmlns='" NS_XMPP "xmpp-bind'><jid>"+
|
|
nick +"@" SERVER_HOST "/"+ resource +"</jid>"
|
|
"</bind></iq>");
|
|
return 0;
|
|
} else if (node["/session"]) {
|
|
unless(user) return 0; // what then?
|
|
emit("<iq type='result' id='"+ id +"' from='"
|
|
SERVER_HOST "'/>");
|
|
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(REGISTERED_USERS_ONLY) || defined(_flag_disable_registration_XMPP)
|
|
// super dirty.. this should all be in textdb
|
|
packet = sprintf("<iq type='result' id='%s'>"
|
|
"<query xmlns='jabber:iq:register'/>"
|
|
"<error code='501>Registration by XMPP not permitted.</error></iq>",
|
|
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/></query></iq>",
|
|
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
|
|
}
|
|
unless (user -> isNewbie()) {
|
|
// already registered to someone else
|
|
packet += "</query><error code='406' type='cancel'>"
|
|
"<conflict xmlns='" NS_XMPP "xmpp-stanzas'/>"
|
|
"</error></iq>";
|
|
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 += "</query><error code='406' type='modify'>"
|
|
"<not-acceptable xmlns='" NS_XMPP "xmpp-stanzas'/>"
|
|
"</error></iq>";
|
|
emit(packet);
|
|
// QUIT
|
|
} else {
|
|
#if defined(REGISTERED_USERS_ONLY) || 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("<iq type='result' id='%s'/>", id));
|
|
#endif
|
|
}
|
|
user = 0;
|
|
}
|
|
}
|
|
break;
|
|
case "starttls":
|
|
#if __EFUN_DEFINED__(tls_available)
|
|
if (tls_available()) {
|
|
emitraw("<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
|
|
emitraw("<failure xmlns='" NS_XMPP "xmpp-tls'/></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];
|
|
emitraw("<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];
|
|
emitraw("<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(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(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;
|
|
emitraw("<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;
|
|
emitraw("<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 {
|
|
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 = "<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'>"
|
|
#ifndef _flag_disable_authentication_digest_MD5
|
|
"<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(certinfo) && certinfo[0] == 0
|
|
&& certificate_check_jabbername(0, certinfo)) {
|
|
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, certinfo) {
|
|
// plan: prefer subjectAltName:id-on-xmppAddr,
|
|
// but allow email (1.2.840.113549.1.9.1)
|
|
// and subjectAltName:rfc822Name
|
|
return 0;
|
|
}
|