Back to index

citadel  8.12
serv_xmpp.c
Go to the documentation of this file.
00001 /*
00002  * XMPP (Jabber) service for the Citadel system
00003  * Copyright (c) 2007-2011 by Art Cancro
00004  *
00005  * This program is open source software; you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License as published by
00007  * the Free Software Foundation; either version 3 of the License, or
00008  * (at your option) any later version.
00009  *
00010  * This program is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program; if not, write to the Free Software
00017  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00018  */
00019 
00020 #include "sysdep.h"
00021 #include <stdlib.h>
00022 #include <unistd.h>
00023 #include <stdio.h>
00024 #include <fcntl.h>
00025 #include <signal.h>
00026 #include <pwd.h>
00027 #include <errno.h>
00028 #include <sys/types.h>
00029 
00030 #if TIME_WITH_SYS_TIME
00031 # include <sys/time.h>
00032 # include <time.h>
00033 #else
00034 # if HAVE_SYS_TIME_H
00035 #  include <sys/time.h>
00036 # else
00037 #  include <time.h>
00038 # endif
00039 #endif
00040 
00041 #include <sys/wait.h>
00042 #include <string.h>
00043 #include <limits.h>
00044 #include <ctype.h>
00045 #include <libcitadel.h>
00046 #include <expat.h>
00047 #include "citadel.h"
00048 #include "server.h"
00049 #include "citserver.h"
00050 #include "support.h"
00051 #include "config.h"
00052 #include "user_ops.h"
00053 #include "database.h"
00054 #include "msgbase.h"
00055 #include "internet_addressing.h"
00056 #include "md5.h"
00057 #include "ctdl_module.h"
00058 #include "serv_xmpp.h"
00059 
00060 /* XML_StopParser is present in expat 2.x */
00061 #if XML_MAJOR_VERSION > 1
00062 #define HAVE_XML_STOPPARSER
00063 #endif
00064 
00065 struct xmpp_event *xmpp_queue = NULL;
00066 
00067 int XMPPSrvDebugEnable = 0;
00068 
00069 
00070 
00071 #ifdef HAVE_XML_STOPPARSER
00072 /* Stop the parser if an entity declaration is hit. */
00073 static void xmpp_entity_declaration(void *userData, const XML_Char *entityName,
00074                             int is_parameter_entity, const XML_Char *value,
00075                             int value_length, const XML_Char *base,
00076                             const XML_Char *systemId, const XML_Char *publicId,
00077                             const XML_Char *notationName
00078 ) {
00079        XMPPM_syslog(LOG_WARNING, "Illegal entity declaration encountered; stopping parser.");
00080        XML_StopParser(XMPP->xp, XML_FALSE);
00081 }
00082 #endif
00083 
00084 
00085 
00086 /*
00087  * Given a source string and a target buffer, returns the string
00088  * properly escaped for insertion into an XML stream.  Returns a
00089  * pointer to the target buffer for convenience.
00090  *
00091  * BUG: this does not properly handle UTF-8
00092  */
00093 char *xmlesc(char *buf, char *str, int bufsiz)
00094 {
00095        char *ptr;
00096        unsigned char ch;
00097        int len = 0;
00098 
00099        if (!buf) return(NULL);
00100        buf[0] = 0;
00101        len = 0;
00102        if (!str) {
00103               return(buf);
00104        }
00105 
00106        for (ptr=str; *ptr; ptr++) {
00107               ch = *ptr;
00108               if (ch == '<') {
00109                      strcpy(&buf[len], "&lt;");
00110                      len += 4;
00111               }
00112               else if (ch == '>') {
00113                      strcpy(&buf[len], "&gt;");
00114                      len += 4;
00115               }
00116               else if (ch == '&') {
00117                      strcpy(&buf[len], "&amp;");
00118                      len += 5;
00119               }
00120               else if ((ch >= 0x20) && (ch <= 0x7F)) {
00121                      buf[len++] = ch;
00122                      buf[len] = 0;
00123               }
00124               else if (ch < 0x20) {
00125                      /* we probably shouldn't be doing this */
00126                      buf[len++] = '_';
00127                      buf[len] = 0;
00128               }
00129               else {
00130                      char oct[10];
00131                      sprintf(oct, "&#%o;", ch);
00132                      strcpy(&buf[len], oct);
00133                      len += strlen(oct);
00134               }
00135               if ((len + 6) > bufsiz) {
00136                      return(buf);
00137               }
00138        }
00139        return(buf);
00140 }
00141 
00142 
00143 /*
00144  * We have just received a <stream> tag from the client, so send them ours
00145  */
00146 void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
00147 {
00148        char xmlbuf[256];
00149 
00150        while (*attr) {
00151               if (!strcasecmp(attr[0], "to")) {
00152                      safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name);
00153               }
00154               attr += 2;
00155        }
00156 
00157        cprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
00158 
00159        cprintf("<stream:stream ");
00160        cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->server_name, sizeof xmlbuf));
00161        cprintf("id=\"%08x\" ", CC->cs_pid);
00162        cprintf("version=\"1.0\" ");
00163        cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
00164        cprintf("xmlns=\"jabber:client\">");
00165 
00166        /* The features of this stream are... */
00167        cprintf("<stream:features>");
00168 
00169        /*
00170         * TLS encryption (but only if it isn't already active)
00171         */ 
00172 #ifdef HAVE_OPENSSL
00173        if (!CC->redirect_ssl) {
00174               cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
00175        }
00176 #endif
00177 
00178        if (!CC->logged_in) {
00179               /* If we're not logged in yet, offer SASL as our feature set */
00180               xmpp_output_auth_mechs();
00181 
00182               /* Also offer non-SASL authentication */
00183               cprintf("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
00184        }
00185 
00186        /* Offer binding and sessions as part of our feature set */
00187        cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
00188        cprintf("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
00189 
00190        cprintf("</stream:features>");
00191 
00192        CC->is_async = 1;           /* XMPP sessions are inherently async-capable */
00193 }
00194 
00195 
00196 void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) {
00197        char el[256];
00198        char *sep = NULL;
00199        int i;
00200 
00201        /* Axe the namespace, we don't care about it */
00202        safestrncpy(el, supplied_el, sizeof el);
00203        while (sep = strchr(el, ':'), sep) {
00204               strcpy(el, ++sep);
00205        }
00206 
00207        /*
00208        XMPP_syslog(LOG_DEBUG, "XMPP ELEMENT START: <%s>\n", el);
00209        for (i=0; attr[i] != NULL; i+=2) {
00210               XMPP_syslog(LOG_DEBUG, "                    Attribute '%s' = '%s'\n", attr[i], attr[i+1]);
00211        }
00212        uncomment for more verbosity */
00213 
00214        if (!strcasecmp(el, "stream")) {
00215               xmpp_stream_start(data, supplied_el, attr);
00216        }
00217 
00218        else if (!strcasecmp(el, "query")) {
00219               XMPP->iq_query_xmlns[0] = 0;
00220               safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns);
00221        }
00222 
00223        else if (!strcasecmp(el, "bind")) {
00224               XMPP->bind_requested = 1;
00225        }
00226 
00227        else if (!strcasecmp(el, "iq")) {
00228               for (i=0; attr[i] != NULL; i+=2) {
00229                      if (!strcasecmp(attr[i], "type")) {
00230                             safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type);
00231                      }
00232                      else if (!strcasecmp(attr[i], "id")) {
00233                             safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id);
00234                      }
00235                      else if (!strcasecmp(attr[i], "from")) {
00236                             safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from);
00237                      }
00238                      else if (!strcasecmp(attr[i], "to")) {
00239                             safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to);
00240                      }
00241               }
00242        }
00243 
00244        else if (!strcasecmp(el, "auth")) {
00245               XMPP->sasl_auth_mech[0] = 0;
00246               for (i=0; attr[i] != NULL; i+=2) {
00247                      if (!strcasecmp(attr[i], "mechanism")) {
00248                             safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech);
00249                      }
00250               }
00251        }
00252 
00253        else if (!strcasecmp(el, "message")) {
00254               for (i=0; attr[i] != NULL; i+=2) {
00255                      if (!strcasecmp(attr[i], "to")) {
00256                             safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to);
00257                      }
00258               }
00259        }
00260 
00261        else if (!strcasecmp(el, "html")) {
00262               ++XMPP->html_tag_level;
00263        }
00264 }
00265 
00266 
00267 
00268 void xmpp_xml_end(void *data, const char *supplied_el) {
00269        char el[256];
00270        char *sep = NULL;
00271        char xmlbuf[256];
00272 
00273        /* Axe the namespace, we don't care about it */
00274        safestrncpy(el, supplied_el, sizeof el);
00275        while (sep = strchr(el, ':'), sep) {
00276               strcpy(el, ++sep);
00277        }
00278 
00279        /*
00280        XMPP_syslog(LOG_DEBUG, "XMPP ELEMENT END  : <%s>\n", el);
00281        if (XMPP->chardata_len > 0) {
00282               XMPP_syslog(LOG_DEBUG, "          chardata: %s\n", XMPP->chardata);
00283        }
00284        uncomment for more verbosity */
00285 
00286        if (!strcasecmp(el, "resource")) {
00287               if (XMPP->chardata_len > 0) {
00288                      safestrncpy(XMPP->iq_client_resource, XMPP->chardata,
00289                             sizeof XMPP->iq_client_resource);
00290                      striplt(XMPP->iq_client_resource);
00291               }
00292        }
00293 
00294        else if (!strcasecmp(el, "username")) {          /* NON SASL ONLY */
00295               if (XMPP->chardata_len > 0) {
00296                      safestrncpy(XMPP->iq_client_username, XMPP->chardata,
00297                             sizeof XMPP->iq_client_username);
00298                      striplt(XMPP->iq_client_username);
00299               }
00300        }
00301 
00302        else if (!strcasecmp(el, "password")) {          /* NON SASL ONLY */
00303               if (XMPP->chardata_len > 0) {
00304                      safestrncpy(XMPP->iq_client_password, XMPP->chardata,
00305                             sizeof XMPP->iq_client_password);
00306                      striplt(XMPP->iq_client_password);
00307               }
00308        }
00309 
00310        else if (!strcasecmp(el, "iq")) {
00311 
00312               /*
00313                * iq type="get" (handle queries)
00314                */
00315               if (!strcasecmp(XMPP->iq_type, "get")) {
00316 
00317                      /*
00318                       * Query on a namespace
00319                       */
00320                      if (!IsEmptyStr(XMPP->iq_query_xmlns)) {
00321                             xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from,
00322                                           XMPP->iq_to, XMPP->iq_query_xmlns);
00323                      }
00324 
00325                      /*
00326                       * ping ( http://xmpp.org/extensions/xep-0199.html )
00327                       */
00328                      else if (XMPP->ping_requested) {
00329                             cprintf("<iq type=\"result\" ");
00330                             if (!IsEmptyStr(XMPP->iq_from)) {
00331                                    cprintf("to=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
00332                             }
00333                             if (!IsEmptyStr(XMPP->iq_to)) {
00334                                    cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_to, sizeof xmlbuf));
00335                             }
00336                             cprintf("id=\"%s\"/>", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
00337                      }
00338 
00339                      /*
00340                       * Unknown query ... return the XML equivalent of a blank stare
00341                       */
00342                      else {
00343                             XMPP_syslog(LOG_DEBUG,
00344                                        "Unknown query <%s> - returning <service-unavailable/>\n",
00345                                        el
00346                             );
00347                             cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
00348                             cprintf("<error code=\"503\" type=\"cancel\">"
00349                                    "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
00350                                    "</error>"
00351                             );
00352                             cprintf("</iq>");
00353                      }
00354               }
00355 
00356               /*
00357                * Non SASL authentication
00358                */
00359               else if (
00360                      (!strcasecmp(XMPP->iq_type, "set"))
00361                      && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query"))
00362                      ) {
00363 
00364                      xmpp_non_sasl_authenticate(
00365                             XMPP->iq_id,
00366                             XMPP->iq_client_username,
00367                             XMPP->iq_client_password,
00368                             XMPP->iq_client_resource
00369                      );
00370               }      
00371 
00372               /*
00373                * If this <iq> stanza was a "bind" attempt, process it ...
00374                */
00375               else if (
00376                      (XMPP->bind_requested)
00377                      && (!IsEmptyStr(XMPP->iq_id))
00378                      && (!IsEmptyStr(XMPP->iq_client_resource))
00379                      && (CC->logged_in)
00380                      ) {
00381 
00382                      /* Generate the "full JID" of the client resource */
00383 
00384                      snprintf(XMPP->client_jid, sizeof XMPP->client_jid,
00385                             "%s/%s",
00386                             CC->cs_inet_email,
00387                             XMPP->iq_client_resource
00388                      );
00389 
00390                      /* Tell the client what its JID is */
00391 
00392                      cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
00393                      cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
00394                      cprintf("<jid>%s</jid>", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf));
00395                      cprintf("</bind>");
00396                      cprintf("</iq>");
00397               }
00398 
00399               else if (XMPP->iq_session) {
00400                      cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
00401                      cprintf("</iq>");
00402               }
00403 
00404               else {
00405                      cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
00406                      cprintf("<error>Don't know howto do '%s'!</error>", xmlesc(xmlbuf, XMPP->iq_type, sizeof xmlbuf));
00407                      cprintf("</iq>");
00408               }
00409 
00410               /* Now clear these fields out so they don't get used by a future stanza */
00411               XMPP->iq_id[0] = 0;
00412               XMPP->iq_from[0] = 0;
00413               XMPP->iq_to[0] = 0;
00414               XMPP->iq_type[0] = 0;
00415               XMPP->iq_client_resource[0] = 0;
00416               XMPP->iq_session = 0;
00417               XMPP->iq_query_xmlns[0] = 0;
00418               XMPP->bind_requested = 0;
00419               XMPP->ping_requested = 0;
00420        }
00421 
00422        else if (!strcasecmp(el, "auth")) {
00423 
00424               /* Try to authenticate (this function is responsible for the output stanza) */
00425               xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") );
00426 
00427               /* Now clear these fields out so they don't get used by a future stanza */
00428               XMPP->sasl_auth_mech[0] = 0;
00429        }
00430 
00431        else if (!strcasecmp(el, "session")) {
00432               XMPP->iq_session = 1;
00433        }
00434 
00435        else if (!strcasecmp(el, "presence")) {
00436 
00437               /* Respond to a <presence> update by firing back with presence information
00438                * on the entire wholist.  Check this assumption, it's probably wrong.
00439                */
00440               xmpp_wholist_presence_dump();
00441        }
00442 
00443        else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) {
00444               if (XMPP->message_body != NULL) {
00445                      free(XMPP->message_body);
00446                      XMPP->message_body = NULL;
00447               }
00448               if (XMPP->chardata_len > 0) {
00449                      XMPP->message_body = strdup(XMPP->chardata);
00450               }
00451        }
00452 
00453        else if (!strcasecmp(el, "message")) {
00454               xmpp_send_message(XMPP->message_to, XMPP->message_body);
00455               XMPP->html_tag_level = 0;
00456        }
00457 
00458        else if (!strcasecmp(el, "html")) {
00459               --XMPP->html_tag_level;
00460        }
00461 
00462        else if (!strcasecmp(el, "starttls")) {
00463 #ifdef HAVE_OPENSSL
00464               cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
00465               CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
00466               if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
00467 #else
00468               cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
00469               CC->kill_me = KILLME_NO_CRYPTO;
00470 #endif
00471        }
00472 
00473        else if (!strcasecmp(el, "ping")) {
00474               XMPP->ping_requested = 1;
00475        }
00476 
00477        else if (!strcasecmp(el, "stream")) {
00478               XMPPM_syslog(LOG_DEBUG, "XMPP client shut down their stream\n");
00479               xmpp_massacre_roster();
00480               cprintf("</stream>\n");
00481               CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
00482        }
00483 
00484        else {
00485               XMPP_syslog(LOG_DEBUG, "Ignoring unknown tag <%s>\n", el);
00486        }
00487 
00488        XMPP->chardata_len = 0;
00489        if (XMPP->chardata_alloc > 0) {
00490               XMPP->chardata[0] = 0;
00491        }
00492 }
00493 
00494 
00495 void xmpp_xml_chardata(void *data, const XML_Char *s, int len)
00496 {
00497        citxmpp *X = XMPP;
00498 
00499        if (X->chardata_alloc == 0) {
00500               X->chardata_alloc = SIZ;
00501               X->chardata = malloc(X->chardata_alloc);
00502        }
00503        if ((X->chardata_len + len + 1) > X->chardata_alloc) {
00504               X->chardata_alloc = X->chardata_len + len + 1024;
00505               X->chardata = realloc(X->chardata, X->chardata_alloc);
00506        }
00507        memcpy(&X->chardata[X->chardata_len], s, len);
00508        X->chardata_len += len;
00509        X->chardata[X->chardata_len] = 0;
00510 }
00511 
00512 
00513 /*
00514  * This cleanup function blows away the temporary memory and files used by the XMPP service.
00515  */
00516 void xmpp_cleanup_function(void) {
00517 
00518        /* Don't do this stuff if this is not a XMPP session! */
00519        if (CC->h_command_function != xmpp_command_loop) return;
00520 
00521        if (XMPP->chardata != NULL) {
00522               free(XMPP->chardata);
00523               XMPP->chardata = NULL;
00524               XMPP->chardata_len = 0;
00525               XMPP->chardata_alloc = 0;
00526               if (XMPP->message_body != NULL) {
00527                      free(XMPP->message_body);
00528               }
00529        }
00530        XML_ParserFree(XMPP->xp);
00531        free(XMPP);
00532 }
00533 
00534 
00535 
00536 /*
00537  * Here's where our XMPP session begins its happy day.
00538  */
00539 void xmpp_greeting(void) {
00540        client_set_inbound_buf(4);
00541        strcpy(CC->cs_clientname, "XMPP session");
00542        CC->session_specific_data = malloc(sizeof(citxmpp));
00543        memset(XMPP, 0, sizeof(citxmpp));
00544        XMPP->last_event_processed = queue_event_seq;
00545 
00546        /* XMPP does not use a greeting, but we still have to initialize some things. */
00547 
00548        XMPP->xp = XML_ParserCreateNS("UTF-8", ':');
00549        if (XMPP->xp == NULL) {
00550               XMPPM_syslog(LOG_ALERT, "Cannot create XML parser!\n");
00551               CC->kill_me = KILLME_XML_PARSER;
00552               return;
00553        }
00554 
00555        XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end);
00556        XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata);
00557        // XML_SetUserData(XMPP->xp, something...);
00558 
00559        /* Prevent the "billion laughs" attack against expat by disabling
00560         * internal entity expansion.  With 2.x, forcibly stop the parser
00561         * if an entity is declared - this is safer and a more obvious
00562         * failure mode.  With older versions, simply prevent expansion
00563         * of such entities. */
00564 #ifdef HAVE_XML_STOPPARSER
00565        XML_SetEntityDeclHandler(XMPP->xp, xmpp_entity_declaration);
00566 #else
00567        XML_SetDefaultHandler(XMPP->xp, NULL);
00568 #endif
00569 
00570        CC->can_receive_im = 1;            /* This protocol is capable of receiving instant messages */
00571 }
00572 
00573 
00574 /* 
00575  * Main command loop for XMPP sessions.
00576  */
00577 void xmpp_command_loop(void) {
00578        int rc;
00579        StrBuf *stream_input = NewStrBuf();
00580 
00581        time(&CC->lastcmd);
00582        rc = client_read_random_blob(stream_input, 30);
00583        if (rc > 0) {
00584               XML_Parse(XMPP->xp, ChrPtr(stream_input), rc, 0);
00585        }
00586        else {
00587               XMPPM_syslog(LOG_ERR, "client disconnected: ending session.\n");
00588               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
00589        }
00590        FreeStrBuf(&stream_input);
00591 }
00592 
00593 
00594 /*
00595  * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
00596  */
00597 void xmpp_async_loop(void) {
00598        xmpp_process_events();
00599        xmpp_output_incoming_messages();
00600 }
00601 
00602 
00603 /*
00604  * Login hook for XMPP sessions
00605  */
00606 void xmpp_login_hook(void) {
00607        xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_inet_email);
00608 }
00609 
00610 
00611 /*
00612  * Logout hook for XMPP sessions
00613  */
00614 void xmpp_logout_hook(void) {
00615        xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_inet_email);
00616 }
00617 
00618 
00619 void LogXMPPSrvDebugEnable(const int n)
00620 {
00621        XMPPSrvDebugEnable = n;
00622 }
00623 const char *CitadelServiceXMPP="XMPP";
00624 extern void xmpp_cleanup_events(void);
00625 CTDL_MODULE_INIT(xmpp)
00626 {
00627        if (!threading) {
00628               CtdlRegisterServiceHook(config.c_xmpp_c2s_port,
00629                                    NULL,
00630                                    xmpp_greeting,
00631                                    xmpp_command_loop,
00632                                    xmpp_async_loop,
00633                                    CitadelServiceXMPP
00634               );
00635               CtdlRegisterDebugFlagHook(HKEY("serv_xmpp"), LogXMPPSrvDebugEnable, &XMPPSrvDebugEnable);
00636               CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP, PRIO_STOP + 70);
00637                 CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN, PRIO_LOGIN + 90);
00638                 CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 90);
00639                 CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH, PRIO_UNSTEALTH + 1);
00640                 CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH, PRIO_STEALTH + 1);
00641               CtdlRegisterCleanupHook(xmpp_cleanup_events);
00642 
00643        }
00644 
00645        /* return our module name for the log */
00646        return "xmpp";
00647 }