/* * Implements the XMPP protocol using libiksemel >= 1.2. * * climm Copyright (C) © 2001-2010 Rüdiger Kuhlmann * * climm 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; version 2 dated June, 1991. * * climm 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. * * In addition, as a special exception permission is granted to link the * code of this release of climm with the OpenSSL project's "OpenSSL" * library, and distribute the linked executables. You must obey the GNU * General Public License in all respects for all of the code used other * than "OpenSSL". If you modify this file, you may extend this exception * to your version of the file, but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your version * of this file. * * You should have received a copy of the GNU General Public License * along with this package; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * $Id$ */ //#define IKS_TRANS_USER_DATA Server //#define IKS_FILTER_USER_DATA Server //#define IKS_SOCK_USER_DATA Connection #define IKS_TRANS_USER_DATA void #define IKS_FILTER_USER_DATA void #define IKS_SOCK_USER_DATA void #include "climm.h" #include #include #if HAVE_SYS_STAT_H #include #endif #if HAVE_WINSOCK2_H #include #endif #include #include #if ENABLE_XMPP #include #include "xmpp_base.h" #include "connection.h" #include "contact.h" #include "conv.h" #include "oscar_dc.h" #include "util_io.h" #include "im_response.h" #include "im_request.h" #include "util_ui.h" #include "util_rl.h" #include "buildmark.h" #include "preferences.h" #include "oscar_dc_file.h" #include "io/io_dns.h" #include "os.h" #include "util_parse.h" static jump_conn_f XMPPCallbackDispatch; static int iks_climm_TConnect (iksparser *prs, IKS_SOCK_USER_DATA **socketptr, const char *server, int port) { Server *serv = iks_stream_user_data (prs); Connection *conn = serv->conn; *((Connection **)socketptr) = conn; return IKS_OK; } static void iks_climm_TClose (IKS_SOCK_USER_DATA *conn) { UtilIOClose ((Connection *)conn); } static int iks_climm_TSend (IKS_SOCK_USER_DATA *conn, const char *data, size_t len) { io_err_t rc = UtilIOWrite ((Connection *)conn, data, len); if (rc != IO_OK) return IKS_NET_RWERR; return IKS_OK; } static int iks_climm_TRecv (IKS_SOCK_USER_DATA *conn, char *data, size_t len, int timeout) { int rc = UtilIORead ((Connection *)conn, data, len); if (rc > 0) return rc; if (rc == IO_OK || rc == IO_CONNECTED) return 0; return -1; } static ikstransport iks_climm_transport = { IKS_TRANSPORT_V1, iks_climm_TConnect, iks_climm_TSend, iks_climm_TRecv, iks_climm_TClose, NULL, }; static void XMPPCallBackTimeout (Event *event) { Connection *conn = event->conn; if (!conn) { EventD (event); return; } assert (conn->serv); assert (conn->serv->type == TYPE_XMPP_SERVER); if (~conn->connect & CONNECT_OK) rl_print ("# XMPP timeout\n"); EventD (event); } void XmppStreamError (Server *serv, const char *text) { rl_printf ("Stream level error occurred: %s [%s]\n", text, UtilIOErr (serv->conn) ? UtilIOErr (serv->conn) : ""); XMPPLogout (serv); } Event *XMPPLogin (Server *serv) { const char *scrserv, *sp; char *semi; Event *event; time_t now = time (NULL); assert (serv->type == TYPE_XMPP_SERVER); if (!serv->screen || !serv->passwd) return NULL; if (!strchr (serv->screen, '@')) { const char *jid = s_sprintf ("%s@jabber.com", serv->screen); s_repl (&serv->screen, jid); } scrserv = strchr (serv->screen, '@') + 1; if (!serv->conn->server || !strcmp (scrserv, serv->conn->server)) { const char *rrdns = io_dns_resolve (s_sprintf ("_xmpp-client._tcp.%s", scrserv)); s_repl (&serv->conn->server, rrdns ? rrdns : scrserv); } XMPPLogout (serv); s_repl (&serv->xmpp_stamp, "YYYYmmddHHMMSS"); strftime (serv->xmpp_stamp, 15, "%Y%m%d%H%M%S", gmtime (&now)); s_repl (&serv->xmpp_privacy_list, NULL); s_repl (&serv->xmpp_privacy_items, NULL); semi = strchr (serv->conn->server, ';'); if (semi) *semi = 0; sp = s_sprintf ("%s", s_wordquote (strchr (serv->conn->server, ':') ? serv->conn->server : s_sprintf ("%s:%lu", serv->conn->server, serv->conn->port))); rl_printf (i18n (2620, "Opening XMPP connection for %s at %s...\n"), s_wordquote (serv->screen), sp); if (semi) *semi = ';'; if (!serv->conn->port) serv->conn->port = ~0; serv->conn->dispatch = &XMPPCallbackDispatch; serv->conn->connect = 0; if ((event = QueueDequeue2 (serv->conn, QUEUE_DEP_WAITLOGIN, 0, NULL))) { event->attempts++; event->due = time (NULL) + 10 * event->attempts + 10; event->callback = &XMPPCallBackTimeout; QueueEnqueue (event); } else event = QueueEnqueueData (serv->conn, QUEUE_DEP_WAITLOGIN, 0, time (NULL) + 5, NULL, serv->conn->cont, NULL, &XMPPCallBackTimeout); UtilIOConnectTCP (serv->conn); return event; } static void XmppSaveLog (IKS_TRANS_USER_DATA *userv, const char *text, size_t size, int in) { Server *serv = (Server *)userv; const char *data; char *textnonl; size_t rc; int i; data = s_sprintf ("%s%s %s%s ", iks_is_secure (serv->xmpp_parser) ? "SSL " : "", s_now, in & 1 ? "<<<" : ">>>", in & 2 ? " residual:" : ""); if (in) DebugH (DEB_XMPPIN, "%s", data); else DebugH (DEB_XMPPOUT, "%s", data); if (!ServerPrefVal (serv, CO_LOGSTREAM)) return; if (serv->logfd < 0) { const char *dir, *file; dir = s_sprintf ("%sdebug", PrefUserDir (prG)); mkdir (dir, 0700); dir = s_sprintf ("%sdebug" _OS_PATHSEPSTR "packets.xmpp.%s", PrefUserDir (prG), serv->screen); mkdir (dir, 0700); file = s_sprintf ("%sdebug" _OS_PATHSEPSTR "packets.xmpp.%s/%lu", PrefUserDir (prG), serv->screen, time (NULL)); #if !defined(O_SYNC) #define O_SYNC 0 #endif serv->logfd = open (file, O_WRONLY | O_CREAT | O_APPEND | O_SYNC, 0600); } if (serv->logfd < 0) return; rc = write (serv->logfd, data, strlen (data)); textnonl = strdup (text); for (i = 0; textnonl[i]; i++) if (textnonl[i] == '\n' || textnonl[i] == '\r') textnonl[i] = ' '; rc = write (serv->logfd, textnonl, strlen (textnonl)); rc = write (serv->logfd, "\n", 1); free (textnonl); } static void GetBothContacts (iksid *j, Server *conn, Contact **b, Contact **f, char crea) { Contact *bb, *ff, **t; char *jb = j->partial; char *jr = j->resource; if ((bb = ContactFindScreen (conn->contacts, jb))) { if (!(ff = ContactFindScreenP (conn->contacts, bb, jr ? jr : ""))) { ff = ContactScreenP (conn, bb, jr ? jr : ""); } } else { bb = ContactScreen (conn, jb); if (crea) ContactCreate (conn, bb); ff = ContactScreenP (conn, bb, jr ? jr : ""); } assert (bb); if (ff) { /* make ff the firstchild or the firstchild->next of bb */ if (!bb->firstchild) bb->firstchild = ff; else if (bb->firstchild != ff && bb->firstchild->next != ff) { for (t = &bb->firstchild; *t; t = &((*t)->next)) if (*t == ff) { *t = ff->next; ff->next = bb->firstchild->next; bb->firstchild->next = ff; t = NULL; break; } if (t) { ff->next = bb->firstchild->next; bb->firstchild->next = ff; } } } else ff = bb; *b = bb; *f = ff; } static iks *find_with_ns_attrib (iks *tag, const char *childname, const char *childnamespace) { iks *p, *a; for (p = iks_first_tag (tag); p; p = iks_next_tag(p)) { for (a = iks_attrib (p); a; a = iks_next (a)) { if (iks_type (a) != IKS_ATTRIBUTE) continue; if (strncmp (iks_name (a), "xmlns", 5)) continue; if (strcmp (iks_cdata (a), childnamespace)) continue; if (!iks_name (a)[5] || iks_name (a)[5] == ':') return p; } } return NULL; } #define foreach_subtag(iter,parent,name) \ iks *iter; \ for (iter = iks_first_tag (parent); iter; iter = iks_next_tag (iter)) \ if (iks_type (iter) == IKS_TAG && !strcmp (iks_name (iter), name)) static enum ikshowtype StatusToIksstatus (status_t *status) { switch (*status) { case ims_online: return IKS_SHOW_AVAILABLE; break; case ims_ffc: return IKS_SHOW_CHAT; break; case ims_away: return IKS_SHOW_AWAY; break; case ims_occ: *status = ims_dnd; case ims_dnd: return IKS_SHOW_DND; break; case ims_na: return IKS_SHOW_XA; break; case ims_offline: *status = ims_inv; default: return IKS_SHOW_UNAVAILABLE; break; } } /****************** send stuff ******************/ static void XmppSendIqGmail (Server *serv, int64_t newer, const char *newertid, const char *query) { iks *x = iks_new ("iq"), *q; iks_insert_attrib (x, "type", "get"); iks_insert_attrib (x, "to", serv->xmpp_id->partial); iks_insert_attrib (x, "id", s_sprintf ("%s-%s-%x", query ? "mailq" : "mail", serv->xmpp_stamp, serv->xmpp_sequence++)); q = iks_insert (x, "query"); iks_insert_attrib (q, "xmlns", "google:mail:notify"); if (newer != 0) iks_insert_attrib (q, "newer-than-time", s_sprintf ("%llu", newer)); if (newertid && *newertid) iks_insert_attrib (q, "newer-than-tid", newertid); if (query && *query) iks_insert_attrib (q, "q", query); iks_send (serv->xmpp_parser, x); iks_delete (x); } static void XmppSendIqTime (Server *serv) { iks *x = iks_new ("iq"); iks_insert_attrib (x, "type", "get"); iks_insert_attrib (iks_insert (x, "time"), "xmlns", "urn:xmpp:time"); iks_insert_attrib (x, "id", s_sprintf ("time-%s-%x", serv->xmpp_stamp, serv->xmpp_sequence++)); iks_insert_attrib (x, "to", serv->xmpp_id->server); iks_send (serv->xmpp_parser, x); iks_delete (x); } static iks *xmpp_make_iq_privacy (Server *serv, xmpp_priv_t ntype, const char *list) { iks *child, *x, *l = NULL; const char *id; const char *type = ntype == p_active ? "active" : ntype == p_default ? "default" : ntype == p_show || ntype == p_show_quiet ? "list" : NULL; id = s_sprintf ("priv-%d-%s-%x", ntype, serv->xmpp_stamp, ++serv->xmpp_sequence); x = iks_new ("iq"); iks_insert_attrib (x, "type", ntype == p_active || ntype == p_default ? "set" : "get"); iks_insert_attrib (x, "id", id); iks_insert_attrib (x, "from", serv->xmpp_id->full); child = iks_insert (x, "query"); iks_insert_attrib (child, "xmlns", "jabber:iq:privacy"); if (type) l = iks_insert (child, type); if (l && list) iks_insert_attrib (l, "name", list); return x; } static void XMPPSendIqPrivacy (Server *serv, xmpp_priv_t ntype, const char *list) { iks *x = xmpp_make_iq_privacy (serv, ntype, list); iks_send (serv->xmpp_parser, x); iks_delete (x); } static int xmpp_uint_compare (const char *a, const char *b) { if (*a == '+') a++; if (*b == '+') b++; while (*a == '0') a++; while (*b == '0') b++; if (strlen (a) < strlen (b)) return -1; if (strlen (a) > strlen (b)) return 1; while (*a && *b) { if (*a < *b) return -1; if (*a > *b) return -1; a++; b++; } return 0; } static char *xmpp_uint_add (const char *a, const char *b) { int la = strlen (a), lb = strlen (b), lo = la + lb + 1; char *op = malloc (la + lb + 2), *ret; int o = 0; op[lo] = 0; while (la && lb) { la--; lb--; o += (a[la] - '0') + (b[lb] - '0'); op[--lo] = (o % 10) + '0'; o /= 10; } if (lb) { la = lb; a = b; } while (la) { la--; o += (a[la] - '0'); op[--lo] = (o % 10) + '0'; o /= 10; } if (o) op[--lo] = o + '0'; ret = strdup (op + lo); free (op); return ret; } static void XMPPSendIqPrivacyEdit (Server *serv, xmpp_priv_t ntype, const char *list, const char *edit) { iks *child, *x, *l = NULL, *old = NULL, *xx; UDWORD ig = 0; UDWORD count = 0; const char *insertiontstr, *id; char *insertionstr = strdup ("1"); int allow; id = s_sprintf ("priv-%d-%s-%x", ntype, serv->xmpp_stamp, ++serv->xmpp_sequence); x = iks_new ("iq"); iks_insert_attrib (x, "type", "set"); iks_insert_attrib (x, "id", id); iks_insert_attrib (x, "from", serv->xmpp_id->full); child = iks_insert (x, "query"); iks_insert_attrib (child, "xmlns", "jabber:iq:privacy"); l = iks_insert (child, "list"); iks_insert_attrib (l, "name", list); xx = xmpp_make_iq_privacy (serv, p_show, list); if (ntype == p_edit) { insertiontstr = s_parseint_s (&edit, &ig, DEFAULT_SEP, 0); if (!insertiontstr || *insertiontstr == 'x' || *insertiontstr == '-') insertiontstr = "0"; insertionstr = strdup (insertiontstr); old = iks_tree (serv->xmpp_privacy_items, 0, NULL); { foreach_subtag (item_i, old, "item") { const char *iorder = iks_find_attrib (item_i, "order"); if (iorder && xmpp_uint_compare (iorder, insertionstr) < 0) iks_insert_node (l, iks_copy_within (item_i, iks_stack (l))); }} } while ((s_parsekey (&edit, "allow") && (allow=1)) || (s_parsekey (&edit, "deny") && (allow=2))) { strc_t par; iks *ni = iks_insert (l, "item"); char *newsum = xmpp_uint_add (insertionstr, s_sprintf ("%lu", count)); iks_insert_attrib (ni, "order", newsum); free (newsum); iks_insert_attrib (ni, "action", allow == 1 ? "allow" : "deny"); if (!s_parsekey (&edit, "all")) while (1) { if (s_parsekey_s (&edit, "msg", MULTI_SEP)) iks_insert (ni, "message"); else if (s_parsekey_s (&edit, "pin", MULTI_SEP)) iks_insert (ni, "presence-in"); else if (s_parsekey_s (&edit, "pout", MULTI_SEP)) iks_insert (ni, "presence-out"); else if (s_parsekey_s (&edit, "iq", MULTI_SEP)) iks_insert (ni, "iq"); else break; } if (s_parsekey (&edit, "subscription") || s_parsekey (&edit, "sub")) { iks_insert_attrib (ni, "type", "subscription"); if (s_parsekey (&edit, "both")) iks_insert_attrib (ni, "value", "both"); else if (s_parsekey (&edit, "to")) iks_insert_attrib (ni, "value", "to"); else if (s_parsekey (&edit, "from")) iks_insert_attrib (ni, "value", "from"); else { s_parsekey (&edit, "none"); iks_insert_attrib (ni, "value", "none"); } } else if (s_parsekey (&edit, "group") && (par = s_parse (&edit))) { iks_insert_attrib (ni, "type", "group"); iks_insert_attrib (ni, "value", par->txt); } else if ((s_parsekey (&edit, "jid") || 1) && (par = s_parse (&edit))) { iks_insert_attrib (ni, "type", "jid"); iks_insert_attrib (ni, "value", par->txt); } count++; } if (ntype == p_edit) { char *shift, *skipstr; const char *skiptstr; if (s_parsekey (&edit, "delete") && (skiptstr = s_parseint_s (&edit, &ig, DEFAULT_SEP, 0))) { char *ninsertionstr = xmpp_uint_add (insertionstr, skiptstr); free (insertionstr); insertionstr = ninsertionstr; skipstr = strdup (skiptstr); } else skipstr = strdup ("0"); shift = xmpp_uint_add (skipstr, s_sprintf ("%lu", count)); { foreach_subtag (item_i, old, "item") { const char *iorder = iks_find_attrib (item_i, "order"); if (iorder && xmpp_uint_compare (iorder, insertionstr) >= 0) { iks *node = iks_copy_within (item_i, iks_stack (l)); char *newsum = xmpp_uint_add (iorder, shift); iks_insert_attrib (node, "order", newsum); free (newsum); iks_insert_node (l, node); } }} free (shift); free (skipstr); } free (insertionstr); iks_send (serv->xmpp_parser, x); iks_delete (x); iks_send (serv->xmpp_parser, xx); iks_delete (xx); } static void sendIqGmailReqs (Event *event) { Server *serv; if (!event->conn) { EventD (event); return; } serv = event->conn->serv; XmppSendIqGmail (serv, serv->xmpp_gmail_new_newer, serv->xmpp_gmail_new_newertid, NULL); event->due += 300; QueueEnqueue (event); } static void sendIqTimeReqs (Event *event) { if (!event->conn) { EventD (event); return; } XmppSendIqTime (event->conn->serv); event->due += 300; QueueEnqueue (event); } /****************** IqHandler **********/ static int XmppHandleIqGmail (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; Contact *cont = serv->conn->cont; iks *mb = find_with_ns_attrib (pak->x, "mailbox", "google:mail:notify"); char *n_s, *foundnewer; const char *ntid = ""; int n = 0, ismail = 0; if (!mb) return IKS_FILTER_PASS; n_s = iks_find_attrib (mb, "total-matched"); if (n_s) n = atoi (n_s); if (pak->id && !strncmp (pak->id, "mail-", 5)) { ismail = 1; if (n || !strcmp (pak->id + strlen (pak->id) - 2, "-0")) rl_printf (i18n (2738, "Found %d new mails for %s.\n"), n, serv->xmpp_id->partial); } else rl_printf (i18n (2739, "Found %d mails for %s.\n"), n, serv->xmpp_id->partial); if (!n) return IKS_FILTER_EAT; { foreach_subtag (mb_c, mb, "mail-thread-info") { char *sub = iks_find_cdata (mb_c, "subject"); char *snip = iks_find_cdata (mb_c, "snippet"); char *dato = iks_find_attrib (mb_c, "date"); time_t t = atoll (dato) / 1000ULL; ntid = iks_find_attrib (mb_c, "tid"); rl_printf ("%s ", s_time (&t)); rl_printf ("%s%s %s%s%s", COLMESSAGE, sub ? sub : "", COLQUOTE, COLSINGLE, snip ? snip : ""); rl_print ("\n"); { foreach_subtag (mb_s_c, iks_find (mb_c, "senders"), "sender") { if (!ismail || (iks_find_attrib (mb_s_c, "unread") && !strcmp (iks_find_attrib (mb_s_c, "unread"), "1"))) { char *email = iks_find_attrib (mb_s_c, "address"); char *name = iks_find_attrib (mb_s_c, "name"); rl_printf (" %s%s%s <%s%s%s>\n", COLQUOTE, name ? name : "", COLNONE, COLCONTACT, email ? email : "", COLNONE); } }} }} foundnewer = iks_find_attrib (mb, "result-time"); if (ismail) { serv->xmpp_gmail_new_newer = atoll (foundnewer); s_repl (&serv->xmpp_gmail_new_newertid, ntid); } else { serv->xmpp_gmail_newer = atoll (foundnewer); s_repl (&serv->xmpp_gmail_newertid, ntid); } return IKS_FILTER_EAT; } static int XmppHandleIqDisco (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; if (pak->subtype == IKS_TYPE_RESULT) { /* what did we ask for? */ if (!pak->from || pak->from->resource || pak->from->user || strcmp (pak->from->server, serv->xmpp_id->server)) return IKS_FILTER_PASS; /* disco is from server */ if (iks_find_with_attrib (iks_find (pak->x, "query"), "feature", "var", "google:mail:notify")) { /* have Gmail, so start requesting */ XmppSendIqGmail (serv, 0, NULL, NULL); QueueEnqueueData2 (serv->conn, QUEUE_XMPP_GMAIL, 0, 300, NULL, &sendIqGmailReqs, NULL); iks_filter_add_rule (serv->xmpp_filter, XmppHandleIqGmail , serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_NS, "google:mail:notify", IKS_RULE_DONE); return IKS_FILTER_EAT; } } else if (pak->subtype == IKS_TYPE_GET) { iks *x = iks_new ("iq"), *q, *f, *i; iks_insert_attrib (x, "type", "result"); iks_insert_attrib (x, "id", pak->id); iks_insert_attrib (x, "to", pak->from->full); q = iks_insert (x, "query"); iks_insert_attrib (q, "xmlns", "http://jabber.org/protocol/disco#info"); iks_insert_attrib (q, "node", iks_find_attrib (pak->query, "node")); f = iks_insert (q, "feature"); iks_insert_attrib (f, "var", "http://jabber.org/protocol/disco#info"); f = iks_insert (q, "feature"); iks_insert_attrib (f, "var", "http://jabber.org/protocol/chatstates"); f = iks_insert (q, "feature"); iks_insert_attrib (f, "var", "jabber:iq:version"); f = iks_insert (q, "feature"); iks_insert_attrib (f, "var", "jabber:iq:last"); i = iks_insert (q, "identity"); iks_insert_attrib (i, "category", "client"); iks_insert_attrib (i, "type", "console"); iks_insert_attrib (i, "name", "climm"); iks_send (serv->xmpp_parser, x); iks_delete (x); return IKS_FILTER_EAT; } return IKS_FILTER_PASS; } static int XmppHandleIqXEP12 (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; if (pak->subtype == IKS_TYPE_GET) { iks *x = iks_new ("iq"), *q; iks_insert_attrib (x, "type", "result"); iks_insert_attrib (x, "id", pak->id); iks_insert_attrib (x, "to", pak->from->full); q = iks_insert (x, "query"); iks_insert_attrib (q, "xmlns", "jabber:iq:last"); iks_insert_attrib (q, "seconds", s_sprintf ("%ld", uiG.idle_val ? os_DetermineIdleTime (time (NULL), uiG.idle_val) : 0)); iks_send (serv->xmpp_parser, x); iks_delete (x); return IKS_FILTER_EAT; } return IKS_FILTER_PASS; } static int XmppHandleIqXEP92 (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; if (pak->subtype == IKS_TYPE_GET) { iks *x = iks_new ("iq"), *q; iks_insert_attrib (x, "type", "result"); iks_insert_attrib (x, "id", pak->id); iks_insert_attrib (x, "to", pak->from->full); q = iks_insert (x, "query"); iks_insert_attrib (q, "xmlns", "jabber:iq:version"); iks_insert_cdata (iks_insert (q, "name"), "climm", 0); iks_insert_cdata (iks_insert (q, "version"), BuildVersionStr, 0); iks_insert_cdata (iks_insert (q, "os"), BuildPlatformStr, 0); iks_send (serv->xmpp_parser, x); iks_delete (x); return IKS_FILTER_EAT; } return IKS_FILTER_PASS; } static int XmppHandleIqRoster (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; ContactGroup *cg; Contact *contr, *contb; int i; if (pak->subtype == IKS_TYPE_RESULT) { Contact *cont; for (i = 0, cg = serv->contacts; (cont = ContactIndex (cg, i)); i++) { OptSetVal(&cont->copts, CO_ISSBL, 0); OptSetVal(&cont->copts, CO_TO_SBL, 0); OptSetVal(&cont->copts, CO_FROM_SBL, 0); OptSetVal(&cont->copts, CO_ASK_SBL, 0); } } { foreach_subtag(item_c, pak->query, "item") { char *jid = iks_find_attrib (item_c, "jid"); char *subs = iks_find_attrib (item_c, "subscription"); char *name = iks_find_attrib (item_c, "name"); char *ask = iks_find_attrib (item_c, "ask"); iksid *jjid; if (!jid || !subs) continue; jjid = iks_id_new (iks_stack (pak->x), jid); GetBothContacts (jjid, serv, &contb, &contr, 1); OptSetVal(&contb->copts, CO_ISSBL, 1); OptSetVal(&contb->copts, CO_TO_SBL, !strcmp (subs, "both") || !strcmp (subs, "to")); OptSetVal(&contb->copts, CO_FROM_SBL, !strcmp (subs, "both") || !strcmp (subs, "from")); OptSetVal(&contb->copts, CO_ASK_SBL, ask && !strcmp (ask, "subscribe")); if (name && !ContactFindScreen (serv->contacts, name)) ContactAddAlias (contb, name); { foreach_subtag(group, item_c, "group") { char *g = iks_cdata (group); if (g && *g) { cg = ContactGroupFind (serv, 0, g); if (!cg) cg = ContactGroupC (serv, 0, g); if (!ContactHas (cg, contb)) ContactAdd (cg, contb); } }} }} return IKS_FILTER_EAT; } static int XmppHandleIqPrivacy (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; iks *c; const char *name = NULL; int type = 0; if (strlen (pak->id) > 5 && pak->id[5] >= '1' && pak->id[5] <= '8') type = pak->id[5] - '0'; if (pak->subtype == IKS_TYPE_RESULT && type) { if (type == p_list || type == p_list_quiet) { const char *active = NULL, *def = NULL; int items = 0; if ((c = iks_find (pak->query, "active"))) active = iks_find_attrib (c, "name"); if ((c = iks_find (pak->query, "default"))) def = iks_find_attrib (c, "name"); s_repl (&serv->xmpp_privacy_list, NULL); s_repl (&serv->xmpp_privacy_items, NULL); if (type == p_list_quiet) return IKS_FILTER_EAT; if (active) rl_printf (i18n (2761, "Active privacy list: %s.\n"), s_wordquote (active)); else rl_printf (i18n (2762, "No active privacy list.\n")); if (def) rl_printf (i18n (2763, "Default privacy list: %s.\n"), s_wordquote (def)); else rl_printf (i18n (2764, "No default privacy list.\n")); rl_print (i18n (2765, "Available privacy lists: ")); { foreach_subtag(item_c, pak->query, "list") { if (!(name = iks_find_attrib (item_c, "name"))) continue; rl_printf (items++ ? ", %s" : "%s", s_wordquote (name)); }} rl_printf (".\n"); return IKS_FILTER_EAT; } else if (type == p_show || type == p_show_quiet) { { foreach_subtag(item_c, pak->query, "list") { if (!(name = iks_find_attrib (item_c, "name"))) continue; s_repl (&serv->xmpp_privacy_list, name); s_repl (&serv->xmpp_privacy_items, iks_string (iks_stack (item_c), item_c)); if (type == p_show_quiet) return IKS_FILTER_EAT; rl_printf (i18n (2766, "Listing privacy list: %s.\n"), s_wordquote (name)); { foreach_subtag(item_i, item_c, "item") { const char *itype = iks_find_attrib (item_i, "type"); const char *ivalue = iks_find_attrib (item_i, "value"); const char *iaction = iks_find_attrib (item_i, "action"); const char *iorder = iks_find_attrib (item_i, "order"); iks *b_ms = iks_find (item_i, "message"); iks *b_pi = iks_find (item_i, "presence-in"); iks *b_po = iks_find (item_i, "presence-out"); iks *b_iq = iks_find (item_i, "iq"); const char *affects; if (b_ms || b_pi || b_po || b_iq) { affects = s_sprintf ("%s%s%s%s", b_ms ? "msg," : "", b_pi ? "pi" : "", b_po ? "po," : "", b_iq ? "iq," : ""); affects = s_sprintf ("%*s", (int)(strlen (affects) - 1), affects); } else affects = "all"; rl_printf ("%-4s %-5s %-5s %-5s %s\n", iorder ? iorder : 0, iaction ? iaction : "", affects, itype ? itype : "", ivalue ? ivalue : ""); }} }} return IKS_FILTER_EAT; } else if (type == p_active || type == p_default) { s_repl (&serv->xmpp_privacy_list, NULL); s_repl (&serv->xmpp_privacy_items, NULL); XMPPPrivacy (serv, p_list, NULL, NULL); return IKS_FILTER_EAT; } } else if (pak->subtype == IKS_TYPE_SET) { if ((c = iks_find (pak->query, "list"))) name = iks_find_attrib (c, "name"); s_repl (&serv->xmpp_privacy_list, NULL); s_repl (&serv->xmpp_privacy_items, NULL); rl_printf (i18n (2767, "Server updates privacy list %s.\n"), s_wordquote (name)); return IKS_FILTER_EAT; } return 0; } static int XmppHandleIqTime (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { return IKS_FILTER_EAT; } static int XmppHandleIqDefault (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; iks *x, *e, *t, *c; x = iks_new ("iq"); iks_insert_attrib (x, "type", "error"); iks_insert_attrib (x, "id", pak->id); e = iks_insert (x, "error"); iks_insert_attrib (e, "type", "cancel"); t = iks_insert (e, "feature-not-implemented"); iks_insert_attrib (t, "xmlns", "urn:ietf:params:xml:ns:xmpp-stanzas"); c = iks_tree (iks_string (iks_stack (x), iks_first_tag (x)), 0, NULL); iks_insert_node (x, c); iks_send (serv->xmpp_parser, x); iks_delete (c); iks_delete (x); return IKS_FILTER_PASS; } /*********** other handlers *************/ static int __SkipChar (const char **s, char c) { if (**s == c && **s) (*s)++; if (**s) return *((*s)++) - '0'; return 0; } static time_t ParseUTCDate (const char *s) { struct tm tm; tm.tm_year = __SkipChar (&s, 0) * 1000; tm.tm_year += __SkipChar (&s, 0) * 100; tm.tm_year += __SkipChar (&s, 0) * 10; tm.tm_year += __SkipChar (&s, 0) - 1900; tm.tm_mon = __SkipChar (&s, '-') * 10; tm.tm_mon += __SkipChar (&s, 0) - 1; tm.tm_mday = __SkipChar (&s, '-') * 10; tm.tm_mday += __SkipChar (&s, 0); tm.tm_hour = __SkipChar (&s, 'T') * 10; tm.tm_hour += __SkipChar (&s, 0); tm.tm_min = __SkipChar (&s, ':') * 10; tm.tm_min += __SkipChar (&s, 0); tm.tm_sec = __SkipChar (&s, ':') * 10; if (!*s) return -1; tm.tm_sec += __SkipChar (&s, 0); if (*s) return -1; return timegm (&tm); } static time_t XmppHandleXEP91 (iks *x) { time_t date = NOW; iks *delay; if ((delay = find_with_ns_attrib (x, "x", "jabber:x:delay"))) { struct tm; char *stamp; // char *dfrom = iks_find_attrib (delay, "from"); stamp = iks_find_attrib (delay, "stamp"); date = ParseUTCDate (stamp); // if (date != NOW) // DropAttrib (delay, "stamp"); // DropAttrib (delay, "from"); // CheckInvalid (delay); } return date; } static void XmppHandleXEP115 (iks *x, Contact *contr) { iks *caps; if ((caps = find_with_ns_attrib (x, "c", "http://jabber.org/protocol/caps"))) { char *node = iks_find_attrib (caps, "node"); char *ver = iks_find_attrib (caps, "ver"); char *ext = iks_find_attrib (caps, "ext"); if (!strcmp (node, "http://www.climm.org/xmpp/caps")) node = "climm"; else if (!strcmp (node, "http://mail.google.com/xmpp/client/caps")) node = "GoogleMail"; else if (!strcmp (node, "http://www.google.com/xmpp/client/caps")) node = "GoogleTalk"; else if (!strcmp (node, "http://www.android.com/gtalk/client/caps")) node = "Android"; else if (!strcmp (node, "http://pidgin.im/caps")) node = "Pidgin"; else if (!strcmp (node, "http://gaim.sf.net/caps")) node = "Gaim"; else if (!strcmp (node, "http://kopete.kde.org/jabber/caps")) node = "Kopete"; else if (!strcmp (node, "http://psi-im.org/caps")) node = "Psi"; else if (!strcmp (node, "http://miranda-im.org/caps")) node = "Miranda"; else if (!strcmp (node, "apple:ichat:caps") || !strcmp (node, "http://www.apple.com/ichat/caps")) node = "iChat"; else if (!strcmp (node, "http://telepathy.freedesktop.org/caps")) node = "Telepathy"; else if (!strcmp (node, "http://talkgadget.google.com/client/caps")) node = "TalkGadget"; else if (!strcmp (node, "http://trillian.im/caps")) node = "Trillian"; s_repl (&contr->cap_string, ext); s_repl (&contr->version, s_sprintf ("%s %s", node, ver)); // DropAttrib (caps, "ver"); // DropAttrib (caps, "ext"); // DropAttrib (caps, "node"); // CheckInvalid (caps); } } static void XmppHandleXEP22a (Server *serv, iks *x, Contact *cfrom) { char *refid; int ref = -1; int_msg_t type; iks *ch; if ((refid = iks_find_cdata (x, "id"))) { // DropCData (tid); // CheckInvalid (tid); } if (refid && !strncmp (refid, "xmpp-", 5) && !strncmp (refid + 5, serv->xmpp_stamp, 14) && strlen (refid) > 19 && refid[19] == '-') sscanf (refid + 20, "%x", &ref); if ((ch = iks_find (x, "offline"))) { type = INT_MSGOFF; // CheckInvalid (dotag); } else if ((ch = iks_find (x, "paused"))) { // CheckInvalid (dotag); IMIntMsg (cfrom, NOW, ims_offline, INT_MSGNOCOMP, ""); ref = -1; } else if ((ch = iks_find (x, "delivered"))) { type = INT_MSGACK_TYPE2; // CheckInvalid (dotag); } else if ((ch = iks_find (x, "displayed"))) { type = INT_MSGDISPL; // CheckInvalid (dotag); } else if ((ch = iks_find (x, "composing"))) { // CheckInvalid (dotag); IMIntMsg (cfrom, NOW, ims_offline, INT_MSGCOMP, ""); ref = -1; } else ref = -1; if (ref != -1) { Event *event = QueueDequeue (serv->conn, QUEUE_XMPP_RESEND_ACK, ref); if (event) { Message *msg = (Message *)event->data; assert (msg); if (msg->send_message && !msg->otrinjected) { msg->type = type; IMIntMsgMsg (msg, NOW, ims_offline); } event->attempts += 5; QueueEnqueue (event); } } // CheckInvalid (XEP22); } static void XmppHandleXEP22c (Server *serv, iksid *from, char *tof, char *id, char *type) { iks *msg = iks_make_msg (IKS_TYPE_NONE, from->full, NULL), *x; iks_insert_attrib (msg, "id", s_sprintf ("ack-%s-%x", serv->xmpp_stamp, ++serv->xmpp_sequence)); x = iks_insert (msg, "x"); iks_insert_attrib (x, "xmlns", "jabber:x:event"); iks_insert (x, type); iks_insert_cdata (iks_insert (x, "id"), id, 0); iks_send (serv->xmpp_parser, msg); iks_delete (msg); } static void XmppHandleXEP22b (Server *serv, iks *x, iksid *from, char *tof, char *id) { iks *ch; if ((ch = iks_find (x, "delivered"))) { XmppHandleXEP22c (serv, from, tof, id, "delivered"); // CheckInvalid (dotag); } if ((ch = iks_find (x, "displayed"))) { XmppHandleXEP22c (serv, from, tof, id, "displayed"); // CheckInvalid (dotag); } if ((ch = iks_find (x, "composing"))) { // CheckInvalid (dotag); } if ((ch = iks_find (x, "offline"))) { // CheckInvalid (dotag); } } static char XmppHandleXEP22 (Server *serv, iks *t, Contact *cfrom, iksid *from, char *tof, char *id) { char ret = 0; iks *ch; if ((ch = find_with_ns_attrib (t, "x", "jabber:x:event"))) { ret = 1; if (!iks_find (t, "body")) { XmppHandleXEP22a (serv, ch, cfrom); return 1; } else XmppHandleXEP22b (serv, ch, from, tof, id); } return 0; } static char XmppHandleXEP85 (Server *serv, iks *t, Contact *cfrom, iksid *from, char *tof, char *id) { char ret = 0; iks *ch; if ((ch = find_with_ns_attrib (t, "addresses", "http://jabber.org/protocol/address"))) { // DropAllChildsTree (address, "address"); // CheckInvalid (address); ret = 1; } if ((ch = find_with_ns_attrib (t, "active", "http://jabber.org/protocol/chatstates"))) { // CheckInvalid (active); ret = 1; } if ((ch = find_with_ns_attrib (t, "composing", "http://jabber.org/protocol/chatstates"))) { // CheckInvalid (composing); IMIntMsg (cfrom, NOW, ims_offline, INT_MSGCOMP, ""); ret = 1; } if ((ch = find_with_ns_attrib (t, "paused", "http://jabber.org/protocol/chatstates"))) { // CheckInvalid (paused); IMIntMsg (cfrom, NOW, ims_offline, INT_MSGNOCOMP, ""); ret = 1; } if ((ch = find_with_ns_attrib (t, "inactive", "http://jabber.org/protocol/chatstates"))) { // CheckInvalid (inactive); ret = 1; } if ((ch = find_with_ns_attrib (t, "gone", "http://jabber.org/protocol/chatstates"))) { // CheckInvalid (gone); ret = 1; } if (ret && !iks_find (t, "body")) return 1; return 0; } static int XmppHandleMessage (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; // char *toX = iks_find_attrib (pak->x, "to"); // iksid *to = iks_id_new (iks_stack (pak->x), toX); // Contact *cto = ContactScreen (serv, pak->tof); // std::string subtypeval = t->findAttribute ("type"); // std::string body = t->body(); // std::string subject = t->subject(); // std::string html; // gloox::Tag *htmltag = find_with_ns_attrib (t, "html", "http://jabber.org/protocol/xhtml-im"); // if (htmltag) // htmltag = find_with_ns_attrib (htmltag, "body", "http://www.w3.org/1999/xhtml"); // if (htmltag) // html = htmltag->cdata(); // DropAttrib (t, "type"); time_t delay = -1; Contact *contb, *contr; Opt *opt; GetBothContacts (pak->from, serv, &contb, &contr, 0); // DropAllChilds (t, "subject"); // DropAllChilds (t, "thread"); // handleXEP71 (t); // handleGoogleNosave (t); // handleGoogleSig (t); // handleGoogleChatstate (t); // handleXEP136 (t); delay = XmppHandleXEP91 (pak->x); XmppHandleXEP115 (pak->x, contr); // entity capabilities (used also for client version) if (XmppHandleXEP22 (serv, pak->x, contr, pak->from, iks_find_attrib (pak->x, "to"), pak->id)) return IKS_FILTER_EAT; if (XmppHandleXEP85 (serv, pak->x, contr, pak->from, iks_find_attrib (pak->x, "to"), pak->id)) return IKS_FILTER_EAT; // DropAllChilds (t, "body"); opt = OptSetVals (NULL, CO_ORIGIN, CV_ORIGIN_v8, CO_MSGTYPE, MSG_NORM, CO_MSGTEXT, iks_find_cdata (pak->x, "body"), 0); // if (!subject.empty()) // opt = OptSetVals (opt, CO_MSGTYPE, MSG_NORM_SUBJ, CO_SUBJECT, subject.c_str(), 0); // if (!strcmp (html.c_str(), body.c_str())) // opt = OptSetVals (opt, CO_SAMEHTML, 1); IMSrvMsgFat (contr, delay, opt); // if (gloox::Tag *x = t->findChild ("x")) // CheckInvalid (x); return IKS_FILTER_EAT; } static int XmppHandlePresenceErr (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Contact *contb, *contr; Server *serv = (Server *)fserv; GetBothContacts (pak->from, serv, &contb, &contr, 1); IMOffline (contr); return IKS_FILTER_EAT; } /* * React to subscription requests. */ static int XmppHandleSubscription (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; Contact *contb, *contr; GetBothContacts (pak->from, serv, &contb, &contr, 1); if (pak->subtype == IKS_TYPE_SUBSCRIBE) IMSrvMsg (contr, NOW, CV_ORIGIN_v8, MSG_AUTH_REQ, NULL); else if (pak->subtype == IKS_TYPE_SUBSCRIBED) IMSrvMsg (contr, NOW, CV_ORIGIN_v8, MSG_AUTH_GRANT, NULL); else if (pak->subtype == IKS_TYPE_UNSUBSCRIBE) IMSrvMsg (contr, NOW, CV_ORIGIN_v8, MSG_AUTH_DENY, NULL); else if (pak->subtype == IKS_TYPE_UNSUBSCRIBED) IMSrvMsg (contr, NOW, CV_ORIGIN_v8, MSG_AUTH_DONE, NULL); else return IKS_FILTER_PASS; return IKS_FILTER_EAT; } static int XmppHandlePresence (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; // ContactGroup *tcg; Contact *contb, *contr; // , *c; status_t status; // std::string pri; time_t delay; GetBothContacts (pak->from, serv, &contb, &contr, 1); // if (gloox::Tag *priority = s->findChild ("priority")) // { // pri = priority->cdata(); // DropCData (priority); // CheckInvalid (priority); // } delay = XmppHandleXEP91 (pak->x); // FIXME: do something with it! XmppHandleXEP115 (pak->x, contr); // entity capabilities (used also for client version) // handleXEP153 (s, contb); // vcard-based avatar, nickname // handleXEP27 (s); // OpenPGP signature (obsolete) // handleXEP8 (s); // iq-based avatar (obsolete) if (pak->subtype == IKS_TYPE_UNAVAILABLE) { status = ims_offline; // DropAttrib (s, "type"); IMOffline (contr); return IKS_FILTER_EAT; } else if (pak->show == IKS_SHOW_CHAT) status = ims_ffc; else if (pak->show == IKS_SHOW_AWAY) status = ims_away; else if (pak->show == IKS_SHOW_DND) status = ims_dnd; else if (pak->show == IKS_SHOW_XA) status = ims_na; else if (pak->show == IKS_SHOW_AVAILABLE) status = ims_online; else assert (0); if (!ContactPrefVal (contb, CO_TO_SBL)) XMPPAuthorize (serv, contb, auth_req, "auto re-sync"); IMOnline (contr, status, imf_none, pak->show, iks_find_cdata (pak->x, "status")); return IKS_FILTER_EAT; } static int XmppUnknown (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; XmppSaveLog (serv, iks_string (iks_stack (pak->x), pak->x), 0, 2); return IKS_FILTER_EAT; } /***************** end handlers *******************/ static void XmppLoggedIn (Server *serv) { iks *x; if (serv->xmpp_filter) iks_filter_delete (serv->xmpp_filter); serv->xmpp_filter = iks_filter_new (); iks_filter_add_rule (serv->xmpp_filter, XmppUnknown, serv, IKS_RULE_DONE); iks_filter_add_rule (serv->xmpp_filter, XmppHandlePresence, serv, IKS_RULE_TYPE, IKS_PAK_PRESENCE, IKS_RULE_DONE); iks_filter_add_rule (serv->xmpp_filter, XmppHandleSubscription, serv, IKS_RULE_TYPE, IKS_PAK_S10N, IKS_RULE_DONE); iks_filter_add_rule (serv->xmpp_filter, XmppHandlePresenceErr, serv, IKS_RULE_TYPE, IKS_PAK_S10N, IKS_RULE_SUBTYPE, IKS_TYPE_ERROR, IKS_RULE_DONE); iks_filter_add_rule (serv->xmpp_filter, XmppHandleMessage, serv, IKS_RULE_TYPE, IKS_PAK_MESSAGE, IKS_RULE_DONE); serv->conn->connect = CONNECT_OK | CONNECT_SELECT_R; XMPPSetstatus (serv, NULL, serv->status, serv->conn->cont->status_message); x = iks_make_iq (IKS_TYPE_GET, IKS_NS_ROSTER); iks_insert_attrib (x, "id", s_sprintf ("roster-%s-%x", serv->xmpp_stamp, serv->xmpp_sequence++)); iks_send (serv->xmpp_parser, x); iks_delete (x); iks_filter_add_rule (serv->xmpp_filter, XmppHandleIqRoster, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_NS, IKS_NS_ROSTER, IKS_RULE_DONE); iks_filter_add_rule (serv->xmpp_filter, XmppHandleIqRoster, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_SET, IKS_RULE_NS, IKS_NS_ROSTER, IKS_RULE_DONE); QueueEnqueueData2 (serv->conn, QUEUE_SRV_KEEPALIVE, 0, 300, NULL, &sendIqTimeReqs, NULL); iks_filter_add_rule (serv->xmpp_filter, XmppHandleIqTime, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_NS, "urn:xmpp:time", IKS_RULE_DONE); x = iks_make_iq (IKS_TYPE_GET, "http://jabber.org/protocol/disco#info"); iks_insert_attrib (x, "id", s_sprintf ("disco-%s-%x", serv->xmpp_stamp, serv->xmpp_sequence++)); iks_insert_attrib (x, "to", serv->xmpp_id->server); iks_send (serv->xmpp_parser, x); iks_delete (x); iks_filter_add_rule (serv->xmpp_filter, XmppHandleIqDisco, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE); iks_filter_add_rule (serv->xmpp_filter, XmppHandleIqXEP92, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_NS, "jabber:iq:version", IKS_RULE_DONE); iks_filter_add_rule (serv->xmpp_filter, XmppHandleIqXEP12, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_NS, "jabber:iq:last", IKS_RULE_DONE); iks_filter_add_rule (serv->xmpp_filter, XmppHandleIqPrivacy, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_NS, "jabber:iq:privacy", IKS_RULE_DONE); iks_filter_add_rule (serv->xmpp_filter, XmppHandleIqDefault, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_SET, IKS_RULE_DONE); iks_filter_add_rule (serv->xmpp_filter, XmppHandleIqDefault, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_GET, IKS_RULE_DONE); { const char *list = ServerPrefStr (serv, CO_XMPP_PRIV); if (list) XMPPSendIqPrivacy (serv, p_active, list); } } static int XmppSessionResult (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; if (pak->subtype != IKS_TYPE_RESULT) XmppStreamError (serv, s_sprintf ("Couldn't get session %s.", iks_string (iks_stack (pak->x), pak->x))); else XmppLoggedIn (serv); return IKS_FILTER_EAT; } static int XmppUserResult (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { /* Server *serv = (Server *)fserv; */ rl_printf ("Message %s resulted in:\n%s\n", pak->id, iks_string (iks_stack (pak->x), pak->x)); return IKS_FILTER_EAT; } static int XmppBindResult (IKS_FILTER_USER_DATA *fserv, ikspak *pak) { Server *serv = (Server *)fserv; if (pak->subtype == IKS_TYPE_RESULT) { iks *bind = iks_find_with_attrib (pak->x, "bind", "xmlns", "urn:ietf:params:xml:ns:xmpp-bind"); char *newjid; if (bind && (newjid = iks_find_cdata (bind, "jid"))) { iksid *newid = iks_id_new (iks_parser_stack (serv->xmpp_parser), newjid); if (strchr (serv->screen, '/')) s_repl (&serv->screen, s_sprintf ("%s/%s", newid->partial, strchr (serv->screen, '/') + 1)); else s_repl (&serv->screen, newid->partial); if (strcmp (serv->xmpp_id->partial, newid->partial)) rl_printf ("Server changed JID from %s to %s.\n", serv->xmpp_id->partial, newid->partial); serv->xmpp_id = newid; } } else XmppStreamError (serv, s_sprintf ("Couldn't bind. %s", iks_string (iks_stack (pak->x), pak->x))); return IKS_FILTER_EAT; } #if LIBIKS_VERSION < 0x0103 || defined(ENABLE_AUTOPACKAGE) static char *base64_encode (const char *data, int count) { char *base64 = iks_base64_encode (data, count); char *b64 = strdup (base64); int len = strlen (b64); iks_free (base64); while ((len % 3) && b64[len - 1] == '=') len--; b64[len] = 0; return b64; } void replace_iks_start_sasl (iksparser *prs, char *user, char *password) { char *base64 = base64_encode (s_sprintf ("%c%s%c%s", 0, user, 0, password), strlen (user) + strlen (password) + 2); iks_send_raw (prs, s_sprintf ("%s", base64)); free (base64); } #endif static int XmppStreamHook (IKS_TRANS_USER_DATA *userv, int type, iks *node) { Server *serv = (Server *)userv; iksparser *prs = serv->xmpp_parser; switch (type) { case IKS_NODE_STOP: XmppStreamError (serv, s_sprintf ("server disconnect [%s]", iks_name (iks_first_tag (node)))); if (node) iks_delete (node); return IKS_NET_DROPPED; case IKS_NODE_ERROR: if (!strcmp (iks_name (iks_first_tag (node)), "conflict")) OptSetVal (&serv->copts, CO_AUTOTAGRES, 1); XmppStreamError (serv, s_sprintf ("stream error [%s]", iks_name (iks_first_tag (node)))); if (node) iks_delete (node); return IKS_NET_DROPPED; case IKS_NODE_START: // nothing to do with it break; case IKS_NODE_NORMAL: if (!strcmp ("stream:features", iks_name (node))) { int feat = iks_stream_features (node); if (feat & IKS_STREAM_STARTTLS && UtilIOSSLSupported() == IO_SSL_OK) iks_send_raw (prs, ""); else if (feat & IKS_STREAM_SASL_MD5) iks_start_sasl (prs, IKS_SASL_DIGEST_MD5, serv->xmpp_id->user, serv->passwd); else if (feat & IKS_STREAM_SASL_PLAIN) #if LIBIKS_VERSION < 0x0103 || defined(USE_AUTOPACKAGE) replace_iks_start_sasl (prs, serv->xmpp_id->user, serv->passwd); #else iks_start_sasl (prs, IKS_SASL_PLAIN, serv->xmpp_id->user, serv->passwd); #endif else { if (feat & (IKS_STREAM_BIND | IKS_STREAM_SESSION)) { if (serv->xmpp_filter) iks_filter_delete (serv->xmpp_filter); serv->xmpp_filter = iks_filter_new (); iks_filter_add_rule (serv->xmpp_filter, XmppUnknown, serv, IKS_RULE_DONE); if (feat & IKS_STREAM_BIND) { iks *t = iks_make_resource_bind (serv->xmpp_id); iks_insert_attrib (t, "id", "bind"); iks_filter_add_rule (serv->xmpp_filter, XmppBindResult, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_ID, "bind", IKS_RULE_DONE); iks_send (prs, t); iks_delete (t); } if (feat & IKS_STREAM_SESSION) { iks *t = iks_make_session (); iks_insert_attrib (t, "id", "auth"); iks_filter_add_rule (serv->xmpp_filter, XmppSessionResult, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_ID, "auth", IKS_RULE_DONE); iks_send (prs, t); iks_delete (t); } } else XmppLoggedIn (serv); } } else if (strcmp ("failure", iks_name (node)) == 0) { s_repl (&serv->passwd, NULL); XmppStreamError (serv, "sasl authentication failed"); } else if (strcmp ("success", iks_name (node)) == 0) iks_send_header (prs, serv->xmpp_id->server); else if (strcmp ("proceed", iks_name (node)) == 0) { io_ssl_err_t rce = UtilIOSSLOpen (serv->conn, 2); if (rce != IO_SSL_OK) { XmppStreamError (serv, s_sprintf ("ssl error %d %s", rce, UtilIOErr (serv->conn))); return IKS_NET_RWERR; } serv->conn->connect &= ~CONNECT_OK & ~4; return IKS_OK; } else { ikspak *pak = iks_packet (node); iks_filter_packet (serv->xmpp_filter, pak); } } if (node) iks_delete (node); return IKS_OK; } static void XMPPCallbackDispatch (Connection *conn) { iksparser *prs = conn->serv->xmpp_parser; io_err_t rce; char *semi; const char *sp; int rc; if (!(conn->connect & (CONNECT_OK | 4))) { rc = UtilIORead (conn, NULL, 0); rce = UtilIOShowError (conn, rc); switch (rce) { case IO_RW: semi = strchr (conn->server, ';'); if (!semi) return; memmove (conn->server, semi + 1, strlen (semi)); semi = strchr (conn->server, ';'); if (semi) *semi = 0; sp = s_sprintf ("%s", s_wordquote (strchr (conn->server, ':') ? conn->server : s_sprintf ("%s:%lu", conn->server, conn->port))); rl_printf (i18n (2620, "Opening XMPP connection for %s at %s...\n"), s_wordquote (conn->serv->screen), sp); if (semi) *semi = ';'; UtilIOConnectTCP (conn); return; case IO_OK: return; case IO_CONNECTED: rl_print (""); if (prG->verbose || (conn->serv && conn == conn->serv->conn)) if (rl_pos () > 0) rl_print (i18n (1634, "ok.\n")); if (conn->port == 443 || conn->port == 5223) { io_ssl_err_t rce = UtilIOSSLOpen (conn, 2); if (rce != IO_SSL_OK) { XmppStreamError (conn->serv, s_sprintf ("ssl error %d %s", rce, UtilIOErr (conn))); EventD (QueueDequeue2 (conn, QUEUE_DEP_WAITLOGIN, 0, NULL)); return; } } if (!conn->serv->xmpp_parser) { prs = iks_stream_new (IKS_NS_CLIENT, conn->serv, &XmppStreamHook); conn->serv->xmpp_id = iks_id_new (iks_parser_stack (prs), conn->serv->screen); if (!conn->serv->xmpp_id->resource) conn->serv->xmpp_id = iks_id_new (iks_parser_stack (prs), s_sprintf ("%s@%s/climm", conn->serv->xmpp_id->user, conn->serv->xmpp_id->server)); if (ServerPrefVal (conn->serv, CO_TAGRESSOURCE) || ServerPrefVal (conn->serv, CO_AUTOTAGRES)) conn->serv->xmpp_id = iks_id_new (iks_parser_stack (prs), s_sprintf ("%s/%s%04X%04X", conn->serv->xmpp_id->partial, conn->serv->xmpp_id->resource, rand() % 0xffff, rand() % 0xffff)); iks_set_log_hook (prs, XmppSaveLog); } conn->serv->xmpp_parser = prs; rc = iks_connect_with (prs, conn->server, conn->port, conn->serv->xmpp_id->server, &iks_climm_transport); if (rc != IKS_OK) { XmppStreamError (conn->serv, "could not send stream header"); return; } conn->connect |= 4; return; default: assert (0); } } conn->connect |= 0x40; rc = iks_recv (prs, 0); conn->connect &= ~0x40; if (rc != IKS_OK && conn->dispatcher) XmppStreamError (conn->serv, s_sprintf ("failing with error code %d", rc)); if (!conn->dispatcher) IMCallBackReconn (conn); } void XMPPLogout (Server *serv) { if (serv->xmpp_parser && ~serv->conn->connect & 0x40) { iksparser *prs = serv->xmpp_parser; serv->xmpp_parser = NULL; iks_disconnect (prs); iks_parser_delete (prs); serv->xmpp_id = NULL; serv->xmpp_filter = NULL; } QueueCancel (serv->conn); UtilIOClose (serv->conn); } /* **************** */ static void SnacCallbackXmpp (Event *event) { Message *msg = (Message *)event->data; assert (event); assert (msg); assert (event->cont); if (event->attempts < 5) { if (msg->send_message && !msg->otrinjected) { msg->type = INT_MSGACK_V8; // IMIntMsgMsg (msg, NOW, ims_offline); } event->attempts = 20; event->due = time (NULL) + 600; QueueEnqueue (event); } else { MsgD (msg); event->data = NULL; EventD (event); } } static void SnacCallbackXmppCancel (Event *event) { Message *msg = (Message *)event->data; if (msg->send_message && !msg->otrinjected) rl_printf (i18n (2234, "Message %s discarded - lost session.\n"), msg->plain_message ? msg->plain_message : msg->send_message); MsgD (msg); event->data = NULL; EventD (event); } UBYTE XMPPSendmsg (Server *serv, Contact *cont, Message *msg) { iks *x, *xx; Event *event; assert (cont); assert (msg->send_message); if (~serv->conn->connect & CONNECT_OK) return RET_DEFER; if (msg->type != MSG_NORM) return RET_DEFER; x = iks_make_msg (IKS_TYPE_CHAT, cont->screen, msg->send_message); iks_insert_attrib (x, "id", s_sprintf ("xmpp-%s-%x", serv->xmpp_stamp, ++serv->xmpp_sequence)); iks_insert_attrib (x, "from", serv->xmpp_id->full); xx = iks_insert (x, "x"); iks_insert_attrib (xx, "xmlns", "jabber:x:event"); iks_insert (xx, "offline"); iks_insert (xx, "delivered"); iks_insert (xx, "displayed"); iks_insert (xx, "composing"); iks_send (serv->xmpp_parser, x); iks_delete (x); event = QueueEnqueueData2 (serv->conn, QUEUE_XMPP_RESEND_ACK, serv->xmpp_sequence, 120, msg, &SnacCallbackXmpp, &SnacCallbackXmppCancel); event->cont = cont; return RET_OK; } void XMPPSetstatus (Server *serv, Contact *cont, status_t status, const char *msg) { enum ikshowtype p = StatusToIksstatus (&status); iks *x = iks_make_pres (p, msg); if (p != IKS_SHOW_UNAVAILABLE) { iks *caps = iks_insert (x, "c"); iks_insert_attrib (caps, "xmlns", "http://jabber.org/protocol/caps"); iks_insert_attrib (caps, "node", "http://www.climm.org/xmpp/caps"); iks_insert_attrib (caps, "ver", BuildVersionStr); iks_insert_cdata (iks_insert (x, "priority"), "5", 0); } if (cont) iks_insert_attrib (x, "to", cont->screen); else { if (serv->conn->cont->status_message != msg) s_repl (&serv->conn->cont->status_message, msg); serv->status = status; serv->nativestatus = p; } iks_send (serv->xmpp_parser, x); iks_delete (x); } UBYTE XMPPSendIq (Server *serv, int8_t which, const char *screen, const char *msg) { int err; iks *child = iks_tree (msg, 0, &err), *x; const char *id; if (!child) return RET_FAIL; id = s_sprintf ("user-%s-%x", serv->xmpp_stamp, ++serv->xmpp_sequence); iks_filter_add_rule (serv->xmpp_filter, XmppUserResult, serv, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_ID, id, IKS_RULE_DONE); x = iks_new_within ("iq", iks_stack (child)); iks_insert_attrib (x, "type", which ? "set" : "get"); iks_insert_attrib (x, "id", id); iks_insert_attrib (x, "from", serv->xmpp_id->full); if (screen && *screen) iks_insert_attrib (x, "to", screen); iks_insert_node (x, child); iks_send (serv->xmpp_parser, x); iks_delete (x); return RET_OK; } void XMPPAuthorize (Server *serv, Contact *cont, auth_t how, const char *msg) { iks *x; while (cont->parent && cont->parent->serv == cont->serv) cont = cont->parent; x = iks_make_s10n (how == auth_grant ? IKS_TYPE_SUBSCRIBED : how == auth_deny ? IKS_TYPE_UNSUBSCRIBED : how == auth_req ? IKS_TYPE_SUBSCRIBE : IKS_TYPE_UNSUBSCRIBE, cont->screen, msg); iks_send (serv->xmpp_parser, x); iks_delete (x); } void XMPPGoogleMail (Server *serv, time_t since, const char *query) { assert (query); if (since == 1) XmppSendIqGmail (serv, serv->xmpp_gmail_newer, serv->xmpp_gmail_newertid, serv->xmpp_gmail_query); else XmppSendIqGmail (serv, since * 1000ULL, NULL, query); s_repl (&serv->xmpp_gmail_query, query); } void XMPPPrivacy (Server *serv, xmpp_priv_t type, const char *list, const char *edit) { if (type == p_edit) { if (!list) return; if (!serv->xmpp_privacy_list || strcmp (list, serv->xmpp_privacy_list)) { s_repl (&serv->xmpp_privacy_list, NULL); s_repl (&serv->xmpp_privacy_items, NULL); } if (!serv->xmpp_privacy_items) { XMPPPrivacy (serv, p_show_quiet, list, NULL); rl_printf (i18n (2768, "Fetching privacy list (was not cached), please try again.\n")); return; } } if (type == p_edit || type == p_set) XMPPSendIqPrivacyEdit (serv, type, list, edit ? edit : ""); else XMPPSendIqPrivacy (serv, type, list); } #if 0 class CLIMMXMPP: public gloox::ConnectionListener, public gloox::MessageHandler, private : void handleXEP8 (gloox::Tag *t); void handleXEP27 (gloox::Tag *t); void handleXEP71 (gloox::Tag *t); void handleXEP153 (gloox::Tag *t, Contact *contr); void handleGoogleNosave (gloox::Tag *t); void handleGoogleSig (gloox::Tag *t); void handleGoogleChatstate (gloox::Tag *t); void handleXEP136 (gloox::Tag *t); virtual void handleSubscription (gloox::Stanza *stanza); }; static bool DropChild (gloox::Tag *s, gloox::Tag *c) { if (s->children().size()) s->setCData (""); s->children().remove (c); delete c; } static bool DropAttrib (gloox::Tag *s, const std::string &a) { if (s->children().size()) s->setCData (""); #if defined(LIBGLOOX_VERSION) && LIBGLOOX_VERSION >= 0x000900 s->attributes().remove (gloox::Tag::Attribute (a, s->findAttribute (a))); #else s->attributes().erase (a); #endif } static bool DropCData (gloox::Tag *s) { s->setCData (""); } static bool CheckInvalid (gloox::Tag *s) { if (!s->attributes().size() && !s->children().size() && s->cdata().empty()) { if (s->parent()) DropChild (s->parent(), s); return true; } return false; } static void DropAllChilds (gloox::Tag *s, const std::string &a) { gloox::Tag::TagList::const_iterator it; for (it = s->children().begin(); it != s->children().end(); ++it) { if ((*it)->name() == a) { gloox::Tag *b = *it; DropCData (b); DropAttrib (b, "xml:lang"); if (CheckInvalid (b)) return (DropAllChilds (s, a)); } } } static void DropAllChildsTree (gloox::Tag *s, const std::string &a) { gloox::Tag::TagList::const_iterator it; if (a.empty ()) { s->attributes().clear(); for (it = s->children().begin(); it != s->children().end(); it = s->children().begin()) { gloox::Tag *b = *it; DropCData (b); DropAllChildsTree (b, ""); CheckInvalid (b); } } else { for (it = s->children().begin(); it != s->children().end(); ++it) { while ((*it)->name() == a) { gloox::Tag *b = *it; DropCData (b); DropAllChildsTree (b, ""); CheckInvalid (b); it = s->children().begin(); } } } } void CLIMMXMPP::handleXEP8 (gloox::Tag *t) { if (gloox::Tag *avatar = find_with_ns_attrib (t, "x", "jabber:x:avatar")) { if (gloox::Tag *hash = avatar->findChild ("hash")) { DropCData (hash); CheckInvalid (hash); } CheckInvalid (avatar); } } void CLIMMXMPP::handleXEP27 (gloox::Tag *t) { if (gloox::Tag *sig = find_with_ns_attrib (t, "x", "jabber:x:signed")) { DropCData (sig); CheckInvalid (sig); } } void CLIMMXMPP::handleXEP71 (gloox::Tag *t) { if (gloox::Tag *xhtmlim = find_with_ns_attrib (t, "html", "http://jabber.org/protocol/xhtml-im")) { DropAllChildsTree (xhtmlim, "body"); CheckInvalid (xhtmlim); } } void CLIMMXMPP::handleXEP136 (gloox::Tag *t) { if (gloox::Tag *arc = find_with_ns_attrib (t, "record", "http://jabber.org/protocol/archive")) { DropAttrib (arc, "otr"); CheckInvalid (arc); } } void CLIMMXMPP::handleXEP153 (gloox::Tag *t, Contact *contr) { if (gloox::Tag *vcard = find_with_ns_attrib (t, "x", "vcard-temp:x:update")) { if (gloox::Tag *photo = vcard->findChild ("photo")) { DropCData (photo); CheckInvalid (photo); } if (gloox::Tag *nick = vcard->findChild ("nickname")) { std::string nickname = nick->cdata(); ContactAddAlias (contr, nickname.c_str()); DropCData (nick); CheckInvalid (nick); } CheckInvalid (vcard); } } void CLIMMXMPP::handleGoogleNosave (gloox::Tag *t) { if (gloox::Tag *nosave = find_with_ns_attrib (t, "x", "google:nosave")) { DropAttrib (nosave, "value"); CheckInvalid (nosave); } } void CLIMMXMPP::handleGoogleSig (gloox::Tag *t) { if (gloox::Tag *sig = find_with_ns_attrib (t, "google-mail-signature", "google:metadata")) { DropCData (sig); CheckInvalid (sig); } } void CLIMMXMPP::handleGoogleChatstate(gloox::Tag *t) { if (gloox::Tag *chat = find_with_ns_attrib (t, "active", "http://jabber.org/protocol/chatstates")) CheckInvalid (chat); } #if defined(LIBGLOOX_VERSION) && LIBGLOOX_VERSION >= 0x000900 void CLIMMXMPP::handleMessage (gloox::Stanza *s, gloox::MessageSession *session) #else void CLIMMXMPP::handleMessage (gloox::Stanza *s) #endif { assert (s); assert (s->type() == gloox::StanzaMessage); #if defined(LIBGLOOX_VERSION) && LIBGLOOX_VERSION >= 0x000900 gloox::Stanza *t = new gloox::Stanza (s); #else gloox::Stanza *t = s->clone(); #endif if (t->subtype() == gloox::StanzaMessageError) DropAllChildsTree (t, ""); else handleMessage2 (t, s->from(), s->to().full(), s->id(), s->subtype ()); DropAttrib (t, "from"); DropAttrib (t, "to"); DropAttrib (t, "id"); DropAttrib (t, "xml:lang"); if (t->hasAttribute ("xmlns", "jabber:client")) DropAttrib (t, "xmlns"); if (!CheckInvalid (t)) { std::string txml = t->xml(); CLIMMXMPPSave (m_serv, txml.c_str(), 3); } delete t; } void CLIMMXMPP::handlePresence (gloox::Stanza *s) { assert (s); assert (s->type() == gloox::StanzaPresence); #if defined(LIBGLOOX_VERSION) && LIBGLOOX_VERSION >= 0x000900 gloox::Stanza *t = new gloox::Stanza(s); #else gloox::Stanza *t = s->clone(); #endif if (t->subtype() == gloox::StanzaPresenceError) DropAllChildsTree (t, ""); else handlePresence2 (t, s->from(), s->to(), s->status()); DropAttrib (t, "from"); DropAttrib (t, "to"); DropAttrib (t, "id"); DropAttrib (t, "xml:lang"); if (t->hasAttribute ("xmlns", "jabber:client")) DropAttrib (t, "xmlns"); DropAllChilds (t, "status"); if (!CheckInvalid (t)) { std::string txml = t->xml(); CLIMMXMPPSave (m_serv, txml.c_str(), 3); } delete t; } void CLIMMXMPP::handleSubscription (gloox::Stanza *s) { assert (s); assert (s->type() == gloox::StanzaS10n); rl_printf ("handleSubscription from:<%s> to:<%s> type:<%d> id:<%s> status:<%s> prio:<%d> body:<%s> subj:<%s> thread:<%s> pres:<%d> xml:<%s>\n", s->from().full().c_str(), s->to().full().c_str(), s->subtype(), s->id().c_str(), s->status().c_str(), s->priority(), s->body().c_str(), s->subject().c_str(), #if defined(LIBGLOOX_VERSION) && LIBGLOOX_VERSION >= 0x000900 s->thread().c_str(), s->presence(), #else s->thread().c_str(), s->show(), #endif s->xml().c_str()); } #endif #endif