424c0fdbb20608668c744f99fdd289ee2d6eb3e2
[citadel.git] / citadel / modules / xmpp / serv_xmpp.c
1 /*
2  * XMPP (Jabber) service for the Citadel system
3  * Copyright (c) 2007-2011 by Art Cancro
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 <stdarg.h>
25 #include <fcntl.h>
26 #include <signal.h>
27 #include <pwd.h>
28 #include <errno.h>
29 #include <sys/types.h>
30
31 #if TIME_WITH_SYS_TIME
32 # include <sys/time.h>
33 # include <time.h>
34 #else
35 # if HAVE_SYS_TIME_H
36 #  include <sys/time.h>
37 # else
38 #  include <time.h>
39 # endif
40 #endif
41
42 #include <sys/wait.h>
43 #include <string.h>
44 #include <limits.h>
45 #include <ctype.h>
46 #define SHOW_ME_VAPPEND_PRINTF
47 #include <libcitadel.h>
48 #include <expat.h>
49 #include "citadel.h"
50 #include "server.h"
51 #include "citserver.h"
52 #include "support.h"
53 #include "config.h"
54 #include "user_ops.h"
55 #include "database.h"
56 #include "msgbase.h"
57 #include "internet_addressing.h"
58 #include "md5.h"
59 #include "ctdl_module.h"
60 #include "serv_xmpp.h"
61
62 /* XML_StopParser is present in expat 2.x */
63 #if XML_MAJOR_VERSION > 1
64 #define HAVE_XML_STOPPARSER
65 #endif
66
67 HashList *XMPP_StartHandlers = NULL;
68 HashList *XMPP_EndHandlers = NULL;
69 HashList *XMPP_SupportedNamespaces = NULL;
70 HashList *XMPP_NameSpaces = NULL;
71 HashList *FlatToken = NULL;
72
73 int XMPPSrvDebugEnable = 0;
74
75 void XUnbuffer(void)
76 {
77         citxmpp *Xmpp = XMPP;
78
79         cputbuf(Xmpp->OutBuf);
80         FlushStrBuf(Xmpp->OutBuf);
81 }
82
83 void XPutBody(const char *Str, long Len)
84 {
85         StrBufXMLEscAppend(XMPP->OutBuf, NULL, Str, Len, 0);
86 }
87
88 void XPutProp(const char *Str, long Len)
89 {
90         StrEscAppend(XMPP->OutBuf, NULL, Str, 0, 1);
91 }
92
93 void XPut(const char *Str, long Len)
94 {
95         StrBufAppendBufPlain(XMPP->OutBuf, Str, Len, 0);
96 }
97
98 void XPrintf(const char *Format, ...)
99 {
100         va_list arg_ptr;
101         va_start(arg_ptr, Format);
102         StrBufVAppendPrintf(XMPP->OutBuf, Format, arg_ptr);
103         va_end(arg_ptr);
104 }
105
106
107 void XPrint(const char *Token, long tlen,
108             int Flags,
109             ...)
110
111 {
112         int BodySeen = 0;
113         int ArgType;
114         int Finished = 0;
115         char *PName;
116         long PLen;
117         char *Val;
118         long VLen;
119         va_list arg_ptr;
120
121         XPUT("<");
122         XPut(Token, tlen);
123
124         va_start(arg_ptr, Flags);
125         while (!Finished)
126         {
127                 ArgType = va_arg(arg_ptr, int);
128                 switch (ArgType)
129                 {
130                 case TYPE_STR:
131                         PName = va_arg(arg_ptr, char*);
132                         PLen  = va_arg(arg_ptr, long);
133                         Val   = va_arg(arg_ptr, char*);
134                         VLen  = va_arg(arg_ptr, long);
135                         XPUT(" ");
136                         XPut(PName, PLen);
137                         XPUT("=\"");
138                         XPutProp(Val, VLen);
139                         XPUT("\"");
140                         break;
141                 case TYPE_OPTSTR:
142                         PName = va_arg(arg_ptr, char*);
143                         PLen  = va_arg(arg_ptr, long);
144                         Val   = va_arg(arg_ptr, char*);
145                         VLen  = va_arg(arg_ptr, long);
146                         if (VLen > 0)
147                         {
148                                 XPUT(" ");
149                                 XPut(PName, PLen);
150                                 XPUT("=\"");
151                                 XPutProp(Val, VLen);
152                                 XPUT("\"");
153                         }
154                         break;
155                 case TYPE_INT:
156                         PName = va_arg(arg_ptr, char*);
157                         PLen  = va_arg(arg_ptr, long);
158                         VLen  = va_arg(arg_ptr, long);
159                         XPUT(" ");
160                         XPut(PName, PLen);
161                         XPUT("=\"");
162                         XPrintf("%ld", VLen);
163                         XPUT("\"");
164                         break;
165                 case TYPE_BODYSTR:
166                         BodySeen = 1;
167                         XPUT(">");
168                         Val   = va_arg(arg_ptr, char*);
169                         VLen  = va_arg(arg_ptr, long);
170                         XPutBody(Val, VLen);
171                         break;
172                 case TYPE_ARGEND:
173                         Finished = 1;
174                         break;
175                 }
176         }
177         if (Flags == XCLOSED)
178         {
179                 if (BodySeen)
180                 {
181                         XPUT("</");
182                         XPut(Token, tlen);
183                         XPUT(">");
184                 }
185                 else
186                 {
187                         XPUT("></");
188                         XPut(Token, tlen);
189                         XPUT(">");
190                 }
191         }
192         else
193                 XPUT(">");
194         va_end(arg_ptr);
195 }
196
197 #ifdef HAVE_XML_STOPPARSER
198 /* Stop the parser if an entity declaration is hit. */
199 static void xmpp_entity_declaration(void *userData, const XML_Char *entityName,
200                                 int is_parameter_entity, const XML_Char *value,
201                                 int value_length, const XML_Char *base,
202                                 const XML_Char *systemId, const XML_Char *publicId,
203                                 const XML_Char *notationName
204 ) {
205         XMPPM_syslog(LOG_WARNING, "Illegal entity declaration encountered; stopping parser.");
206         XML_StopParser(XMPP->xp, XML_FALSE);
207 }
208 #endif
209
210 /*
211  * We have just received a <stream> tag from the client, so send them ours
212  */
213 void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
214 {
215         citxmpp *Xmpp = XMPP;
216
217         while (*attr) {
218                 if (!strcasecmp(attr[0], "to")) {
219                         safestrncpy(Xmpp->server_name, attr[1], sizeof Xmpp->server_name);
220                 }
221                 attr += 2;
222         }
223
224         XPUT("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
225
226         XPUT("<stream:stream ");
227         XPUT("from=\"");
228         XPutProp(Xmpp->server_name, strlen(Xmpp->server_name));
229         XPUT("\" id=\"");
230         XPrintf("%08x\" ", CC->cs_pid);
231         XPUT("version=\"1.0\" "
232                   "xmlns:stream=\"http://etherx.jabber.org/streams\" "
233                   "xmlns=\"jabber:client\">"
234         /* The features of this stream are... */
235              "<stream:features>");
236
237         /*
238          * TLS encryption (but only if it isn't already active)
239          */ 
240
241 #ifdef HAVE_OPENSSL
242         if (!CC->redirect_ssl) {
243                 XPUT("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
244         }
245 #endif
246         //*/
247         if (!CC->logged_in) {
248                 /* If we're not logged in yet, offer SASL as our feature set */
249                 xmpp_output_auth_mechs();
250
251                 /* Also offer non-SASL authentication */
252                 XPUT("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
253         }
254
255         /* Offer binding and sessions as part of our feature set */
256         XPUT("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"
257                   "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
258                   "</stream:features>");
259
260         CC->is_async = 1;               /* XMPP sessions are inherently async-capable */
261 }
262
263 void xmpp_start_query(void *data, const char *supplied_el, const char **attr)
264 {
265         XMPP->iq_query_xmlns[0] = 0;
266         safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns);
267 }
268
269 void xmpp_start_bind(void *data, const char *supplied_el, const char **attr)
270 {
271         XMPP->bind_requested = 1;
272 }
273
274 void xmpp_start_auth(void *data, const char *supplied_el, const char **attr)
275 {
276         int i;
277
278         XMPP->sasl_auth_mech[0] = 0;
279         for (i=0; attr[i] != NULL; i+=2) {
280                 if (!strcasecmp(attr[i], "mechanism")) {
281                         safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech);
282                 }
283         }
284 }
285 /*
286 void xmpp_start_message(void *data, const char *supplied_el, const char **attr)
287 {
288         int i;
289
290         for (i=0; attr[i] != NULL; i+=2) {
291                 if (!strcasecmp(attr[i], "to")) {
292                         safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to);
293                 }
294         }
295 }
296 */
297 void xmpp_start_html(void *data, const char *supplied_el, const char **attr)
298 {
299         ++XMPP->html_tag_level;
300 }
301
302 void xmpp_xml_start(void *data, const char *supplied_el, const char **attr)
303 {
304         HashList *ThisNamespace = NULL;
305         const char *pToken;
306         const char *pch;
307         const char *NS = NULL;
308         long NSLen;
309         long len;
310         void *pv;
311         
312         /* Axe the namespace, we don't care about it */
313         pToken = supplied_el;
314         pch = strchr(pToken, ':');
315         while (pch != NULL)
316         {
317                 pToken = pch;
318                 pch = strchr(pToken  + 1, ':');
319         }
320
321         if (*pToken == ':')
322         {
323                 NS = supplied_el;
324                 NSLen = pToken - supplied_el;
325                 if (GetHash(XMPP_NameSpaces, NS, NSLen, &pv))
326                 {
327                         ThisNamespace = pv;
328
329                 }
330                 
331                 pToken ++;
332         }
333
334         len = strlen(pToken);
335
336
337         if (ThisNamespace != NULL)
338         {
339                 if (GetHash(ThisNamespace, pToken, len, &pv))
340                 {
341                         TokenHandler *th;
342                         void *value;
343                         long i = 0;
344
345                         th = (TokenHandler*) pv;
346                         value = th->GetToken();
347
348                         while (attr[i] != NULL)
349                         {
350
351                                 if (GetHash(th->Properties, attr[i], strlen(attr[i]), &pv))
352                                 {
353                                         PropertyHandler* ph = pv;
354                                         void *val;
355                                         StrBuf **pVal;
356                                         long len;
357
358                                         len = strlen(attr[i+1]);
359                                         val = value;
360                                         val += ph->offset;
361                                         pVal = (StrBuf**) val;
362                                         if (*pVal != NULL)
363                                                 StrBufPlain(*pVal, attr[i+1], len);
364                                         else
365                                                 *pVal = NewStrBufPlain(attr[i+1], len);
366                                 }
367                                 i+=2;
368                         }
369                         return;
370                 }
371
372         }
373         /*
374         XMPP_syslog(LOG_DEBUG, "XMPP ELEMENT START: <%s>\n", el);
375         for (i=0; attr[i] != NULL; i+=2) {
376                 XMPP_syslog(LOG_DEBUG, "                    Attribute '%s' = '%s'\n", attr[i], attr[i+1]);
377         }
378         uncomment for more verbosity */
379
380         if (GetHash(XMPP_StartHandlers, pToken, len, &pv))
381         {
382                 xmpp_handler *h;
383                 h = (xmpp_handler*) pv;
384                 h->Handler(data, supplied_el, attr);
385         }
386 }
387
388 void xmpp_end_resource(void *data, const char *supplied_el, const char **attr)
389 {
390         if (XMPP->chardata_len > 0) {
391                 safestrncpy(XMPP->iq_client_resource, XMPP->chardata,
392                             sizeof XMPP->iq_client_resource);
393                 striplt(XMPP->iq_client_resource);
394         }
395 }
396
397 void xmpp_end_username(void *data, const char *supplied_el, const char **attr)
398 {
399         /* NON SASL ONLY */
400         if (XMPP->chardata_len > 0) {
401                 safestrncpy(XMPP->iq_client_username, XMPP->chardata,
402                             sizeof XMPP->iq_client_username);
403                 striplt(XMPP->iq_client_username);
404         }
405 }
406
407 void xmpp_end_password(void *data, const char *supplied_el, const char **attr)
408 {               /* NON SASL ONLY */
409         if (XMPP->chardata_len > 0) {
410                 safestrncpy(XMPP->iq_client_password, XMPP->chardata,
411                             sizeof XMPP->iq_client_password);
412                 striplt(XMPP->iq_client_password);
413         }
414 }
415
416 void xmpp_end_iq(void *data, const char *supplied_el, const char **attr)
417 {
418         citxmpp *Xmpp = XMPP;
419
420         /*
421          * iq type="get" (handle queries)
422          */
423         if (!strcasecmp(ChrPtr(Xmpp->IQ.type), "get"))
424         {
425                 /*
426                  * Query on a namespace
427                  */
428                 if (!IsEmptyStr(Xmpp->iq_query_xmlns)) {
429                         xmpp_query_namespace(&Xmpp->IQ, Xmpp->iq_query_xmlns);
430                 }
431                 
432                 /*
433                  * ping ( http://xmpp.org/extensions/xep-0199.html )
434                  */
435                 else if (Xmpp->ping_requested) {
436                         XPUT("<iq type=\"result\" ");
437                         if (StrLength(Xmpp->IQ.from) > 0) {
438                                 XPUT("to=\"");
439                                 XPutSProp(Xmpp->IQ.from);
440                                 XPUT("\" ");
441                         }
442                         if (StrLength(Xmpp->IQ.to)>0) {
443                                 XPUT("from=\"");
444                                 XPutSProp(Xmpp->IQ.to);
445                                 XPUT("\" ");
446                         }
447                         XPUT("id=\"");
448                         XPutSProp(Xmpp->IQ.id);
449                         XPUT("\"/>");
450                 }
451
452                 /*
453                  * Unknown query ... return the XML equivalent of a blank stare
454                  */
455                 else {
456 /*
457                         Xmpp_syslog(LOG_DEBUG,
458                                     "Unknown query <%s> - returning <service-unavailable/>\n",
459                                     el
460                                 );
461 */
462                         XPUT("<iq type=\"error\" id=\"");
463                         XPutSProp(Xmpp->IQ.id);
464                         XPUT("\">"
465                              "<error code=\"503\" type=\"cancel\">"
466                              "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
467                              "</error>"
468                              "</iq>");
469                 }
470         }
471
472         /*
473          * Non SASL authentication
474          */
475         else if (
476                 (!strcasecmp(ChrPtr(Xmpp->IQ.type), "set"))
477                 && (!strcasecmp(Xmpp->iq_query_xmlns, "jabber:iq:auth:query"))
478                 ) {
479                 
480                 xmpp_non_sasl_authenticate(
481                         Xmpp->IQ.id,
482                         Xmpp->iq_client_username,
483                         Xmpp->iq_client_password,
484                         Xmpp->iq_client_resource
485                         );
486         }
487
488         /*
489          * If this <iq> stanza was a "bind" attempt, process it ...
490          */
491         else if (
492                 (Xmpp->bind_requested)
493                 && (StrLength(Xmpp->IQ.id)>0)
494                 && (!IsEmptyStr(Xmpp->iq_client_resource))
495                 && (CC->logged_in)
496                 ) {
497
498                 /* Generate the "full JID" of the client resource */
499
500                 snprintf(Xmpp->client_jid, sizeof Xmpp->client_jid,
501                          "%s/%s",
502                          CC->cs_inet_email,
503                          Xmpp->iq_client_resource
504                         );
505
506                 /* Tell the client what its JID is */
507
508                 XPUT("<iq type=\"result\" id=\"");
509                 XPutSProp(Xmpp->IQ.id);
510                 XPUT("\">"
511                      "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
512                 XPUT("<jid>");
513                 XPutBody(Xmpp->client_jid, strlen(Xmpp->client_jid));
514                 XPUT("</jid>"
515                      "</bind>"
516                      "</iq>");
517         }
518
519         else if (Xmpp->iq_session) {
520                 XPUT("<iq type=\"result\" id=\"");
521                 XPutSProp(Xmpp->IQ.id);
522                 XPUT("\">"
523                      "</iq>");
524         }
525
526         else {
527                 XPUT("<iq type=\"error\" id=\"");
528                 XPutSProp(Xmpp->IQ.id);
529                 XPUT("\">");
530                 XPUT("<error>Don't know howto do '");
531                 XPutBody(SKEY(Xmpp->IQ.type));
532                 XPUT("'!</error>"
533                      "</iq>");
534         }
535
536         /* Now clear these fields out so they don't get used by a future stanza */
537         FlushStrBuf(Xmpp->IQ.id);
538         FlushStrBuf(Xmpp->IQ.from);
539         FlushStrBuf(Xmpp->IQ.to);
540         FlushStrBuf(Xmpp->IQ.type);
541         Xmpp->iq_client_resource[0] = 0;
542         Xmpp->iq_session = 0;
543         Xmpp->iq_query_xmlns[0] = 0;
544         Xmpp->bind_requested = 0;
545         Xmpp->ping_requested = 0;
546 }
547
548
549 void xmpp_end_auth(void *data, const char *supplied_el, const char **attr)
550 {
551         /* Try to authenticate (this function is responsible for the output stanza) */
552         xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") );
553         
554         /* Now clear these fields out so they don't get used by a future stanza */
555         XMPP->sasl_auth_mech[0] = 0;
556 }
557
558 void xmpp_end_session(void *data, const char *supplied_el, const char **attr)
559 {
560         XMPP->iq_session = 1;
561 }
562
563 void xmpp_end_body(void *data, const char *supplied_el, const char **attr)
564 {
565         if (XMPP->html_tag_level == 0)
566         {
567                 if (XMPP->message_body != NULL)
568                 {
569                         free(XMPP->message_body);
570                         XMPP->message_body = NULL;
571                 }
572                 if (XMPP->chardata_len > 0) {
573                         XMPP->message_body = strdup(XMPP->chardata);
574                 }
575         }
576 }
577
578 void xmpp_end_html(void *data, const char *supplied_el, const char **attr)
579 {
580         --XMPP->html_tag_level;
581 }
582
583 void xmpp_end_starttls(void *data, const char *supplied_el, const char **attr)
584 {
585 #ifdef HAVE_OPENSSL
586         XPUT("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
587         XUnbuffer();
588         CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
589         if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
590 #else
591         XPUT("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
592         CC->kill_me = KILLME_NO_CRYPTO;
593 #endif
594 }
595
596 void xmpp_end_ping(void *data, const char *supplied_el, const char **attr)
597 {
598         XMPP->ping_requested = 1;
599 }
600
601 void xmpp_end_stream(void *data, const char *supplied_el, const char **attr)
602 {
603         XMPPM_syslog(LOG_DEBUG, "XMPP client shut down their stream\n");
604         xmpp_massacre_roster();
605         XPUT("</stream>\n");
606         CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
607 }
608
609 void xmpp_xml_end(void *data, const char *supplied_el)
610 {
611         const char *pToken;
612         const char *pch;
613         long len;
614         void *pv;
615
616         /* Axe the namespace, we don't care about it */
617         pToken = supplied_el;
618         pch = strchr(pToken, ':');
619         while (pch != NULL)
620         {
621                 pToken = pch;
622                 pch = strchr(pToken  + 1, ':');
623         }
624
625         if (*pToken == ':')
626                 pToken ++;
627
628         len = strlen(pToken);
629
630         /*
631         XMPP_syslog(LOG_DEBUG, "XMPP ELEMENT END  : <%s>\n", el);
632         if (XMPP->chardata_len > 0) {
633                 XMPP_syslog(LOG_DEBUG, "          chardata: %s\n", XMPP->chardata);
634         }
635         uncomment for more verbosity */
636
637         if (GetHash(XMPP_EndHandlers, pToken, len, &pv))
638         {
639                 xmpp_handler *h;
640                 h = (xmpp_handler*) pv;
641                 h->Handler(data, supplied_el, NULL);
642         }
643         else
644         {
645                 XMPP_syslog(LOG_DEBUG, "Ignoring unknown tag <%s>\n", pToken);
646         }
647
648         XMPP->chardata_len = 0;
649         if (XMPP->chardata_alloc > 0) {
650                 XMPP->chardata[0] = 0;
651         }
652 }
653
654
655 void xmpp_xml_chardata(void *data, const XML_Char *s, int len)
656 {
657         citxmpp *X = XMPP;
658
659         if (X->chardata_alloc == 0) {
660                 X->chardata_alloc = SIZ;
661                 X->chardata = malloc(X->chardata_alloc);
662         }
663         if ((X->chardata_len + len + 1) > X->chardata_alloc) {
664                 X->chardata_alloc = X->chardata_len + len + 1024;
665                 X->chardata = realloc(X->chardata, X->chardata_alloc);
666         }
667         memcpy(&X->chardata[X->chardata_len], s, len);
668         X->chardata_len += len;
669         X->chardata[X->chardata_len] = 0;
670 }
671
672
673 /*
674  * This cleanup function blows away the temporary memory and files used by the XMPP service.
675  */
676 void xmpp_cleanup_function(void) {
677
678         /* Don't do this stuff if this is not a XMPP session! */
679         if (CC->h_command_function != xmpp_command_loop) return;
680
681         if (XMPP->chardata != NULL) {
682                 free(XMPP->chardata);
683                 XMPP->chardata = NULL;
684                 XMPP->chardata_len = 0;
685                 XMPP->chardata_alloc = 0;
686                 if (XMPP->message_body != NULL) {
687                         free(XMPP->message_body);
688                 }
689         }
690         free_buf_iq(&XMPP->IQ);
691
692         XML_ParserFree(XMPP->xp);
693         FreeStrBuf(&XMPP->OutBuf);
694         free(XMPP);
695 }
696
697
698
699 /*
700  * Here's where our XMPP session begins its happy day.
701  */
702 void xmpp_greeting(void) {
703         client_set_inbound_buf(4);
704         strcpy(CC->cs_clientname, "XMPP session");
705         CC->session_specific_data = malloc(sizeof(citxmpp));
706         memset(XMPP, 0, sizeof(citxmpp));
707         XMPP->last_event_processed = queue_event_seq;
708         XMPP->OutBuf = NewStrBufPlain(NULL, SIZ);
709         /* XMPP does not use a greeting, but we still have to initialize some things. */
710
711         XMPP->xp = XML_ParserCreateNS("UTF-8", ':');
712         if (XMPP->xp == NULL) {
713                 XMPPM_syslog(LOG_ALERT, "Cannot create XML parser!\n");
714                 CC->kill_me = KILLME_XML_PARSER;
715                 return;
716         }
717
718         XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end);
719         XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata);
720         // XML_SetUserData(XMPP->xp, something...);
721
722         /* Prevent the "billion laughs" attack against expat by disabling
723          * internal entity expansion.  With 2.x, forcibly stop the parser
724          * if an entity is declared - this is safer and a more obvious
725          * failure mode.  With older versions, simply prevent expansion
726          * of such entities. */
727 #ifdef HAVE_XML_STOPPARSER
728         XML_SetEntityDeclHandler(XMPP->xp, xmpp_entity_declaration);
729 #else
730         XML_SetDefaultHandler(XMPP->xp, NULL);
731 #endif
732
733         CC->can_receive_im = 1;         /* This protocol is capable of receiving instant messages */
734         XUnbuffer();
735 }
736
737
738 /* 
739  * Main command loop for XMPP sessions.
740  */
741 void xmpp_command_loop(void) {
742         int rc;
743         StrBuf *stream_input = NewStrBuf();
744
745         time(&CC->lastcmd);
746         rc = client_read_random_blob(stream_input, 30);
747         if (rc > 0) {
748                 XML_Parse(XMPP->xp, ChrPtr(stream_input), rc, 0);
749         }
750         else {
751                 XMPPM_syslog(LOG_ERR, "client disconnected: ending session.\n");
752                 CC->kill_me = KILLME_CLIENT_DISCONNECTED;
753         }
754         FreeStrBuf(&stream_input);
755         XUnbuffer();
756 }
757
758
759 /*
760  * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
761  */
762 void xmpp_async_loop(void) {
763         xmpp_process_events();
764         xmpp_output_incoming_messages();
765 }
766
767
768 /*
769  * Login hook for XMPP sessions
770  */
771 void xmpp_login_hook(void) {
772         xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_inet_email);
773 }
774
775
776 /*
777  * Logout hook for XMPP sessions
778  */
779 void xmpp_logout_hook(void) {
780         xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_inet_email);
781 }
782
783
784 void LogXMPPSrvDebugEnable(const int n)
785 {
786         XMPPSrvDebugEnable = n;
787 }
788 const char *CitadelServiceXMPP="XMPP";
789
790
791
792 /******************************************************************************
793  *                    XMPP handler registering logic                           *
794  ******************************************************************************/
795
796 void AddXMPPStartHandler(const char *key,
797                          long len,
798                          xmpp_handler_func Handler,
799                          int Flags)
800 {
801         xmpp_handler *h;
802         h = (xmpp_handler*) malloc(sizeof (xmpp_handler));
803         h->Flags = Flags;
804         h->Handler = Handler;
805         Put(XMPP_StartHandlers, key, len, h, NULL);
806 }
807
808 void AddXMPPEndHandler(const char *key,
809                        long len,
810                        xmpp_handler_func Handler,
811                        int Flags)
812 {
813         xmpp_handler *h;
814         h = (xmpp_handler*) malloc(sizeof (xmpp_handler));
815         h->Flags = Flags;
816         h->Handler = Handler;
817         Put(XMPP_EndHandlers, key, len, h, NULL);
818 }
819
820 void HFreePropertyHandler(void *FreeMe)
821 {
822         free(FreeMe);
823 }
824
825 void HDeleteTokenHandler(void *FreeMe)
826 {
827         TokenHandler *th = (TokenHandler *) FreeMe;
828         DeleteHash(&th->Properties);
829         free(th);
830 }
831
832 void XMPP_RegisterTokenProperty(const char *NS, long NSLen,
833                                 const char *Token, long TLen,
834                                 const char *Property, long PLen,
835                                 GetTokenDataFunc GetToken,
836                                 long offset)
837 {
838         void *pv;
839         HashList *ThisNamespace = NULL;
840         PropertyHandler *h;
841         TokenHandler *th;
842
843         h = (PropertyHandler*) malloc(sizeof(PropertyHandler));
844         h->NameSpace = NS;
845         h->NameSpaceLen = NSLen;
846         h->Token = Token;
847         h->TokenLen = TLen;
848         h->Property = Property;
849         h->PropertyLen = PLen;
850         h->offset = offset;
851
852         if (!GetHash(XMPP_SupportedNamespaces, NS, NSLen, &pv))
853         {
854                 Put(XMPP_SupportedNamespaces, NS, NSLen, NewStrBufPlain(NS, NSLen), HFreeStrBuf);
855         }
856                 
857         
858         if (GetHash(XMPP_NameSpaces, NS, NSLen, &pv))
859         {
860                 ThisNamespace = pv;
861         }
862         else
863         {
864                 ThisNamespace = NewHash(1, NULL);
865                 Put(XMPP_NameSpaces, NS, NSLen, ThisNamespace, HDeleteHash);
866         }
867
868         if (GetHash(ThisNamespace, Token, TLen, &pv))
869         {
870                 th = pv;
871         }
872         else
873         {
874                 th = (TokenHandler*) malloc (sizeof(TokenHandler));
875                 th->GetToken = GetToken;
876                 th->Properties = NewHash(1, NULL);
877                 Put(ThisNamespace, Token, TLen, th, HDeleteTokenHandler);
878         }
879
880
881         if (PLen > 0)
882                 Put(th->Properties, Property, PLen, h, HFreePropertyHandler);
883         /*
884         if (!GetHash(FlatToken, Token, TLen, &pv))
885         {
886                 // todo mark pv as non uniq
887                 Put(FlatToken, Token, TLen, ThisToken, reference_free_handler);
888         }       
889         */
890 }
891
892 void xmpp_cleanup(void)
893 {
894         DeleteHash(&XMPP_StartHandlers);
895         DeleteHash(&XMPP_EndHandlers);
896         DeleteHash(&XMPP_SupportedNamespaces);
897         DeleteHash(&XMPP_NameSpaces);
898         DeleteHash(&FlatToken);
899 }
900
901 CTDL_MODULE_INIT(xmpp)
902 {
903         if (!threading) {
904                 CtdlRegisterServiceHook(config.c_xmpp_c2s_port,
905                                         NULL,
906                                         xmpp_greeting,
907                                         xmpp_command_loop,
908                                         xmpp_async_loop,
909                                         CitadelServiceXMPP);
910
911
912                 XMPP_StartHandlers = NewHash(1, NULL);
913                 XMPP_EndHandlers = NewHash(1, NULL);
914                 XMPP_NameSpaces = NewHash(1, NULL);
915                 XMPP_SupportedNamespaces = NewHash(1, NULL);
916                 FlatToken = NewHash(1, NULL);
917
918                 AddXMPPEndHandler(HKEY("resource"),      xmpp_end_resource, 0);
919                 AddXMPPEndHandler(HKEY("username"),      xmpp_end_username, 0);
920                 AddXMPPEndHandler(HKEY("password"),      xmpp_end_password, 0);
921                 AddXMPPEndHandler(HKEY("iq"),            xmpp_end_iq, 0);
922                 AddXMPPEndHandler(HKEY("auth"),          xmpp_end_auth, 0);
923                 AddXMPPEndHandler(HKEY("session"),       xmpp_end_session, 0);
924                 AddXMPPEndHandler(HKEY("body"),          xmpp_end_body, 0);
925                 AddXMPPEndHandler(HKEY("html"),          xmpp_end_html, 0);
926                 AddXMPPEndHandler(HKEY("starttls"),      xmpp_end_starttls, 0);
927                 AddXMPPEndHandler(HKEY("ping"),          xmpp_end_ping, 0);
928                 AddXMPPEndHandler(HKEY("stream"),        xmpp_end_stream, 0);
929
930                 AddXMPPStartHandler(HKEY("stream"),     xmpp_stream_start, 0);
931                 AddXMPPStartHandler(HKEY("query"),      xmpp_start_query, 0);
932                 AddXMPPStartHandler(HKEY("bind"),       xmpp_start_bind, 0);
933                 AddXMPPStartHandler(HKEY("auth"),       xmpp_start_auth, 0);
934 ///             AddXMPPStartHandler(HKEY("message"),    xmpp_start_message, 0);
935                 AddXMPPStartHandler(HKEY("html"),       xmpp_start_html, 0);
936
937
938                 CtdlRegisterDebugFlagHook(HKEY("serv_xmpp"), LogXMPPSrvDebugEnable, &XMPPSrvDebugEnable);
939                 CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP, PRIO_STOP + 70);
940                 CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN, PRIO_LOGIN + 90);
941                 CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 90);
942                 CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH, PRIO_UNSTEALTH + 1);
943                 CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH, PRIO_STEALTH + 1);
944                 CtdlRegisterCleanupHook(xmpp_cleanup);
945         }
946
947         /* return our module name for the log */
948         return "xmpp";
949 }
950
951