+++ /dev/null
-/*
- * $Id$
- *
- * XMPP (Jabber) service for the Citadel system
- * Copyright (c) 2007-2010 by Art Cancro
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <libcitadel.h>
-#include <expat.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "md5.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-struct xmpp_event *xmpp_queue = NULL;
-
-/* We have just received a <stream> tag from the client, so send them ours */
-
-void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
-{
- while (*attr) {
- if (!strcasecmp(attr[0], "to")) {
- safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name);
- }
- attr += 2;
- }
-
- cprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-
- cprintf("<stream:stream ");
- cprintf("from=\"%s\" ", XMPP->server_name);
- cprintf("id=\"%08x\" ", CC->cs_pid);
- cprintf("version=\"1.0\" ");
- cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
- cprintf("xmlns=\"jabber:client\">");
-
- /* The features of this stream are... */
- cprintf("<stream:features>");
-
-#ifdef HAVE_OPENSSL_XXXX_COMMENTED_OUT
- /* TLS encryption (but only if it isn't already active) */
- if (!CC->redirect_ssl) {
- cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
- }
-#endif
-
- if (!CC->logged_in) {
- /* If we're not logged in yet, offer SASL as our feature set */
- xmpp_output_auth_mechs();
-
- /* Also offer non-SASL authentication */
- cprintf("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
- }
-
- /* Offer binding and sessions as part of our feature set */
- cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
- cprintf("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
-
- cprintf("</stream:features>");
-
- CC->is_async = 1; /* XMPP sessions are inherently async-capable */
-}
-
-
-void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) {
- char el[256];
- char *sep = NULL;
- int i;
-
- /* Axe the namespace, we don't care about it */
- safestrncpy(el, supplied_el, sizeof el);
- while (sep = strchr(el, ':'), sep) {
- strcpy(el, ++sep);
- }
-
- /*
- CtdlLogPrintf(CTDL_DEBUG, "XMPP ELEMENT START: <%s>\n", el);
- for (i=0; attr[i] != NULL; i+=2) {
- CtdlLogPrintf(CTDL_DEBUG, " Attribute '%s' = '%s'\n", attr[i], attr[i+1]);
- }
- uncomment for more verbosity */
-
- if (!strcasecmp(el, "stream")) {
- xmpp_stream_start(data, supplied_el, attr);
- }
-
- else if (!strcasecmp(el, "query")) {
- XMPP->iq_query_xmlns[0] = 0;
- safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns);
- }
-
- else if (!strcasecmp(el, "bind")) {
- XMPP->bind_requested = 1;
- }
-
- else if (!strcasecmp(el, "iq")) {
- for (i=0; attr[i] != NULL; i+=2) {
- if (!strcasecmp(attr[i], "type")) {
- safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type);
- }
- else if (!strcasecmp(attr[i], "id")) {
- safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id);
- }
- else if (!strcasecmp(attr[i], "from")) {
- safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from);
- }
- else if (!strcasecmp(attr[i], "to")) {
- safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to);
- }
- }
- }
-
- else if (!strcasecmp(el, "auth")) {
- XMPP->sasl_auth_mech[0] = 0;
- for (i=0; attr[i] != NULL; i+=2) {
- if (!strcasecmp(attr[i], "mechanism")) {
- safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech);
- }
- }
- }
-
- else if (!strcasecmp(el, "message")) {
- for (i=0; attr[i] != NULL; i+=2) {
- if (!strcasecmp(attr[i], "to")) {
- safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to);
- }
- }
- }
-
- else if (!strcasecmp(el, "html")) {
- ++XMPP->html_tag_level;
- }
-}
-
-
-
-void xmpp_xml_end(void *data, const char *supplied_el) {
- char el[256];
- char *sep = NULL;
-
- /* Axe the namespace, we don't care about it */
- safestrncpy(el, supplied_el, sizeof el);
- while (sep = strchr(el, ':'), sep) {
- strcpy(el, ++sep);
- }
-
- /*
- CtdlLogPrintf(CTDL_DEBUG, "XMPP ELEMENT END : <%s>\n", el);
- if (XMPP->chardata_len > 0) {
- CtdlLogPrintf(CTDL_DEBUG, " chardata: %s\n", XMPP->chardata);
- }
- uncomment for more verbosity */
-
- if (!strcasecmp(el, "resource")) {
- if (XMPP->chardata_len > 0) {
- safestrncpy(XMPP->iq_client_resource, XMPP->chardata,
- sizeof XMPP->iq_client_resource);
- striplt(XMPP->iq_client_resource);
- }
- }
-
- if (!strcasecmp(el, "username")) { /* NON SASL ONLY */
- if (XMPP->chardata_len > 0) {
- safestrncpy(XMPP->iq_client_username, XMPP->chardata,
- sizeof XMPP->iq_client_username);
- striplt(XMPP->iq_client_username);
- }
- }
-
- if (!strcasecmp(el, "password")) { /* NON SASL ONLY */
- if (XMPP->chardata_len > 0) {
- safestrncpy(XMPP->iq_client_password, XMPP->chardata,
- sizeof XMPP->iq_client_password);
- striplt(XMPP->iq_client_password);
- }
- }
-
- else if (!strcasecmp(el, "iq")) {
-
- /*
- * iq type="get" (handle queries)
- */
- if (!strcasecmp(XMPP->iq_type, "get")) {
-
- /*
- * Query on a namespace
- */
- if (!IsEmptyStr(XMPP->iq_query_xmlns)) {
- xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from,
- XMPP->iq_to, XMPP->iq_query_xmlns);
- }
-
- /*
- * ping ( http://xmpp.org/extensions/xep-0199.html )
- */
- else if (XMPP->ping_requested) {
- cprintf("<iq type=\"result\" ");
- if (!IsEmptyStr(XMPP->iq_from)) {
- cprintf("to=\"%s\" ", XMPP->iq_from);
- }
- if (!IsEmptyStr(XMPP->iq_to)) {
- cprintf("from=\"%s\" ", XMPP->iq_to);
- }
- cprintf("id=\"%s\"/>", XMPP->iq_id);
- }
-
- /*
- * Unknown queries ... return the XML equivalent of a blank stare
- */
- else {
- CtdlLogPrintf(CTDL_DEBUG, "Unknown query; <service-unavailable/>\n");
- cprintf("<iq type=\"error\" id=\"%s\">", XMPP->iq_id);
- cprintf("<error code=\"503\" type=\"cancel\">"
- "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
- "</error>"
- );
- cprintf("</iq>");
- }
- }
-
- /*
- * Non SASL authentication
- */
- else if (
- (!strcasecmp(XMPP->iq_type, "set"))
- && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query"))
- ) {
-
- jabber_non_sasl_authenticate(
- XMPP->iq_id,
- XMPP->iq_client_username,
- XMPP->iq_client_password,
- XMPP->iq_client_resource
- );
- }
-
- /*
- * If this <iq> stanza was a "bind" attempt, process it ...
- */
- else if (
- (XMPP->bind_requested)
- && (!IsEmptyStr(XMPP->iq_id))
- && (!IsEmptyStr(XMPP->iq_client_resource))
- && (CC->logged_in)
- ) {
-
- /* Generate the "full JID" of the client resource */
-
- snprintf(XMPP->client_jid, sizeof XMPP->client_jid,
- "%s/%s",
- CC->cs_inet_email,
- XMPP->iq_client_resource
- );
-
- /* Tell the client what its JID is */
-
- cprintf("<iq type=\"result\" id=\"%s\">", XMPP->iq_id);
- cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
- cprintf("<jid>%s</jid>", XMPP->client_jid);
- cprintf("</bind>");
- cprintf("</iq>");
- }
-
- else if (XMPP->iq_session) {
- cprintf("<iq type=\"result\" id=\"%s\">", XMPP->iq_id);
- cprintf("</iq>");
- }
-
- else {
- cprintf("<iq type=\"error\" id=\"%s\">", XMPP->iq_id);
- cprintf("<error></error>");
- cprintf("</iq>");
- }
-
- /* Now clear these fields out so they don't get used by a future stanza */
- XMPP->iq_id[0] = 0;
- XMPP->iq_from[0] = 0;
- XMPP->iq_to[0] = 0;
- XMPP->iq_type[0] = 0;
- XMPP->iq_client_resource[0] = 0;
- XMPP->iq_session = 0;
- XMPP->iq_query_xmlns[0] = 0;
- XMPP->bind_requested = 0;
- XMPP->ping_requested = 0;
- }
-
- else if (!strcasecmp(el, "auth")) {
-
- /* Try to authenticate (this function is responsible for the output stanza) */
- xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") );
-
- /* Now clear these fields out so they don't get used by a future stanza */
- XMPP->sasl_auth_mech[0] = 0;
- }
-
- else if (!strcasecmp(el, "session")) {
- XMPP->iq_session = 1;
- }
-
- else if (!strcasecmp(el, "presence")) {
-
- /* Respond to a <presence> update by firing back with presence information
- * on the entire wholist. Check this assumption, it's probably wrong.
- */
- jabber_wholist_presence_dump();
- }
-
- else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) {
- if (XMPP->message_body != NULL) {
- free(XMPP->message_body);
- XMPP->message_body = NULL;
- }
- if (XMPP->chardata_len > 0) {
- XMPP->message_body = strdup(XMPP->chardata);
- }
- }
-
- else if (!strcasecmp(el, "message")) {
- jabber_send_message(XMPP->message_to, XMPP->message_body);
- XMPP->html_tag_level = 0;
- }
-
- else if (!strcasecmp(el, "html")) {
- --XMPP->html_tag_level;
- }
-
- else if (!strcasecmp(el, "starttls")) {
-#ifdef HAVE_OPENSSL
- cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
- CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
- if (!CC->redirect_ssl) CC->kill_me = 1;
-#else
- cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
- CC->kill_me = 1;
-#endif
- }
-
- else if (!strcasecmp(el, "ping")) {
- XMPP->ping_requested = 1;
- }
-
- XMPP->chardata_len = 0;
- if (XMPP->chardata_alloc > 0) {
- XMPP->chardata[0] = 0;
- }
-}
-
-
-void xmpp_xml_chardata(void *data, const XML_Char *s, int len)
-{
- struct citxmpp *X = XMPP;
-
- if (X->chardata_alloc == 0) {
- X->chardata_alloc = SIZ;
- X->chardata = malloc(X->chardata_alloc);
- }
- if ((X->chardata_len + len + 1) > X->chardata_alloc) {
- X->chardata_alloc = X->chardata_len + len + 1024;
- X->chardata = realloc(X->chardata, X->chardata_alloc);
- }
- memcpy(&X->chardata[X->chardata_len], s, len);
- X->chardata_len += len;
- X->chardata[X->chardata_len] = 0;
-}
-
-
-/*
- * This cleanup function blows away the temporary memory and files used by the XMPP service.
- */
-void xmpp_cleanup_function(void) {
-
- /* Don't do this stuff if this is not a XMPP session! */
- if (CC->h_command_function != xmpp_command_loop) return;
-
- if (XMPP->chardata != NULL) {
- free(XMPP->chardata);
- XMPP->chardata = NULL;
- XMPP->chardata_len = 0;
- XMPP->chardata_alloc = 0;
- if (XMPP->message_body != NULL) {
- free(XMPP->message_body);
- }
- }
- XML_ParserFree(XMPP->xp);
- free(XMPP);
-}
-
-
-
-/*
- * Here's where our XMPP session begins its happy day.
- */
-void xmpp_greeting(void) {
- strcpy(CC->cs_clientname, "Jabber session");
- CC->session_specific_data = malloc(sizeof(struct citxmpp));
- memset(XMPP, 0, sizeof(struct citxmpp));
- XMPP->last_event_processed = queue_event_seq;
-
- /* XMPP does not use a greeting, but we still have to initialize some things. */
-
- XMPP->xp = XML_ParserCreateNS("UTF-8", ':');
- if (XMPP->xp == NULL) {
- CtdlLogPrintf(CTDL_ALERT, "Cannot create XML parser!\n");
- CC->kill_me = 1;
- return;
- }
-
- XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end);
- XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata);
- // XML_SetUserData(XMPP->xp, something...);
-
- CC->can_receive_im = 1; /* This protocol is capable of receiving instant messages */
-}
-
-
-/*
- * Main command loop for XMPP sessions.
- */
-void xmpp_command_loop(void) {
- char cmdbuf[16];
- int retval;
-
- time(&CC->lastcmd);
- memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
- retval = client_read(cmdbuf, 1);
- if (retval != 1) {
- CtdlLogPrintf(CTDL_ERR, "Client disconnected: ending session.\r\n");
- CC->kill_me = 1;
- return;
- }
-
- /* FIXME ... this is woefully inefficient. */
-
- XML_Parse(XMPP->xp, cmdbuf, 1, 0);
-}
-
-
-/*
- * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
- */
-void xmpp_async_loop(void) {
- xmpp_process_events();
- jabber_output_incoming_messages();
-}
-
-
-/*
- * Login hook for XMPP sessions
- */
-void xmpp_login_hook(void) {
- xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_inet_email);
-}
-
-
-/*
- * Logout hook for XMPP sessions
- */
-void xmpp_logout_hook(void) {
- xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_inet_email);
-}
-
-
-const char *CitadelServiceXMPP="XMPP";
-
-CTDL_MODULE_INIT(jabber)
-{
- if (!threading) {
- CtdlRegisterServiceHook(config.c_xmpp_c2s_port,
- NULL,
- xmpp_greeting,
- xmpp_command_loop,
- xmpp_async_loop,
- CitadelServiceXMPP);
- CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP);
- CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN);
- CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT);
- CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH);
- CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH);
- }
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * Copyright (c) 2007-2009 by Art Cancro
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-struct citxmpp { /* Information about the current session */
- XML_Parser xp; /* XML parser instance for incoming client stream */
- char server_name[256]; /* who they think we are */
- char *chardata;
- int chardata_len;
- int chardata_alloc;
- char client_jid[256]; /* "full JID" of the client */
- int last_event_processed;
-
- char iq_type[256]; /* for <iq> stanzas */
- char iq_id[256];
- char iq_from[256];
- char iq_to[256];
- char iq_client_username[256]; /* username requested by the client (NON SASL ONLY) */
- char iq_client_password[256]; /* password requested by the client (NON SASL ONLY) */
- char iq_client_resource[256]; /* resource name requested by the client */
- int iq_session; /* nonzero == client is requesting a session */
- char iq_query_xmlns[256]; /* Namespace of <query> */
-
- char sasl_auth_mech[32]; /* SASL auth mechanism requested by the client */
-
- char message_to[256];
- char *message_body; /* Message body in transit */
- int html_tag_level; /* <html> tag nesting level */
-
- int bind_requested; /* In this stanza, client is asking server to bind a resource. */
- int ping_requested; /* In this stanza, client is pinging the server. */
-};
-
-#define XMPP ((struct citxmpp *)CC->session_specific_data)
-
-struct xmpp_event {
- struct xmpp_event *next;
- int event_seq;
- time_t event_time;
- int event_type;
- char event_jid[256];
- int session_which_generated_this_event;
-};
-
-extern struct xmpp_event *xmpp_queue;
-extern int queue_event_seq;
-
-enum {
- XMPP_EVT_LOGIN,
- XMPP_EVT_LOGOUT
-};
-
-void xmpp_cleanup_function(void);
-void xmpp_greeting(void);
-void xmpp_command_loop(void);
-void xmpp_async_loop(void);
-void xmpp_sasl_auth(char *, char *);
-void xmpp_output_auth_mechs(void);
-void xmpp_query_namespace(char *, char *, char *, char *);
-void jabber_wholist_presence_dump(void);
-void jabber_output_incoming_messages(void);
-void xmpp_queue_event(int, char *);
-void xmpp_process_events(void);
-void xmpp_presence_notify(char *, int);
-void jabber_roster_item(struct CitContext *);
-void jabber_send_message(char *, char *);
-void jabber_non_sasl_authenticate(char *, char *, char *, char *);
+++ /dev/null
-/*
- * $Id$
- *
- * Handle messages sent and received using XMPP (Jabber) protocol
- *
- * Copyright (c) 2007-2009 by Art Cancro
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "md5.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * This function is called by the XMPP service's async loop.
- * If the client session has instant messages waiting, it outputs
- * unsolicited XML stanzas containing them.
- */
-void jabber_output_incoming_messages(void) {
-
- struct ExpressMessage *ptr;
-
- while (CC->FirstExpressMessage != NULL) {
-
- begin_critical_section(S_SESSION_TABLE);
- ptr = CC->FirstExpressMessage;
- CC->FirstExpressMessage = CC->FirstExpressMessage->next;
- end_critical_section(S_SESSION_TABLE);
-
- cprintf("<message to=\"%s\" from=\"%s\" type=\"chat\">",
- XMPP->client_jid,
- ptr->sender_email);
- if (ptr->text != NULL) {
- striplt(ptr->text);
- cprintf("<body>%s</body>", ptr->text);
- free(ptr->text);
- }
- cprintf("</message>");
- free(ptr);
- }
-}
-
-/*
- * Client is sending a message.
- */
-void jabber_send_message(char *message_to, char *message_body) {
- char *recp = NULL;
- int message_sent = 0;
- struct CitContext *cptr;
-
- if (message_body == NULL) return;
- if (message_to == NULL) return;
- if (IsEmptyStr(message_to)) return;
- if (!CC->logged_in) return;
-
- for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
- if ( (cptr->logged_in)
- && (cptr->can_receive_im)
- && (!strcasecmp(cptr->cs_inet_email, message_to))
- ) {
- recp = cptr->user.fullname;
- }
- }
-
- if (recp) {
- message_sent = PerformXmsgHooks(CC->user.fullname, CC->cs_inet_email, recp, message_body);
- }
-
- free(XMPP->message_body);
- XMPP->message_body = NULL;
- XMPP->message_to[0] = 0;
- time(&CC->lastidle);
-}
-
+++ /dev/null
-/*
- * $Id$
- *
- * Handle XMPP presence exchanges
- *
- * Copyright (c) 2007-2009 by Art Cancro
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "md5.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * Initial dump of the entire wholist
- */
-void jabber_wholist_presence_dump(void)
-{
- struct CitContext *cptr = NULL;
- int nContexts, i;
-
- int aide = (CC->user.axlevel >= 6);
-
- cptr = CtdlGetContextArray(&nContexts);
- if (!cptr)
- return ; /** FIXME: Does jabber need to send something to maintain the protocol? */
-
- for (i=0; i<nContexts; i++) {
- if (cptr[i].logged_in) {
- if (
- (((cptr[i].cs_flags&CS_STEALTH)==0) || (aide)) /* aides see everyone */
- && (cptr[i].user.usernum != CC->user.usernum) /* don't show myself */
- && (cptr[i].can_receive_im) /* IM-capable session */
- ) {
- cprintf("<presence type=\"available\" from=\"%s\"></presence>",
- cptr[i].cs_inet_email);
- }
- }
- }
- free(cptr);
-}
-
-
-
-/*
- * When a user logs in or out of the local Citadel system, notify all Jabber sessions
- * about it.
- */
-void xmpp_presence_notify(char *presence_jid, int event_type) {
- struct CitContext *cptr;
- static int unsolicited_id;
- int visible_sessions = 0;
- int nContexts, i;
- int aide = (CC->user.axlevel >= 6);
-
- if (IsEmptyStr(presence_jid)) return;
-
- cptr = CtdlGetContextArray(&nContexts);
- if (!cptr)
- return ; /** FIXME: Does jabber need to send something to maintain the protocol? */
-
- /* Count the visible sessions for this user */
- for (i=0; i<nContexts; i++) {
- if (cptr[i].logged_in) {
- if (
- (!strcasecmp(cptr[i].cs_inet_email, presence_jid))
- && (((cptr[i].cs_flags&CS_STEALTH)==0) || (aide))
- && (cptr[i].can_receive_im)
- ) {
- ++visible_sessions;
- }
- }
- }
-
- CtdlLogPrintf(CTDL_DEBUG, "%d sessions for <%s> are now visible to session %d\n",
- visible_sessions, presence_jid, CC->cs_pid);
-
- if ( (event_type == XMPP_EVT_LOGIN) && (visible_sessions == 1) ) {
-
- CtdlLogPrintf(CTDL_DEBUG, "Telling session %d that <%s> logged in\n", CC->cs_pid, presence_jid);
-
- /* Do an unsolicited roster update that adds a new contact. */
- for (i=0; i<nContexts; i++) {
- if (cptr[i].logged_in) {
- if (!strcasecmp(cptr[i].cs_inet_email, presence_jid)) {
- cprintf("<iq id=\"unsolicited_%x\" type=\"result\">",
- ++unsolicited_id);
- cprintf("<query xmlns=\"jabber:iq:roster\">");
- jabber_roster_item(&cptr[i]);
- cprintf("</query>"
- "</iq>");
- }
- }
- }
-
- /* Transmit presence information */
- cprintf("<presence type=\"available\" from=\"%s\"></presence>", presence_jid);
- }
-
- if (visible_sessions == 0) {
- CtdlLogPrintf(CTDL_DEBUG, "Telling session %d that <%s> logged out\n", CC->cs_pid, presence_jid);
-
- /* Transmit non-presence information */
- cprintf("<presence type=\"unavailable\" from=\"%s\"></presence>", presence_jid);
- cprintf("<presence type=\"unsubscribed\" from=\"%s\"></presence>", presence_jid);
-
- /* Do an unsolicited roster update that deletes the contact. */
- cprintf("<iq id=\"unsolicited_%x\" type=\"result\">", ++unsolicited_id);
- cprintf("<query xmlns=\"jabber:iq:roster\">");
- cprintf("<item jid=\"%s\" subscription=\"remove\">", presence_jid);
- cprintf("<group>%s</group>", config.c_humannode);
- cprintf("</item>");
- cprintf("</query>"
- "</iq>");
- }
- free(cptr);
-}
+++ /dev/null
-/*
- * $Id$
- *
- * Handle <iq> <get> <query> type situations (namespace queries)
- *
- * Copyright (c) 2007-2009 by Art Cancro
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "md5.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * Output a single roster item, for roster queries or pushes
- */
-void jabber_roster_item(struct CitContext *cptr) {
- cprintf("<item jid=\"%s\" name=\"%s\" subscription=\"both\">",
- cptr->cs_inet_email,
- cptr->user.fullname
- );
- cprintf("<group>%s</group>", config.c_humannode);
- cprintf("</item>");
-}
-
-/*
- * Return the results for a "jabber:iq:roster:query"
- *
- * Since we are not yet managing a roster, we simply return the entire wholist
- * (minus any entries for this user -- don't tell me about myself)
- *
- */
-void jabber_iq_roster_query(void)
-{
- struct CitContext *cptr;
- int nContexts, i;
- int aide = (CC->user.axlevel >= 6);
-
- cprintf("<query xmlns=\"jabber:iq:roster\">");
-
- cptr = CtdlGetContextArray(&nContexts);
- if (cptr) {
- for (i=0; i<nContexts; i++) {
- if (cptr[i].logged_in) {
- if (
- (((cptr[i].cs_flags&CS_STEALTH)==0) || (aide))
- && (cptr[i].user.usernum != CC->user.usernum)
- ) {
- jabber_roster_item(&cptr[i]);
- }
- }
- }
- free (cptr);
- }
- cprintf("</query>");
-}
-
-
-/*
- * TODO: handle queries on some or all of these namespaces
- *
-xmpp_query_namespace(purple5b5c1e58, splorph.xand.com, http://jabber.org/protocol/disco#items:query)
-xmpp_query_namespace(purple5b5c1e59, splorph.xand.com, http://jabber.org/protocol/disco#info:query)
-xmpp_query_namespace(purple5b5c1e5a, , vcard-temp:query)
- *
- */
-
-void xmpp_query_namespace(char *iq_id, char *iq_from, char *iq_to, char *query_xmlns)
-{
- int supported_namespace = 0;
-
- /* We need to know before we begin the response whether this is a supported namespace, so
- * unfortunately all supported namespaces need to be defined here *and* down below where
- * they are handled.
- */
- if (
- (!strcasecmp(query_xmlns, "jabber:iq:roster:query"))
- && (!strcasecmp(query_xmlns, "jabber:iq:auth:query"))
- ) {
- supported_namespace = 1;
- }
-
- CtdlLogPrintf(CTDL_DEBUG, "xmpp_query_namespace(%s, %s, %s, %s)\n", iq_id, iq_from, iq_to, query_xmlns);
-
- /*
- * Beginning of query result.
- */
- if (supported_namespace) {
- cprintf("<iq type=\"result\" ");
- }
- else {
- cprintf("<iq type=\"error\" ");
- }
- if (!IsEmptyStr(iq_from)) {
- cprintf("to=\"%s\" ", iq_from);
- }
- cprintf("id=\"%s\">", iq_id);
-
- /*
- * Is this a query we know how to handle?
- */
-
- if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) {
- jabber_iq_roster_query();
- }
-
- else if (!strcasecmp(query_xmlns, "jabber:iq:auth:query")) {
- cprintf("<query xmlns=\"jabber:iq:auth\">"
- "<username/><password/><resource/>"
- "</query>"
- );
- }
-
- else {
- CtdlLogPrintf(CTDL_DEBUG, "Unknown namespace; returning <service-unavailable/>\n");
- cprintf("<error code=\"503\" type=\"cancel\">"
- "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
- "</error>"
- );
- }
-
- /*
- * End of query result. If we didn't hit any known namespaces then we should
- * deliver a "service unavailable" error (see RFC3921 section 2.4 and 11.1.5.4)
- */
- cprintf("</iq>");
-}
+++ /dev/null
-/*
- * $Id$
- *
- * XMPP event queue
- *
- * Copyright (c) 2007-2009 by Art Cancro
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "md5.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-int queue_event_seq = 0;
-
-void xmpp_queue_event(int event_type, char *email_addr) {
-
- struct xmpp_event *xptr = NULL;
- struct xmpp_event *new_event = NULL;
- struct xmpp_event *last = NULL;
- int purged_something = 0;
- struct CitContext *cptr;
-
- CtdlLogPrintf(CTDL_DEBUG, "xmpp_queue_event(%d, %s)\n", event_type, email_addr);
-
- /* Purge events more than a minute old */
- begin_critical_section(S_XMPP_QUEUE);
- do {
- purged_something = 0;
- if (xmpp_queue != NULL) {
- if ((time(NULL) - xmpp_queue->event_time) > 60) {
- xptr = xmpp_queue->next;
- free(xmpp_queue);
- xmpp_queue = xptr;
- purged_something = 1;
- }
- }
- } while(purged_something);
- end_critical_section(S_XMPP_QUEUE);
-
- /* Create a new event */
- new_event = (struct xmpp_event *) malloc(sizeof(struct xmpp_event));
- new_event->next = NULL;
- new_event->event_time = time(NULL);
- new_event->event_seq = ++queue_event_seq;
- new_event->event_type = event_type;
- new_event->session_which_generated_this_event = CC->cs_pid;
- safestrncpy(new_event->event_jid, email_addr, sizeof new_event->event_jid);
-
- /* Add it to the list */
- begin_critical_section(S_XMPP_QUEUE);
- if (xmpp_queue == NULL) {
- xmpp_queue = new_event;
- }
- else {
- for (xptr = xmpp_queue; xptr != NULL; xptr = xptr->next) {
- if (xptr->next == NULL) {
- last = xptr;
- }
- }
- last->next = new_event;
- }
- end_critical_section(S_XMPP_QUEUE);
-
- /* Tell the sessions that something is happening */
- begin_critical_section(S_SESSION_TABLE);
- for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
- if ((cptr->logged_in) && (cptr->h_async_function == xmpp_async_loop)) {
- cptr->async_waiting = 1;
- }
- }
- end_critical_section(S_SESSION_TABLE);
-}
-
-
-/*
- * Are we interested in anything from the queue? (Called in async loop)
- */
-void xmpp_process_events(void) {
- struct xmpp_event *xptr = NULL;
- int highest_event = 0;
-
- for (xptr=xmpp_queue; xptr!=NULL; xptr=xptr->next) {
- if (xptr->event_seq > XMPP->last_event_processed) {
-
- switch(xptr->event_type) {
-
- case XMPP_EVT_LOGIN:
- case XMPP_EVT_LOGOUT:
- if (xptr->session_which_generated_this_event != CC->cs_pid) {
- xmpp_presence_notify(xptr->event_jid, xptr->event_type);
- }
- break;
- }
-
- if (xptr->event_seq > highest_event) {
- highest_event = xptr->event_seq;
- }
- }
- }
-
- XMPP->last_event_processed = highest_event;
-}
+++ /dev/null
-/*
- * $Id$
- *
- * Barebones SASL authentication service for XMPP (Jabber) clients.
- *
- * Note: RFC3920 says we "must" support DIGEST-MD5 but we only support PLAIN.
- *
- * Copyright (c) 2007-2009 by Art Cancro
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-#include "md5.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * PLAIN authentication. Returns zero on success, nonzero on failure.
- */
-int xmpp_auth_plain(char *authstring)
-{
- char decoded_authstring[1024];
- char ident[256];
- char user[256];
- char pass[256];
- int result;
-
-
- /* Take apart the authentication string */
- memset(pass, 0, sizeof(pass));
-
- CtdlDecodeBase64(decoded_authstring, authstring, strlen(authstring));
- safestrncpy(ident, decoded_authstring, sizeof ident);
- safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
- safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
-
-
- /* If there are underscores in either string, change them to spaces. Some clients
- * do not allow spaces so we can tell the user to substitute underscores if their
- * login name contains spaces.
- */
- convert_spaces_to_underscores(ident);
- convert_spaces_to_underscores(user);
-
- /* Now attempt authentication */
-
- if (!IsEmptyStr(ident)) {
- result = CtdlLoginExistingUser(user, ident);
- }
- else {
- result = CtdlLoginExistingUser(NULL, user);
- }
-
- if (result == login_ok) {
- if (CtdlTryPassword(pass) == pass_ok) {
- return(0); /* success */
- }
- }
-
- return(1); /* failure */
-}
-
-
-/*
- * Output the list of SASL mechanisms offered by this stream.
- */
-void xmpp_output_auth_mechs(void) {
- cprintf("<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
- cprintf("<mechanism>PLAIN</mechanism>");
- cprintf("</mechanisms>");
-}
-
-/*
- * Here we go ... client is trying to authenticate.
- */
-void xmpp_sasl_auth(char *sasl_auth_mech, char *authstring) {
-
- if (strcasecmp(sasl_auth_mech, "PLAIN")) {
- cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
- cprintf("<invalid-mechanism/>");
- cprintf("</failure>");
- return;
- }
-
- if (CC->logged_in) CtdlUserLogout(); /* Client may try to log in twice. Handle this. */
-
- if (CC->nologin) {
- cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
- cprintf("<system-shutdown/>");
- cprintf("</failure>");
- }
-
- else if (xmpp_auth_plain(authstring) == 0) {
- cprintf("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>");
- }
-
- else {
- cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
- cprintf("<not-authorized/>");
- cprintf("</failure>");
- }
-}
-
-
-
-/*
- * Non-SASL authentication
- */
-void jabber_non_sasl_authenticate(char *iq_id, char *username, char *password, char *resource) {
- int result;
-
- if (CC->logged_in) CtdlUserLogout(); /* Client may try to log in twice. Handle this. */
-
- result = CtdlLoginExistingUser(NULL, username);
- if (result == login_ok) {
- result = CtdlTryPassword(password);
- if (result == pass_ok) {
- cprintf("<iq type=\"result\" id=\"%s\"></iq>", iq_id); /* success */
- return;
- }
- }
-
- /* failure */
- cprintf("<iq type=\"error\" id=\"%s\">", iq_id);
- cprintf("<error code=\"401\" type=\"auth\">"
- "<not-authorized xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
- "</error>"
- "</iq>"
- );
-}
--- /dev/null
+/*
+ * $Id$
+ *
+ * XMPP (Jabber) service for the Citadel system
+ * Copyright (c) 2007-2010 by Art Cancro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <libcitadel.h>
+#include <expat.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "internet_addressing.h"
+#include "md5.h"
+#include "ctdl_module.h"
+#include "serv_xmpp.h"
+
+struct xmpp_event *xmpp_queue = NULL;
+
+/* We have just received a <stream> tag from the client, so send them ours */
+
+void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
+{
+ while (*attr) {
+ if (!strcasecmp(attr[0], "to")) {
+ safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name);
+ }
+ attr += 2;
+ }
+
+ cprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+
+ cprintf("<stream:stream ");
+ cprintf("from=\"%s\" ", XMPP->server_name);
+ cprintf("id=\"%08x\" ", CC->cs_pid);
+ cprintf("version=\"1.0\" ");
+ cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
+ cprintf("xmlns=\"jabber:client\">");
+
+ /* The features of this stream are... */
+ cprintf("<stream:features>");
+
+#ifdef HAVE_OPENSSL_XXXX_COMMENTED_OUT
+ /* TLS encryption (but only if it isn't already active) */
+ if (!CC->redirect_ssl) {
+ cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
+ }
+#endif
+
+ if (!CC->logged_in) {
+ /* If we're not logged in yet, offer SASL as our feature set */
+ xmpp_output_auth_mechs();
+
+ /* Also offer non-SASL authentication */
+ cprintf("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
+ }
+
+ /* Offer binding and sessions as part of our feature set */
+ cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
+ cprintf("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
+
+ cprintf("</stream:features>");
+
+ CC->is_async = 1; /* XMPP sessions are inherently async-capable */
+}
+
+
+void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) {
+ char el[256];
+ char *sep = NULL;
+ int i;
+
+ /* Axe the namespace, we don't care about it */
+ safestrncpy(el, supplied_el, sizeof el);
+ while (sep = strchr(el, ':'), sep) {
+ strcpy(el, ++sep);
+ }
+
+ /*
+ CtdlLogPrintf(CTDL_DEBUG, "XMPP ELEMENT START: <%s>\n", el);
+ for (i=0; attr[i] != NULL; i+=2) {
+ CtdlLogPrintf(CTDL_DEBUG, " Attribute '%s' = '%s'\n", attr[i], attr[i+1]);
+ }
+ uncomment for more verbosity */
+
+ if (!strcasecmp(el, "stream")) {
+ xmpp_stream_start(data, supplied_el, attr);
+ }
+
+ else if (!strcasecmp(el, "query")) {
+ XMPP->iq_query_xmlns[0] = 0;
+ safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns);
+ }
+
+ else if (!strcasecmp(el, "bind")) {
+ XMPP->bind_requested = 1;
+ }
+
+ else if (!strcasecmp(el, "iq")) {
+ for (i=0; attr[i] != NULL; i+=2) {
+ if (!strcasecmp(attr[i], "type")) {
+ safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type);
+ }
+ else if (!strcasecmp(attr[i], "id")) {
+ safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id);
+ }
+ else if (!strcasecmp(attr[i], "from")) {
+ safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from);
+ }
+ else if (!strcasecmp(attr[i], "to")) {
+ safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to);
+ }
+ }
+ }
+
+ else if (!strcasecmp(el, "auth")) {
+ XMPP->sasl_auth_mech[0] = 0;
+ for (i=0; attr[i] != NULL; i+=2) {
+ if (!strcasecmp(attr[i], "mechanism")) {
+ safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech);
+ }
+ }
+ }
+
+ else if (!strcasecmp(el, "message")) {
+ for (i=0; attr[i] != NULL; i+=2) {
+ if (!strcasecmp(attr[i], "to")) {
+ safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to);
+ }
+ }
+ }
+
+ else if (!strcasecmp(el, "html")) {
+ ++XMPP->html_tag_level;
+ }
+}
+
+
+
+void xmpp_xml_end(void *data, const char *supplied_el) {
+ char el[256];
+ char *sep = NULL;
+
+ /* Axe the namespace, we don't care about it */
+ safestrncpy(el, supplied_el, sizeof el);
+ while (sep = strchr(el, ':'), sep) {
+ strcpy(el, ++sep);
+ }
+
+ /*
+ CtdlLogPrintf(CTDL_DEBUG, "XMPP ELEMENT END : <%s>\n", el);
+ if (XMPP->chardata_len > 0) {
+ CtdlLogPrintf(CTDL_DEBUG, " chardata: %s\n", XMPP->chardata);
+ }
+ uncomment for more verbosity */
+
+ if (!strcasecmp(el, "resource")) {
+ if (XMPP->chardata_len > 0) {
+ safestrncpy(XMPP->iq_client_resource, XMPP->chardata,
+ sizeof XMPP->iq_client_resource);
+ striplt(XMPP->iq_client_resource);
+ }
+ }
+
+ if (!strcasecmp(el, "username")) { /* NON SASL ONLY */
+ if (XMPP->chardata_len > 0) {
+ safestrncpy(XMPP->iq_client_username, XMPP->chardata,
+ sizeof XMPP->iq_client_username);
+ striplt(XMPP->iq_client_username);
+ }
+ }
+
+ if (!strcasecmp(el, "password")) { /* NON SASL ONLY */
+ if (XMPP->chardata_len > 0) {
+ safestrncpy(XMPP->iq_client_password, XMPP->chardata,
+ sizeof XMPP->iq_client_password);
+ striplt(XMPP->iq_client_password);
+ }
+ }
+
+ else if (!strcasecmp(el, "iq")) {
+
+ /*
+ * iq type="get" (handle queries)
+ */
+ if (!strcasecmp(XMPP->iq_type, "get")) {
+
+ /*
+ * Query on a namespace
+ */
+ if (!IsEmptyStr(XMPP->iq_query_xmlns)) {
+ xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from,
+ XMPP->iq_to, XMPP->iq_query_xmlns);
+ }
+
+ /*
+ * ping ( http://xmpp.org/extensions/xep-0199.html )
+ */
+ else if (XMPP->ping_requested) {
+ cprintf("<iq type=\"result\" ");
+ if (!IsEmptyStr(XMPP->iq_from)) {
+ cprintf("to=\"%s\" ", XMPP->iq_from);
+ }
+ if (!IsEmptyStr(XMPP->iq_to)) {
+ cprintf("from=\"%s\" ", XMPP->iq_to);
+ }
+ cprintf("id=\"%s\"/>", XMPP->iq_id);
+ }
+
+ /*
+ * Unknown queries ... return the XML equivalent of a blank stare
+ */
+ else {
+ CtdlLogPrintf(CTDL_DEBUG, "Unknown query; <service-unavailable/>\n");
+ cprintf("<iq type=\"error\" id=\"%s\">", XMPP->iq_id);
+ cprintf("<error code=\"503\" type=\"cancel\">"
+ "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+ "</error>"
+ );
+ cprintf("</iq>");
+ }
+ }
+
+ /*
+ * Non SASL authentication
+ */
+ else if (
+ (!strcasecmp(XMPP->iq_type, "set"))
+ && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query"))
+ ) {
+
+ xmpp_non_sasl_authenticate(
+ XMPP->iq_id,
+ XMPP->iq_client_username,
+ XMPP->iq_client_password,
+ XMPP->iq_client_resource
+ );
+ }
+
+ /*
+ * If this <iq> stanza was a "bind" attempt, process it ...
+ */
+ else if (
+ (XMPP->bind_requested)
+ && (!IsEmptyStr(XMPP->iq_id))
+ && (!IsEmptyStr(XMPP->iq_client_resource))
+ && (CC->logged_in)
+ ) {
+
+ /* Generate the "full JID" of the client resource */
+
+ snprintf(XMPP->client_jid, sizeof XMPP->client_jid,
+ "%s/%s",
+ CC->cs_inet_email,
+ XMPP->iq_client_resource
+ );
+
+ /* Tell the client what its JID is */
+
+ cprintf("<iq type=\"result\" id=\"%s\">", XMPP->iq_id);
+ cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
+ cprintf("<jid>%s</jid>", XMPP->client_jid);
+ cprintf("</bind>");
+ cprintf("</iq>");
+ }
+
+ else if (XMPP->iq_session) {
+ cprintf("<iq type=\"result\" id=\"%s\">", XMPP->iq_id);
+ cprintf("</iq>");
+ }
+
+ else {
+ cprintf("<iq type=\"error\" id=\"%s\">", XMPP->iq_id);
+ cprintf("<error></error>");
+ cprintf("</iq>");
+ }
+
+ /* Now clear these fields out so they don't get used by a future stanza */
+ XMPP->iq_id[0] = 0;
+ XMPP->iq_from[0] = 0;
+ XMPP->iq_to[0] = 0;
+ XMPP->iq_type[0] = 0;
+ XMPP->iq_client_resource[0] = 0;
+ XMPP->iq_session = 0;
+ XMPP->iq_query_xmlns[0] = 0;
+ XMPP->bind_requested = 0;
+ XMPP->ping_requested = 0;
+ }
+
+ else if (!strcasecmp(el, "auth")) {
+
+ /* Try to authenticate (this function is responsible for the output stanza) */
+ xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") );
+
+ /* Now clear these fields out so they don't get used by a future stanza */
+ XMPP->sasl_auth_mech[0] = 0;
+ }
+
+ else if (!strcasecmp(el, "session")) {
+ XMPP->iq_session = 1;
+ }
+
+ else if (!strcasecmp(el, "presence")) {
+
+ /* Respond to a <presence> update by firing back with presence information
+ * on the entire wholist. Check this assumption, it's probably wrong.
+ */
+ xmpp_wholist_presence_dump();
+ }
+
+ else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) {
+ if (XMPP->message_body != NULL) {
+ free(XMPP->message_body);
+ XMPP->message_body = NULL;
+ }
+ if (XMPP->chardata_len > 0) {
+ XMPP->message_body = strdup(XMPP->chardata);
+ }
+ }
+
+ else if (!strcasecmp(el, "message")) {
+ xmpp_send_message(XMPP->message_to, XMPP->message_body);
+ XMPP->html_tag_level = 0;
+ }
+
+ else if (!strcasecmp(el, "html")) {
+ --XMPP->html_tag_level;
+ }
+
+ else if (!strcasecmp(el, "starttls")) {
+#ifdef HAVE_OPENSSL
+ cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+ CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+ if (!CC->redirect_ssl) CC->kill_me = 1;
+#else
+ cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+ CC->kill_me = 1;
+#endif
+ }
+
+ else if (!strcasecmp(el, "ping")) {
+ XMPP->ping_requested = 1;
+ }
+
+ XMPP->chardata_len = 0;
+ if (XMPP->chardata_alloc > 0) {
+ XMPP->chardata[0] = 0;
+ }
+}
+
+
+void xmpp_xml_chardata(void *data, const XML_Char *s, int len)
+{
+ struct citxmpp *X = XMPP;
+
+ if (X->chardata_alloc == 0) {
+ X->chardata_alloc = SIZ;
+ X->chardata = malloc(X->chardata_alloc);
+ }
+ if ((X->chardata_len + len + 1) > X->chardata_alloc) {
+ X->chardata_alloc = X->chardata_len + len + 1024;
+ X->chardata = realloc(X->chardata, X->chardata_alloc);
+ }
+ memcpy(&X->chardata[X->chardata_len], s, len);
+ X->chardata_len += len;
+ X->chardata[X->chardata_len] = 0;
+}
+
+
+/*
+ * This cleanup function blows away the temporary memory and files used by the XMPP service.
+ */
+void xmpp_cleanup_function(void) {
+
+ /* Don't do this stuff if this is not a XMPP session! */
+ if (CC->h_command_function != xmpp_command_loop) return;
+
+ if (XMPP->chardata != NULL) {
+ free(XMPP->chardata);
+ XMPP->chardata = NULL;
+ XMPP->chardata_len = 0;
+ XMPP->chardata_alloc = 0;
+ if (XMPP->message_body != NULL) {
+ free(XMPP->message_body);
+ }
+ }
+ XML_ParserFree(XMPP->xp);
+ free(XMPP);
+}
+
+
+
+/*
+ * Here's where our XMPP session begins its happy day.
+ */
+void xmpp_greeting(void) {
+ strcpy(CC->cs_clientname, "XMPP session");
+ CC->session_specific_data = malloc(sizeof(struct citxmpp));
+ memset(XMPP, 0, sizeof(struct citxmpp));
+ XMPP->last_event_processed = queue_event_seq;
+
+ /* XMPP does not use a greeting, but we still have to initialize some things. */
+
+ XMPP->xp = XML_ParserCreateNS("UTF-8", ':');
+ if (XMPP->xp == NULL) {
+ CtdlLogPrintf(CTDL_ALERT, "Cannot create XML parser!\n");
+ CC->kill_me = 1;
+ return;
+ }
+
+ XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end);
+ XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata);
+ // XML_SetUserData(XMPP->xp, something...);
+
+ CC->can_receive_im = 1; /* This protocol is capable of receiving instant messages */
+}
+
+
+/*
+ * Main command loop for XMPP sessions.
+ */
+void xmpp_command_loop(void) {
+ char cmdbuf[16];
+ int retval;
+
+ time(&CC->lastcmd);
+ memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+ retval = client_read(cmdbuf, 1);
+ if (retval != 1) {
+ CtdlLogPrintf(CTDL_ERR, "Client disconnected: ending session.\r\n");
+ CC->kill_me = 1;
+ return;
+ }
+
+ /* FIXME ... this is woefully inefficient. */
+
+ XML_Parse(XMPP->xp, cmdbuf, 1, 0);
+}
+
+
+/*
+ * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
+ */
+void xmpp_async_loop(void) {
+ xmpp_process_events();
+ xmpp_output_incoming_messages();
+}
+
+
+/*
+ * Login hook for XMPP sessions
+ */
+void xmpp_login_hook(void) {
+ xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_inet_email);
+}
+
+
+/*
+ * Logout hook for XMPP sessions
+ */
+void xmpp_logout_hook(void) {
+ xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_inet_email);
+}
+
+
+const char *CitadelServiceXMPP="XMPP";
+
+CTDL_MODULE_INIT(xmpp)
+{
+ if (!threading) {
+ CtdlRegisterServiceHook(config.c_xmpp_c2s_port,
+ NULL,
+ xmpp_greeting,
+ xmpp_command_loop,
+ xmpp_async_loop,
+ CitadelServiceXMPP);
+ CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP);
+ CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN);
+ CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT);
+ CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH);
+ CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH);
+ }
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * Copyright (c) 2007-2009 by Art Cancro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+struct citxmpp { /* Information about the current session */
+ XML_Parser xp; /* XML parser instance for incoming client stream */
+ char server_name[256]; /* who they think we are */
+ char *chardata;
+ int chardata_len;
+ int chardata_alloc;
+ char client_jid[256]; /* "full JID" of the client */
+ int last_event_processed;
+
+ char iq_type[256]; /* for <iq> stanzas */
+ char iq_id[256];
+ char iq_from[256];
+ char iq_to[256];
+ char iq_client_username[256]; /* username requested by the client (NON SASL ONLY) */
+ char iq_client_password[256]; /* password requested by the client (NON SASL ONLY) */
+ char iq_client_resource[256]; /* resource name requested by the client */
+ int iq_session; /* nonzero == client is requesting a session */
+ char iq_query_xmlns[256]; /* Namespace of <query> */
+
+ char sasl_auth_mech[32]; /* SASL auth mechanism requested by the client */
+
+ char message_to[256];
+ char *message_body; /* Message body in transit */
+ int html_tag_level; /* <html> tag nesting level */
+
+ int bind_requested; /* In this stanza, client is asking server to bind a resource. */
+ int ping_requested; /* In this stanza, client is pinging the server. */
+};
+
+#define XMPP ((struct citxmpp *)CC->session_specific_data)
+
+struct xmpp_event {
+ struct xmpp_event *next;
+ int event_seq;
+ time_t event_time;
+ int event_type;
+ char event_jid[256];
+ int session_which_generated_this_event;
+};
+
+extern struct xmpp_event *xmpp_queue;
+extern int queue_event_seq;
+
+enum {
+ XMPP_EVT_LOGIN,
+ XMPP_EVT_LOGOUT
+};
+
+void xmpp_cleanup_function(void);
+void xmpp_greeting(void);
+void xmpp_command_loop(void);
+void xmpp_async_loop(void);
+void xmpp_sasl_auth(char *, char *);
+void xmpp_output_auth_mechs(void);
+void xmpp_query_namespace(char *, char *, char *, char *);
+void xmpp_wholist_presence_dump(void);
+void xmpp_output_incoming_messages(void);
+void xmpp_queue_event(int, char *);
+void xmpp_process_events(void);
+void xmpp_presence_notify(char *, int);
+void xmpp_roster_item(struct CitContext *);
+void xmpp_send_message(char *, char *);
+void xmpp_non_sasl_authenticate(char *, char *, char *, char *);
--- /dev/null
+/*
+ * $Id$
+ *
+ * Handle messages sent and received using XMPP (Jabber) protocol
+ *
+ * Copyright (c) 2007-2009 by Art Cancro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "internet_addressing.h"
+#include "md5.h"
+#include "ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * This function is called by the XMPP service's async loop.
+ * If the client session has instant messages waiting, it outputs
+ * unsolicited XML stanzas containing them.
+ */
+void xmpp_output_incoming_messages(void) {
+
+ struct ExpressMessage *ptr;
+
+ while (CC->FirstExpressMessage != NULL) {
+
+ begin_critical_section(S_SESSION_TABLE);
+ ptr = CC->FirstExpressMessage;
+ CC->FirstExpressMessage = CC->FirstExpressMessage->next;
+ end_critical_section(S_SESSION_TABLE);
+
+ cprintf("<message to=\"%s\" from=\"%s\" type=\"chat\">",
+ XMPP->client_jid,
+ ptr->sender_email);
+ if (ptr->text != NULL) {
+ striplt(ptr->text);
+ cprintf("<body>%s</body>", ptr->text);
+ free(ptr->text);
+ }
+ cprintf("</message>");
+ free(ptr);
+ }
+}
+
+/*
+ * Client is sending a message.
+ */
+void xmpp_send_message(char *message_to, char *message_body) {
+ char *recp = NULL;
+ int message_sent = 0;
+ struct CitContext *cptr;
+
+ if (message_body == NULL) return;
+ if (message_to == NULL) return;
+ if (IsEmptyStr(message_to)) return;
+ if (!CC->logged_in) return;
+
+ for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+ if ( (cptr->logged_in)
+ && (cptr->can_receive_im)
+ && (!strcasecmp(cptr->cs_inet_email, message_to))
+ ) {
+ recp = cptr->user.fullname;
+ }
+ }
+
+ if (recp) {
+ message_sent = PerformXmsgHooks(CC->user.fullname, CC->cs_inet_email, recp, message_body);
+ }
+
+ free(XMPP->message_body);
+ XMPP->message_body = NULL;
+ XMPP->message_to[0] = 0;
+ time(&CC->lastidle);
+}
+
--- /dev/null
+/*
+ * $Id$
+ *
+ * Handle XMPP presence exchanges
+ *
+ * Copyright (c) 2007-2009 by Art Cancro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "internet_addressing.h"
+#include "md5.h"
+#include "ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * Initial dump of the entire wholist
+ */
+void xmpp_wholist_presence_dump(void)
+{
+ struct CitContext *cptr = NULL;
+ int nContexts, i;
+
+ int aide = (CC->user.axlevel >= 6);
+
+ cptr = CtdlGetContextArray(&nContexts);
+ if (!cptr)
+ return ; /** FIXME: Does xmpp need to send something to maintain the protocol? */
+
+ for (i=0; i<nContexts; i++) {
+ if (cptr[i].logged_in) {
+ if (
+ (((cptr[i].cs_flags&CS_STEALTH)==0) || (aide)) /* aides see everyone */
+ && (cptr[i].user.usernum != CC->user.usernum) /* don't show myself */
+ && (cptr[i].can_receive_im) /* IM-capable session */
+ ) {
+ cprintf("<presence type=\"available\" from=\"%s\"></presence>",
+ cptr[i].cs_inet_email);
+ }
+ }
+ }
+ free(cptr);
+}
+
+
+
+/*
+ * When a user logs in or out of the local Citadel system, notify all XMPP sessions about it.
+ */
+void xmpp_presence_notify(char *presence_jid, int event_type) {
+ struct CitContext *cptr;
+ static int unsolicited_id;
+ int visible_sessions = 0;
+ int nContexts, i;
+ int aide = (CC->user.axlevel >= 6);
+
+ if (IsEmptyStr(presence_jid)) return;
+
+ cptr = CtdlGetContextArray(&nContexts);
+ if (!cptr)
+ return ; /** FIXME: Does XMPP need to send something to maintain the protocol? */
+
+ /* Count the visible sessions for this user */
+ for (i=0; i<nContexts; i++) {
+ if (cptr[i].logged_in) {
+ if (
+ (!strcasecmp(cptr[i].cs_inet_email, presence_jid))
+ && (((cptr[i].cs_flags&CS_STEALTH)==0) || (aide))
+ && (cptr[i].can_receive_im)
+ ) {
+ ++visible_sessions;
+ }
+ }
+ }
+
+ CtdlLogPrintf(CTDL_DEBUG, "%d sessions for <%s> are now visible to session %d\n",
+ visible_sessions, presence_jid, CC->cs_pid);
+
+ if ( (event_type == XMPP_EVT_LOGIN) && (visible_sessions == 1) ) {
+
+ CtdlLogPrintf(CTDL_DEBUG, "Telling session %d that <%s> logged in\n", CC->cs_pid, presence_jid);
+
+ /* Do an unsolicited roster update that adds a new contact. */
+ for (i=0; i<nContexts; i++) {
+ if (cptr[i].logged_in) {
+ if (!strcasecmp(cptr[i].cs_inet_email, presence_jid)) {
+ cprintf("<iq id=\"unsolicited_%x\" type=\"result\">",
+ ++unsolicited_id);
+ cprintf("<query xmlns=\"jabber:iq:roster\">");
+ xmpp_roster_item(&cptr[i]);
+ cprintf("</query>"
+ "</iq>");
+ }
+ }
+ }
+
+ /* Transmit presence information */
+ cprintf("<presence type=\"available\" from=\"%s\"></presence>", presence_jid);
+ }
+
+ if (visible_sessions == 0) {
+ CtdlLogPrintf(CTDL_DEBUG, "Telling session %d that <%s> logged out\n", CC->cs_pid, presence_jid);
+
+ /* Transmit non-presence information */
+ cprintf("<presence type=\"unavailable\" from=\"%s\"></presence>", presence_jid);
+ cprintf("<presence type=\"unsubscribed\" from=\"%s\"></presence>", presence_jid);
+
+ /* Do an unsolicited roster update that deletes the contact. */
+ cprintf("<iq id=\"unsolicited_%x\" type=\"result\">", ++unsolicited_id);
+ cprintf("<query xmlns=\"jabber:iq:roster\">");
+ cprintf("<item jid=\"%s\" subscription=\"remove\">", presence_jid);
+ cprintf("<group>%s</group>", config.c_humannode);
+ cprintf("</item>");
+ cprintf("</query>"
+ "</iq>");
+ }
+ free(cptr);
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * Handle <iq> <get> <query> type situations (namespace queries)
+ *
+ * Copyright (c) 2007-2009 by Art Cancro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "internet_addressing.h"
+#include "md5.h"
+#include "ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * Output a single roster item, for roster queries or pushes
+ */
+void xmpp_roster_item(struct CitContext *cptr) {
+ cprintf("<item jid=\"%s\" name=\"%s\" subscription=\"both\">",
+ cptr->cs_inet_email,
+ cptr->user.fullname
+ );
+ cprintf("<group>%s</group>", config.c_humannode);
+ cprintf("</item>");
+}
+
+/*
+ * Return the results for a "jabber:iq:roster:query"
+ *
+ * Since we are not yet managing a roster, we simply return the entire wholist
+ * (minus any entries for this user -- don't tell me about myself)
+ *
+ */
+void xmpp_iq_roster_query(void)
+{
+ struct CitContext *cptr;
+ int nContexts, i;
+ int aide = (CC->user.axlevel >= 6);
+
+ cprintf("<query xmlns=\"jabber:iq:roster\">");
+
+ cptr = CtdlGetContextArray(&nContexts);
+ if (cptr) {
+ for (i=0; i<nContexts; i++) {
+ if (cptr[i].logged_in) {
+ if (
+ (((cptr[i].cs_flags&CS_STEALTH)==0) || (aide))
+ && (cptr[i].user.usernum != CC->user.usernum)
+ ) {
+ xmpp_roster_item(&cptr[i]);
+ }
+ }
+ }
+ free (cptr);
+ }
+ cprintf("</query>");
+}
+
+
+/*
+ * TODO: handle queries on some or all of these namespaces
+ *
+xmpp_query_namespace(purple5b5c1e58, splorph.xand.com, http://jabber.org/protocol/disco#items:query)
+xmpp_query_namespace(purple5b5c1e59, splorph.xand.com, http://jabber.org/protocol/disco#info:query)
+xmpp_query_namespace(purple5b5c1e5a, , vcard-temp:query)
+ *
+ */
+
+void xmpp_query_namespace(char *iq_id, char *iq_from, char *iq_to, char *query_xmlns)
+{
+ int supported_namespace = 0;
+
+ /* We need to know before we begin the response whether this is a supported namespace, so
+ * unfortunately all supported namespaces need to be defined here *and* down below where
+ * they are handled.
+ */
+ if (
+ (!strcasecmp(query_xmlns, "jabber:iq:roster:query"))
+ && (!strcasecmp(query_xmlns, "jabber:iq:auth:query"))
+ ) {
+ supported_namespace = 1;
+ }
+
+ CtdlLogPrintf(CTDL_DEBUG, "xmpp_query_namespace(%s, %s, %s, %s)\n", iq_id, iq_from, iq_to, query_xmlns);
+
+ /*
+ * Beginning of query result.
+ */
+ if (supported_namespace) {
+ cprintf("<iq type=\"result\" ");
+ }
+ else {
+ cprintf("<iq type=\"error\" ");
+ }
+ if (!IsEmptyStr(iq_from)) {
+ cprintf("to=\"%s\" ", iq_from);
+ }
+ cprintf("id=\"%s\">", iq_id);
+
+ /*
+ * Is this a query we know how to handle?
+ */
+
+ if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) {
+ xmpp_iq_roster_query();
+ }
+
+ else if (!strcasecmp(query_xmlns, "jabber:iq:auth:query")) {
+ cprintf("<query xmlns=\"jabber:iq:auth\">"
+ "<username/><password/><resource/>"
+ "</query>"
+ );
+ }
+
+ else {
+ CtdlLogPrintf(CTDL_DEBUG, "Unknown namespace; returning <service-unavailable/>\n");
+ cprintf("<error code=\"503\" type=\"cancel\">"
+ "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+ "</error>"
+ );
+ }
+
+ /*
+ * End of query result. If we didn't hit any known namespaces then we should
+ * deliver a "service unavailable" error (see RFC3921 section 2.4 and 11.1.5.4)
+ */
+ cprintf("</iq>");
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * XMPP event queue
+ *
+ * Copyright (c) 2007-2009 by Art Cancro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "internet_addressing.h"
+#include "md5.h"
+#include "ctdl_module.h"
+#include "serv_xmpp.h"
+
+int queue_event_seq = 0;
+
+void xmpp_queue_event(int event_type, char *email_addr) {
+
+ struct xmpp_event *xptr = NULL;
+ struct xmpp_event *new_event = NULL;
+ struct xmpp_event *last = NULL;
+ int purged_something = 0;
+ struct CitContext *cptr;
+
+ CtdlLogPrintf(CTDL_DEBUG, "xmpp_queue_event(%d, %s)\n", event_type, email_addr);
+
+ /* Purge events more than a minute old */
+ begin_critical_section(S_XMPP_QUEUE);
+ do {
+ purged_something = 0;
+ if (xmpp_queue != NULL) {
+ if ((time(NULL) - xmpp_queue->event_time) > 60) {
+ xptr = xmpp_queue->next;
+ free(xmpp_queue);
+ xmpp_queue = xptr;
+ purged_something = 1;
+ }
+ }
+ } while(purged_something);
+ end_critical_section(S_XMPP_QUEUE);
+
+ /* Create a new event */
+ new_event = (struct xmpp_event *) malloc(sizeof(struct xmpp_event));
+ new_event->next = NULL;
+ new_event->event_time = time(NULL);
+ new_event->event_seq = ++queue_event_seq;
+ new_event->event_type = event_type;
+ new_event->session_which_generated_this_event = CC->cs_pid;
+ safestrncpy(new_event->event_jid, email_addr, sizeof new_event->event_jid);
+
+ /* Add it to the list */
+ begin_critical_section(S_XMPP_QUEUE);
+ if (xmpp_queue == NULL) {
+ xmpp_queue = new_event;
+ }
+ else {
+ for (xptr = xmpp_queue; xptr != NULL; xptr = xptr->next) {
+ if (xptr->next == NULL) {
+ last = xptr;
+ }
+ }
+ last->next = new_event;
+ }
+ end_critical_section(S_XMPP_QUEUE);
+
+ /* Tell the sessions that something is happening */
+ begin_critical_section(S_SESSION_TABLE);
+ for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+ if ((cptr->logged_in) && (cptr->h_async_function == xmpp_async_loop)) {
+ cptr->async_waiting = 1;
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+}
+
+
+/*
+ * Are we interested in anything from the queue? (Called in async loop)
+ */
+void xmpp_process_events(void) {
+ struct xmpp_event *xptr = NULL;
+ int highest_event = 0;
+
+ for (xptr=xmpp_queue; xptr!=NULL; xptr=xptr->next) {
+ if (xptr->event_seq > XMPP->last_event_processed) {
+
+ switch(xptr->event_type) {
+
+ case XMPP_EVT_LOGIN:
+ case XMPP_EVT_LOGOUT:
+ if (xptr->session_which_generated_this_event != CC->cs_pid) {
+ xmpp_presence_notify(xptr->event_jid, xptr->event_type);
+ }
+ break;
+ }
+
+ if (xptr->event_seq > highest_event) {
+ highest_event = xptr->event_seq;
+ }
+ }
+ }
+
+ XMPP->last_event_processed = highest_event;
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * Barebones SASL authentication service for XMPP (Jabber) clients.
+ *
+ * Note: RFC3920 says we "must" support DIGEST-MD5 but we only support PLAIN.
+ *
+ * Copyright (c) 2007-2009 by Art Cancro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "user_ops.h"
+#include "internet_addressing.h"
+#include "md5.h"
+#include "ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * PLAIN authentication. Returns zero on success, nonzero on failure.
+ */
+int xmpp_auth_plain(char *authstring)
+{
+ char decoded_authstring[1024];
+ char ident[256];
+ char user[256];
+ char pass[256];
+ int result;
+
+
+ /* Take apart the authentication string */
+ memset(pass, 0, sizeof(pass));
+
+ CtdlDecodeBase64(decoded_authstring, authstring, strlen(authstring));
+ safestrncpy(ident, decoded_authstring, sizeof ident);
+ safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
+ safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
+
+
+ /* If there are underscores in either string, change them to spaces. Some clients
+ * do not allow spaces so we can tell the user to substitute underscores if their
+ * login name contains spaces.
+ */
+ convert_spaces_to_underscores(ident);
+ convert_spaces_to_underscores(user);
+
+ /* Now attempt authentication */
+
+ if (!IsEmptyStr(ident)) {
+ result = CtdlLoginExistingUser(user, ident);
+ }
+ else {
+ result = CtdlLoginExistingUser(NULL, user);
+ }
+
+ if (result == login_ok) {
+ if (CtdlTryPassword(pass) == pass_ok) {
+ return(0); /* success */
+ }
+ }
+
+ return(1); /* failure */
+}
+
+
+/*
+ * Output the list of SASL mechanisms offered by this stream.
+ */
+void xmpp_output_auth_mechs(void) {
+ cprintf("<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+ cprintf("<mechanism>PLAIN</mechanism>");
+ cprintf("</mechanisms>");
+}
+
+/*
+ * Here we go ... client is trying to authenticate.
+ */
+void xmpp_sasl_auth(char *sasl_auth_mech, char *authstring) {
+
+ if (strcasecmp(sasl_auth_mech, "PLAIN")) {
+ cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+ cprintf("<invalid-mechanism/>");
+ cprintf("</failure>");
+ return;
+ }
+
+ if (CC->logged_in) CtdlUserLogout(); /* Client may try to log in twice. Handle this. */
+
+ if (CC->nologin) {
+ cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+ cprintf("<system-shutdown/>");
+ cprintf("</failure>");
+ }
+
+ else if (xmpp_auth_plain(authstring) == 0) {
+ cprintf("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>");
+ }
+
+ else {
+ cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+ cprintf("<not-authorized/>");
+ cprintf("</failure>");
+ }
+}
+
+
+
+/*
+ * Non-SASL authentication
+ */
+void xmpp_non_sasl_authenticate(char *iq_id, char *username, char *password, char *resource) {
+ int result;
+
+ if (CC->logged_in) CtdlUserLogout(); /* Client may try to log in twice. Handle this. */
+
+ result = CtdlLoginExistingUser(NULL, username);
+ if (result == login_ok) {
+ result = CtdlTryPassword(password);
+ if (result == pass_ok) {
+ cprintf("<iq type=\"result\" id=\"%s\"></iq>", iq_id); /* success */
+ return;
+ }
+ }
+
+ /* failure */
+ cprintf("<iq type=\"error\" id=\"%s\">", iq_id);
+ cprintf("<error code=\"401\" type=\"auth\">"
+ "<not-authorized xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+ "</error>"
+ "</iq>"
+ );
+}