X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Fjabber%2Fserv_xmpp.c;h=3b448fbf491eec93ea7b9e8755ef9ca6a664ddcb;hb=cf934431c7c8c1091b38c0b374f6a3b9293841ca;hp=57b9b3d4b3995bfa8fa9aebccce970b64a18134e;hpb=55f0266810a4e017993a89fa3b8a48183aeaa4e6;p=citadel.git diff --git a/citadel/modules/jabber/serv_xmpp.c b/citadel/modules/jabber/serv_xmpp.c index 57b9b3d4b..3b448fbf4 100644 --- a/citadel/modules/jabber/serv_xmpp.c +++ b/citadel/modules/jabber/serv_xmpp.c @@ -1,5 +1,5 @@ /* - * $Id: $ + * $Id$ * * XMPP (Jabber) service for the Citadel system * Copyright (c) 2007 by Art Cancro @@ -33,6 +33,7 @@ #include #include #include +#include #include "citadel.h" #include "server.h" #include "citserver.h" @@ -46,18 +47,16 @@ #include "internet_addressing.h" #include "md5.h" #include "ctdl_module.h" - -#ifdef HAVE_EXPAT -#include #include "serv_xmpp.h" +struct xmpp_event *xmpp_queue = NULL; /* We have just received a tag from the client, so send them ours */ void xmpp_stream_start(void *data, const char *supplied_el, const char **attr) { + lprintf(CTDL_DEBUG, "New XMPP stream.\n"); - lprintf(CTDL_DEBUG, "New stream detected.\n"); while (*attr) { if (!strcasecmp(attr[0], "to")) { safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name); @@ -77,17 +76,28 @@ void xmpp_stream_start(void *data, const char *supplied_el, const char **attr) /* The features of this stream are... */ cprintf(""); - /* Binding... */ - cprintf(""); +#ifdef HAVE_OPENSSL_XXXX_COMMENTED_OUT + /* TLS encryption (but only if it isn't already active) */ + if (!CC->redirect_ssl) { + cprintf(""); + } +#endif - /* Sessions... */ - cprintf(""); + if (!CC->logged_in) { + /* If we're not logged in yet, offer SASL as our feature set */ + xmpp_output_auth_mechs(); + + /* Also offer non-SASL authentication */ + cprintf(""); + } - /* A really bad SASL implementation... */ - xmpp_output_auth_mechs(); + /* Offer binding and sessions as part of our feature set */ + cprintf(""); + cprintf(""); - /* ...and the ability to close XML tags using angle brackets. We should patent this. */ cprintf(""); + + CC->is_async = 1; /* XMPP sessions are inherently async-capable */ } @@ -112,16 +122,28 @@ void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) { xmpp_stream_start(data, supplied_el, attr); } + else if (!strcasecmp(el, "query")) { + XMPP->iq_query_xmlns[0] = 0; + safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns); + } + + else if (!strcasecmp(el, "bind")) { + XMPP->bind_requested = 1; + } + else if (!strcasecmp(el, "iq")) { - const char *iqtype = NULL; - const char *iqid = NULL; for (i=0; attr[i] != NULL; i+=2) { - if (!strcasecmp(attr[i], "type")) iqtype = attr[i+1]; - if (!strcasecmp(attr[i], "id")) iqid = attr[i+1]; - } - if ((iqtype != NULL) && (iqid != NULL)) { - if (!strcasecmp(iqtype, "set")) { - safestrncpy(XMPP->iq_bind_id, iqid, sizeof XMPP->iq_bind_id); + if (!strcasecmp(attr[i], "type")) { + safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type); + } + else if (!strcasecmp(attr[i], "id")) { + safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id); + } + else if (!strcasecmp(attr[i], "from")) { + safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from); + } + else if (!strcasecmp(attr[i], "to")) { + safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to); } } } @@ -134,6 +156,18 @@ void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) { } } } + + else if (!strcasecmp(el, "message")) { + for (i=0; attr[i] != NULL; i+=2) { + if (!strcasecmp(attr[i], "to")) { + safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to); + } + } + } + + else if (!strcasecmp(el, "html")) { + ++XMPP->html_tag_level; + } } @@ -157,43 +191,113 @@ void xmpp_xml_end(void *data, const char *supplied_el) { if (XMPP->chardata_len > 0) { safestrncpy(XMPP->iq_client_resource, XMPP->chardata, sizeof XMPP->iq_client_resource); + striplt(XMPP->iq_client_resource); + } + } + + if (!strcasecmp(el, "username")) { /* NON SASL ONLY */ + if (XMPP->chardata_len > 0) { + safestrncpy(XMPP->iq_client_username, XMPP->chardata, + sizeof XMPP->iq_client_username); + striplt(XMPP->iq_client_username); + } + } + + if (!strcasecmp(el, "password")) { /* NON SASL ONLY */ + if (XMPP->chardata_len > 0) { + safestrncpy(XMPP->iq_client_password, XMPP->chardata, + sizeof XMPP->iq_client_password); + striplt(XMPP->iq_client_password); } } else if (!strcasecmp(el, "iq")) { + /* + * iq type="get" (handle queries) + */ + if (!strcasecmp(XMPP->iq_type, "get")) { + + /* + * Query on a namespace + */ + if (!IsEmptyStr(XMPP->iq_query_xmlns)) { + xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from, + XMPP->iq_to, XMPP->iq_query_xmlns); + } - /* If this stanza was a "bind" attempt, process it ... */ + /* + * Unknown queries ... return the XML equivalent of a blank stare + */ + else { + cprintf("", XMPP->iq_id); + cprintf(""); + } + } - if ( (!IsEmptyStr(XMPP->iq_bind_id)) && (!IsEmptyStr(XMPP->iq_client_resource)) ) { + /* + * Non SASL authentication + */ + else if ( + (!strcasecmp(XMPP->iq_type, "set")) + && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query")) + ) { + + jabber_non_sasl_authenticate( + XMPP->iq_id, + XMPP->iq_client_username, + XMPP->iq_client_password, + XMPP->iq_client_resource + ); + } + + /* + * If this stanza was a "bind" attempt, process it ... + */ + else if ( + (XMPP->bind_requested) + && (!IsEmptyStr(XMPP->iq_id)) + && (!IsEmptyStr(XMPP->iq_client_resource)) + && (CC->logged_in) + ) { /* Generate the "full JID" of the client resource */ snprintf(XMPP->client_jid, sizeof XMPP->client_jid, - "%d@%s/%s", - CC->cs_pid, - config.c_fqdn, + "%s/%s", + CC->cs_inet_email, XMPP->iq_client_resource ); /* Tell the client what its JID is */ - cprintf("", XMPP->iq_bind_id); + cprintf("", XMPP->iq_id); cprintf(""); cprintf("%s", XMPP->client_jid); cprintf(""); cprintf(""); } + else if (XMPP->iq_session) { + cprintf("", XMPP->iq_id); + cprintf(""); + } + else { - cprintf("", XMPP->iq_bind_id); + cprintf("", XMPP->iq_id); cprintf(""); cprintf(""); } /* Now clear these fields out so they don't get used by a future stanza */ - XMPP->iq_bind_id[0] = 0; + XMPP->iq_id[0] = 0; + XMPP->iq_from[0] = 0; + XMPP->iq_to[0] = 0; + XMPP->iq_type[0] = 0; XMPP->iq_client_resource[0] = 0; + XMPP->iq_session = 0; + XMPP->iq_query_xmlns[0] = 0; + XMPP->bind_requested = 0; } else if (!strcasecmp(el, "auth")) { @@ -205,6 +309,48 @@ void xmpp_xml_end(void *data, const char *supplied_el) { XMPP->sasl_auth_mech[0] = 0; } + else if (!strcasecmp(el, "session")) { + XMPP->iq_session = 1; + } + + else if (!strcasecmp(el, "presence")) { + + /* Respond to a update by firing back with presence information + * on the entire wholist. Check this assumption, it's probably wrong. + */ + jabber_wholist_presence_dump(); + } + + else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) { + if (XMPP->message_body != NULL) { + free(XMPP->message_body); + XMPP->message_body = NULL; + } + if (XMPP->chardata_len > 0) { + XMPP->message_body = strdup(XMPP->chardata); + } + } + + else if (!strcasecmp(el, "message")) { + jabber_send_message(XMPP->message_to, XMPP->message_body); + XMPP->html_tag_level = 0; + } + + else if (!strcasecmp(el, "html")) { + --XMPP->html_tag_level; + } + + else if (!strcasecmp(el, "starttls")) { +#ifdef HAVE_OPENSSL + cprintf(""); + CtdlModuleStartCryptoMsgs(NULL, NULL, NULL); + if (!CC->redirect_ssl) CC->kill_me = 1; +#else + cprintf(""); + CC->kill_me = 1; +#endif + } + XMPP->chardata_len = 0; if (XMPP->chardata_alloc > 0) { XMPP->chardata[0] = 0; @@ -244,6 +390,9 @@ void xmpp_cleanup_function(void) { XMPP->chardata = NULL; XMPP->chardata_len = 0; XMPP->chardata_alloc = 0; + if (XMPP->message_body != NULL) { + free(XMPP->message_body); + } } XML_ParserFree(XMPP->xp); free(XMPP); @@ -258,6 +407,7 @@ void xmpp_greeting(void) { strcpy(CC->cs_clientname, "Jabber session"); CC->session_specific_data = malloc(sizeof(struct citxmpp)); memset(XMPP, 0, sizeof(struct citxmpp)); + XMPP->last_event_processed = queue_event_seq; /* XMPP does not use a greeting, but we still have to initialize some things. */ @@ -295,27 +445,50 @@ void xmpp_command_loop(void) { XML_Parse(XMPP->xp, cmdbuf, 1, 0); } -const char *CitadelServiceXMPP="XMPP"; -#endif /* HAVE_EXPAT */ +/* + * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas) + */ +void xmpp_async_loop(void) { + xmpp_process_events(); + jabber_output_incoming_messages(); +} + + +/* + * Login hook for XMPP sessions + */ +void xmpp_login_hook(void) { + xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_inet_email); +} + + +/* + * Logout hook for XMPP sessions + */ +void xmpp_logout_hook(void) { + xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_inet_email); +} + + +const char *CitadelServiceXMPP="XMPP"; CTDL_MODULE_INIT(jabber) { -#ifdef HAVE_EXPAT if (!threading) { - /* CtdlRegisterServiceHook(config.c_xmpp_port, FIXME */ - CtdlRegisterServiceHook(5222, + CtdlRegisterServiceHook(config.c_xmpp_c2s_port, NULL, xmpp_greeting, xmpp_command_loop, - NULL, + xmpp_async_loop, CitadelServiceXMPP); CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP); - #else - lprintf(CTDL_INFO, "This server is missing the Expat XML parser. Jabber service will be disabled.\n"); -#endif + CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN); + CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT); + CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH); + CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH); } /* return our Subversion id for the Log */ - return "$Id: $"; + return "$Id$"; }