mirror of
git://git.psyced.org/git/psyced
synced 2024-08-15 03:25:10 +00:00
574 lines
17 KiB
C
574 lines
17 KiB
C
// $Id: gateway.c,v 1.461 2008/10/22 16:35:59 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 "uniform.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 ))
|
|
// sometimes we get complete presence packets in the socket close
|
|
// remainder. probably broken xmpp implementations, let's try and
|
|
// do the best we can with it by forwarding stuff to feed().
|
|
if (remainder && strlen(remainder)) feed(remainder);
|
|
if (objectp(active)) active -> removeGateway(streamid);
|
|
#ifdef _flag_log_sockets_XMPP
|
|
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
|
|
// similar code in other files
|
|
tls_logon(result) {
|
|
if (result == 0) {
|
|
certinfo = tls_certificate(ME, 0);
|
|
P3(("%O tls_logon fetching certificate: %O\n", ME, certinfo))
|
|
# ifdef ERR_TLS_NOT_DETECTED
|
|
} else if (result == ERR_TLS_NOT_DETECTED) {
|
|
// just go on without encryption
|
|
# endif
|
|
} else if (result < 0) {
|
|
P1(("%O TLS error %d: %O\n", ME, result, tls_error(result)))
|
|
QUIT
|
|
} 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
|
|
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") {
|
|
emitraw("</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>
|
|
|
|
emitraw("<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(_host_XMPP) into the localhost mapping pleeeease
|
|
//if (target != NAMEPREP(_host_XMPP)) {
|
|
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(_host_XMPP),
|
|
"_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: == _host_XMPP)
|
|
* 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()) {
|
|
emitraw("<proceed xmlns='" NS_XMPP "xmpp-tls'/>");
|
|
# if __EFUN_DEFINED__(tls_want_peer_certificate)
|
|
tls_want_peer_certificate(ME);
|
|
# endif
|
|
tls_init_connection(ME, #'tls_logon);
|
|
return;
|
|
}
|
|
#endif
|
|
emitraw("<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) {
|
|
emitraw("<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",
|
|
_host_XMPP, 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='" _host_XMPP "' ";
|
|
}
|
|
if (node["@from"]) {
|
|
packet += "to='" + node["@from"] + "' ";
|
|
}
|
|
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
|
|
# ifndef _flag_disable_authentication_digest_MD5
|
|
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) : _host_XMPP;
|
|
sendmsg(origin, mc, data, vars);
|
|
}
|