* More license declarations
[citadel.git] / citadel / modules / jabber / serv_xmpp.c
1 /*
2  * $Id$ 
3  *
4  * XMPP (Jabber) service for the Citadel system
5  * Copyright (c) 2007-2009 by Art Cancro
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "sysdep.h"
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <stdio.h>
26 #include <fcntl.h>
27 #include <signal.h>
28 #include <pwd.h>
29 #include <errno.h>
30 #include <sys/types.h>
31
32 #if TIME_WITH_SYS_TIME
33 # include <sys/time.h>
34 # include <time.h>
35 #else
36 # if HAVE_SYS_TIME_H
37 #  include <sys/time.h>
38 # else
39 #  include <time.h>
40 # endif
41 #endif
42
43 #include <sys/wait.h>
44 #include <string.h>
45 #include <limits.h>
46 #include <ctype.h>
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 "room_ops.h"
55 #include "user_ops.h"
56 #include "policy.h"
57 #include "database.h"
58 #include "msgbase.h"
59 #include "internet_addressing.h"
60 #include "md5.h"
61 #include "ctdl_module.h"
62 #include "serv_xmpp.h"
63
64 struct xmpp_event *xmpp_queue = NULL;
65
66 /* We have just received a <stream> tag from the client, so send them ours */
67
68 void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
69 {
70         while (*attr) {
71                 if (!strcasecmp(attr[0], "to")) {
72                         safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name);
73                 }
74                 attr += 2;
75         }
76
77         cprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
78
79         cprintf("<stream:stream ");
80         cprintf("from=\"%s\" ", XMPP->server_name);
81         cprintf("id=\"%08x\" ", CC->cs_pid);
82         cprintf("version=\"1.0\" ");
83         cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
84         cprintf("xmlns=\"jabber:client\">");
85
86         /* The features of this stream are... */
87         cprintf("<stream:features>");
88
89 #ifdef HAVE_OPENSSL_XXXX_COMMENTED_OUT
90         /* TLS encryption (but only if it isn't already active) */
91         if (!CC->redirect_ssl) {
92                 cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
93         }
94 #endif
95
96         if (!CC->logged_in) {
97                 /* If we're not logged in yet, offer SASL as our feature set */
98                 xmpp_output_auth_mechs();
99
100                 /* Also offer non-SASL authentication */
101                 cprintf("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
102         }
103
104         /* Offer binding and sessions as part of our feature set */
105         cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
106         cprintf("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
107
108         cprintf("</stream:features>");
109
110         CC->is_async = 1;               /* XMPP sessions are inherently async-capable */
111 }
112
113
114 void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) {
115         char el[256];
116         char *sep = NULL;
117         int i;
118
119         /* Axe the namespace, we don't care about it */
120         safestrncpy(el, supplied_el, sizeof el);
121         while (sep = strchr(el, ':'), sep) {
122                 strcpy(el, ++sep);
123         }
124
125         /*
126         CtdlLogPrintf(CTDL_DEBUG, "XMPP ELEMENT START: <%s>\n", el);
127         for (i=0; attr[i] != NULL; i+=2) {
128                 CtdlLogPrintf(CTDL_DEBUG, "                    Attribute '%s' = '%s'\n", attr[i], attr[i+1]);
129         }
130         uncomment for more verbosity */
131
132         if (!strcasecmp(el, "stream")) {
133                 xmpp_stream_start(data, supplied_el, attr);
134         }
135
136         else if (!strcasecmp(el, "query")) {
137                 XMPP->iq_query_xmlns[0] = 0;
138                 safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns);
139         }
140
141         else if (!strcasecmp(el, "bind")) {
142                 XMPP->bind_requested = 1;
143         }
144
145         else if (!strcasecmp(el, "iq")) {
146                 for (i=0; attr[i] != NULL; i+=2) {
147                         if (!strcasecmp(attr[i], "type")) {
148                                 safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type);
149                         }
150                         else if (!strcasecmp(attr[i], "id")) {
151                                 safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id);
152                         }
153                         else if (!strcasecmp(attr[i], "from")) {
154                                 safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from);
155                         }
156                         else if (!strcasecmp(attr[i], "to")) {
157                                 safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to);
158                         }
159                 }
160         }
161
162         else if (!strcasecmp(el, "auth")) {
163                 XMPP->sasl_auth_mech[0] = 0;
164                 for (i=0; attr[i] != NULL; i+=2) {
165                         if (!strcasecmp(attr[i], "mechanism")) {
166                                 safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech);
167                         }
168                 }
169         }
170
171         else if (!strcasecmp(el, "message")) {
172                 for (i=0; attr[i] != NULL; i+=2) {
173                         if (!strcasecmp(attr[i], "to")) {
174                                 safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to);
175                         }
176                 }
177         }
178
179         else if (!strcasecmp(el, "html")) {
180                 ++XMPP->html_tag_level;
181         }
182 }
183
184
185
186 void xmpp_xml_end(void *data, const char *supplied_el) {
187         char el[256];
188         char *sep = NULL;
189
190         /* Axe the namespace, we don't care about it */
191         safestrncpy(el, supplied_el, sizeof el);
192         while (sep = strchr(el, ':'), sep) {
193                 strcpy(el, ++sep);
194         }
195
196         /*
197         CtdlLogPrintf(CTDL_DEBUG, "XMPP ELEMENT END  : <%s>\n", el);
198         if (XMPP->chardata_len > 0) {
199                 CtdlLogPrintf(CTDL_DEBUG, "          chardata: %s\n", XMPP->chardata);
200         }
201         uncomment for more verbosity */
202
203         if (!strcasecmp(el, "resource")) {
204                 if (XMPP->chardata_len > 0) {
205                         safestrncpy(XMPP->iq_client_resource, XMPP->chardata,
206                                 sizeof XMPP->iq_client_resource);
207                         striplt(XMPP->iq_client_resource);
208                 }
209         }
210
211         if (!strcasecmp(el, "username")) {              /* NON SASL ONLY */
212                 if (XMPP->chardata_len > 0) {
213                         safestrncpy(XMPP->iq_client_username, XMPP->chardata,
214                                 sizeof XMPP->iq_client_username);
215                         striplt(XMPP->iq_client_username);
216                 }
217         }
218
219         if (!strcasecmp(el, "password")) {              /* NON SASL ONLY */
220                 if (XMPP->chardata_len > 0) {
221                         safestrncpy(XMPP->iq_client_password, XMPP->chardata,
222                                 sizeof XMPP->iq_client_password);
223                         striplt(XMPP->iq_client_password);
224                 }
225         }
226
227         else if (!strcasecmp(el, "iq")) {
228
229                 /*
230                  * iq type="get" (handle queries)
231                  */
232                 if (!strcasecmp(XMPP->iq_type, "get")) {
233
234                         /*
235                          * Query on a namespace
236                          */
237                         if (!IsEmptyStr(XMPP->iq_query_xmlns)) {
238                                 xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from,
239                                                 XMPP->iq_to, XMPP->iq_query_xmlns);
240                         }
241
242                         /*
243                          * Unknown queries ... return the XML equivalent of a blank stare
244                          */
245                         else {
246                                 cprintf("<iq type=\"result\" id=\"%s\">", XMPP->iq_id);
247                                 cprintf("</iq>");
248                         }
249                 }
250
251                 /*
252                  * Non SASL authentication
253                  */
254                 else if (
255                         (!strcasecmp(XMPP->iq_type, "set"))
256                         && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query"))
257                         ) {
258
259                         jabber_non_sasl_authenticate(
260                                 XMPP->iq_id,
261                                 XMPP->iq_client_username,
262                                 XMPP->iq_client_password,
263                                 XMPP->iq_client_resource
264                         );
265                 }       
266
267                 /*
268                  * If this <iq> stanza was a "bind" attempt, process it ...
269                  */
270                 else if (
271                         (XMPP->bind_requested)
272                         && (!IsEmptyStr(XMPP->iq_id))
273                         && (!IsEmptyStr(XMPP->iq_client_resource))
274                         && (CC->logged_in)
275                         ) {
276
277                         /* Generate the "full JID" of the client resource */
278
279                         snprintf(XMPP->client_jid, sizeof XMPP->client_jid,
280                                 "%s/%s",
281                                 CC->cs_inet_email,
282                                 XMPP->iq_client_resource
283                         );
284
285                         /* Tell the client what its JID is */
286
287                         cprintf("<iq type=\"result\" id=\"%s\">", XMPP->iq_id);
288                         cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
289                         cprintf("<jid>%s</jid>", XMPP->client_jid);
290                         cprintf("</bind>");
291                         cprintf("</iq>");
292                 }
293
294                 else if (XMPP->iq_session) {
295                         cprintf("<iq type=\"result\" id=\"%s\">", XMPP->iq_id);
296                         cprintf("</iq>");
297                 }
298
299                 else {
300                         cprintf("<iq type=\"error\" id=\"%s\">", XMPP->iq_id);
301                         cprintf("<error></error>");
302                         cprintf("</iq>");
303                 }
304
305                 /* Now clear these fields out so they don't get used by a future stanza */
306                 XMPP->iq_id[0] = 0;
307                 XMPP->iq_from[0] = 0;
308                 XMPP->iq_to[0] = 0;
309                 XMPP->iq_type[0] = 0;
310                 XMPP->iq_client_resource[0] = 0;
311                 XMPP->iq_session = 0;
312                 XMPP->iq_query_xmlns[0] = 0;
313                 XMPP->bind_requested = 0;
314         }
315
316         else if (!strcasecmp(el, "auth")) {
317
318                 /* Try to authenticate (this function is responsible for the output stanza) */
319                 xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") );
320
321                 /* Now clear these fields out so they don't get used by a future stanza */
322                 XMPP->sasl_auth_mech[0] = 0;
323         }
324
325         else if (!strcasecmp(el, "session")) {
326                 XMPP->iq_session = 1;
327         }
328
329         else if (!strcasecmp(el, "presence")) {
330
331                 /* Respond to a <presence> update by firing back with presence information
332                  * on the entire wholist.  Check this assumption, it's probably wrong.
333                  */
334                 jabber_wholist_presence_dump();
335         }
336
337         else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) {
338                 if (XMPP->message_body != NULL) {
339                         free(XMPP->message_body);
340                         XMPP->message_body = NULL;
341                 }
342                 if (XMPP->chardata_len > 0) {
343                         XMPP->message_body = strdup(XMPP->chardata);
344                 }
345         }
346
347         else if (!strcasecmp(el, "message")) {
348                 jabber_send_message(XMPP->message_to, XMPP->message_body);
349                 XMPP->html_tag_level = 0;
350         }
351
352         else if (!strcasecmp(el, "html")) {
353                 --XMPP->html_tag_level;
354         }
355
356         else if (!strcasecmp(el, "starttls")) {
357 #ifdef HAVE_OPENSSL
358         cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
359         CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
360         if (!CC->redirect_ssl) CC->kill_me = 1;
361 #else
362         cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
363         CC->kill_me = 1;
364 #endif
365         }
366
367         XMPP->chardata_len = 0;
368         if (XMPP->chardata_alloc > 0) {
369                 XMPP->chardata[0] = 0;
370         }
371 }
372
373
374 void xmpp_xml_chardata(void *data, const XML_Char *s, int len)
375 {
376         struct citxmpp *X = XMPP;
377
378         if (X->chardata_alloc == 0) {
379                 X->chardata_alloc = SIZ;
380                 X->chardata = malloc(X->chardata_alloc);
381         }
382         if ((X->chardata_len + len + 1) > X->chardata_alloc) {
383                 X->chardata_alloc = X->chardata_len + len + 1024;
384                 X->chardata = realloc(X->chardata, X->chardata_alloc);
385         }
386         memcpy(&X->chardata[X->chardata_len], s, len);
387         X->chardata_len += len;
388         X->chardata[X->chardata_len] = 0;
389 }
390
391
392 /*
393  * This cleanup function blows away the temporary memory and files used by the XMPP service.
394  */
395 void xmpp_cleanup_function(void) {
396
397         /* Don't do this stuff if this is not a XMPP session! */
398         if (CC->h_command_function != xmpp_command_loop) return;
399
400         if (XMPP->chardata != NULL) {
401                 free(XMPP->chardata);
402                 XMPP->chardata = NULL;
403                 XMPP->chardata_len = 0;
404                 XMPP->chardata_alloc = 0;
405                 if (XMPP->message_body != NULL) {
406                         free(XMPP->message_body);
407                 }
408         }
409         XML_ParserFree(XMPP->xp);
410         free(XMPP);
411 }
412
413
414
415 /*
416  * Here's where our XMPP session begins its happy day.
417  */
418 void xmpp_greeting(void) {
419         strcpy(CC->cs_clientname, "Jabber session");
420         CC->session_specific_data = malloc(sizeof(struct citxmpp));
421         memset(XMPP, 0, sizeof(struct citxmpp));
422         XMPP->last_event_processed = queue_event_seq;
423
424         /* XMPP does not use a greeting, but we still have to initialize some things. */
425
426         XMPP->xp = XML_ParserCreateNS("UTF-8", ':');
427         if (XMPP->xp == NULL) {
428                 CtdlLogPrintf(CTDL_ALERT, "Cannot create XML parser!\n");
429                 CC->kill_me = 1;
430                 return;
431         }
432
433         XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end);
434         XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata);
435         // XML_SetUserData(XMPP->xp, something...);
436
437         CC->can_receive_im = 1;         /* This protocol is capable of receiving instant messages */
438 }
439
440
441 /* 
442  * Main command loop for XMPP sessions.
443  */
444 void xmpp_command_loop(void) {
445         char cmdbuf[16];
446         int retval;
447
448         time(&CC->lastcmd);
449         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
450         retval = client_read(cmdbuf, 1);
451         if (retval != 1) {
452                 CtdlLogPrintf(CTDL_ERR, "Client disconnected: ending session.\r\n");
453                 CC->kill_me = 1;
454                 return;
455         }
456
457         /* FIXME ... this is woefully inefficient. */
458
459         XML_Parse(XMPP->xp, cmdbuf, 1, 0);
460 }
461
462
463 /*
464  * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
465  */
466 void xmpp_async_loop(void) {
467         xmpp_process_events();
468         jabber_output_incoming_messages();
469 }
470
471
472 /*
473  * Login hook for XMPP sessions
474  */
475 void xmpp_login_hook(void) {
476         xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_inet_email);
477 }
478
479
480 /*
481  * Logout hook for XMPP sessions
482  */
483 void xmpp_logout_hook(void) {
484         xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_inet_email);
485 }
486
487
488 const char *CitadelServiceXMPP="XMPP";
489
490 CTDL_MODULE_INIT(jabber)
491 {
492         if (!threading) {
493                 CtdlRegisterServiceHook(config.c_xmpp_c2s_port,
494                                         NULL,
495                                         xmpp_greeting,
496                                         xmpp_command_loop,
497                                         xmpp_async_loop,
498                                         CitadelServiceXMPP);
499                 CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP);
500                 CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN);
501                 CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT);
502                 CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH);
503                 CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH);
504         }
505
506         /* return our Subversion id for the Log */
507         return "$Id$";
508 }