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