4 * XMPP (Jabber) service for the Citadel system
5 * Copyright (c) 2007 by Art Cancro
6 * This code is released under the terms of the GNU General Public License.
18 #include <sys/types.h>
20 #if TIME_WITH_SYS_TIME
21 # include <sys/time.h>
25 # include <sys/time.h>
35 #include <libcitadel.h>
39 #include "citserver.h"
47 #include "internet_addressing.h"
49 #include "ctdl_module.h"
50 #include "serv_xmpp.h"
52 struct xmpp_event
*xmpp_queue
= NULL
;
54 /* We have just received a <stream> tag from the client, so send them ours */
56 void xmpp_stream_start(void *data
, const char *supplied_el
, const char **attr
)
58 CtdlLogPrintf(CTDL_DEBUG
, "New XMPP stream.\n");
61 if (!strcasecmp(attr
[0], "to")) {
62 safestrncpy(XMPP
->server_name
, attr
[1], sizeof XMPP
->server_name
);
67 cprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
69 cprintf("<stream:stream ");
70 cprintf("from=\"%s\" ", XMPP
->server_name
);
71 cprintf("id=\"%08x\" ", CC
->cs_pid
);
72 cprintf("version=\"1.0\" ");
73 cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
74 cprintf("xmlns=\"jabber:client\">");
76 /* The features of this stream are... */
77 cprintf("<stream:features>");
79 #ifdef HAVE_OPENSSL_XXXX_COMMENTED_OUT
80 /* TLS encryption (but only if it isn't already active) */
81 if (!CC
->redirect_ssl
) {
82 cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
87 /* If we're not logged in yet, offer SASL as our feature set */
88 xmpp_output_auth_mechs();
90 /* Also offer non-SASL authentication */
91 cprintf("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
94 /* Offer binding and sessions as part of our feature set */
95 cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
96 cprintf("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
98 cprintf("</stream:features>");
100 CC
->is_async
= 1; /* XMPP sessions are inherently async-capable */
104 void xmpp_xml_start(void *data
, const char *supplied_el
, const char **attr
) {
109 /* Axe the namespace, we don't care about it */
110 safestrncpy(el
, supplied_el
, sizeof el
);
111 while (sep
= strchr(el
, ':'), sep
) {
115 CtdlLogPrintf(CTDL_DEBUG
, "XMPP ELEMENT START: <%s>\n", el
);
117 for (i
=0; attr
[i
] != NULL
; i
+=2) {
118 CtdlLogPrintf(CTDL_DEBUG
, " Attribute '%s' = '%s'\n", attr
[i
], attr
[i
+1]);
121 if (!strcasecmp(el
, "stream")) {
122 xmpp_stream_start(data
, supplied_el
, attr
);
125 else if (!strcasecmp(el
, "query")) {
126 XMPP
->iq_query_xmlns
[0] = 0;
127 safestrncpy(XMPP
->iq_query_xmlns
, supplied_el
, sizeof XMPP
->iq_query_xmlns
);
130 else if (!strcasecmp(el
, "bind")) {
131 XMPP
->bind_requested
= 1;
134 else if (!strcasecmp(el
, "iq")) {
135 for (i
=0; attr
[i
] != NULL
; i
+=2) {
136 if (!strcasecmp(attr
[i
], "type")) {
137 safestrncpy(XMPP
->iq_type
, attr
[i
+1], sizeof XMPP
->iq_type
);
139 else if (!strcasecmp(attr
[i
], "id")) {
140 safestrncpy(XMPP
->iq_id
, attr
[i
+1], sizeof XMPP
->iq_id
);
142 else if (!strcasecmp(attr
[i
], "from")) {
143 safestrncpy(XMPP
->iq_from
, attr
[i
+1], sizeof XMPP
->iq_from
);
145 else if (!strcasecmp(attr
[i
], "to")) {
146 safestrncpy(XMPP
->iq_to
, attr
[i
+1], sizeof XMPP
->iq_to
);
151 else if (!strcasecmp(el
, "auth")) {
152 XMPP
->sasl_auth_mech
[0] = 0;
153 for (i
=0; attr
[i
] != NULL
; i
+=2) {
154 if (!strcasecmp(attr
[i
], "mechanism")) {
155 safestrncpy(XMPP
->sasl_auth_mech
, attr
[i
+1], sizeof XMPP
->sasl_auth_mech
);
160 else if (!strcasecmp(el
, "message")) {
161 for (i
=0; attr
[i
] != NULL
; i
+=2) {
162 if (!strcasecmp(attr
[i
], "to")) {
163 safestrncpy(XMPP
->message_to
, attr
[i
+1], sizeof XMPP
->message_to
);
168 else if (!strcasecmp(el
, "html")) {
169 ++XMPP
->html_tag_level
;
175 void xmpp_xml_end(void *data
, const char *supplied_el
) {
179 /* Axe the namespace, we don't care about it */
180 safestrncpy(el
, supplied_el
, sizeof el
);
181 while (sep
= strchr(el
, ':'), sep
) {
185 CtdlLogPrintf(CTDL_DEBUG
, "XMPP ELEMENT END : <%s>\n", el
);
186 if (XMPP
->chardata_len
> 0) {
187 CtdlLogPrintf(CTDL_DEBUG
, " chardata: %s\n", XMPP
->chardata
);
190 if (!strcasecmp(el
, "resource")) {
191 if (XMPP
->chardata_len
> 0) {
192 safestrncpy(XMPP
->iq_client_resource
, XMPP
->chardata
,
193 sizeof XMPP
->iq_client_resource
);
194 striplt(XMPP
->iq_client_resource
);
198 if (!strcasecmp(el
, "username")) { /* NON SASL ONLY */
199 if (XMPP
->chardata_len
> 0) {
200 safestrncpy(XMPP
->iq_client_username
, XMPP
->chardata
,
201 sizeof XMPP
->iq_client_username
);
202 striplt(XMPP
->iq_client_username
);
206 if (!strcasecmp(el
, "password")) { /* NON SASL ONLY */
207 if (XMPP
->chardata_len
> 0) {
208 safestrncpy(XMPP
->iq_client_password
, XMPP
->chardata
,
209 sizeof XMPP
->iq_client_password
);
210 striplt(XMPP
->iq_client_password
);
214 else if (!strcasecmp(el
, "iq")) {
217 * iq type="get" (handle queries)
219 if (!strcasecmp(XMPP
->iq_type
, "get")) {
222 * Query on a namespace
224 if (!IsEmptyStr(XMPP
->iq_query_xmlns
)) {
225 xmpp_query_namespace(XMPP
->iq_id
, XMPP
->iq_from
,
226 XMPP
->iq_to
, XMPP
->iq_query_xmlns
);
230 * Unknown queries ... return the XML equivalent of a blank stare
233 cprintf("<iq type=\"result\" id=\"%s\">", XMPP
->iq_id
);
239 * Non SASL authentication
242 (!strcasecmp(XMPP
->iq_type
, "set"))
243 && (!strcasecmp(XMPP
->iq_query_xmlns
, "jabber:iq:auth:query"))
246 jabber_non_sasl_authenticate(
248 XMPP
->iq_client_username
,
249 XMPP
->iq_client_password
,
250 XMPP
->iq_client_resource
255 * If this <iq> stanza was a "bind" attempt, process it ...
258 (XMPP
->bind_requested
)
259 && (!IsEmptyStr(XMPP
->iq_id
))
260 && (!IsEmptyStr(XMPP
->iq_client_resource
))
264 /* Generate the "full JID" of the client resource */
266 snprintf(XMPP
->client_jid
, sizeof XMPP
->client_jid
,
269 XMPP
->iq_client_resource
272 /* Tell the client what its JID is */
274 cprintf("<iq type=\"result\" id=\"%s\">", XMPP
->iq_id
);
275 cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
276 cprintf("<jid>%s</jid>", XMPP
->client_jid
);
281 else if (XMPP
->iq_session
) {
282 cprintf("<iq type=\"result\" id=\"%s\">", XMPP
->iq_id
);
287 cprintf("<iq type=\"error\" id=\"%s\">", XMPP
->iq_id
);
288 cprintf("<error></error>");
292 /* Now clear these fields out so they don't get used by a future stanza */
294 XMPP
->iq_from
[0] = 0;
296 XMPP
->iq_type
[0] = 0;
297 XMPP
->iq_client_resource
[0] = 0;
298 XMPP
->iq_session
= 0;
299 XMPP
->iq_query_xmlns
[0] = 0;
300 XMPP
->bind_requested
= 0;
303 else if (!strcasecmp(el
, "auth")) {
305 /* Try to authenticate (this function is responsible for the output stanza) */
306 xmpp_sasl_auth(XMPP
->sasl_auth_mech
, (XMPP
->chardata
!= NULL
? XMPP
->chardata
: "") );
308 /* Now clear these fields out so they don't get used by a future stanza */
309 XMPP
->sasl_auth_mech
[0] = 0;
312 else if (!strcasecmp(el
, "session")) {
313 XMPP
->iq_session
= 1;
316 else if (!strcasecmp(el
, "presence")) {
318 /* Respond to a <presence> update by firing back with presence information
319 * on the entire wholist. Check this assumption, it's probably wrong.
321 jabber_wholist_presence_dump();
324 else if ( (!strcasecmp(el
, "body")) && (XMPP
->html_tag_level
== 0) ) {
325 if (XMPP
->message_body
!= NULL
) {
326 free(XMPP
->message_body
);
327 XMPP
->message_body
= NULL
;
329 if (XMPP
->chardata_len
> 0) {
330 XMPP
->message_body
= strdup(XMPP
->chardata
);
334 else if (!strcasecmp(el
, "message")) {
335 jabber_send_message(XMPP
->message_to
, XMPP
->message_body
);
336 XMPP
->html_tag_level
= 0;
339 else if (!strcasecmp(el
, "html")) {
340 --XMPP
->html_tag_level
;
343 else if (!strcasecmp(el
, "starttls")) {
345 cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
346 CtdlModuleStartCryptoMsgs(NULL
, NULL
, NULL
);
347 if (!CC
->redirect_ssl
) CC
->kill_me
= 1;
349 cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
354 XMPP
->chardata_len
= 0;
355 if (XMPP
->chardata_alloc
> 0) {
356 XMPP
->chardata
[0] = 0;
361 void xmpp_xml_chardata(void *data
, const XML_Char
*s
, int len
)
363 struct citxmpp
*X
= XMPP
;
365 if (X
->chardata_alloc
== 0) {
366 X
->chardata_alloc
= SIZ
;
367 X
->chardata
= malloc(X
->chardata_alloc
);
369 if ((X
->chardata_len
+ len
+ 1) > X
->chardata_alloc
) {
370 X
->chardata_alloc
= X
->chardata_len
+ len
+ 1024;
371 X
->chardata
= realloc(X
->chardata
, X
->chardata_alloc
);
373 memcpy(&X
->chardata
[X
->chardata_len
], s
, len
);
374 X
->chardata_len
+= len
;
375 X
->chardata
[X
->chardata_len
] = 0;
380 * This cleanup function blows away the temporary memory and files used by the XMPP service.
382 void xmpp_cleanup_function(void) {
384 /* Don't do this stuff if this is not a XMPP session! */
385 if (CC
->h_command_function
!= xmpp_command_loop
) return;
387 CtdlLogPrintf(CTDL_DEBUG
, "Performing XMPP cleanup hook\n");
388 if (XMPP
->chardata
!= NULL
) {
389 free(XMPP
->chardata
);
390 XMPP
->chardata
= NULL
;
391 XMPP
->chardata_len
= 0;
392 XMPP
->chardata_alloc
= 0;
393 if (XMPP
->message_body
!= NULL
) {
394 free(XMPP
->message_body
);
397 XML_ParserFree(XMPP
->xp
);
404 * Here's where our XMPP session begins its happy day.
406 void xmpp_greeting(void) {
407 strcpy(CC
->cs_clientname
, "Jabber session");
408 CC
->session_specific_data
= malloc(sizeof(struct citxmpp
));
409 memset(XMPP
, 0, sizeof(struct citxmpp
));
410 XMPP
->last_event_processed
= queue_event_seq
;
412 /* XMPP does not use a greeting, but we still have to initialize some things. */
414 XMPP
->xp
= XML_ParserCreateNS("UTF-8", ':');
415 if (XMPP
->xp
== NULL
) {
416 CtdlLogPrintf(CTDL_ALERT
, "Cannot create XML parser!\n");
421 XML_SetElementHandler(XMPP
->xp
, xmpp_xml_start
, xmpp_xml_end
);
422 XML_SetCharacterDataHandler(XMPP
->xp
, xmpp_xml_chardata
);
423 // XML_SetUserData(XMPP->xp, something...);
428 * Main command loop for XMPP sessions.
430 void xmpp_command_loop(void) {
435 memset(cmdbuf
, 0, sizeof cmdbuf
); /* Clear it, just in case */
436 retval
= client_read(cmdbuf
, 1);
438 CtdlLogPrintf(CTDL_ERR
, "Client disconnected: ending session.\r\n");
443 /* FIXME ... this is woefully inefficient. */
445 XML_Parse(XMPP
->xp
, cmdbuf
, 1, 0);
450 * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
452 void xmpp_async_loop(void) {
453 xmpp_process_events();
454 jabber_output_incoming_messages();
459 * Login hook for XMPP sessions
461 void xmpp_login_hook(void) {
462 xmpp_queue_event(XMPP_EVT_LOGIN
, CC
->cs_inet_email
);
467 * Logout hook for XMPP sessions
469 void xmpp_logout_hook(void) {
470 xmpp_queue_event(XMPP_EVT_LOGOUT
, CC
->cs_inet_email
);
474 const char *CitadelServiceXMPP
="XMPP";
476 CTDL_MODULE_INIT(jabber
)
479 CtdlRegisterServiceHook(config
.c_xmpp_c2s_port
,
485 CtdlRegisterSessionHook(xmpp_cleanup_function
, EVT_STOP
);
486 CtdlRegisterSessionHook(xmpp_login_hook
, EVT_LOGIN
);
487 CtdlRegisterSessionHook(xmpp_logout_hook
, EVT_LOGOUT
);
488 CtdlRegisterSessionHook(xmpp_login_hook
, EVT_UNSTEALTH
);
489 CtdlRegisterSessionHook(xmpp_logout_hook
, EVT_STEALTH
);
492 /* return our Subversion id for the Log */