"Fossies" - the Fresh Open Source Software Archive

Member "citadel/modules/xmpp/serv_xmpp.c" (5 Jun 2021, 18244 Bytes) of package /linux/www/citadel.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "serv_xmpp.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.01_vs_902.

    1 /*
    2  * XMPP (Jabber) service for the Citadel system
    3  * Copyright (c) 2007-2021 by Art Cancro and citadel.org
    4  *
    5  * This program is open source software; you can redistribute it and/or modify
    6  * it under the terms of the GNU General Public License as published by
    7  * the Free Software Foundation; either version 3 of the License, or
    8  * (at your option) any later version.
    9  *
   10  * This program is distributed in the hope that it will be useful,
   11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13  * GNU General Public License for more details.
   14  *
   15  * You should have received a copy of the GNU General Public License
   16  * along with this program; if not, write to the Free Software
   17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   18  */
   19 
   20 #include "sysdep.h"
   21 #include <stdlib.h>
   22 #include <unistd.h>
   23 #include <stdio.h>
   24 #include <fcntl.h>
   25 #include <signal.h>
   26 #include <pwd.h>
   27 #include <errno.h>
   28 #include <sys/types.h>
   29 #include <time.h>
   30 #include <sys/wait.h>
   31 #include <string.h>
   32 #include <limits.h>
   33 #include <ctype.h>
   34 #include <libcitadel.h>
   35 #include <expat.h>
   36 #include "citadel.h"
   37 #include "server.h"
   38 #include "citserver.h"
   39 #include "support.h"
   40 #include "config.h"
   41 #include "user_ops.h"
   42 #include "database.h"
   43 #include "msgbase.h"
   44 #include "internet_addressing.h"
   45 #include "ctdl_module.h"
   46 #include "serv_xmpp.h"
   47 
   48 /* uncomment for more verbosity - it will log all received XML tags */
   49 // #define XMPP_XML_DEBUG
   50 
   51 /* XML_StopParser is present in expat 2.x */
   52 #if XML_MAJOR_VERSION > 1
   53 #define HAVE_XML_STOPPARSER
   54 #endif
   55 
   56 struct xmpp_event *xmpp_queue = NULL;
   57 
   58 #ifdef HAVE_XML_STOPPARSER
   59 /* Stop the parser if an entity declaration is hit. */
   60 static void xmpp_entity_declaration(void *userData, const XML_Char *entityName,
   61                 int is_parameter_entity, const XML_Char *value,
   62                 int value_length, const XML_Char *base,
   63                 const XML_Char *systemId, const XML_Char *publicId,
   64                 const XML_Char *notationName
   65 ) {
   66     syslog(LOG_WARNING, "xmpp: illegal entity declaration encountered; stopping parser.");
   67     XML_StopParser(XMPP->xp, XML_FALSE);
   68 }
   69 #endif
   70 
   71 
   72 /*
   73  * support function for xmlesc() which helps with UTF-8 strings
   74  */
   75 static inline int Ctdl_GetUtf8SequenceLength(const char *CharS, const char *CharE)
   76 {
   77     int n = 0;
   78         unsigned char test = (1<<7);
   79 
   80     if ((*CharS & 0xC0) != 0xC0) {
   81         return 1;
   82     }
   83 
   84     while ((n < 8) && ((test & ((unsigned char)*CharS)) != 0))  {
   85         test = test >> 1;
   86         n ++;
   87     }
   88     if ((n > 6) || ((CharE - CharS) < n)) {
   89         n = 0;
   90     }
   91     return n;
   92 }
   93 
   94 
   95 /*
   96  * Given a source string and a target buffer, returns the string
   97  * properly escaped for insertion into an XML stream.  Returns a
   98  * pointer to the target buffer for convenience.
   99  */
  100 char *xmlesc(char *buf, char *str, int bufsiz)
  101 {
  102     int IsUtf8Sequence;
  103     char *ptr, *pche;
  104     unsigned char ch;
  105     int inlen;
  106     int len = 0;
  107 
  108     if (!buf) return(NULL);
  109     buf[0] = 0;
  110     len = 0;
  111     if (!str) {
  112         return(buf);
  113     }
  114     inlen = strlen(str);
  115     pche = str + inlen;
  116 
  117     for (ptr=str; *ptr; ptr++) {
  118         ch = *ptr;
  119         if (ch == '<') {
  120             strcpy(&buf[len], "&lt;");
  121             len += 4;
  122         }
  123         else if (ch == '>') {
  124             strcpy(&buf[len], "&gt;");
  125             len += 4;
  126         }
  127         else if (ch == '&') {
  128             strcpy(&buf[len], "&amp;");
  129             len += 5;
  130         }
  131         else if ((ch >= 0x20) && (ch <= 0x7F)) {
  132             buf[len++] = ch;
  133             buf[len] = 0;
  134         }
  135         else if (ch < 0x20) {
  136             buf[len++] = '_';   // we probably shouldn't be doing this
  137             buf[len] = 0;
  138         }
  139         else {
  140             IsUtf8Sequence =  Ctdl_GetUtf8SequenceLength(ptr, pche);
  141             if (IsUtf8Sequence)
  142             {
  143                 while ((IsUtf8Sequence > 0) && 
  144                        (ptr < pche))
  145                 {
  146                     buf[len] = *ptr;
  147                     ptr ++;
  148                     --IsUtf8Sequence;
  149                 }
  150             }
  151             else
  152             {
  153                 char oct[10];
  154                 sprintf(oct, "&#%o;", ch);
  155                 strcpy(&buf[len], oct);
  156                 len += strlen(oct);
  157             }
  158         }
  159         if ((len + 6) > bufsiz) {
  160             return(buf);
  161         }
  162     }
  163     return(buf);
  164 }
  165 
  166 
  167 /*
  168  * We have just received a <stream> tag from the client, so send them ours
  169  */
  170 void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
  171 {
  172     char xmlbuf[256];
  173 
  174     while (*attr) {
  175         if (!strcasecmp(attr[0], "to")) {
  176             safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name);
  177         }
  178         attr += 2;
  179     }
  180 
  181     cprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  182     cprintf("<stream:stream ");
  183     cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->server_name, sizeof xmlbuf));
  184     cprintf("id=\"%08x\" ", CC->cs_pid);
  185     cprintf("version=\"1.0\" ");
  186     cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
  187     cprintf("xmlns=\"jabber:client\">");
  188 
  189     /* The features of this stream are... */
  190     cprintf("<stream:features>");
  191 
  192     /*
  193      * TLS encryption (but only if it isn't already active)
  194      */ 
  195 #ifdef HAVE_OPENSSL
  196     if (!CC->redirect_ssl) {
  197         cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
  198     }
  199 #endif
  200 
  201     if (!CC->logged_in) {
  202         /* If we're not logged in yet, offer SASL as our feature set */
  203         xmpp_output_auth_mechs();
  204 
  205         /* Also offer non-SASL authentication */
  206         cprintf("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
  207     }
  208 
  209     /* Offer binding and sessions as part of our feature set */
  210     cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
  211     cprintf("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
  212     cprintf("</stream:features>");
  213 
  214     CC->is_async = 1;       /* XMPP sessions are inherently async-capable */
  215 }
  216 
  217 
  218 void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) {
  219     char el[256];
  220     char *sep = NULL;
  221     int i;
  222 
  223     /* Create a version of the element with the namespace removed.
  224      * Now we can access "el" or "supplied_el" depending on whether we want to see the whole namespace.
  225      */
  226     safestrncpy(el, supplied_el, sizeof el);
  227     while (sep = strchr(el, ':'), sep) {
  228         strcpy(el, ++sep);
  229     }
  230 
  231 #ifdef XMPP_XML_DEBUG
  232     syslog(LOG_DEBUG, "xmpp: ELEMENT START: <%s>", el);
  233     for (i=0; attr[i] != NULL; i+=2) {
  234         syslog(LOG_DEBUG, "xmpp: Attribute '%s' = '%s'", attr[i], attr[i+1]);
  235     }
  236 #endif
  237 
  238     if (!strcasecmp(el, "stream")) {
  239         xmpp_stream_start(data, supplied_el, attr);
  240     }
  241 
  242     else if (!strcasecmp(el, "query")) {
  243         XMPP->iq_query_xmlns[0] = 0;
  244         safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns);
  245     }
  246 
  247     else if (!strcasecmp(el, "bind")) {
  248         XMPP->bind_requested = 1;
  249     }
  250 
  251     else if (!strcasecmp(el, "iq")) {
  252         for (i=0; attr[i] != NULL; i+=2) {
  253             if (!strcasecmp(attr[i], "type")) {
  254                 safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type);
  255             }
  256             else if (!strcasecmp(attr[i], "id")) {
  257                 safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id);
  258             }
  259             else if (!strcasecmp(attr[i], "from")) {
  260                 safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from);
  261             }
  262             else if (!strcasecmp(attr[i], "to")) {
  263                 safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to);
  264             }
  265         }
  266     }
  267 
  268     else if (!strcasecmp(el, "auth")) {
  269         XMPP->sasl_auth_mech[0] = 0;
  270         for (i=0; attr[i] != NULL; i+=2) {
  271             if (!strcasecmp(attr[i], "mechanism")) {
  272                 safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech);
  273             }
  274         }
  275     }
  276 
  277     else if (!strcasecmp(el, "message")) {
  278         for (i=0; attr[i] != NULL; i+=2) {
  279             if (!strcasecmp(attr[i], "to")) {
  280                 safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to);
  281             }
  282         }
  283     }
  284 
  285     else if (!strcasecmp(el, "html")) {
  286         ++XMPP->html_tag_level;
  287     }
  288 }
  289 
  290 
  291 void xmpp_xml_end(void *data, const char *supplied_el) {
  292     char el[256];
  293     char *sep = NULL;
  294     char xmlbuf[256];
  295 
  296     /* Create a version of the element with the namespace removed.
  297      * Now we can access "el" or "supplied_el" depending on whether we want to see the whole namespace.
  298      */
  299     safestrncpy(el, supplied_el, sizeof el);
  300     while (sep = strchr(el, ':'), sep) {
  301         strcpy(el, ++sep);
  302     }
  303 
  304 #ifdef XMPP_XML_DEBUG
  305     syslog(LOG_DEBUG, "xmpp: ELEMENT END  : <%s>", el);
  306     if (XMPP->chardata_len > 0) {
  307         syslog(LOG_DEBUG, "xmpp: chardata: %s", XMPP->chardata);
  308     }
  309 #endif
  310 
  311     if (!strcasecmp(el, "resource")) {
  312         if (XMPP->chardata_len > 0) {
  313             safestrncpy(XMPP->iq_client_resource, XMPP->chardata, sizeof XMPP->iq_client_resource);
  314             striplt(XMPP->iq_client_resource);
  315         }
  316     }
  317 
  318     else if (!strcasecmp(el, "username")) {     /* NON SASL ONLY */
  319         if (XMPP->chardata_len > 0) {
  320             safestrncpy(XMPP->iq_client_username, XMPP->chardata, sizeof XMPP->iq_client_username);
  321             striplt(XMPP->iq_client_username);
  322         }
  323     }
  324 
  325     else if (!strcasecmp(el, "password")) {     /* NON SASL ONLY */
  326         if (XMPP->chardata_len > 0) {
  327             safestrncpy(XMPP->iq_client_password, XMPP->chardata, sizeof XMPP->iq_client_password);
  328             striplt(XMPP->iq_client_password);
  329         }
  330     }
  331 
  332     else if (!strcasecmp(el, "iq")) {
  333 
  334         /*
  335          * iq type="get" (handle queries)
  336          */
  337         if (!strcasecmp(XMPP->iq_type, "get")) {
  338 
  339             /*
  340              * Query on a namespace
  341              */
  342             if (!IsEmptyStr(XMPP->iq_query_xmlns)) {
  343                 xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from, XMPP->iq_to, XMPP->iq_query_xmlns);
  344             }
  345 
  346             /*
  347              * ping ( http://xmpp.org/extensions/xep-0199.html )
  348              */
  349             else if (XMPP->ping_requested) {
  350                 cprintf("<iq type=\"result\" ");
  351                 if (!IsEmptyStr(XMPP->iq_from)) {
  352                     cprintf("to=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
  353                 }
  354                 if (!IsEmptyStr(XMPP->iq_to)) {
  355                     cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_to, sizeof xmlbuf));
  356                 }
  357                 cprintf("id=\"%s\"/>", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
  358             }
  359 
  360             /*
  361              * Client is requesting its own vCard
  362              * (If we make this more elaborate, move it to a separate function)
  363              */
  364             else if (XMPP->iq_vcard) {
  365                 cprintf("<iq type=\"result\" id=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
  366                 cprintf("to=\"%s\">", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
  367                 cprintf("<vCard xmlns=\"vcard-temp\">");
  368                 cprintf("<fn>%s</fn>", xmlesc(xmlbuf, CC->user.fullname, sizeof xmlbuf));
  369                 cprintf("<nickname>%s</nickname>", xmlesc(xmlbuf, CC->user.fullname, sizeof xmlbuf));
  370                 cprintf("</vCard>");
  371                 cprintf("</iq>");
  372             }
  373 
  374             /*
  375              * Unknown query ... return the XML equivalent of a blank stare
  376              */
  377             else {
  378                 syslog(LOG_DEBUG, "xmpp: Unknown query <%s> - returning <service-unavailable/>", el);
  379                 cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
  380                 cprintf("<error code=\"503\" type=\"cancel\">"
  381                     "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
  382                     "</error>"
  383                 );
  384                 cprintf("</iq>");
  385             }
  386         }
  387 
  388         /*
  389          * Non SASL authentication
  390          */
  391         else if (
  392             (!strcasecmp(XMPP->iq_type, "set"))
  393             && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query"))
  394         ) {
  395             xmpp_non_sasl_authenticate(XMPP->iq_id, XMPP->iq_client_username, XMPP->iq_client_password);
  396         }   
  397 
  398         /*
  399          * If this <iq> stanza was a "bind" attempt, process it ...
  400          */
  401         else if (
  402             (XMPP->bind_requested)
  403             && (!IsEmptyStr(XMPP->iq_id))
  404             && (CC->logged_in)
  405         ) {
  406             /* If the client has not specified a client resource, generate one */
  407             if (IsEmptyStr(XMPP->iq_client_resource)) {
  408                 snprintf(XMPP->iq_client_resource, sizeof XMPP->iq_client_resource, "%d", CC->cs_pid);
  409             }
  410 
  411             /* Generate the "full JID" of the client (user@host/resource) */
  412             snprintf(XMPP->client_jid, sizeof XMPP->client_jid, "%s/%s", CC->cs_principal_id, XMPP->iq_client_resource);
  413 
  414             /* Tell the client what its JID is */
  415             cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
  416             cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
  417             cprintf("<jid>%s</jid>", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf));
  418             cprintf("</bind>");
  419             cprintf("</iq>");
  420         }
  421 
  422         else if (XMPP->iq_session) {
  423             cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
  424             cprintf("</iq>");
  425         }
  426 
  427         else {
  428             cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
  429             cprintf("<error>Don't know how to do '%s'!</error>", xmlesc(xmlbuf, XMPP->iq_type, sizeof xmlbuf));
  430             cprintf("</iq>");
  431             syslog(LOG_DEBUG, "XMPP: don't know how to do iq_type='%s' with iq_query_xmlns='%s'", XMPP->iq_type, XMPP->iq_query_xmlns);
  432         }
  433 
  434         /* Now clear these fields out so they don't get used by a future stanza */
  435         XMPP->iq_id[0] = 0;
  436         XMPP->iq_from[0] = 0;
  437         XMPP->iq_to[0] = 0;
  438         XMPP->iq_type[0] = 0;
  439         XMPP->iq_client_resource[0] = 0;
  440         XMPP->iq_session = 0;
  441         XMPP->iq_vcard = 0;
  442         XMPP->iq_query_xmlns[0] = 0;
  443         XMPP->bind_requested = 0;
  444         XMPP->ping_requested = 0;
  445     }
  446 
  447     else if (!strcasecmp(el, "auth")) {
  448         /* Try to authenticate (this function is responsible for the output stanza) */
  449         xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") );
  450 
  451         /* Now clear these fields out so they don't get used by a future stanza */
  452         XMPP->sasl_auth_mech[0] = 0;
  453     }
  454 
  455     else if (!strcasecmp(el, "session")) {
  456         XMPP->iq_session = 1;
  457     }
  458 
  459     else if (!strcasecmp(supplied_el, "vcard-temp:vCard")) {
  460         XMPP->iq_vcard = 1;
  461     }
  462 
  463     else if (!strcasecmp(el, "presence")) {
  464         /* Respond to a <presence> update by firing back with presence information
  465          * on the entire wholist.  Check this assumption, it's probably wrong.
  466          */
  467         xmpp_wholist_presence_dump();
  468     }
  469 
  470     else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) {
  471         if (XMPP->message_body != NULL) {
  472             free(XMPP->message_body);
  473             XMPP->message_body = NULL;
  474         }
  475         if (XMPP->chardata_len > 0) {
  476             XMPP->message_body = strdup(XMPP->chardata);
  477         }
  478     }
  479 
  480     else if (!strcasecmp(el, "message")) {
  481         xmpp_send_message(XMPP->message_to, XMPP->message_body);
  482         XMPP->html_tag_level = 0;
  483     }
  484 
  485     else if (!strcasecmp(el, "html")) {
  486         --XMPP->html_tag_level;
  487     }
  488 
  489     else if (!strcasecmp(el, "starttls")) {
  490 #ifdef HAVE_OPENSSL
  491         cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
  492         CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
  493         if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
  494 #else
  495         cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
  496         CC->kill_me = KILLME_NO_CRYPTO;
  497 #endif
  498     }
  499 
  500     else if (!strcasecmp(el, "ping")) {
  501         XMPP->ping_requested = 1;
  502     }
  503 
  504     else if (!strcasecmp(el, "stream")) {
  505         syslog(LOG_DEBUG, "xmpp: client shut down their stream");
  506         xmpp_massacre_roster();
  507         cprintf("</stream>\n");
  508         CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
  509     }
  510 
  511     else if (!strcasecmp(el, "query")) {
  512         /* already processed , no further action needed here */
  513     }
  514 
  515     else if (!strcasecmp(el, "bind")) {
  516         /* already processed , no further action needed here */
  517     }
  518 
  519     else {
  520         syslog(LOG_DEBUG, "xmpp: ignoring unknown tag <%s>", el);
  521     }
  522 
  523     XMPP->chardata_len = 0;
  524     if (XMPP->chardata_alloc > 0) {
  525         XMPP->chardata[0] = 0;
  526     }
  527 }
  528 
  529 
  530 void xmpp_xml_chardata(void *data, const XML_Char *s, int len)
  531 {
  532     citxmpp *X = XMPP;
  533 
  534     if (X->chardata_alloc == 0) {
  535         X->chardata_alloc = SIZ;
  536         X->chardata = malloc(X->chardata_alloc);
  537     }
  538     if ((X->chardata_len + len + 1) > X->chardata_alloc) {
  539         X->chardata_alloc = X->chardata_len + len + 1024;
  540         X->chardata = realloc(X->chardata, X->chardata_alloc);
  541     }
  542     memcpy(&X->chardata[X->chardata_len], s, len);
  543     X->chardata_len += len;
  544     X->chardata[X->chardata_len] = 0;
  545 }
  546 
  547 
  548 /*
  549  * This cleanup function blows away the temporary memory and files used by the XMPP service.
  550  */
  551 void xmpp_cleanup_function(void) {
  552 
  553     /* Don't do this stuff if this is not a XMPP session! */
  554     if (CC->h_command_function != xmpp_command_loop) return;
  555 
  556     if (XMPP->chardata != NULL) {
  557         free(XMPP->chardata);
  558         XMPP->chardata = NULL;
  559         XMPP->chardata_len = 0;
  560         XMPP->chardata_alloc = 0;
  561         if (XMPP->message_body != NULL) {
  562             free(XMPP->message_body);
  563         }
  564     }
  565     XML_ParserFree(XMPP->xp);
  566     free(XMPP);
  567 }
  568 
  569 
  570 /*
  571  * Here's where our XMPP session begins its happy day.
  572  */
  573 void xmpp_greeting(void) {
  574     client_set_inbound_buf(4);
  575     strcpy(CC->cs_clientname, "XMPP session");
  576     CC->session_specific_data = malloc(sizeof(citxmpp));
  577     memset(XMPP, 0, sizeof(citxmpp));
  578     XMPP->last_event_processed = queue_event_seq;
  579 
  580     /* XMPP does not use a greeting, but we still have to initialize some things. */
  581 
  582     XMPP->xp = XML_ParserCreateNS("UTF-8", ':');
  583     if (XMPP->xp == NULL) {
  584         syslog(LOG_ERR, "xmpp: cannot create XML parser");
  585         CC->kill_me = KILLME_XML_PARSER;
  586         return;
  587     }
  588 
  589     XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end);
  590     XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata);
  591     // XML_SetUserData(XMPP->xp, something...);
  592 
  593     /* Prevent the "billion laughs" attack against expat by disabling
  594      * internal entity expansion.  With 2.x, forcibly stop the parser
  595      * if an entity is declared - this is safer and a more obvious
  596      * failure mode.  With older versions, simply prevent expansion
  597      * of such entities. */
  598 #ifdef HAVE_XML_STOPPARSER
  599     XML_SetEntityDeclHandler(XMPP->xp, xmpp_entity_declaration);
  600 #else
  601     XML_SetDefaultHandler(XMPP->xp, NULL);
  602 #endif
  603 
  604     CC->can_receive_im = 1;     /* This protocol is capable of receiving instant messages */
  605 }
  606 
  607 
  608 /* 
  609  * Main command loop for XMPP sessions.
  610  */
  611 void xmpp_command_loop(void) {
  612     int rc;
  613     StrBuf *stream_input = NewStrBuf();
  614 
  615     time(&CC->lastcmd);
  616     rc = client_read_random_blob(stream_input, 30);
  617     if (rc > 0) {
  618         XML_Parse(XMPP->xp, ChrPtr(stream_input), rc, 0);
  619     }
  620     else {
  621         syslog(LOG_ERR, "xmpp: client disconnected: ending session.");
  622         CC->kill_me = KILLME_CLIENT_DISCONNECTED;
  623     }
  624     FreeStrBuf(&stream_input);
  625 }
  626 
  627 
  628 /*
  629  * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
  630  */
  631 void xmpp_async_loop(void) {
  632     xmpp_process_events();
  633     xmpp_output_incoming_messages();
  634 }
  635 
  636 
  637 /*
  638  * Login hook for XMPP sessions
  639  */
  640 void xmpp_login_hook(void) {
  641     xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_principal_id);
  642 }
  643 
  644 
  645 /*
  646  * Logout hook for XMPP sessions
  647  */
  648 void xmpp_logout_hook(void) {
  649     xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_principal_id);
  650 }
  651 
  652 
  653 const char *CitadelServiceXMPP="XMPP";
  654 CTDL_MODULE_INIT(xmpp)
  655 {
  656     if (!threading) {
  657         CtdlRegisterServiceHook(CtdlGetConfigInt("c_xmpp_c2s_port"),
  658                     NULL,
  659                     xmpp_greeting,
  660                     xmpp_command_loop,
  661                     xmpp_async_loop,
  662                     CitadelServiceXMPP
  663         );
  664         CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP, PRIO_STOP + 70);
  665                 CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN, PRIO_LOGIN + 90);
  666                 CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 90);
  667                 CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH, PRIO_UNSTEALTH + 1);
  668                 CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH, PRIO_STEALTH + 1);
  669 
  670     }
  671 
  672     /* return our module name for the log */
  673     return "xmpp";
  674 }