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