presence.2007: self activity conversion to Status
[siplcs.git] / src / core / sipe.c
blob4c941e5e704cdbbce01edee3349ef3f6d1c642ae
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 pier11 <pier11@operamail.com>
7 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
10 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
13 * ***
14 * Thanks to Google's Summer of Code Program and the helpful mentors
15 * ***
17 * Session-based SIP MESSAGE documentation:
18 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 #ifndef _WIN32
36 #include <sys/socket.h>
37 #include <sys/ioctl.h>
38 #include <sys/types.h>
39 #include <netinet/in.h>
40 #include <net/if.h>
41 #else
42 #ifdef _DLL
43 #define _WS2TCPIP_H_
44 #define _WINSOCK2API_
45 #define _LIBC_INTERNAL_
46 #endif /* _DLL */
47 #include "internal.h"
48 #endif /* _WIN32 */
50 #include <time.h>
51 #include <stdio.h>
52 #include <errno.h>
53 #include <string.h>
54 #include <unistd.h>
55 #include <glib.h>
58 #include "accountopt.h"
59 #include "blist.h"
60 #include "conversation.h"
61 #include "dnsquery.h"
62 #include "debug.h"
63 #include "notify.h"
64 #include "privacy.h"
65 #include "prpl.h"
66 #include "plugin.h"
67 #include "util.h"
68 #include "version.h"
69 #include "network.h"
70 #include "xmlnode.h"
71 #include "mime.h"
72 #include "core.h"
74 #include "sipe.h"
75 #include "sipe-cal.h"
76 #include "sipe-ews.h"
77 #include "sipe-chat.h"
78 #include "sipe-conf.h"
79 #include "sip-csta.h"
80 #include "sipe-dialog.h"
81 #include "sipe-nls.h"
82 #include "sipe-session.h"
83 #include "sipe-utils.h"
84 #include "sipmsg.h"
85 #include "sipe-sign.h"
86 #include "dnssrv.h"
87 #include "request.h"
89 /* Backward compatibility when compiling against 2.4.x API */
90 #if !PURPLE_VERSION_CHECK(2,5,0)
91 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
92 #endif
94 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
95 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
97 /* Keep in sync with sipe_transport_type! */
98 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
99 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
101 /* Status identifiers (see also: sipe_status_types()) */
102 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
103 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
104 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
105 /* PURPLE_STATUS_UNAVAILABLE: */
106 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
107 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
108 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
109 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
110 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
111 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
112 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
113 /* PURPLE_STATUS_AWAY: */
114 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
115 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
116 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
117 /** Reuters status (user settable) */
118 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
119 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
120 /* ??? PURPLE_STATUS_MOBILE */
121 /* ??? PURPLE_STATUS_TUNE */
123 /* Status attributes (see also sipe_status_types() */
124 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
126 /** Activity (token and description) 2007 */
127 typedef enum
129 SIPE_ACTIVITY_UNSET = 0,
130 SIPE_ACTIVITY_ONLINE,
131 SIPE_ACTIVITY_INACTIVE,
132 SIPE_ACTIVITY_BUSY,
133 SIPE_ACTIVITY_BUSYIDLE,
134 SIPE_ACTIVITY_DND,
135 SIPE_ACTIVITY_BRB,
136 SIPE_ACTIVITY_AWAY,
137 SIPE_ACTIVITY_LUNCH,
138 SIPE_ACTIVITY_OFFLINE,
139 SIPE_ACTIVITY_ON_PHONE,
140 SIPE_ACTIVITY_IN_CONF,
141 SIPE_ACTIVITY_IN_MEETING,
142 SIPE_ACTIVITY_OOF,
143 SIPE_ACTIVITY_URGENT_ONLY,
144 SIPE_ACTIVITY_NUM_TYPES
145 } sipe_activity;
147 static struct sipe_activity_map_struct
149 sipe_activity type;
150 const char *token;
151 const char *desc;
152 const char *status_id;
154 } const sipe_activity_map[] =
156 /* This has nothing to do with Availability numbers, like 3500 (online).
157 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
159 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
160 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
161 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , SIPE_STATUS_ID_IDLE },
162 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
163 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("BusyIdle") , SIPE_STATUS_ID_BUSYIDLE },
164 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , NULL },
165 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
166 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
167 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , SIPE_STATUS_ID_LUNCH },
168 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
169 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , SIPE_STATUS_ID_ON_PHONE },
170 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , SIPE_STATUS_ID_IN_CONF },
171 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , SIPE_STATUS_ID_IN_MEETING },
172 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
173 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
176 /* Action name templates */
177 #define ACTION_NAME_PRESENCE "<presence><%s>"
179 static sipe_activity
180 sipe_get_activity_by_token(const char *token)
182 int i;
184 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
186 if (!strcmp(token, sipe_activity_map[i].token))
187 return sipe_activity_map[i].type;
190 return sipe_activity_map[0].type;
193 static const char *
194 sipe_get_activity_desc_by_token(const char *token)
196 if (!token) return NULL;
198 return sipe_activity_map[sipe_get_activity_by_token(token)].desc;
201 /** Allows to send typed messages from chat window again after account reinstantiation. */
202 static void
203 sipe_rejoin_chat(PurpleConversation *conv)
205 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
206 PURPLE_CONV_CHAT(conv)->left)
208 PURPLE_CONV_CHAT(conv)->left = FALSE;
209 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
213 static char *genbranch()
215 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
216 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
217 rand() & 0xFFFF, rand() & 0xFFFF);
221 static char *default_ua = NULL;
222 static const char*
223 sipe_get_useragent(struct sipe_account_data *sip)
225 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
226 if (is_empty(useragent)) {
227 if (!default_ua) {
228 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
229 /* ref: lzodefs.h */
230 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
231 #define SIPE_TARGET_PLATFORM "linux"
232 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
233 #define SIPE_TARGET_PLATFORM "bsd"
234 # elif defined(__APPLE__) || defined(__MACOS__)
235 #define SIPE_TARGET_PLATFORM "macosx"
236 #elif defined(__solaris__) || defined(__sun)
237 #define SIPE_TARGET_PLATFORM "sun"
238 #elif defined(_WIN32)
239 #define SIPE_TARGET_PLATFORM "win"
240 #else
241 #define SIPE_TARGET_PLATFORM "generic"
242 #endif
244 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
245 #define SIPE_TARGET_ARCH "x86_64"
246 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
247 #define SIPE_TARGET_ARCH "i386"
248 #elif defined(__ppc64__)
249 #define SIPE_TARGET_ARCH "ppc64"
250 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
251 #define SIPE_TARGET_ARCH "ppc"
252 #else
253 #define SIPE_TARGET_ARCH "other"
254 #endif
256 default_ua = g_strdup_printf("Purple/%s Sipe/%s (%s-%s; %s)",
257 purple_core_get_version(),
258 SIPE_VERSION,
259 SIPE_TARGET_PLATFORM,
260 SIPE_TARGET_ARCH,
261 sip->server_version ? sip->server_version : "");
263 useragent = default_ua;
265 return useragent;
268 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
269 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
271 return "sipe";
274 static void sipe_plugin_destroy(PurplePlugin *plugin);
276 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
278 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
279 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
280 gpointer data);
282 static void sipe_close(PurpleConnection *gc);
284 static void send_presence_status(struct sipe_account_data *sip);
286 static void sendout_pkt(PurpleConnection *gc, const char *buf);
288 static void sipe_keep_alive(PurpleConnection *gc)
290 struct sipe_account_data *sip = gc->proto_data;
291 if (sip->transport == SIPE_TRANSPORT_UDP) {
292 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
293 gchar buf[2] = {0, 0};
294 purple_debug_info("sipe", "sending keep alive\n");
295 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
296 } else {
297 time_t now = time(NULL);
298 if ((sip->keepalive_timeout > 0) &&
299 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
300 #if PURPLE_VERSION_CHECK(2,4,0)
301 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
302 #endif
304 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
305 sendout_pkt(gc, "\r\n\r\n");
306 sip->last_keepalive = now;
311 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
313 struct sip_connection *ret = NULL;
314 GSList *entry = sip->openconns;
315 while (entry) {
316 ret = entry->data;
317 if (ret->fd == fd) return ret;
318 entry = entry->next;
320 return NULL;
323 static void sipe_auth_free(struct sip_auth *auth)
325 g_free(auth->opaque);
326 auth->opaque = NULL;
327 g_free(auth->realm);
328 auth->realm = NULL;
329 g_free(auth->target);
330 auth->target = NULL;
331 auth->type = AUTH_TYPE_UNSET;
332 auth->retries = 0;
333 auth->expires = 0;
334 g_free(auth->gssapi_data);
335 auth->gssapi_data = NULL;
336 sip_sec_destroy_context(auth->gssapi_context);
337 auth->gssapi_context = NULL;
340 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
342 struct sip_connection *ret = g_new0(struct sip_connection, 1);
343 ret->fd = fd;
344 sip->openconns = g_slist_append(sip->openconns, ret);
345 return ret;
348 static void connection_remove(struct sipe_account_data *sip, int fd)
350 struct sip_connection *conn = connection_find(sip, fd);
351 if (conn) {
352 sip->openconns = g_slist_remove(sip->openconns, conn);
353 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
354 g_free(conn->inbuf);
355 g_free(conn);
359 static void connection_free_all(struct sipe_account_data *sip)
361 struct sip_connection *ret = NULL;
362 GSList *entry = sip->openconns;
363 while (entry) {
364 ret = entry->data;
365 connection_remove(sip, ret->fd);
366 entry = sip->openconns;
370 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
372 gchar noncecount[9];
373 const char *authuser = sip->authuser;
374 gchar *response;
375 gchar *ret;
377 if (!authuser || strlen(authuser) < 1) {
378 authuser = sip->username;
381 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
382 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
384 // If we have a signature for the message, include that
385 if (msg->signature) {
386 return g_strdup_printf("%s qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth_protocol, auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
389 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
390 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
391 gchar *gssapi_data;
392 gchar *opaque;
394 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
395 &(auth->expires),
396 auth->type,
397 purple_account_get_bool(sip->account, "sso", TRUE),
398 sip->authdomain ? sip->authdomain : "",
399 authuser,
400 sip->password,
401 auth->target,
402 auth->gssapi_data);
403 if (!gssapi_data || !auth->gssapi_context) {
404 sip->gc->wants_to_die = TRUE;
405 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
406 return NULL;
409 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
410 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
411 g_free(opaque);
412 g_free(gssapi_data);
413 return ret;
416 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
418 } else { /* Digest */
420 /* Calculate new session key */
421 if (!auth->opaque) {
422 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
423 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
424 authuser, auth->realm, sip->password,
425 auth->gssapi_data, NULL);
428 sprintf(noncecount, "%08d", auth->nc++);
429 response = purple_cipher_http_digest_calculate_response("md5",
430 msg->method, msg->target, NULL, NULL,
431 auth->gssapi_data, noncecount, NULL,
432 auth->opaque);
433 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
435 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->gssapi_data, msg->target, noncecount, response);
436 g_free(response);
437 return ret;
441 static char *parse_attribute(const char *attrname, const char *source)
443 const char *tmp, *tmp2;
444 char *retval = NULL;
445 int len = strlen(attrname);
447 if (!strncmp(source, attrname, len)) {
448 tmp = source + len;
449 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
450 if (tmp2)
451 retval = g_strndup(tmp, tmp2 - tmp);
452 else
453 retval = g_strdup(tmp);
456 return retval;
459 static void fill_auth(gchar *hdr, struct sip_auth *auth)
461 int i;
462 gchar **parts;
464 if (!hdr) {
465 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
466 return;
469 if (!g_strncasecmp(hdr, "NTLM", 4)) {
470 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
471 auth->type = AUTH_TYPE_NTLM;
472 hdr += 5;
473 auth->nc = 1;
474 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
475 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
476 auth->type = AUTH_TYPE_KERBEROS;
477 hdr += 9;
478 auth->nc = 3;
479 } else {
480 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
481 auth->type = AUTH_TYPE_DIGEST;
482 hdr += 7;
485 parts = g_strsplit(hdr, "\", ", 0);
486 for (i = 0; parts[i]; i++) {
487 char *tmp;
489 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
491 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
492 g_free(auth->gssapi_data);
493 auth->gssapi_data = tmp;
495 if (auth->type == AUTH_TYPE_NTLM) {
496 /* NTLM module extracts nonce from gssapi-data */
497 auth->nc = 3;
500 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
501 /* Only used with AUTH_TYPE_DIGEST */
502 g_free(auth->gssapi_data);
503 auth->gssapi_data = tmp;
504 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
505 g_free(auth->opaque);
506 auth->opaque = tmp;
507 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
508 g_free(auth->realm);
509 auth->realm = tmp;
511 if (auth->type == AUTH_TYPE_DIGEST) {
512 /* Throw away old session key */
513 g_free(auth->opaque);
514 auth->opaque = NULL;
515 auth->nc = 1;
518 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
519 g_free(auth->target);
520 auth->target = tmp;
523 g_strfreev(parts);
525 return;
528 static void sipe_canwrite_cb(gpointer data,
529 SIPE_UNUSED_PARAMETER gint source,
530 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
532 PurpleConnection *gc = data;
533 struct sipe_account_data *sip = gc->proto_data;
534 gsize max_write;
535 gssize written;
537 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
539 if (max_write == 0) {
540 if (sip->tx_handler != 0){
541 purple_input_remove(sip->tx_handler);
542 sip->tx_handler = 0;
544 return;
547 written = write(sip->fd, sip->txbuf->outptr, max_write);
549 if (written < 0 && errno == EAGAIN)
550 written = 0;
551 else if (written <= 0) {
552 /*TODO: do we really want to disconnect on a failure to write?*/
553 purple_connection_error(gc, _("Could not write"));
554 return;
557 purple_circ_buffer_mark_read(sip->txbuf, written);
560 static void sipe_canwrite_cb_ssl(gpointer data,
561 SIPE_UNUSED_PARAMETER gint src,
562 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
564 PurpleConnection *gc = data;
565 struct sipe_account_data *sip = gc->proto_data;
566 gsize max_write;
567 gssize written;
569 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
571 if (max_write == 0) {
572 if (sip->tx_handler != 0) {
573 purple_input_remove(sip->tx_handler);
574 sip->tx_handler = 0;
575 return;
579 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
581 if (written < 0 && errno == EAGAIN)
582 written = 0;
583 else if (written <= 0) {
584 /*TODO: do we really want to disconnect on a failure to write?*/
585 purple_connection_error(gc, _("Could not write"));
586 return;
589 purple_circ_buffer_mark_read(sip->txbuf, written);
592 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
594 static void send_later_cb(gpointer data, gint source,
595 SIPE_UNUSED_PARAMETER const gchar *error)
597 PurpleConnection *gc = data;
598 struct sipe_account_data *sip;
599 struct sip_connection *conn;
601 if (!PURPLE_CONNECTION_IS_VALID(gc))
603 if (source >= 0)
604 close(source);
605 return;
608 if (source < 0) {
609 purple_connection_error(gc, _("Could not connect"));
610 return;
613 sip = gc->proto_data;
614 sip->fd = source;
615 sip->connecting = FALSE;
616 sip->last_keepalive = time(NULL);
618 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
620 /* If there is more to write now, we need to register a handler */
621 if (sip->txbuf->bufused > 0)
622 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
624 conn = connection_create(sip, source);
625 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
628 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
630 struct sipe_account_data *sip;
631 struct sip_connection *conn;
633 if (!PURPLE_CONNECTION_IS_VALID(gc))
635 if (gsc) purple_ssl_close(gsc);
636 return NULL;
639 sip = gc->proto_data;
640 sip->fd = gsc->fd;
641 sip->gsc = gsc;
642 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
643 sip->connecting = FALSE;
644 sip->last_keepalive = time(NULL);
646 conn = connection_create(sip, gsc->fd);
648 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
650 return sip;
653 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
654 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
656 PurpleConnection *gc = data;
657 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
658 if (sip == NULL) return;
660 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
662 /* If there is more to write now */
663 if (sip->txbuf->bufused > 0) {
664 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
669 static void sendlater(PurpleConnection *gc, const char *buf)
671 struct sipe_account_data *sip = gc->proto_data;
673 if (!sip->connecting) {
674 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
675 if (sip->transport == SIPE_TRANSPORT_TLS){
676 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
677 } else {
678 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
679 purple_connection_error(gc, _("Could not create socket"));
682 sip->connecting = TRUE;
685 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
686 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
688 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
691 static void sendout_pkt(PurpleConnection *gc, const char *buf)
693 struct sipe_account_data *sip = gc->proto_data;
694 time_t currtime = time(NULL);
695 int writelen = strlen(buf);
696 char *tmp;
698 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
699 g_free(tmp);
700 if (sip->transport == SIPE_TRANSPORT_UDP) {
701 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
702 purple_debug_info("sipe", "could not send packet\n");
704 } else {
705 int ret;
706 if (sip->fd < 0) {
707 sendlater(gc, buf);
708 return;
711 if (sip->tx_handler) {
712 ret = -1;
713 errno = EAGAIN;
714 } else{
715 if (sip->gsc){
716 ret = purple_ssl_write(sip->gsc, buf, writelen);
717 }else{
718 ret = write(sip->fd, buf, writelen);
722 if (ret < 0 && errno == EAGAIN)
723 ret = 0;
724 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
725 sendlater(gc, buf);
726 return;
729 if (ret < writelen) {
730 if (!sip->tx_handler){
731 if (sip->gsc){
732 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
734 else{
735 sip->tx_handler = purple_input_add(sip->fd,
736 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
737 gc);
741 /* XXX: is it OK to do this? You might get part of a request sent
742 with part of another. */
743 if (sip->txbuf->bufused > 0)
744 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
746 purple_circ_buffer_append(sip->txbuf, buf + ret,
747 writelen - ret);
752 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
754 sendout_pkt(gc, buf);
755 return len;
758 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
760 GSList *tmp = msg->headers;
761 gchar *name;
762 gchar *value;
763 GString *outstr = g_string_new("");
764 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
765 while (tmp) {
766 name = ((struct siphdrelement*) (tmp->data))->name;
767 value = ((struct siphdrelement*) (tmp->data))->value;
768 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
769 tmp = g_slist_next(tmp);
771 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
772 sendout_pkt(sip->gc, outstr->str);
773 g_string_free(outstr, TRUE);
776 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
778 gchar * buf;
780 if (sip->registrar.type == AUTH_TYPE_UNSET) {
781 return;
784 if (sip->registrar.gssapi_context) {
785 struct sipmsg_breakdown msgbd;
786 gchar *signature_input_str;
787 msgbd.msg = msg;
788 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
789 msgbd.rand = g_strdup_printf("%08x", g_random_int());
790 sip->registrar.ntlm_num++;
791 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
792 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
793 if (signature_input_str != NULL) {
794 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
795 msg->signature = signature_hex;
796 msg->rand = g_strdup(msgbd.rand);
797 msg->num = g_strdup(msgbd.num);
798 g_free(signature_input_str);
800 sipmsg_breakdown_free(&msgbd);
803 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
804 buf = auth_header(sip, &sip->registrar, msg);
805 if (buf) {
806 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
808 g_free(buf);
809 } else if (!strcmp(method,"SUBSCRIBE") || !strcmp(method,"SERVICE") || !strcmp(method,"MESSAGE") || !strcmp(method,"INVITE") || !strcmp(method, "ACK") || !strcmp(method, "NOTIFY") || !strcmp(method, "BYE") || !strcmp(method, "INFO") || !strcmp(method, "OPTIONS") || !strcmp(method, "REFER")) {
810 sip->registrar.nc = 3;
811 #ifdef USE_KERBEROS
812 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
813 #endif
814 sip->registrar.type = AUTH_TYPE_NTLM;
815 #ifdef USE_KERBEROS
816 } else {
817 sip->registrar.type = AUTH_TYPE_KERBEROS;
819 #endif
822 buf = auth_header(sip, &sip->registrar, msg);
823 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
824 g_free(buf);
825 } else {
826 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
830 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
831 const char *text, const char *body)
833 gchar *name;
834 gchar *value;
835 GString *outstr = g_string_new("");
836 struct sipe_account_data *sip = gc->proto_data;
837 gchar *contact;
838 GSList *tmp;
839 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
841 contact = get_contact(sip);
842 sipmsg_add_header(msg, "Contact", contact);
843 g_free(contact);
845 if (body) {
846 gchar len[12];
847 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
848 sipmsg_add_header(msg, "Content-Length", len);
849 } else {
850 sipmsg_add_header(msg, "Content-Length", "0");
853 msg->response = code;
855 sipmsg_strip_headers(msg, keepers);
856 sipmsg_merge_new_headers(msg);
857 sign_outgoing_message(msg, sip, msg->method);
859 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
860 tmp = msg->headers;
861 while (tmp) {
862 name = ((struct siphdrelement*) (tmp->data))->name;
863 value = ((struct siphdrelement*) (tmp->data))->value;
865 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
866 tmp = g_slist_next(tmp);
868 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
869 sendout_pkt(gc, outstr->str);
870 g_string_free(outstr, TRUE);
873 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
875 if (sip->transactions) {
876 sip->transactions = g_slist_remove(sip->transactions, trans);
877 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
879 if (trans->msg) sipmsg_free(trans->msg);
880 if (trans->payload) {
881 (*trans->payload->destroy)(trans->payload->data);
882 g_free(trans->payload);
884 g_free(trans->key);
885 g_free(trans);
889 static struct transaction *
890 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
892 gchar *call_id = NULL;
893 gchar *cseq = NULL;
894 struct transaction *trans = g_new0(struct transaction, 1);
896 trans->time = time(NULL);
897 trans->msg = (struct sipmsg *)msg;
898 call_id = sipmsg_find_header(trans->msg, "Call-ID");
899 cseq = sipmsg_find_header(trans->msg, "CSeq");
900 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
901 trans->callback = callback;
902 sip->transactions = g_slist_append(sip->transactions, trans);
903 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
904 return trans;
907 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
909 struct transaction *trans;
910 GSList *transactions = sip->transactions;
911 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
912 gchar *cseq = sipmsg_find_header(msg, "CSeq");
913 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
915 while (transactions) {
916 trans = transactions->data;
917 if (!g_strcasecmp(trans->key, key)) {
918 g_free(key);
919 return trans;
921 transactions = transactions->next;
924 g_free(key);
925 return NULL;
928 struct transaction *
929 send_sip_request(PurpleConnection *gc, const gchar *method,
930 const gchar *url, const gchar *to, const gchar *addheaders,
931 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
933 struct sipe_account_data *sip = gc->proto_data;
934 const char *addh = "";
935 char *buf;
936 struct sipmsg *msg;
937 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
938 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
939 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
940 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
941 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
942 gchar *route = g_strdup("");
943 gchar *epid = get_epid(sip);
944 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
945 struct transaction *trans = NULL;
947 if (dialog && dialog->routes)
949 GSList *iter = dialog->routes;
951 while(iter)
953 char *tmp = route;
954 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
955 g_free(tmp);
956 iter = g_slist_next(iter);
960 if (!ourtag && !dialog) {
961 ourtag = gentag();
964 if (!strcmp(method, "REGISTER")) {
965 if (sip->regcallid) {
966 g_free(callid);
967 callid = g_strdup(sip->regcallid);
968 } else {
969 sip->regcallid = g_strdup(callid);
971 cseq = ++sip->cseq;
974 if (addheaders) addh = addheaders;
976 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
977 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
978 "From: <sip:%s>%s%s;epid=%s\r\n"
979 "To: <%s>%s%s%s%s\r\n"
980 "Max-Forwards: 70\r\n"
981 "CSeq: %d %s\r\n"
982 "User-Agent: %s\r\n"
983 "Call-ID: %s\r\n"
984 "%s%s"
985 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
986 method,
987 dialog && dialog->request ? dialog->request : url,
988 TRANSPORT_DESCRIPTOR,
989 purple_network_get_my_ip(-1),
990 sip->listenport,
991 branch ? ";branch=" : "",
992 branch ? branch : "",
993 sip->username,
994 ourtag ? ";tag=" : "",
995 ourtag ? ourtag : "",
996 epid,
998 theirtag ? ";tag=" : "",
999 theirtag ? theirtag : "",
1000 theirepid ? ";epid=" : "",
1001 theirepid ? theirepid : "",
1002 cseq,
1003 method,
1004 sipe_get_useragent(sip),
1005 callid,
1006 route,
1007 addh,
1008 body ? (gsize) strlen(body) : 0,
1009 body ? body : "");
1012 //printf ("parsing msg buf:\n%s\n\n", buf);
1013 msg = sipmsg_parse_msg(buf);
1015 g_free(buf);
1016 g_free(ourtag);
1017 g_free(theirtag);
1018 g_free(theirepid);
1019 g_free(branch);
1020 g_free(callid);
1021 g_free(route);
1022 g_free(epid);
1024 sign_outgoing_message (msg, sip, method);
1026 buf = sipmsg_to_string (msg);
1028 /* add to ongoing transactions */
1029 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1030 if (strcmp(method, "ACK")) {
1031 trans = transactions_add_buf(sip, msg, tc);
1032 } else {
1033 sipmsg_free(msg);
1035 sendout_pkt(gc, buf);
1036 g_free(buf);
1038 return trans;
1042 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1044 static void
1045 send_soap_request_with_cb(struct sipe_account_data *sip,
1046 gchar *from0,
1047 gchar *body,
1048 TransCallback callback,
1049 struct transaction_payload *payload)
1051 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1052 gchar *contact = get_contact(sip);
1053 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1054 "Content-Type: application/SOAP+xml\r\n",contact);
1056 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1057 trans->payload = payload;
1059 g_free(from);
1060 g_free(contact);
1061 g_free(hdr);
1064 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1066 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1069 static char *get_contact_register(struct sipe_account_data *sip)
1071 char *epid = get_epid(sip);
1072 char *uuid = generateUUIDfromEPID(epid);
1073 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1074 g_free(uuid);
1075 g_free(epid);
1076 return(buf);
1079 static void do_register_exp(struct sipe_account_data *sip, int expire)
1081 char *uri;
1082 char *expires;
1083 char *to;
1084 char *contact;
1085 char *hdr;
1087 if (!sip->sipdomain) return;
1089 uri = sip_uri_from_name(sip->sipdomain);
1090 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1091 to = sip_uri_self(sip);
1092 contact = get_contact_register(sip);
1093 hdr = g_strdup_printf("Contact: %s\r\n"
1094 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1095 "Event: registration\r\n"
1096 "Allow-Events: presence\r\n"
1097 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1098 "%s", contact, expires);
1099 g_free(contact);
1100 g_free(expires);
1102 sip->registerstatus = 1;
1104 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1105 process_register_response);
1107 g_free(hdr);
1108 g_free(uri);
1109 g_free(to);
1112 static void do_register_cb(struct sipe_account_data *sip,
1113 SIPE_UNUSED_PARAMETER void *unused)
1115 do_register_exp(sip, -1);
1116 sip->reregister_set = FALSE;
1119 static void do_register(struct sipe_account_data *sip)
1121 do_register_exp(sip, -1);
1124 static void
1125 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1127 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1128 send_soap_request(sip, body);
1129 g_free(body);
1132 static void
1133 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1135 if (allow) {
1136 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1137 } else {
1138 purple_debug_info("sipe", "Blocking contact %s\n", who);
1141 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1144 static
1145 void sipe_auth_user_cb(void * data)
1147 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1148 if (!job) return;
1150 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1151 g_free(job);
1154 static
1155 void sipe_deny_user_cb(void * data)
1157 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1158 if (!job) return;
1160 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1161 g_free(job);
1164 static void
1165 sipe_add_permit(PurpleConnection *gc, const char *name)
1167 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1168 sipe_contact_allow_deny(sip, name, TRUE);
1171 static void
1172 sipe_add_deny(PurpleConnection *gc, const char *name)
1174 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1175 sipe_contact_allow_deny(sip, name, FALSE);
1178 /*static void
1179 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1181 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1182 sipe_contact_set_acl(sip, name, "");
1185 static void
1186 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1188 xmlnode *watchers;
1189 xmlnode *watcher;
1190 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1191 if (msg->response != 0 && msg->response != 200) return;
1193 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1195 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1196 if (!watchers) return;
1198 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1199 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1200 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1201 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1203 // TODO pull out optional displayName to pass as alias
1204 if (remote_user) {
1205 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1206 job->who = remote_user;
1207 job->sip = sip;
1208 purple_account_request_authorization(
1209 sip->account,
1210 remote_user,
1211 _("you"), /* id */
1212 alias,
1213 NULL, /* message */
1214 on_list,
1215 sipe_auth_user_cb,
1216 sipe_deny_user_cb,
1217 (void *) job);
1222 xmlnode_free(watchers);
1223 return;
1226 static void
1227 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1229 PurpleGroup * purple_group = purple_find_group(group->name);
1230 if (!purple_group) {
1231 purple_group = purple_group_new(group->name);
1232 purple_blist_add_group(purple_group, NULL);
1235 if (purple_group) {
1236 group->purple_group = purple_group;
1237 sip->groups = g_slist_append(sip->groups, group);
1238 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1239 } else {
1240 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1244 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1246 struct sipe_group *group;
1247 GSList *entry;
1248 if (sip == NULL) {
1249 return NULL;
1252 entry = sip->groups;
1253 while (entry) {
1254 group = entry->data;
1255 if (group->id == id) {
1256 return group;
1258 entry = entry->next;
1260 return NULL;
1263 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1265 struct sipe_group *group;
1266 GSList *entry;
1267 if (sip == NULL) {
1268 return NULL;
1271 entry = sip->groups;
1272 while (entry) {
1273 group = entry->data;
1274 if (!strcmp(group->name, name)) {
1275 return group;
1277 entry = entry->next;
1279 return NULL;
1282 static void
1283 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1285 gchar *body;
1286 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1287 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1288 send_soap_request(sip, body);
1289 g_free(body);
1290 g_free(group->name);
1291 group->name = g_strdup(name);
1295 * Only appends if no such value already stored.
1296 * Like Set in Java.
1298 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1299 GSList * res = list;
1300 if (!g_slist_find_custom(list, data, func)) {
1301 res = g_slist_insert_sorted(list, data, func);
1303 return res;
1306 static int
1307 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1308 return group1->id - group2->id;
1312 * Returns string like "2 4 7 8" - group ids buddy belong to.
1314 static gchar *
1315 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1316 int i = 0;
1317 gchar *res;
1318 //creating array from GList, converting int to gchar*
1319 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1320 GSList *entry = buddy->groups;
1321 while (entry) {
1322 struct sipe_group * group = entry->data;
1323 ids_arr[i] = g_strdup_printf("%d", group->id);
1324 entry = entry->next;
1325 i++;
1327 ids_arr[i] = NULL;
1328 res = g_strjoinv(" ", ids_arr);
1329 g_strfreev(ids_arr);
1330 return res;
1334 * Sends buddy update to server
1336 static void
1337 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1339 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1340 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1342 if (buddy && purple_buddy) {
1343 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1344 gchar *body;
1345 gchar *groups = sipe_get_buddy_groups_string(buddy);
1346 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1348 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1349 alias, groups, "true", buddy->name, sip->contacts_delta++
1351 send_soap_request(sip, body);
1352 g_free(groups);
1353 g_free(body);
1357 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1359 if (msg->response == 200) {
1360 struct sipe_group *group;
1361 struct group_user_context *ctx = trans->payload->data;
1362 xmlnode *xml;
1363 xmlnode *node;
1364 char *group_id;
1365 struct sipe_buddy *buddy;
1367 xml = xmlnode_from_str(msg->body, msg->bodylen);
1368 if (!xml) {
1369 return FALSE;
1372 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1373 if (!node) {
1374 xmlnode_free(xml);
1375 return FALSE;
1378 group_id = xmlnode_get_data(node);
1379 if (!group_id) {
1380 xmlnode_free(xml);
1381 return FALSE;
1384 group = g_new0(struct sipe_group, 1);
1385 group->id = (int)g_ascii_strtod(group_id, NULL);
1386 g_free(group_id);
1387 group->name = g_strdup(ctx->group_name);
1389 sipe_group_add(sip, group);
1391 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1392 if (buddy) {
1393 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1396 sipe_group_set_user(sip, ctx->user_name);
1398 xmlnode_free(xml);
1399 return TRUE;
1401 return FALSE;
1404 static void sipe_group_context_destroy(gpointer data)
1406 struct group_user_context *ctx = data;
1407 g_free(ctx->group_name);
1408 g_free(ctx->user_name);
1409 g_free(ctx);
1412 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1414 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1415 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1416 gchar *body;
1417 ctx->group_name = g_strdup(name);
1418 ctx->user_name = g_strdup(who);
1419 payload->destroy = sipe_group_context_destroy;
1420 payload->data = ctx;
1422 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1423 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1424 g_free(body);
1428 * Data structure for scheduled actions
1431 struct scheduled_action {
1433 * Name of action.
1434 * Format is <Event>[<Data>...]
1435 * Example: <presence><sip:user@domain.com> or <registration>
1437 gchar *name;
1438 guint timeout_handler;
1439 gboolean repetitive;
1440 Action action;
1441 GDestroyNotify destroy;
1442 struct sipe_account_data *sip;
1443 void *payload;
1447 * A timer callback
1448 * Should return FALSE if repetitive action is not needed
1450 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1452 gboolean ret;
1453 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1454 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1455 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1456 (sched_action->action)(sched_action->sip, sched_action->payload);
1457 ret = sched_action->repetitive;
1458 if (sched_action->destroy) {
1459 (*sched_action->destroy)(sched_action->payload);
1461 g_free(sched_action->name);
1462 g_free(sched_action);
1463 return ret;
1467 * Kills action timer effectively cancelling
1468 * scheduled action
1470 * @param name of action
1472 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1474 GSList *entry;
1476 if (!sip->timeouts || !name) return;
1478 entry = sip->timeouts;
1479 while (entry) {
1480 struct scheduled_action *sched_action = entry->data;
1481 if(!strcmp(sched_action->name, name)) {
1482 GSList *to_delete = entry;
1483 entry = entry->next;
1484 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1485 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1486 purple_timeout_remove(sched_action->timeout_handler);
1487 if (sched_action->destroy) {
1488 (*sched_action->destroy)(sched_action->payload);
1490 g_free(sched_action->name);
1491 g_free(sched_action);
1492 } else {
1493 entry = entry->next;
1498 static void
1499 sipe_schedule_action0(const gchar *name,
1500 int timeout,
1501 gboolean isSeconds,
1502 Action action,
1503 GDestroyNotify destroy,
1504 struct sipe_account_data *sip,
1505 void *payload)
1507 struct scheduled_action *sched_action;
1509 /* Make sure each action only exists once */
1510 sipe_cancel_scheduled_action(sip, name);
1512 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1513 sched_action = g_new0(struct scheduled_action, 1);
1514 sched_action->repetitive = FALSE;
1515 sched_action->name = g_strdup(name);
1516 sched_action->action = action;
1517 sched_action->destroy = destroy;
1518 sched_action->sip = sip;
1519 sched_action->payload = payload;
1520 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1521 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1522 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1523 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1526 void
1527 sipe_schedule_action(const gchar *name,
1528 int timeout,
1529 Action action,
1530 GDestroyNotify destroy,
1531 struct sipe_account_data *sip,
1532 void *payload)
1534 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1538 * Same as sipe_schedule_action() but timeout is in milliseconds.
1540 static void
1541 sipe_schedule_action_msec(const gchar *name,
1542 int timeout,
1543 Action action,
1544 GDestroyNotify destroy,
1545 struct sipe_account_data *sip,
1546 void *payload)
1548 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1551 static void
1552 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1553 time_t calculate_from);
1555 static int
1556 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1558 static const char*
1559 sipe_get_status_by_availability(int avail,
1560 const char* activity);
1562 static void
1563 sipe_apply_calendar_status(struct sipe_account_data *sip,
1564 struct sipe_buddy *sbuddy,
1565 const char *status_id)
1567 time_t cal_avail_since;
1568 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1569 int avail;
1570 gchar *self_uri = sip_uri_self(sip);
1572 if (!sbuddy) return;
1574 if (cal_status < SIPE_CAL_NO_DATA) {
1575 purple_debug_info("sipe", "update_calendar_status_cb: cal_status : %d for %s\n", cal_status, sbuddy->name);
1576 purple_debug_info("sipe", "update_calendar_status_cb: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1579 /* after msrtc processing */
1580 if (status_id) {
1581 sbuddy->last_non_cal_status_id = status_id;
1582 g_free(sbuddy->last_non_cal_activity);
1583 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
1584 } else {
1585 status_id = sbuddy->last_non_cal_status_id;
1588 /* adjust to calendar status */
1589 if (cal_status != SIPE_CAL_NO_DATA) {
1590 purple_debug_info("sipe", "update_calendar_status_cb: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1592 if (cal_status == SIPE_CAL_BUSY
1593 && cal_avail_since > sbuddy->user_avail_since
1594 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1596 status_id = SIPE_STATUS_ID_IN_MEETING;
1597 g_free(sbuddy->activity);
1598 sbuddy->activity = g_strdup(sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].desc);
1600 avail = sipe_get_availability_by_status(status_id, NULL);
1602 purple_debug_info("sipe", "update_calendar_status_cb: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1603 if (cal_avail_since > sbuddy->activity_since) {
1604 if (cal_status == SIPE_CAL_OOF
1605 && avail >= 15000) /* 12000 in 2007 */
1607 g_free(sbuddy->activity);
1608 sbuddy->activity = g_strdup(sipe_activity_map[SIPE_ACTIVITY_OOF].desc);
1613 /* then set status_id actually */
1614 purple_debug_info("sipe", "sipe_got_user_status: to %s for %s\n", status_id, sbuddy->name);
1615 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1617 /* set our account state to the one in roaming (including calendar info) */
1618 if (!strcmp(sbuddy->name, self_uri)) {
1619 PurpleStatus *status = purple_account_get_active_status(sip->account);
1620 const gchar *curr_note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
1622 g_free(sip->status);
1623 if (strcmp(status_id, SIPE_STATUS_ID_OFFLINE)) { /* not offline */
1624 sip->status = g_strdup(status_id);
1625 } else {
1626 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1629 purple_debug_info("sipe", "sipe_got_user_status: to %s for the account\n", sip->status);
1630 purple_prpl_got_account_status(sip->account, sip->status, SIPE_STATUS_ATTR_ID_MESSAGE, curr_note, NULL);
1632 g_free(self_uri);
1635 static void
1636 sipe_got_user_status(struct sipe_account_data *sip,
1637 const char* uri,
1638 const char *status_id)
1640 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1642 if (!sbuddy) return;
1644 /* Check if on 2005 system contact's calendar,
1645 * then set/preserve it.
1647 if (!sip->ocs2007) {
1648 sipe_apply_calendar_status(sip, sbuddy, status_id);
1649 } else {
1650 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1654 static void
1655 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1656 struct sipe_buddy *sbuddy,
1657 struct sipe_account_data *sip)
1659 sipe_apply_calendar_status(sip, sbuddy, NULL);
1663 * Updates contact's status
1664 * based on their calendar information.
1666 * Applicability: 2005 systems
1668 static void
1669 update_calendar_status(struct sipe_account_data *sip)
1671 purple_debug_info("sipe", "update_calendar_status() started.\n");
1672 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1674 /* repeat scheduling */
1675 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1679 * Schedules process of contacts' status update
1680 * based on their calendar information.
1681 * Should be scheduled to the beginning of every
1682 * 15 min interval, like:
1683 * 13:00, 13:15, 13:30, 13:45, etc.
1685 * Applicability: 2005 systems
1687 static void
1688 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1689 time_t calculate_from)
1691 int interval = 15*60;
1692 /** start of the beginning of closest 15 min interval. */
1693 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1695 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1696 asctime(localtime(&calculate_from)));
1697 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1698 asctime(localtime(&next_start)));
1700 sipe_schedule_action("<+2005-cal-status>",
1701 (int)(next_start - time(NULL)),
1702 (Action)update_calendar_status,
1703 NULL,
1704 sip,
1705 NULL);
1709 * Schedules process of self status publish
1710 * based on own calendar information.
1711 * Should be scheduled to the beginning of every
1712 * 15 min interval, like:
1713 * 13:00, 13:15, 13:30, 13:45, etc.
1715 * Applicability: 2007+ systems
1717 static void
1718 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1719 time_t calculate_from)
1721 int interval = 5*60;
1722 /** start of the beginning of closest 5 min interval. */
1723 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1725 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1726 asctime(localtime(&calculate_from)));
1727 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1728 asctime(localtime(&next_start)));
1730 sipe_schedule_action("<+2007-cal-status>",
1731 (int)(next_start - time(NULL)),
1732 (Action)publish_calendar_status_self,
1733 NULL,
1734 sip,
1735 NULL);
1738 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1740 /** Should be g_free()'d
1742 static gchar *
1743 sipe_get_subscription_key(gchar *event,
1744 gchar *with)
1746 gchar *key = NULL;
1748 if (is_empty(event)) return NULL;
1750 if (event && !g_ascii_strcasecmp(event, "presence")) {
1751 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1752 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1754 /* @TODO drop participated buddies' just_added flag */
1755 } else if (event) {
1756 /* Subscription is identified by <event> key */
1757 key = g_strdup_printf("<%s>", event);
1760 return key;
1763 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1764 SIPE_UNUSED_PARAMETER struct transaction *trans)
1766 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1767 gchar *event = sipmsg_find_header(msg, "Event");
1768 gchar *key;
1770 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1771 if (!event) {
1772 struct sipmsg *request_msg = trans->msg;
1773 event = sipmsg_find_header(request_msg, "Event");
1776 key = sipe_get_subscription_key(event, with);
1778 /* 200 OK; 481 Call Leg Does Not Exist */
1779 if (key && (msg->response == 200 || msg->response == 481)) {
1780 if (g_hash_table_lookup(sip->subscriptions, key)) {
1781 g_hash_table_remove(sip->subscriptions, key);
1782 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1786 /* create/store subscription dialog if not yet */
1787 if (msg->response == 200) {
1788 gchar *callid = sipmsg_find_header(msg, "Call-ID");
1789 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1791 if (key) {
1792 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1793 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1795 subscription->dialog.callid = g_strdup(callid);
1796 subscription->dialog.cseq = atoi(cseq);
1797 subscription->dialog.with = g_strdup(with);
1798 subscription->event = g_strdup(event);
1799 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1801 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1804 g_free(cseq);
1807 g_free(key);
1808 g_free(with);
1810 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1812 process_incoming_notify(sip, msg, FALSE, FALSE);
1814 return TRUE;
1817 static void sipe_subscribe_resource_uri(const char *name,
1818 SIPE_UNUSED_PARAMETER gpointer value,
1819 gchar **resources_uri)
1821 gchar *tmp = *resources_uri;
1822 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1823 g_free(tmp);
1826 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1828 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1829 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1830 gchar *tmp = *resources_uri;
1832 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1834 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1835 g_free(tmp);
1839 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1840 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1841 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1842 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1843 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1846 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1848 gchar *key;
1849 gchar *contact = get_contact(sip);
1850 gchar *request;
1851 gchar *content;
1852 gchar *require = "";
1853 gchar *accept = "";
1854 gchar *autoextend = "";
1855 gchar *content_type;
1856 struct sip_dialog *dialog;
1858 if (sip->ocs2007) {
1859 require = ", categoryList";
1860 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1861 content_type = "application/msrtc-adrl-categorylist+xml";
1862 content = g_strdup_printf(
1863 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1864 "<action name=\"subscribe\" id=\"63792024\">\n"
1865 "<adhocList>\n%s</adhocList>\n"
1866 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1867 "<category name=\"calendarData\"/>\n"
1868 "<category name=\"contactCard\"/>\n"
1869 "<category name=\"note\"/>\n"
1870 "<category name=\"state\"/>\n"
1871 "</categoryList>\n"
1872 "</action>\n"
1873 "</batchSub>", sip->username, resources_uri);
1874 } else {
1875 autoextend = "Supported: com.microsoft.autoextend\r\n";
1876 content_type = "application/adrl+xml";
1877 content = g_strdup_printf(
1878 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1879 "<create xmlns=\"\">\n%s</create>\n"
1880 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1882 g_free(resources_uri);
1884 request = g_strdup_printf(
1885 "Require: adhoclist%s\r\n"
1886 "Supported: eventlist\r\n"
1887 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1888 "Supported: ms-piggyback-first-notify\r\n"
1889 "%sSupported: ms-benotify\r\n"
1890 "Proxy-Require: ms-benotify\r\n"
1891 "Event: presence\r\n"
1892 "Content-Type: %s\r\n"
1893 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1894 g_free(contact);
1896 /* subscribe to buddy presence */
1897 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1898 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1899 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1900 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1902 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1904 g_free(content);
1905 g_free(to);
1906 g_free(request);
1907 g_free(key);
1910 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1911 SIPE_UNUSED_PARAMETER void *unused)
1913 gchar *to = sip_uri_self(sip);
1914 gchar *resources_uri = g_strdup("");
1915 if (sip->ocs2007) {
1916 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1917 } else {
1918 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1921 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1924 struct presence_batched_routed {
1925 gchar *host;
1926 GSList *buddies;
1929 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1931 struct presence_batched_routed *data = payload;
1932 GSList *buddies = data->buddies;
1933 while (buddies) {
1934 g_free(buddies->data);
1935 buddies = buddies->next;
1937 g_slist_free(data->buddies);
1938 g_free(data->host);
1939 g_free(payload);
1942 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1944 struct presence_batched_routed *data = payload;
1945 GSList *buddies = data->buddies;
1946 gchar *resources_uri = g_strdup("");
1947 while (buddies) {
1948 gchar *tmp = resources_uri;
1949 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1950 g_free(tmp);
1951 buddies = buddies->next;
1953 sipe_subscribe_presence_batched_to(sip, resources_uri,
1954 g_strdup(data->host));
1958 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1959 * The user sends a single SUBSCRIBE request to the subscribed contact.
1960 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1964 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1967 gchar *key;
1968 gchar *to = sip_uri((char *)buddy_name);
1969 gchar *tmp = get_contact(sip);
1970 gchar *request;
1971 gchar *content = NULL;
1972 gchar *autoextend = "";
1973 gchar *content_type = "";
1974 struct sip_dialog *dialog;
1975 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
1976 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1978 if (sbuddy) sbuddy->just_added = FALSE;
1980 if (sip->ocs2007) {
1981 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1982 } else {
1983 autoextend = "Supported: com.microsoft.autoextend\r\n";
1986 request = g_strdup_printf(
1987 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1988 "Supported: ms-piggyback-first-notify\r\n"
1989 "%s%sSupported: ms-benotify\r\n"
1990 "Proxy-Require: ms-benotify\r\n"
1991 "Event: presence\r\n"
1992 "Contact: %s\r\n", autoextend, content_type, tmp);
1994 if (sip->ocs2007) {
1995 content = g_strdup_printf(
1996 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1997 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1998 "<resource uri=\"%s\"%s\n"
1999 "</adhocList>\n"
2000 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2001 "<category name=\"calendarData\"/>\n"
2002 "<category name=\"contactCard\"/>\n"
2003 "<category name=\"note\"/>\n"
2004 "<category name=\"state\"/>\n"
2005 "</categoryList>\n"
2006 "</action>\n"
2007 "</batchSub>", sip->username, to, context);
2010 g_free(tmp);
2012 /* subscribe to buddy presence */
2013 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2014 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2015 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2016 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2018 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2020 g_free(content);
2021 g_free(to);
2022 g_free(request);
2023 g_free(key);
2026 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2028 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2030 if (!purple_status_is_active(status))
2031 return;
2033 if (account->gc) {
2034 struct sipe_account_data *sip = account->gc->proto_data;
2036 if (sip) {
2037 gchar *action_name;
2038 const char* status_id = purple_status_get_id(status);
2039 const char* note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2041 if (status_id && sip->status && !strcmp(status_id, sip->status) && /* same status */
2042 ((!note && !sip->note) || (note && sip->note && !strcmp(note, sip->note))) && /* same note */
2043 sip->initial_state_published
2046 purple_debug_info("sipe", "sipe_set_status: status&note has NOT changed, exiting.\n");
2047 return;
2050 g_free(sip->status);
2051 sip->status = g_strdup(status_id);
2052 g_free(sip->note);
2053 sip->note = g_strdup(note);
2055 /* schedule 2 sec to capture idle flag */
2056 action_name = g_strdup_printf("<%s>", "+set-status");
2057 sipe_schedule_action(action_name, 2, (Action)send_presence_status, NULL, sip, NULL);
2058 g_free(action_name);
2062 static void
2063 sipe_set_idle(PurpleConnection * gc,
2064 int time)
2066 purple_debug_info("sipe", "sipe_set_idle: time=%d\n", time);
2068 if (gc) {
2069 struct sipe_account_data *sip = gc->proto_data;
2071 if (sip) {
2072 sip->was_idle = sip->is_idle;
2073 sip->is_idle = (time > 0);
2078 static void
2079 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2080 SIPE_UNUSED_PARAMETER const char *alias)
2082 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2083 sipe_group_set_user(sip, name);
2086 static void
2087 sipe_group_buddy(PurpleConnection *gc,
2088 const char *who,
2089 const char *old_group_name,
2090 const char *new_group_name)
2092 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2093 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2094 struct sipe_group * old_group = NULL;
2095 struct sipe_group * new_group;
2097 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2098 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2100 if(!buddy) { // buddy not in roaming list
2101 return;
2104 if (old_group_name) {
2105 old_group = sipe_group_find_by_name(sip, old_group_name);
2107 new_group = sipe_group_find_by_name(sip, new_group_name);
2109 if (old_group) {
2110 buddy->groups = g_slist_remove(buddy->groups, old_group);
2111 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2114 if (!new_group) {
2115 sipe_group_create(sip, new_group_name, who);
2116 } else {
2117 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2118 sipe_group_set_user(sip, who);
2122 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2124 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2126 /* libpurple can call us with undefined buddy or group */
2127 if (buddy && group) {
2128 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2130 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2131 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2132 purple_blist_rename_buddy(buddy, buddy_name);
2133 g_free(buddy_name);
2135 /* Prepend sip: if needed */
2136 if (strncmp("sip:", buddy->name, 4)) {
2137 gchar *buf = sip_uri_from_name(buddy->name);
2138 purple_blist_rename_buddy(buddy, buf);
2139 g_free(buf);
2142 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2143 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2144 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2145 b->name = g_strdup(buddy->name);
2146 b->just_added = TRUE;
2147 g_hash_table_insert(sip->buddies, b->name, b);
2148 sipe_group_buddy(gc, b->name, NULL, group->name);
2149 /* @TODO should go to callback */
2150 sipe_subscribe_presence_single(sip, b->name);
2151 } else {
2152 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2157 static void sipe_free_buddy(struct sipe_buddy *buddy)
2159 #ifndef _WIN32
2161 * We are calling g_hash_table_foreach_steal(). That means that no
2162 * key/value deallocation functions are called. Therefore the glib
2163 * hash code does not touch the key (buddy->name) or value (buddy)
2164 * of the to-be-deleted hash node at all. It follows that we
2166 * - MUST free the memory for the key ourselves and
2167 * - ARE allowed to do it in this function
2169 * Conclusion: glib must be broken on the Windows platform if sipe
2170 * crashes with SIGTRAP when closing. You'll have to live
2171 * with the memory leak until this is fixed.
2173 g_free(buddy->name);
2174 #endif
2175 g_free(buddy->activity);
2176 g_free(buddy->meeting_subject);
2177 g_free(buddy->meeting_location);
2178 g_free(buddy->annotation);
2180 g_free(buddy->cal_start_time);
2181 g_free(buddy->cal_free_busy_base64);
2182 g_free(buddy->cal_free_busy);
2183 g_free(buddy->last_non_cal_activity);
2185 sipe_cal_free_working_hours(buddy->cal_working_hours);
2187 g_free(buddy->device_name);
2188 g_slist_free(buddy->groups);
2189 g_free(buddy);
2193 * Unassociates buddy from group first.
2194 * Then see if no groups left, removes buddy completely.
2195 * Otherwise updates buddy groups on server.
2197 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2199 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2200 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
2201 struct sipe_group *g = NULL;
2203 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2205 if (!b) return;
2207 if (group) {
2208 g = sipe_group_find_by_name(sip, group->name);
2211 if (g) {
2212 b->groups = g_slist_remove(b->groups, g);
2213 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2216 if (g_slist_length(b->groups) < 1) {
2217 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2218 sipe_cancel_scheduled_action(sip, action_name);
2219 g_free(action_name);
2221 g_hash_table_remove(sip->buddies, buddy->name);
2223 if (b->name) {
2224 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2225 send_soap_request(sip, body);
2226 g_free(body);
2229 sipe_free_buddy(b);
2230 } else {
2231 //updates groups on server
2232 sipe_group_set_user(sip, b->name);
2237 static void
2238 sipe_rename_group(PurpleConnection *gc,
2239 const char *old_name,
2240 PurpleGroup *group,
2241 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2243 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2244 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2245 if (s_group) {
2246 sipe_group_rename(sip, s_group, group->name);
2247 } else {
2248 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2252 static void
2253 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2255 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2256 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2257 if (s_group) {
2258 gchar *body;
2259 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2260 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2261 send_soap_request(sip, body);
2262 g_free(body);
2264 sip->groups = g_slist_remove(sip->groups, s_group);
2265 g_free(s_group->name);
2266 g_free(s_group);
2267 } else {
2268 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2272 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2274 PurpleStatusType *type;
2275 GList *types = NULL;
2277 /* Macros to reduce code repetition.
2278 Translators: noun */
2279 #define SIPE_ADD_STATUS(prim,id,name) type = purple_status_type_new_with_attrs( \
2280 prim, id, name, \
2281 TRUE, TRUE, FALSE, \
2282 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2283 NULL); \
2284 types = g_list_append(types, type);
2285 #define SIPE_ADD_STATUS_NO_MSG(prim,id,name,user) type = purple_status_type_new( \
2286 prim, id, name, user); \
2287 types = g_list_append(types, type);
2289 /* Online */
2290 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2291 NULL, NULL);
2293 /* Busy */
2294 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2295 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2296 sipe_activity_map[SIPE_ACTIVITY_BUSY].desc);
2298 /* BusyIdle (not user settable) */
2299 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_UNAVAILABLE,
2300 sipe_activity_map[SIPE_ACTIVITY_BUSYIDLE].status_id,
2301 sipe_activity_map[SIPE_ACTIVITY_BUSYIDLE].desc,
2302 FALSE);
2304 /* Do Not Disturb */
2305 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2306 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2307 NULL);
2309 /* In a meeting (not user settable) */
2310 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_UNAVAILABLE,
2311 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].status_id,
2312 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].desc,
2313 FALSE);
2315 /* In a conference (not user settable) */
2316 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_UNAVAILABLE,
2317 sipe_activity_map[SIPE_ACTIVITY_IN_CONF].status_id,
2318 sipe_activity_map[SIPE_ACTIVITY_IN_CONF].desc,
2319 FALSE);
2321 /* Be Right Back */
2322 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2323 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2324 sipe_activity_map[SIPE_ACTIVITY_BRB].desc);
2326 /* Away */
2327 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2328 NULL, NULL);
2330 /* On The Phone (not user settable) */
2331 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_UNAVAILABLE,
2332 sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].status_id,
2333 sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].desc,
2334 FALSE);
2336 /* Out To Lunch (not user settable) */
2337 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_AWAY,
2338 sipe_activity_map[SIPE_ACTIVITY_LUNCH].status_id,
2339 sipe_activity_map[SIPE_ACTIVITY_LUNCH].desc,
2340 FALSE);
2342 /* Idle/Inactive (not user settable) */
2343 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_AVAILABLE,
2344 sipe_activity_map[SIPE_ACTIVITY_INACTIVE].status_id,
2345 sipe_activity_map[SIPE_ACTIVITY_INACTIVE].desc,
2346 FALSE);
2348 /* Appear Offline */
2349 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_INVISIBLE,
2350 NULL, NULL,
2351 TRUE);
2353 /* Offline (not user settable) */
2354 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_OFFLINE,
2355 NULL, NULL,
2356 FALSE);
2358 return types;
2362 * A callback for g_hash_table_foreach
2364 static void
2365 sipe_buddy_subscribe_cb(char *buddy_name,
2366 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2367 struct sipe_account_data *sip)
2369 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2370 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2371 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2372 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2374 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2375 g_free(action_name);
2379 * Removes entries from purple buddy list
2380 * that does not correspond ones in the roaming contact list.
2382 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2383 GSList *buddies = purple_find_buddies(sip->account, NULL);
2384 GSList *entry = buddies;
2385 struct sipe_buddy *buddy;
2386 PurpleBuddy *b;
2387 PurpleGroup *g;
2389 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2390 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2391 while (entry) {
2392 b = entry->data;
2393 g = purple_buddy_get_group(b);
2394 buddy = g_hash_table_lookup(sip->buddies, b->name);
2395 if(buddy) {
2396 gboolean in_sipe_groups = FALSE;
2397 GSList *entry2 = buddy->groups;
2398 while (entry2) {
2399 struct sipe_group *group = entry2->data;
2400 if (!strcmp(group->name, g->name)) {
2401 in_sipe_groups = TRUE;
2402 break;
2404 entry2 = entry2->next;
2406 if(!in_sipe_groups) {
2407 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2408 purple_blist_remove_buddy(b);
2410 } else {
2411 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2412 purple_blist_remove_buddy(b);
2414 entry = entry->next;
2416 g_slist_free(buddies);
2419 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2421 int len = msg->bodylen;
2423 gchar *tmp = sipmsg_find_header(msg, "Event");
2424 xmlnode *item;
2425 xmlnode *isc;
2426 const gchar *contacts_delta;
2427 xmlnode *group_node;
2428 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
2429 return FALSE;
2432 /* Convert the contact from XML to Purple Buddies */
2433 isc = xmlnode_from_str(msg->body, len);
2434 if (!isc) {
2435 return FALSE;
2438 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2439 if (contacts_delta) {
2440 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2443 if (!strcmp(isc->name, "contactList")) {
2445 /* Parse groups */
2446 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2447 struct sipe_group * group = g_new0(struct sipe_group, 1);
2448 const char *name = xmlnode_get_attrib(group_node, "name");
2450 if (!strncmp(name, "~", 1)) {
2451 name = _("Other Contacts");
2453 group->name = g_strdup(name);
2454 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2456 sipe_group_add(sip, group);
2459 // Make sure we have at least one group
2460 if (g_slist_length(sip->groups) == 0) {
2461 struct sipe_group * group = g_new0(struct sipe_group, 1);
2462 PurpleGroup *purple_group;
2463 group->name = g_strdup(_("Other Contacts"));
2464 group->id = 1;
2465 purple_group = purple_group_new(group->name);
2466 purple_blist_add_group(purple_group, NULL);
2467 sip->groups = g_slist_append(sip->groups, group);
2470 /* Parse contacts */
2471 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2472 const gchar *uri = xmlnode_get_attrib(item, "uri");
2473 const gchar *name = xmlnode_get_attrib(item, "name");
2474 gchar *buddy_name;
2475 struct sipe_buddy *buddy = NULL;
2476 gchar *tmp;
2477 gchar **item_groups;
2478 int i = 0;
2480 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2481 tmp = sip_uri_from_name(uri);
2482 buddy_name = g_ascii_strdown(tmp, -1);
2483 g_free(tmp);
2485 /* assign to group Other Contacts if nothing else received */
2486 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2487 if(!tmp || !strcmp("", tmp) ) {
2488 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2489 g_free(tmp);
2490 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2492 item_groups = g_strsplit(tmp, " ", 0);
2493 g_free(tmp);
2495 while (item_groups[i]) {
2496 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2498 // If couldn't find the right group for this contact, just put them in the first group we have
2499 if (group == NULL && g_slist_length(sip->groups) > 0) {
2500 group = sip->groups->data;
2503 if (group != NULL) {
2504 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2505 if (!b){
2506 b = purple_buddy_new(sip->account, buddy_name, uri);
2507 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2509 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2512 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2513 if (name != NULL && strlen(name) != 0) {
2514 purple_blist_alias_buddy(b, name);
2516 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2520 if (!buddy) {
2521 buddy = g_new0(struct sipe_buddy, 1);
2522 buddy->name = g_strdup(b->name);
2523 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2526 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2528 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2529 } else {
2530 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2531 name);
2534 i++;
2535 } // while, contact groups
2536 g_strfreev(item_groups);
2537 g_free(buddy_name);
2539 } // for, contacts
2541 sipe_cleanup_local_blist(sip);
2543 /* Add self-contact if not there yet. 2005 systems. */
2544 /* This will resemble subscription to roaming_self in 2007 systems */
2545 if (!sip->ocs2007) {
2546 gchar *self_uri = sip_uri_self(sip);
2547 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2549 if (!buddy) {
2550 buddy = g_new0(struct sipe_buddy, 1);
2551 buddy->name = g_strdup(self_uri);
2552 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2554 g_free(self_uri);
2557 xmlnode_free(isc);
2559 /* subscribe to buddies */
2560 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2561 if (sip->batched_support) {
2562 sipe_subscribe_presence_batched(sip, NULL);
2563 } else {
2564 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2566 sip->subscribed_buddies = TRUE;
2568 /* for 2005 systems schedule contacts' status update
2569 * based on their calendar information
2571 if (!sip->ocs2007) {
2572 sipe_sched_calendar_status_update(sip, time(NULL));
2575 return 0;
2579 * Subscribe roaming contacts
2581 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2583 gchar *to = sip_uri_self(sip);
2584 gchar *tmp = get_contact(sip);
2585 gchar *hdr = g_strdup_printf(
2586 "Event: vnd-microsoft-roaming-contacts\r\n"
2587 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2588 "Supported: com.microsoft.autoextend\r\n"
2589 "Supported: ms-benotify\r\n"
2590 "Proxy-Require: ms-benotify\r\n"
2591 "Supported: ms-piggyback-first-notify\r\n"
2592 "Contact: %s\r\n", tmp);
2593 g_free(tmp);
2595 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2596 g_free(to);
2597 g_free(hdr);
2600 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2601 SIPE_UNUSED_PARAMETER void *unused)
2603 gchar *key;
2604 struct sip_dialog *dialog;
2605 gchar *to = sip_uri_self(sip);
2606 gchar *tmp = get_contact(sip);
2607 gchar *hdr = g_strdup_printf(
2608 "Event: presence.wpending\r\n"
2609 "Accept: text/xml+msrtc.wpending\r\n"
2610 "Supported: com.microsoft.autoextend\r\n"
2611 "Supported: ms-benotify\r\n"
2612 "Proxy-Require: ms-benotify\r\n"
2613 "Supported: ms-piggyback-first-notify\r\n"
2614 "Contact: %s\r\n", tmp);
2615 g_free(tmp);
2617 /* Subscription is identified by <event> key */
2618 key = g_strdup_printf("<%s>", "presence.wpending");
2619 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2620 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2622 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2624 g_free(to);
2625 g_free(hdr);
2626 g_free(key);
2630 * Fires on deregistration event initiated by server.
2631 * [MS-SIPREGE] SIP extension.
2634 // 2007 Example
2636 // Content-Type: text/registration-event
2637 // subscription-state: terminated;expires=0
2638 // ms-diagnostics-public: 4141;reason="User disabled"
2640 // deregistered;event=rejected
2642 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2644 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2645 gchar *event = NULL;
2646 gchar *reason = NULL;
2647 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2649 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2650 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2652 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2653 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2654 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2655 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2656 } else {
2657 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2658 return;
2661 if (warning != NULL) {
2662 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2663 } else { // for LCS2005
2664 int error_id = 0;
2665 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2666 error_id = 4140; // [MS-SIPREGE]
2667 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2668 reason = g_strdup(_("you are already signed in at another location"));
2669 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2670 error_id = 4141;
2671 reason = g_strdup(_("user disabled")); // [MS-OCER]
2672 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2673 error_id = 4142;
2674 reason = g_strdup(_("user moved")); // [MS-OCER]
2677 g_free(event);
2678 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2679 g_free(reason);
2681 sip->gc->wants_to_die = TRUE;
2682 purple_connection_error(sip->gc, warning);
2683 g_free(warning);
2687 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2689 xmlnode *xn_provision_group_list;
2690 xmlnode *node;
2692 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2694 /* provisionGroup */
2695 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2696 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2697 g_free(sip->focus_factory_uri);
2698 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2699 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2700 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2701 break;
2704 xmlnode_free(xn_provision_group_list);
2707 /** for 2005 system */
2708 static void
2709 sipe_process_provisioning(struct sipe_account_data *sip,
2710 struct sipmsg *msg)
2712 xmlnode *xn_provision;
2713 xmlnode *node;
2715 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2716 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2717 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2718 if ((node = xmlnode_get_child(node, "line"))) {
2719 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2720 const gchar *server = xmlnode_get_attrib(node, "server");
2721 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2722 sip_csta_open(sip, line_uri, server);
2725 xmlnode_free(xn_provision);
2728 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2730 const gchar *contacts_delta;
2731 xmlnode *xml;
2733 xml = xmlnode_from_str(msg->body, msg->bodylen);
2734 if (!xml)
2736 return;
2739 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2740 if (contacts_delta)
2742 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2745 xmlnode_free(xml);
2748 static void
2749 free_container(struct sipe_container *container)
2751 GSList *entry;
2753 if (!container) return;
2755 entry = container->members;
2756 while (entry) {
2757 g_free(entry->data);
2758 entry = g_slist_remove(entry, entry->data);
2760 g_free(container);
2764 * Finds locally stored MS-PRES container member
2766 static struct sipe_container_member *
2767 sipe_find_container_member(struct sipe_container *container,
2768 const gchar *type,
2769 const gchar *value)
2771 struct sipe_container_member *member;
2772 GSList *entry;
2774 if (container == NULL || type == NULL) {
2775 return NULL;
2778 entry = container->members;
2779 while (entry) {
2780 member = entry->data;
2781 if (!g_strcasecmp(member->type, type)
2782 && ((!member->value && !value)
2783 || (value && member->value && !g_strcasecmp(member->value, value)))
2785 return member;
2787 entry = entry->next;
2789 return NULL;
2793 * Finds locally stored MS-PRES container by id
2795 static struct sipe_container *
2796 sipe_find_container(struct sipe_account_data *sip,
2797 guint id)
2799 struct sipe_container *container;
2800 GSList *entry;
2802 if (sip == NULL) {
2803 return NULL;
2806 entry = sip->containers;
2807 while (entry) {
2808 container = entry->data;
2809 if (id == container->id) {
2810 return container;
2812 entry = entry->next;
2814 return NULL;
2818 * Access Levels
2819 * 32000 - Blocked
2820 * 400 - Personal
2821 * 300 - Team
2822 * 200 - Company
2823 * 100 - Public
2825 static int
2826 sipe_find_access_level(struct sipe_account_data *sip,
2827 const gchar *type,
2828 const gchar *value)
2830 guint containers[] = {32000, 400, 300, 200, 100};
2831 int i = 0;
2833 for (i = 0; i < 5; i++) {
2834 struct sipe_container_member *member;
2835 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2836 if (!container) continue;
2838 member = sipe_find_container_member(container, type, value);
2839 if (member) {
2840 return containers[i];
2844 return -1;
2847 static void
2848 sipe_send_set_container_members(struct sipe_account_data *sip,
2849 guint container_id,
2850 guint container_version,
2851 const gchar* action,
2852 const gchar* type,
2853 const gchar* value)
2855 gchar *self = sip_uri_self(sip);
2856 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2857 gchar *contact;
2858 gchar *hdr;
2859 gchar *body = g_strdup_printf(
2860 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2861 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2862 "</setContainerMembers>",
2863 container_id,
2864 container_version,
2865 action,
2866 type,
2867 value_str);
2868 g_free(value_str);
2870 contact = get_contact(sip);
2871 hdr = g_strdup_printf("Contact: %s\r\n"
2872 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2873 g_free(contact);
2875 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2877 g_free(hdr);
2878 g_free(body);
2879 g_free(self);
2882 static void
2883 free_publication(struct sipe_publication *publication)
2885 g_free(publication->category);
2886 g_free(publication->cal_event_hash);
2887 g_free(publication->note);
2889 g_free(publication->working_hours_xml_str);
2890 g_free(publication->fb_start_str);
2891 g_free(publication->free_busy_base64);
2893 g_free(publication);
2896 /* key is <category><instance><container> */
2897 static gboolean
2898 sipe_is_our_publication(struct sipe_account_data *sip,
2899 const gchar *key)
2901 GSList *entry;
2903 /* filling keys for our publications if not yet cached */
2904 if (!sip->our_publication_keys) {
2905 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2906 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2907 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2908 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2909 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2910 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2911 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2913 /* device */
2914 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2915 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2917 /* state:machineState */
2918 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2919 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2920 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2921 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2923 /* state:userState */
2924 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2925 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2926 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2927 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2929 /* state:calendarState */
2930 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2931 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2932 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2933 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2935 /* state:calendarState OOF */
2936 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2937 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2938 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2939 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2941 /* note */
2942 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2943 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2944 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2945 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2946 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2947 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2949 /* note OOF */
2950 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2951 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2952 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2953 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2954 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2955 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2957 /* calendarData:WorkingHours */
2958 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2959 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2960 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2961 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2962 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2963 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2964 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2965 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2966 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2967 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2968 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2969 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2971 /* calendarData:FreeBusy */
2972 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2973 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2974 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2975 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2976 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2977 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2978 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2979 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2980 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2981 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2982 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2983 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2985 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2986 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2989 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
2991 entry = sip->our_publication_keys;
2992 while (entry) {
2993 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
2994 if (!strcmp(entry->data, key)) {
2995 return TRUE;
2997 entry = entry->next;
2999 return FALSE;
3002 /** Property names to store in blist.xml */
3003 #define ALIAS_PROP "alias"
3004 #define EMAIL_PROP "email"
3005 #define PHONE_PROP "phone"
3006 #define PHONE_DISPLAY_PROP "phone-display"
3007 #define PHONE_MOBILE_PROP "phone-mobile"
3008 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3009 #define PHONE_HOME_PROP "phone-home"
3010 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3011 #define PHONE_OTHER_PROP "phone-other"
3012 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3013 #define PHONE_CUSTOM1_PROP "phone-custom1"
3014 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3015 #define SITE_PROP "site"
3016 #define COMPANY_PROP "company"
3017 #define DEPARTMENT_PROP "department"
3018 #define TITLE_PROP "title"
3019 #define OFFICE_PROP "office"
3020 /** implies work address */
3021 #define ADDRESS_STREET_PROP "address-street"
3022 #define ADDRESS_CITY_PROP "address-city"
3023 #define ADDRESS_STATE_PROP "address-state"
3024 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3025 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3027 * Update user information
3029 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3030 * @param property_name
3031 * @param property_value may be modified to strip white space
3033 static void
3034 sipe_update_user_info(struct sipe_account_data *sip,
3035 const char *uri,
3036 const char *property_name,
3037 char *property_value)
3039 GSList *buddies, *entry;
3041 if (!property_name || strlen(property_name) == 0) return;
3043 if (property_value)
3044 property_value = g_strstrip(property_value);
3046 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3047 while (entry) {
3048 const char *prop_str;
3049 const char *server_alias;
3050 PurpleBuddy *p_buddy = entry->data;
3052 /* for Display Name */
3053 if (!strcmp(property_name, ALIAS_PROP)) {
3054 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3055 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3056 purple_blist_alias_buddy(p_buddy, property_value);
3059 server_alias = purple_buddy_get_server_alias(p_buddy);
3060 if (property_value && strlen(property_value) > 0 &&
3061 ( (server_alias && strcmp(property_value, server_alias))
3062 || !server_alias || strlen(server_alias) == 0 )
3064 purple_blist_server_alias_buddy(p_buddy, property_value);
3067 /* for other properties */
3068 else {
3069 if (property_value && strlen(property_value) > 0) {
3070 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3071 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3072 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3077 entry = entry->next;
3079 g_slist_free(buddies);
3083 * Update user phone
3084 * Suitable for both 2005 and 2007 systems.
3086 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3087 * @param phone_type
3088 * @param phone may be modified to strip white space
3089 * @param phone_display_string may be modified to strip white space
3091 static void
3092 sipe_update_user_phone(struct sipe_account_data *sip,
3093 const char *uri,
3094 const gchar *phone_type,
3095 gchar *phone,
3096 gchar *phone_display_string)
3098 const char *phone_node = PHONE_PROP; /* work phone by default */
3099 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3101 if(!phone || strlen(phone) == 0) return;
3103 if (phone_type && (!strcmp(phone_type, "mobile") || !strcmp(phone_type, "cell"))) {
3104 phone_node = PHONE_MOBILE_PROP;
3105 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3106 } else if (phone_type && !strcmp(phone_type, "home")) {
3107 phone_node = PHONE_HOME_PROP;
3108 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3109 } else if (phone_type && !strcmp(phone_type, "other")) {
3110 phone_node = PHONE_OTHER_PROP;
3111 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3112 } else if (phone_type && !strcmp(phone_type, "custom1")) {
3113 phone_node = PHONE_CUSTOM1_PROP;
3114 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3117 sipe_update_user_info(sip, uri, phone_node, phone);
3118 if (phone_display_string) {
3119 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3123 static void
3124 sipe_update_calendar(struct sipe_account_data *sip)
3126 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3128 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3130 if (!strcmp(calendar, "EXCH")) {
3131 sipe_ews_update_calendar(sip);
3134 /* schedule repeat */
3135 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3137 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3140 static void
3141 send_publish_category_initial(struct sipe_account_data *sip);
3144 * When we receive some self (BE) NOTIFY with a new subscriber
3145 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3148 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3150 gchar *contact;
3151 gchar *to;
3152 xmlnode *xml;
3153 xmlnode *node;
3154 xmlnode *node2;
3155 char *display_name = NULL;
3156 char *uri;
3157 GSList *category_names = NULL;
3158 int aggreg_avail = 0;
3159 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3161 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3163 xml = xmlnode_from_str(msg->body, msg->bodylen);
3164 if (!xml) return;
3166 contact = get_contact(sip);
3167 to = sip_uri_self(sip);
3170 /* categories */
3171 /* set list of categories participating in this XML */
3172 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3173 const gchar *name = xmlnode_get_attrib(node, "name");
3174 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3176 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3177 category_names ? (int) g_slist_length(category_names) : -1);
3178 /* drop category information */
3179 if (category_names) {
3180 GSList *entry = category_names;
3181 while (entry) {
3182 GHashTable *cat_publications;
3183 const gchar *category = entry->data;
3184 entry = entry->next;
3185 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3186 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3187 if (cat_publications) {
3188 g_hash_table_remove(sip->our_publications, category);
3189 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3193 g_slist_free(category_names);
3194 /* filling our categories reflected in roaming data */
3195 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3196 const gchar *name = xmlnode_get_attrib(node, "name");
3197 const gchar *container = xmlnode_get_attrib(node, "container");
3198 const gchar *instance = xmlnode_get_attrib(node, "instance");
3199 const gchar *version = xmlnode_get_attrib(node, "version");
3200 guint version_int = version ? atoi(version) : 0;
3201 gchar *key;
3203 if (!container || !instance) continue;
3205 /* key is <category><instance><container> */
3206 key = g_strdup_printf("<%s><%s><%s>", name, instance, container);
3207 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version_int);
3209 /* capture all userState publication for later clean up if required */
3210 if (!strcmp(name, "state") && (atoi(container) == 2 || atoi(container) == 3)) {
3211 xmlnode *xn_state = xmlnode_get_child(node, "state");
3213 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3214 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3215 publication->category = g_strdup(name);
3216 publication->instance = atoi(instance);
3217 publication->container = atoi(container);
3218 publication->version = version_int;
3220 if (!sip->user_state_publications) {
3221 sip->user_state_publications = g_hash_table_new_full(
3222 g_str_hash, g_str_equal,
3223 g_free, (GDestroyNotify)free_publication);
3225 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3226 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3227 key, version_int);
3231 if (sipe_is_our_publication(sip, key)) {
3232 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3234 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3235 publication->category = g_strdup(name);
3236 publication->instance = atoi(instance);
3237 publication->container = atoi(container);
3238 publication->version = version_int;
3239 /* filling publication->availability */
3240 if (!strcmp(name, "state")) {
3241 xmlnode *xn_state = xmlnode_get_child(node, "state");
3242 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3244 if (xn_avail) {
3245 gchar *avail_str = xmlnode_get_data(xn_avail);
3246 if (avail_str) {
3247 publication->availability = atoi(avail_str);
3249 g_free(avail_str);
3251 /* for calendarState */
3252 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3253 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3254 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3256 event->start_time = purple_str_to_time(xmlnode_get_attrib(xn_state, "startTime"),
3257 FALSE, NULL, NULL, NULL);
3258 if (xn_activity) {
3259 if (!strcmp(xmlnode_get_attrib(xn_activity, "token"),
3260 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].desc))
3262 event->is_meeting = TRUE;
3265 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3266 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3268 publication->cal_event_hash = sipe_cal_event_hash(event);
3269 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3270 publication->cal_event_hash);
3271 sipe_cal_event_free(event);
3274 /* filling publication->note */
3275 if (!strcmp(name, "note")) {
3276 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3277 if (xn_body) {
3278 publication->note = xmlnode_get_data(xn_body);
3279 g_free(sip->note);
3280 sip->note = g_strdup(publication->note);
3284 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3285 if (!strcmp(name, "calendarData") && (publication->container == 300)) {
3286 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3287 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3288 if (xn_free_busy) {
3289 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3290 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3292 if (xn_working_hours) {
3293 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3297 if (!cat_publications) {
3298 cat_publications = g_hash_table_new_full(
3299 g_str_hash, g_str_equal,
3300 g_free, (GDestroyNotify)free_publication);
3301 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3302 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3304 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3305 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version_int);
3307 g_free(key);
3309 /* aggregateState (not an our publication) from 2-nd container */
3310 if (!strcmp(name, "state") && atoi(container) == 2) {
3311 xmlnode *xn_state = xmlnode_get_child(node, "state");
3313 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3314 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3315 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3317 if (xn_avail) {
3318 gchar *avail_str = xmlnode_get_data(xn_avail);
3319 if (avail_str) {
3320 aggreg_avail = atoi(avail_str);
3322 g_free(avail_str);
3325 if (xn_activity) {
3326 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3328 aggreg_activity = sipe_get_activity_by_token(activity_token);
3333 /* userProperties published by server from AD */
3334 if (!sip->csta && !strcmp(name, "userProperties")) {
3335 xmlnode *line;
3336 /* line, for Remote Call Control (RCC) */
3337 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3338 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3339 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3340 gchar *line_uri;
3342 if (!line_server || (strcmp(line_type, "Rcc") && strcmp(line_type, "Dual"))) continue;
3344 line_uri = xmlnode_get_data(line);
3345 if (line_uri) {
3346 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3347 sip_csta_open(sip, line_uri, line_server);
3349 g_free(line_uri);
3351 break;
3355 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3356 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3358 /* containers */
3359 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3360 guint id = atoi(xmlnode_get_attrib(node, "id"));
3361 struct sipe_container *container = sipe_find_container(sip, id);
3363 if (container) {
3364 sip->containers = g_slist_remove(sip->containers, container);
3365 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3366 free_container(container);
3368 container = g_new0(struct sipe_container, 1);
3369 container->id = id;
3370 container->version = atoi(xmlnode_get_attrib(node, "version"));
3371 sip->containers = g_slist_append(sip->containers, container);
3372 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3374 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3375 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3376 member->type = xmlnode_get_attrib(node2, "type");
3377 member->value = xmlnode_get_attrib(node2, "value");
3378 container->members = g_slist_append(container->members, member);
3379 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3380 member->type, member->value ? member->value : "");
3384 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3385 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3386 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3387 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3388 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3389 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3390 /* initial set-up to let counterparties see your status */
3391 if (sameEnterpriseAL < 0) {
3392 struct sipe_container *container = sipe_find_container(sip, 200);
3393 guint version = container ? container->version : 0;
3394 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3396 if (federatedAL < 0) {
3397 struct sipe_container *container = sipe_find_container(sip, 100);
3398 guint version = container ? container->version : 0;
3399 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3401 sip->access_level_set = TRUE;
3404 /* subscribers */
3405 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3406 const char *user;
3407 const char *acknowledged;
3408 gchar *hdr;
3409 gchar *body;
3411 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3412 if (!user) continue;
3413 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3414 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3415 uri = sip_uri_from_name(user);
3417 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3419 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3420 if(!g_ascii_strcasecmp(acknowledged,"false")){
3421 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3422 if (!purple_find_buddy(sip->account, uri)) {
3423 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3426 hdr = g_strdup_printf(
3427 "Contact: %s\r\n"
3428 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3430 body = g_strdup_printf(
3431 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3432 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3433 "</setSubscribers>", user);
3435 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3436 g_free(body);
3437 g_free(hdr);
3439 g_free(display_name);
3440 g_free(uri);
3443 g_free(contact);
3444 xmlnode_free(xml);
3446 /* Publish initial state if not yet.
3447 * Assuming this happens on initial responce to subscription to roaming-self
3448 * so we've already updated our roaming data in full.
3449 * Only for 2007+
3451 if (!sip->initial_state_published) {
3452 send_publish_category_initial(sip);
3453 sip->initial_state_published = TRUE;
3454 /* dalayed run */
3455 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3456 } else if (aggreg_avail) {
3457 PurpleStatus *status = purple_account_get_active_status(sip->account);
3458 const gchar *curr_note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
3460 g_free(sip->status);
3461 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3462 if (aggreg_activity == SIPE_ACTIVITY_IN_MEETING ||
3463 aggreg_activity == SIPE_ACTIVITY_IN_CONF ||
3464 aggreg_activity == SIPE_ACTIVITY_ON_PHONE)
3466 sip->status = g_strdup(sipe_activity_map[aggreg_activity].status_id);
3468 else
3470 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3472 } else {
3473 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3476 purple_debug_info("sipe", "sipe_process_roaming_self: to %s for the account\n", sip->status);
3477 purple_prpl_got_account_status(sip->account, sip->status, SIPE_STATUS_ATTR_ID_MESSAGE, curr_note, NULL);
3479 //purple_debug_info("sipe", "sipe_process_roaming_self: to %s for %s\n", sipe_get_status_by_availability(aggreg_avail), to);
3480 //purple_prpl_got_user_status(sip->account, to, sipe_get_status_by_availability(aggreg_avail), NULL);
3483 g_free(to);
3486 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3488 gchar *to = sip_uri_self(sip);
3489 gchar *tmp = get_contact(sip);
3490 gchar *hdr = g_strdup_printf(
3491 "Event: vnd-microsoft-roaming-ACL\r\n"
3492 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3493 "Supported: com.microsoft.autoextend\r\n"
3494 "Supported: ms-benotify\r\n"
3495 "Proxy-Require: ms-benotify\r\n"
3496 "Supported: ms-piggyback-first-notify\r\n"
3497 "Contact: %s\r\n", tmp);
3498 g_free(tmp);
3500 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3501 g_free(to);
3502 g_free(hdr);
3506 * To request for presence information about the user, access level settings that have already been configured by the user
3507 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3508 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3511 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3513 gchar *to = sip_uri_self(sip);
3514 gchar *tmp = get_contact(sip);
3515 gchar *hdr = g_strdup_printf(
3516 "Event: vnd-microsoft-roaming-self\r\n"
3517 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3518 "Supported: ms-benotify\r\n"
3519 "Proxy-Require: ms-benotify\r\n"
3520 "Supported: ms-piggyback-first-notify\r\n"
3521 "Contact: %s\r\n"
3522 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3524 gchar *body=g_strdup(
3525 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3526 "<roaming type=\"categories\"/>"
3527 "<roaming type=\"containers\"/>"
3528 "<roaming type=\"subscribers\"/></roamingList>");
3530 g_free(tmp);
3531 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3532 g_free(body);
3533 g_free(to);
3534 g_free(hdr);
3538 * For 2005 version
3540 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3542 gchar *to = sip_uri_self(sip);
3543 gchar *tmp = get_contact(sip);
3544 gchar *hdr = g_strdup_printf(
3545 "Event: vnd-microsoft-provisioning\r\n"
3546 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3547 "Supported: com.microsoft.autoextend\r\n"
3548 "Supported: ms-benotify\r\n"
3549 "Proxy-Require: ms-benotify\r\n"
3550 "Supported: ms-piggyback-first-notify\r\n"
3551 "Expires: 0\r\n"
3552 "Contact: %s\r\n", tmp);
3554 g_free(tmp);
3555 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3556 g_free(to);
3557 g_free(hdr);
3560 /** Subscription for provisioning information to help with initial
3561 * configuration. This subscription is a one-time query (denoted by the Expires header,
3562 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3563 * configuration, meeting policies, and policy settings that Communicator must enforce.
3564 * TODO: for what we need this information.
3567 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3569 gchar *to = sip_uri_self(sip);
3570 gchar *tmp = get_contact(sip);
3571 gchar *hdr = g_strdup_printf(
3572 "Event: vnd-microsoft-provisioning-v2\r\n"
3573 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3574 "Supported: com.microsoft.autoextend\r\n"
3575 "Supported: ms-benotify\r\n"
3576 "Proxy-Require: ms-benotify\r\n"
3577 "Supported: ms-piggyback-first-notify\r\n"
3578 "Expires: 0\r\n"
3579 "Contact: %s\r\n"
3580 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3581 gchar *body = g_strdup(
3582 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3583 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3584 "<provisioningGroup name=\"ucPolicy\"/>"
3585 "</provisioningGroupList>");
3587 g_free(tmp);
3588 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3589 g_free(body);
3590 g_free(to);
3591 g_free(hdr);
3594 static void
3595 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3596 gpointer value, gpointer user_data)
3598 struct sip_subscription *subscription = value;
3599 struct sip_dialog *dialog = &subscription->dialog;
3600 struct sipe_account_data *sip = user_data;
3601 gchar *tmp = get_contact(sip);
3602 gchar *hdr = g_strdup_printf(
3603 "Event: %s\r\n"
3604 "Expires: 0\r\n"
3605 "Contact: %s\r\n", subscription->event, tmp);
3606 g_free(tmp);
3608 /* Rate limit to max. 25 requests per seconds */
3609 g_usleep(1000000 / 25);
3611 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3612 g_free(hdr);
3615 /* IM Session (INVITE and MESSAGE methods) */
3617 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3618 static gchar *
3619 get_end_points (struct sipe_account_data *sip,
3620 struct sip_session *session)
3622 gchar *res;
3624 if (session == NULL) {
3625 return NULL;
3628 res = g_strdup_printf("<sip:%s>", sip->username);
3630 SIPE_DIALOG_FOREACH {
3631 gchar *tmp = res;
3632 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3633 g_free(tmp);
3635 if (dialog->theirepid) {
3636 tmp = res;
3637 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3638 g_free(tmp);
3640 } SIPE_DIALOG_FOREACH_END;
3642 return res;
3645 static gboolean
3646 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3647 struct sipmsg *msg,
3648 SIPE_UNUSED_PARAMETER struct transaction *trans)
3650 gboolean ret = TRUE;
3652 if (msg->response != 200) {
3653 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3654 return FALSE;
3657 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3659 return ret;
3663 * Asks UA/proxy about its capabilities.
3665 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3667 gchar *to = sip_uri(who);
3668 gchar *contact = get_contact(sip);
3669 gchar *request = g_strdup_printf(
3670 "Accept: application/sdp\r\n"
3671 "Contact: %s\r\n", contact);
3672 g_free(contact);
3674 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3676 g_free(to);
3677 g_free(request);
3680 static void
3681 sipe_notify_user(struct sipe_account_data *sip,
3682 struct sip_session *session,
3683 PurpleMessageFlags flags,
3684 const gchar *message)
3686 PurpleConversation *conv;
3688 if (!session->conv) {
3689 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3690 } else {
3691 conv = session->conv;
3693 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3696 void
3697 sipe_present_info(struct sipe_account_data *sip,
3698 struct sip_session *session,
3699 const gchar *message)
3701 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3704 static void
3705 sipe_present_err(struct sipe_account_data *sip,
3706 struct sip_session *session,
3707 const gchar *message)
3709 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3712 void
3713 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3714 struct sip_session *session,
3715 int sip_error,
3716 const gchar *who,
3717 const gchar *message)
3719 char *msg, *msg_tmp, *msg_tmp2;
3720 const char *label;
3722 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
3723 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3724 g_free(msg_tmp);
3725 /* Service unavailable; Server Internal Error; Server Time-out */
3726 if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3727 label = _("This message was not delivered to %s because the service is not available");
3728 } else if (sip_error == 486) { /* Busy Here */
3729 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3730 } else {
3731 label = _("This message was not delivered to %s because one or more recipients are offline");
3734 msg_tmp = g_strdup_printf( "%s:\n%s" ,
3735 msg_tmp2 = g_strdup_printf(label, who ? who : ""), msg ? msg : "");
3736 sipe_present_err(sip, session, msg_tmp);
3737 g_free(msg_tmp2);
3738 g_free(msg_tmp);
3739 g_free(msg);
3743 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
3745 static gboolean
3746 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
3747 SIPE_UNUSED_PARAMETER struct transaction *trans)
3749 gboolean ret = TRUE;
3750 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3751 struct sip_session *session = sipe_session_find_im(sip, with);
3752 struct sip_dialog *dialog;
3753 gchar *cseq;
3754 char *key;
3755 gchar *message;
3757 if (!session) {
3758 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3759 g_free(with);
3760 return FALSE;
3763 dialog = sipe_dialog_find(session, with);
3764 if (!dialog) {
3765 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3766 g_free(with);
3767 return FALSE;
3770 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3771 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3772 g_free(cseq);
3773 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3775 if (msg->response >= 400) {
3776 PurpleBuddy *pbuddy;
3777 gchar *alias = with;
3779 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3781 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3782 alias = (gchar *)purple_buddy_get_alias(pbuddy);
3785 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
3786 ret = FALSE;
3787 } else {
3788 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3789 if (message_id) {
3790 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
3791 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
3792 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3795 g_hash_table_remove(session->unconfirmed_messages, key);
3796 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
3797 key, g_hash_table_size(session->unconfirmed_messages));
3800 g_free(key);
3801 g_free(with);
3803 if (ret) sipe_im_process_queue(sip, session);
3804 return ret;
3807 static gboolean
3808 sipe_is_election_finished(struct sip_session *session);
3810 static void
3811 sipe_election_result(struct sipe_account_data *sip,
3812 void *sess);
3814 static gboolean
3815 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
3816 SIPE_UNUSED_PARAMETER struct transaction *trans)
3818 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3819 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3820 struct sip_dialog *dialog;
3821 struct sip_session *session;
3823 session = sipe_session_find_chat_by_callid(sip, callid);
3824 if (!session) {
3825 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
3826 return FALSE;
3829 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
3830 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3831 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
3832 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
3834 if (xn_request_rm_response) {
3835 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
3836 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
3838 dialog = sipe_dialog_find(session, with);
3839 if (!dialog) {
3840 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
3841 return FALSE;
3844 if (allow && !g_strcasecmp(allow, "true")) {
3845 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
3846 dialog->election_vote = 1;
3847 } else if (allow && !g_strcasecmp(allow, "false")) {
3848 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
3849 dialog->election_vote = -1;
3852 if (sipe_is_election_finished(session)) {
3853 sipe_election_result(sip, session);
3856 } else if (xn_set_rm_response) {
3859 xmlnode_free(xn_action);
3863 return TRUE;
3866 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
3868 gchar *hdr;
3869 gchar *tmp;
3870 char *msgformat;
3871 char *msgtext;
3872 gchar *msgr_value;
3873 gchar *msgr;
3875 sipe_parse_html(msg, &msgformat, &msgtext);
3876 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
3878 msgr_value = sipmsg_get_msgr_string(msgformat);
3879 g_free(msgformat);
3880 if (msgr_value) {
3881 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3882 g_free(msgr_value);
3883 } else {
3884 msgr = g_strdup("");
3887 tmp = get_contact(sip);
3888 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3889 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3890 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3891 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
3892 g_free(tmp);
3893 g_free(msgr);
3895 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
3896 g_free(msgtext);
3897 g_free(hdr);
3901 static void
3902 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
3904 GSList *entry2 = session->outgoing_message_queue;
3905 while (entry2) {
3906 char *queued_msg = entry2->data;
3908 /* for multiparty chat or conference */
3909 if (session->is_multiparty || session->focus_uri) {
3910 gchar *who = sip_uri_self(sip);
3911 serv_got_chat_in(sip->gc, session->chat_id, who,
3912 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
3913 g_free(who);
3916 SIPE_DIALOG_FOREACH {
3917 char *key;
3919 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3921 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3922 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
3923 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
3924 key, g_hash_table_size(session->unconfirmed_messages));
3925 g_free(key);
3927 sipe_send_message(sip, dialog, queued_msg);
3928 } SIPE_DIALOG_FOREACH_END;
3930 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3931 g_free(queued_msg);
3935 static void
3936 sipe_refer_notify(struct sipe_account_data *sip,
3937 struct sip_session *session,
3938 const gchar *who,
3939 int status,
3940 const gchar *desc)
3942 gchar *hdr;
3943 gchar *body;
3944 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3946 hdr = g_strdup_printf(
3947 "Event: refer\r\n"
3948 "Subscription-State: %s\r\n"
3949 "Content-Type: message/sipfrag\r\n",
3950 status >= 200 ? "terminated" : "active");
3952 body = g_strdup_printf(
3953 "SIP/2.0 %d %s\r\n",
3954 status, desc);
3956 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
3958 g_free(hdr);
3959 g_free(body);
3962 static gboolean
3963 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
3965 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3966 struct sip_session *session;
3967 struct sip_dialog *dialog;
3968 char *cseq;
3969 char *key;
3970 gchar *message;
3971 struct sipmsg *request_msg = trans->msg;
3973 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3974 gchar *referred_by;
3976 session = sipe_session_find_chat_by_callid(sip, callid);
3977 if (!session) {
3978 session = sipe_session_find_im(sip, with);
3980 if (!session) {
3981 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
3982 g_free(with);
3983 return FALSE;
3986 dialog = sipe_dialog_find(session, with);
3987 if (!dialog) {
3988 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
3989 g_free(with);
3990 return FALSE;
3993 sipe_dialog_parse(dialog, msg, TRUE);
3995 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3996 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3997 g_free(cseq);
3998 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4000 if (msg->response != 200) {
4001 PurpleBuddy *pbuddy;
4002 gchar *alias = with;
4004 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4006 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4007 alias = (gchar *)purple_buddy_get_alias(pbuddy);
4010 if (message) {
4011 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
4012 } else {
4013 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4014 sipe_present_err(sip, session, tmp_msg);
4015 g_free(tmp_msg);
4018 sipe_dialog_remove(session, with);
4020 g_free(key);
4021 g_free(with);
4022 return FALSE;
4025 dialog->cseq = 0;
4026 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4027 dialog->outgoing_invite = NULL;
4028 dialog->is_established = TRUE;
4030 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4031 if (referred_by) {
4032 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4033 g_free(referred_by);
4036 /* add user to chat if it is a multiparty session */
4037 if (session->is_multiparty) {
4038 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4039 with, NULL,
4040 PURPLE_CBFLAGS_NONE, TRUE);
4043 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4044 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4045 if (session->outgoing_message_queue) {
4046 char *queued_msg = session->outgoing_message_queue->data;
4047 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
4048 g_free(queued_msg);
4052 sipe_im_process_queue(sip, session);
4054 g_hash_table_remove(session->unconfirmed_messages, key);
4055 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4056 key, g_hash_table_size(session->unconfirmed_messages));
4058 g_free(key);
4059 g_free(with);
4060 return TRUE;
4064 void
4065 sipe_invite(struct sipe_account_data *sip,
4066 struct sip_session *session,
4067 const gchar *who,
4068 const gchar *msg_body,
4069 const gchar *referred_by,
4070 const gboolean is_triggered)
4072 gchar *hdr;
4073 gchar *to;
4074 gchar *contact;
4075 gchar *body;
4076 gchar *self;
4077 char *ms_text_format = NULL;
4078 gchar *roster_manager;
4079 gchar *end_points;
4080 gchar *referred_by_str;
4081 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4083 if (dialog && dialog->is_established) {
4084 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4085 return;
4088 if (!dialog) {
4089 dialog = sipe_dialog_add(session);
4090 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4091 dialog->with = g_strdup(who);
4094 if (!(dialog->ourtag)) {
4095 dialog->ourtag = gentag();
4098 to = sip_uri(who);
4100 if (msg_body) {
4101 char *msgformat;
4102 char *msgtext;
4103 char *base64_msg;
4104 gchar *msgr_value;
4105 gchar *msgr;
4106 char *key;
4108 sipe_parse_html(msg_body, &msgformat, &msgtext);
4109 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4111 msgr_value = sipmsg_get_msgr_string(msgformat);
4112 g_free(msgformat);
4113 msgr = "";
4114 if (msgr_value) {
4115 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4116 g_free(msgr_value);
4119 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4120 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
4121 g_free(msgtext);
4122 g_free(msgr);
4123 g_free(base64_msg);
4125 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4126 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
4127 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4128 key, g_hash_table_size(session->unconfirmed_messages));
4129 g_free(key);
4132 contact = get_contact(sip);
4133 end_points = get_end_points(sip, session);
4134 self = sip_uri_self(sip);
4135 roster_manager = g_strdup_printf(
4136 "Roster-Manager: %s\r\n"
4137 "EndPoints: %s\r\n",
4138 self,
4139 end_points);
4140 referred_by_str = referred_by ?
4141 g_strdup_printf(
4142 "Referred-By: %s\r\n",
4143 referred_by)
4144 : g_strdup("");
4145 hdr = g_strdup_printf(
4146 "Supported: ms-sender\r\n"
4147 "%s"
4148 "%s"
4149 "%s"
4150 "%s"
4151 "Contact: %s\r\n%s"
4152 "Content-Type: application/sdp\r\n",
4153 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
4154 referred_by_str,
4155 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4156 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4157 contact,
4158 ms_text_format ? ms_text_format : "");
4159 g_free(ms_text_format);
4160 g_free(self);
4162 body = g_strdup_printf(
4163 "v=0\r\n"
4164 "o=- 0 0 IN IP4 %s\r\n"
4165 "s=session\r\n"
4166 "c=IN IP4 %s\r\n"
4167 "t=0 0\r\n"
4168 "m=%s %d sip null\r\n"
4169 "a=accept-types:text/plain text/html image/gif "
4170 "multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4171 purple_network_get_my_ip(-1),
4172 purple_network_get_my_ip(-1),
4173 sip->ocs2007 ? "message" : "x-ms-message",
4174 sip->realport);
4176 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4177 to, to, hdr, body, dialog, process_invite_response);
4179 g_free(to);
4180 g_free(roster_manager);
4181 g_free(end_points);
4182 g_free(referred_by_str);
4183 g_free(body);
4184 g_free(hdr);
4185 g_free(contact);
4188 static void
4189 sipe_refer(struct sipe_account_data *sip,
4190 struct sip_session *session,
4191 const gchar *who)
4193 gchar *hdr;
4194 gchar *contact;
4195 gchar *epid = get_epid(sip);
4196 struct sip_dialog *dialog = sipe_dialog_find(session,
4197 session->roster_manager);
4199 contact = get_contact(sip);
4200 hdr = g_strdup_printf(
4201 "Contact: %s\r\n"
4202 "Refer-to: <%s>\r\n"
4203 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4204 "Require: com.microsoft.rtc-multiparty\r\n",
4205 contact,
4206 who,
4207 sip->username,
4208 dialog->ourtag ? ";tag=" : "",
4209 dialog->ourtag ? dialog->ourtag : "",
4210 epid);
4211 g_free(epid);
4213 send_sip_request(sip->gc, "REFER",
4214 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4216 g_free(hdr);
4217 g_free(contact);
4220 static void
4221 sipe_send_election_request_rm(struct sipe_account_data *sip,
4222 struct sip_dialog *dialog,
4223 int bid)
4225 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4227 gchar *body = g_strdup_printf(
4228 "<?xml version=\"1.0\"?>\r\n"
4229 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4230 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4231 sip->username, bid);
4233 send_sip_request(sip->gc, "INFO",
4234 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4236 g_free(body);
4239 static void
4240 sipe_send_election_set_rm(struct sipe_account_data *sip,
4241 struct sip_dialog *dialog)
4243 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4245 gchar *body = g_strdup_printf(
4246 "<?xml version=\"1.0\"?>\r\n"
4247 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4248 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4249 sip->username);
4251 send_sip_request(sip->gc, "INFO",
4252 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4254 g_free(body);
4257 static void
4258 sipe_session_close(struct sipe_account_data *sip,
4259 struct sip_session * session)
4261 if (session && session->focus_uri) {
4262 sipe_conf_immcu_closed(sip, session);
4263 conf_session_close(sip, session);
4266 if (session) {
4267 SIPE_DIALOG_FOREACH {
4268 /* @TODO slow down BYE message sending rate */
4269 /* @see single subscription code */
4270 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4271 } SIPE_DIALOG_FOREACH_END;
4273 sipe_session_remove(sip, session);
4277 static void
4278 sipe_session_close_all(struct sipe_account_data *sip)
4280 GSList *entry;
4281 while ((entry = sip->sessions) != NULL) {
4282 sipe_session_close(sip, entry->data);
4286 static void
4287 sipe_convo_closed(PurpleConnection * gc, const char *who)
4289 struct sipe_account_data *sip = gc->proto_data;
4291 purple_debug_info("sipe", "conversation with %s closed\n", who);
4292 sipe_session_close(sip, sipe_session_find_im(sip, who));
4295 static void
4296 sipe_chat_leave (PurpleConnection *gc, int id)
4298 struct sipe_account_data *sip = gc->proto_data;
4299 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4301 sipe_session_close(sip, session);
4304 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4305 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4307 struct sipe_account_data *sip = gc->proto_data;
4308 struct sip_session *session;
4309 struct sip_dialog *dialog;
4310 gchar *uri = sip_uri(who);
4312 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4314 session = sipe_session_find_or_add_im(sip, uri);
4315 dialog = sipe_dialog_find(session, uri);
4317 // Queue the message
4318 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
4320 if (dialog && !dialog->outgoing_invite) {
4321 sipe_im_process_queue(sip, session);
4322 } else if (!dialog || !dialog->outgoing_invite) {
4323 // Need to send the INVITE to get the outgoing dialog setup
4324 sipe_invite(sip, session, uri, what, NULL, FALSE);
4327 g_free(uri);
4328 return 1;
4331 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4332 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4334 struct sipe_account_data *sip = gc->proto_data;
4335 struct sip_session *session;
4337 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4339 session = sipe_session_find_chat_by_id(sip, id);
4341 // Queue the message
4342 if (session && session->dialogs) {
4343 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4344 g_strdup(what));
4345 sipe_im_process_queue(sip, session);
4346 } else if (sip) {
4347 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4348 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4350 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4351 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4353 if (sip->ocs2007) {
4354 struct sip_session *session = sipe_session_add_chat(sip);
4356 session->is_multiparty = FALSE;
4357 session->focus_uri = g_strdup(proto_chat_id);
4358 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4359 g_strdup(what));
4360 sipe_invite_conf_focus(sip, session);
4364 return 1;
4367 /* End IM Session (INVITE and MESSAGE methods) */
4369 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4371 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4372 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4373 gchar *from;
4374 struct sip_session *session;
4376 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4378 /* Call Control protocol */
4379 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4381 process_incoming_info_csta(sip, msg);
4382 return;
4385 from = parse_from(sipmsg_find_header(msg, "From"));
4386 session = sipe_session_find_chat_by_callid(sip, callid);
4387 if (!session) {
4388 session = sipe_session_find_im(sip, from);
4390 if (!session) {
4391 g_free(from);
4392 return;
4395 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4397 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4398 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4399 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4401 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4403 if (xn_request_rm) {
4404 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4405 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
4406 gchar *body = g_strdup_printf(
4407 "<?xml version=\"1.0\"?>\r\n"
4408 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4409 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4410 sip->username,
4411 session->bid < bid ? "true" : "false");
4412 send_sip_response(sip->gc, msg, 200, "OK", body);
4413 g_free(body);
4414 } else if (xn_set_rm) {
4415 gchar *body;
4416 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4417 g_free(session->roster_manager);
4418 session->roster_manager = g_strdup(rm);
4420 body = g_strdup_printf(
4421 "<?xml version=\"1.0\"?>\r\n"
4422 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4423 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4424 sip->username);
4425 send_sip_response(sip->gc, msg, 200, "OK", body);
4426 g_free(body);
4428 xmlnode_free(xn_action);
4431 else
4433 /* looks like purple lacks typing notification for chat */
4434 if (!session->is_multiparty && !session->focus_uri) {
4435 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4436 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4437 "status");
4438 if (status && !strcmp(status, "type")) {
4439 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4440 } else if (status && !strcmp(status, "idle")) {
4441 serv_got_typing_stopped(sip->gc, from);
4443 xmlnode_free(xn_keyboard_activity);
4446 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4448 g_free(from);
4451 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4453 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4454 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4455 struct sip_session *session;
4456 struct sip_dialog *dialog;
4458 /* collect dialog identification
4459 * we need callid, ourtag and theirtag to unambiguously identify dialog
4461 /* take data before 'msg' will be modified by send_sip_response */
4462 dialog = g_new0(struct sip_dialog, 1);
4463 dialog->callid = g_strdup(callid);
4464 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4465 dialog->with = g_strdup(from);
4466 sipe_dialog_parse(dialog, msg, FALSE);
4468 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4470 session = sipe_session_find_chat_by_callid(sip, callid);
4471 if (!session) {
4472 session = sipe_session_find_im(sip, from);
4474 if (!session) {
4475 g_free(from);
4476 return;
4479 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4480 g_free(session->roster_manager);
4481 session->roster_manager = NULL;
4484 /* This what BYE is essentially for - terminating dialog */
4485 sipe_dialog_remove_3(session, dialog);
4486 sipe_dialog_free(dialog);
4487 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4488 sipe_conf_immcu_closed(sip, session);
4489 } else if (session->is_multiparty) {
4490 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4493 g_free(from);
4496 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4498 gchar *self = sip_uri_self(sip);
4499 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4500 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4501 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4502 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4503 struct sip_session *session;
4504 struct sip_dialog *dialog;
4506 session = sipe_session_find_chat_by_callid(sip, callid);
4507 dialog = sipe_dialog_find(session, from);
4509 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
4510 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4511 } else {
4512 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4514 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
4517 g_free(self);
4518 g_free(from);
4519 g_free(refer_to);
4520 g_free(referred_by);
4523 static unsigned int
4524 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4526 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4527 struct sip_session *session;
4528 struct sip_dialog *dialog;
4530 if (state == PURPLE_NOT_TYPING)
4531 return 0;
4533 session = sipe_session_find_im(sip, who);
4534 dialog = sipe_dialog_find(session, who);
4536 if (session && dialog && dialog->is_established) {
4537 send_sip_request(gc, "INFO", who, who,
4538 "Content-Type: application/xml\r\n",
4539 SIPE_SEND_TYPING, dialog, NULL);
4541 return SIPE_TYPING_SEND_TIMEOUT;
4544 static gboolean resend_timeout(struct sipe_account_data *sip)
4546 GSList *tmp = sip->transactions;
4547 time_t currtime = time(NULL);
4548 while (tmp) {
4549 struct transaction *trans = tmp->data;
4550 tmp = tmp->next;
4551 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4552 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4553 /* TODO 408 */
4554 } else {
4555 if ((currtime - trans->time > 2) && trans->retries == 0) {
4556 trans->retries++;
4557 sendout_sipmsg(sip, trans->msg);
4561 return TRUE;
4564 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4565 SIPE_UNUSED_PARAMETER void *unused)
4567 /* register again when security token expires */
4568 /* we have to start a new authentication as the security token
4569 * is almost expired by sending a not signed REGISTER message */
4570 purple_debug_info("sipe", "do a full reauthentication\n");
4571 sipe_auth_free(&sip->registrar);
4572 sipe_auth_free(&sip->proxy);
4573 sip->registerstatus = 0;
4574 do_register(sip);
4575 sip->reauthenticate_set = FALSE;
4578 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4580 gchar *from;
4581 gchar *contenttype;
4582 gboolean found = FALSE;
4584 from = parse_from(sipmsg_find_header(msg, "From"));
4586 if (!from) return;
4588 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4590 contenttype = sipmsg_find_header(msg, "Content-Type");
4591 if (!strncmp(contenttype, "text/plain", 10)
4592 || !strncmp(contenttype, "text/html", 9)
4593 || !strncmp(contenttype, "multipart/related", 17)
4594 || !strncmp(contenttype, "multipart/alternative", 21))
4596 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4597 gchar *html = get_html_message(contenttype, msg->body);
4599 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4600 if (!session) {
4601 session = sipe_session_find_im(sip, from);
4604 if (session && session->focus_uri) { /* a conference */
4605 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4606 gchar *sender = parse_from(tmp);
4607 g_free(tmp);
4608 serv_got_chat_in(sip->gc, session->chat_id, sender,
4609 PURPLE_MESSAGE_RECV, html, time(NULL));
4610 g_free(sender);
4611 } else if (session && session->is_multiparty) { /* a multiparty chat */
4612 serv_got_chat_in(sip->gc, session->chat_id, from,
4613 PURPLE_MESSAGE_RECV, html, time(NULL));
4614 } else {
4615 serv_got_im(sip->gc, from, html, 0, time(NULL));
4617 g_free(html);
4618 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4619 found = TRUE;
4621 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
4622 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4623 xmlnode *state;
4624 gchar *statedata;
4626 if (!isc) {
4627 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4628 return;
4631 state = xmlnode_get_child(isc, "state");
4633 if (!state) {
4634 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4635 xmlnode_free(isc);
4636 return;
4639 statedata = xmlnode_get_data(state);
4640 if (statedata) {
4641 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4642 else serv_got_typing_stopped(sip->gc, from);
4644 g_free(statedata);
4646 xmlnode_free(isc);
4647 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4648 found = TRUE;
4650 if (!found) {
4651 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4652 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4653 if (!session) {
4654 session = sipe_session_find_im(sip, from);
4656 if (session) {
4657 gchar *msg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4658 from);
4659 sipe_present_err(sip, session, msg);
4660 g_free(msg);
4663 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
4664 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
4666 g_free(from);
4669 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
4671 gchar *body;
4672 gchar *newTag;
4673 gchar *oldHeader;
4674 gchar *newHeader;
4675 gboolean is_multiparty = FALSE;
4676 gboolean is_triggered = FALSE;
4677 gboolean was_multiparty = TRUE;
4678 gboolean just_joined = FALSE;
4679 gchar *from;
4680 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4681 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4682 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4683 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4684 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4685 GSList *end_points = NULL;
4686 char *tmp = NULL;
4687 struct sip_session *session;
4689 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
4690 g_free(tmp);
4692 /* Invitation to join conference */
4693 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
4694 process_incoming_invite_conf(sip, msg);
4695 return;
4698 /* Only accept text invitations */
4699 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4700 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4701 return;
4704 // TODO There *must* be a better way to clean up the To header to add a tag...
4705 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4706 oldHeader = sipmsg_find_header(msg, "To");
4707 newTag = gentag();
4708 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4709 sipmsg_remove_header_now(msg, "To");
4710 sipmsg_add_header_now(msg, "To", newHeader);
4711 g_free(newHeader);
4713 if (end_points_hdr) {
4714 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4716 if (g_slist_length(end_points) > 2) {
4717 is_multiparty = TRUE;
4720 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4721 is_triggered = TRUE;
4722 is_multiparty = TRUE;
4725 session = sipe_session_find_chat_by_callid(sip, callid);
4726 /* Convert to multiparty */
4727 if (session && is_multiparty && !session->is_multiparty) {
4728 g_free(session->with);
4729 session->with = NULL;
4730 was_multiparty = FALSE;
4731 session->is_multiparty = TRUE;
4732 session->chat_id = rand();
4735 if (!session && is_multiparty) {
4736 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
4738 /* IM session */
4739 from = parse_from(sipmsg_find_header(msg, "From"));
4740 if (!session) {
4741 session = sipe_session_find_or_add_im(sip, from);
4744 g_free(session->callid);
4745 session->callid = g_strdup(callid);
4747 session->is_multiparty = is_multiparty;
4748 if (roster_manager) {
4749 session->roster_manager = g_strdup(roster_manager);
4752 if (is_multiparty && end_points) {
4753 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
4754 GSList *entry = end_points;
4755 while (entry) {
4756 struct sip_dialog *dialog;
4757 struct sipendpoint *end_point = entry->data;
4758 entry = entry->next;
4760 if (!g_strcasecmp(from, end_point->contact) ||
4761 !g_strcasecmp(to, end_point->contact))
4762 continue;
4764 dialog = sipe_dialog_find(session, end_point->contact);
4765 if (dialog) {
4766 g_free(dialog->theirepid);
4767 dialog->theirepid = end_point->epid;
4768 end_point->epid = NULL;
4769 } else {
4770 dialog = sipe_dialog_add(session);
4772 dialog->callid = g_strdup(session->callid);
4773 dialog->with = end_point->contact;
4774 end_point->contact = NULL;
4775 dialog->theirepid = end_point->epid;
4776 end_point->epid = NULL;
4778 just_joined = TRUE;
4780 /* send triggered INVITE */
4781 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
4784 g_free(to);
4787 if (end_points) {
4788 GSList *entry = end_points;
4789 while (entry) {
4790 struct sipendpoint *end_point = entry->data;
4791 entry = entry->next;
4792 g_free(end_point->contact);
4793 g_free(end_point->epid);
4794 g_free(end_point);
4796 g_slist_free(end_points);
4799 if (session) {
4800 struct sip_dialog *dialog = sipe_dialog_find(session, from);
4801 if (dialog) {
4802 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
4803 } else {
4804 dialog = sipe_dialog_add(session);
4806 dialog->callid = g_strdup(session->callid);
4807 dialog->with = g_strdup(from);
4808 sipe_dialog_parse(dialog, msg, FALSE);
4810 if (!dialog->ourtag) {
4811 dialog->ourtag = newTag;
4812 newTag = NULL;
4815 just_joined = TRUE;
4817 } else {
4818 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
4820 g_free(newTag);
4822 if (is_multiparty && !session->conv) {
4823 gchar *chat_title = sipe_chat_get_name(callid);
4824 gchar *self = sip_uri_self(sip);
4825 /* create prpl chat */
4826 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
4827 session->chat_title = g_strdup(chat_title);
4828 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
4829 /* add self */
4830 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4831 self, NULL,
4832 PURPLE_CBFLAGS_NONE, FALSE);
4833 g_free(chat_title);
4834 g_free(self);
4837 if (is_multiparty && !was_multiparty) {
4838 /* add current IM counterparty to chat */
4839 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4840 sipe_dialog_first(session)->with, NULL,
4841 PURPLE_CBFLAGS_NONE, FALSE);
4844 /* add inviting party to chat */
4845 if (just_joined && session->conv) {
4846 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4847 from, NULL,
4848 PURPLE_CBFLAGS_NONE, TRUE);
4851 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4853 /* This used only in 2005 official client, not 2007 or Reuters.
4854 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4855 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4857 if (is_multiparty) {
4858 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4859 gchar *ms_text_format = sipmsg_find_header(msg, "ms-text-format");
4860 if (ms_text_format) {
4861 if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html")) {
4863 gchar *html = get_html_message(ms_text_format, NULL);
4864 if (html) {
4865 if (is_multiparty) {
4866 serv_got_chat_in(sip->gc, session->chat_id, from,
4867 PURPLE_MESSAGE_RECV, html, time(NULL));
4868 } else {
4869 serv_got_im(sip->gc, from, html, 0, time(NULL));
4871 g_free(html);
4872 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
4879 g_free(from);
4881 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
4882 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4883 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4885 body = g_strdup_printf(
4886 "v=0\r\n"
4887 "o=- 0 0 IN IP4 %s\r\n"
4888 "s=session\r\n"
4889 "c=IN IP4 %s\r\n"
4890 "t=0 0\r\n"
4891 "m=%s %d sip sip:%s\r\n"
4892 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4893 purple_network_get_my_ip(-1),
4894 purple_network_get_my_ip(-1),
4895 sip->ocs2007 ? "message" : "x-ms-message",
4896 sip->realport,
4897 sip->username);
4898 send_sip_response(sip->gc, msg, 200, "OK", body);
4899 g_free(body);
4902 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
4904 gchar *body;
4906 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
4907 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4908 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4910 body = g_strdup_printf(
4911 "v=0\r\n"
4912 "o=- 0 0 IN IP4 0.0.0.0\r\n"
4913 "s=session\r\n"
4914 "c=IN IP4 0.0.0.0\r\n"
4915 "t=0 0\r\n"
4916 "m=%s %d sip sip:%s\r\n"
4917 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4918 sip->ocs2007 ? "message" : "x-ms-message",
4919 sip->realport,
4920 sip->username);
4921 send_sip_response(sip->gc, msg, 200, "OK", body);
4922 g_free(body);
4925 static void sipe_connection_cleanup(struct sipe_account_data *);
4926 static void create_connection(struct sipe_account_data *, gchar *, int);
4928 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
4929 SIPE_UNUSED_PARAMETER struct transaction *trans)
4931 gchar *tmp;
4932 const gchar *expires_header;
4933 int expires, i;
4934 GSList *hdr = msg->headers;
4935 struct siphdrelement *elem;
4937 expires_header = sipmsg_find_header(msg, "Expires");
4938 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
4939 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
4941 switch (msg->response) {
4942 case 200:
4943 if (expires == 0) {
4944 sip->registerstatus = 0;
4945 } else {
4946 gchar *contact_hdr = NULL;
4947 gchar *gruu = NULL;
4948 gchar *epid;
4949 gchar *uuid;
4950 gchar *timeout;
4951 gchar *server_hdr = sipmsg_find_header(msg, "Server");
4953 if (!sip->reregister_set) {
4954 gchar *action_name = g_strdup_printf("<%s>", "registration");
4955 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
4956 g_free(action_name);
4957 sip->reregister_set = TRUE;
4960 sip->registerstatus = 3;
4962 if (server_hdr && !sip->server_version) {
4963 sip->server_version = g_strdup(server_hdr);
4964 g_free(default_ua);
4965 default_ua = NULL;
4968 #ifdef USE_KERBEROS
4969 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4970 #endif
4971 tmp = sipmsg_find_auth_header(msg, "NTLM");
4972 #ifdef USE_KERBEROS
4973 } else {
4974 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4976 #endif
4977 if (tmp) {
4978 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
4979 fill_auth(tmp, &sip->registrar);
4982 if (!sip->reauthenticate_set) {
4983 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
4984 guint reauth_timeout;
4985 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
4986 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
4987 reauth_timeout = sip->registrar.expires - 300;
4988 } else {
4989 /* NTLM: we have to reauthenticate as our security token expires
4990 after eight hours (be five minutes early) */
4991 reauth_timeout = (8 * 3600) - 300;
4993 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
4994 g_free(action_name);
4995 sip->reauthenticate_set = TRUE;
4998 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5000 epid = get_epid(sip);
5001 uuid = generateUUIDfromEPID(epid);
5002 g_free(epid);
5004 // There can be multiple Contact headers (one per location where the user is logged in) so
5005 // make sure to only get the one for this uuid
5006 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5007 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5008 if (valid_contact) {
5009 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5010 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5011 g_free(valid_contact);
5012 break;
5013 } else {
5014 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5017 g_free(uuid);
5019 g_free(sip->contact);
5020 if(gruu) {
5021 sip->contact = g_strdup_printf("<%s>", gruu);
5022 g_free(gruu);
5023 } else {
5024 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5025 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
5027 sip->ocs2007 = FALSE;
5028 sip->batched_support = FALSE;
5030 while(hdr)
5032 elem = hdr->data;
5033 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5034 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5035 /* We interpret this as OCS2007+ indicator */
5036 sip->ocs2007 = TRUE;
5037 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5039 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5040 sip->batched_support = TRUE;
5041 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5044 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5045 gchar **caps = g_strsplit(elem->value,",",0);
5046 i = 0;
5047 while (caps[i]) {
5048 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5049 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5050 i++;
5052 g_strfreev(caps);
5054 hdr = g_slist_next(hdr);
5057 /* rejoin open chats to be able to use them by continue to send messages */
5058 purple_conversation_foreach(sipe_rejoin_chat);
5060 /* subscriptions */
5061 if (!sip->subscribed) { //do it just once, not every re-register
5063 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5064 (GCompareFunc)g_ascii_strcasecmp)) {
5065 sipe_subscribe_roaming_contacts(sip);
5068 /* For 2007+ it does not make sence to subscribe to:
5069 * vnd-microsoft-roaming-ACL
5070 * vnd-microsoft-provisioning (not v2)
5071 * presence.wpending
5072 * These are for backward compatibility.
5074 if (sip->ocs2007)
5076 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5077 (GCompareFunc)g_ascii_strcasecmp)) {
5078 sipe_subscribe_roaming_self(sip);
5080 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5081 (GCompareFunc)g_ascii_strcasecmp)) {
5082 sipe_subscribe_roaming_provisioning_v2(sip);
5085 /* For 2005- servers */
5086 else
5088 //sipe_options_request(sip, sip->sipdomain);
5090 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5091 (GCompareFunc)g_ascii_strcasecmp)) {
5092 sipe_subscribe_roaming_acl(sip);
5094 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5095 (GCompareFunc)g_ascii_strcasecmp)) {
5096 sipe_subscribe_roaming_provisioning(sip);
5098 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5099 (GCompareFunc)g_ascii_strcasecmp)) {
5100 sipe_subscribe_presence_wpending(sip, msg);
5103 /* For 2007+ we publish our initial statuses and calendar data only after
5104 * received our existing publications in sipe_process_roaming_self()
5105 * Only in this case we know versions of current publications made
5106 * on our behalf.
5108 /* For 2005- we publish our initial statuses only after
5109 * received our existing UserInfo data in response to
5110 * self subscription.
5111 * Only in this case we won't override existing UserInfo data
5112 * set earlier or by other client on our behalf.
5116 sip->subscribed = TRUE;
5119 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5120 "timeout=", ";", NULL);
5121 if (timeout != NULL) {
5122 sscanf(timeout, "%u", &sip->keepalive_timeout);
5123 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5124 sip->keepalive_timeout);
5125 g_free(timeout);
5128 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5130 break;
5131 case 301:
5133 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5135 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5136 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5137 gchar **tmp;
5138 gchar *hostname;
5139 int port = 0;
5140 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5141 int i = 1;
5143 tmp = g_strsplit(parts[0], ":", 0);
5144 hostname = g_strdup(tmp[0]);
5145 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5146 g_strfreev(tmp);
5148 while (parts[i]) {
5149 tmp = g_strsplit(parts[i], "=", 0);
5150 if (tmp[1]) {
5151 if (g_strcasecmp("transport", tmp[0]) == 0) {
5152 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5153 transport = SIPE_TRANSPORT_TCP;
5154 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5155 transport = SIPE_TRANSPORT_UDP;
5159 g_strfreev(tmp);
5160 i++;
5162 g_strfreev(parts);
5164 /* Close old connection */
5165 sipe_connection_cleanup(sip);
5167 /* Create new connection */
5168 sip->transport = transport;
5169 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5170 hostname, port, TRANSPORT_DESCRIPTOR);
5171 create_connection(sip, hostname, port);
5173 g_free(redirect);
5175 break;
5176 case 401:
5177 if (sip->registerstatus != 2) {
5178 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5179 if (sip->registrar.retries > 3) {
5180 sip->gc->wants_to_die = TRUE;
5181 purple_connection_error(sip->gc, _("Wrong password"));
5182 return TRUE;
5184 #ifdef USE_KERBEROS
5185 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5186 #endif
5187 tmp = sipmsg_find_auth_header(msg, "NTLM");
5188 #ifdef USE_KERBEROS
5189 } else {
5190 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5192 #endif
5193 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5194 fill_auth(tmp, &sip->registrar);
5195 sip->registerstatus = 2;
5196 if (sip->account->disconnecting) {
5197 do_register_exp(sip, 0);
5198 } else {
5199 do_register(sip);
5202 break;
5203 case 403:
5205 gchar *warning = sipmsg_find_header(msg, "Warning");
5206 gchar **reason = NULL;
5207 if (warning != NULL) {
5208 /* Example header:
5209 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5211 reason = g_strsplit(warning, "\"", 0);
5213 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5214 (reason && reason[1]) ? reason[1] : _("no reason given"));
5215 g_strfreev(reason);
5217 sip->gc->wants_to_die = TRUE;
5218 purple_connection_error(sip->gc, warning);
5219 g_free(warning);
5220 return TRUE;
5222 break;
5223 case 404:
5225 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5226 gchar *reason = NULL;
5227 if (warning != NULL) {
5228 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5230 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5231 warning ? (reason ? reason : _("no reason given")) :
5232 _("SIP is either not enabled for the destination URI or it does not exist"));
5233 g_free(reason);
5235 sip->gc->wants_to_die = TRUE;
5236 purple_connection_error(sip->gc, warning);
5237 g_free(warning);
5238 return TRUE;
5240 break;
5241 case 503:
5242 case 504: /* Server time-out */
5244 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5245 gchar *reason = NULL;
5246 if (warning != NULL) {
5247 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5249 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5250 g_free(reason);
5252 sip->gc->wants_to_die = TRUE;
5253 purple_connection_error(sip->gc, warning);
5254 g_free(warning);
5255 return TRUE;
5257 break;
5259 return TRUE;
5263 * Returns 2005-style activity and Availability.
5265 * @param status Sipe statis id.
5267 static void
5268 sipe_get_act_avail_by_status_2005(const char *status, int *activity, int *availability)
5270 int avail = 300; /* online */
5271 int act = 400; /* Available */
5273 if (!strcmp(status, SIPE_STATUS_ID_AWAY)) {
5274 act = 100;
5275 } else if (!strcmp(status, SIPE_STATUS_ID_LUNCH)) {
5276 act = 150;
5277 } else if (!strcmp(status, SIPE_STATUS_ID_BRB)) {
5278 act = 300;
5279 } else if (!strcmp(status, SIPE_STATUS_ID_AVAILABLE)) {
5280 act = 400;
5281 } else if (!strcmp(status, SIPE_STATUS_ID_ON_PHONE)) {
5282 act = 500;
5283 } else if (!strcmp(status, SIPE_STATUS_ID_BUSY) ||
5284 !strcmp(status, SIPE_STATUS_ID_IN_MEETING) ||
5285 !strcmp(status, SIPE_STATUS_ID_IN_CONF) ||
5286 !strcmp(status, SIPE_STATUS_ID_DND)) {
5287 act = 600;
5288 } else if (!strcmp(status, SIPE_STATUS_ID_INVISIBLE) ||
5289 !strcmp(status, SIPE_STATUS_ID_OFFLINE)) {
5290 avail = 0; /* offline */
5291 act = 100;
5292 } else {
5293 act = 400; /* Available */
5296 if (activity) *activity = act;
5297 if (availability) *availability = avail;
5301 * [MS-SIP] 2.2.1
5303 * @param activity 2005 aggregated activity. Ex.: 600
5304 * @param availablity 2005 aggregated availablity. Ex.: 300
5306 static const char *
5307 sipe_get_status_by_act_avail_2005(const int activity,
5308 const int availablity)
5310 const char *status_id = NULL;
5312 if (activity < 150)
5313 status_id = SIPE_STATUS_ID_AWAY;
5314 else if (activity < 200)
5315 status_id = SIPE_STATUS_ID_LUNCH;
5316 else if (activity < 300)
5317 status_id = SIPE_STATUS_ID_IDLE;
5318 else if (activity < 400)
5319 status_id = SIPE_STATUS_ID_BRB;
5320 else if (activity < 500)
5321 status_id = SIPE_STATUS_ID_AVAILABLE;
5322 else if (activity < 600)
5323 status_id = SIPE_STATUS_ID_ON_PHONE;
5324 else if (activity < 700)
5325 status_id = SIPE_STATUS_ID_BUSY;
5326 else if (activity < 800)
5327 status_id = SIPE_STATUS_ID_AWAY;
5328 else
5329 status_id = SIPE_STATUS_ID_AVAILABLE;
5331 if (availablity < 100)
5332 status_id = SIPE_STATUS_ID_OFFLINE;
5334 return status_id;
5338 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5340 static const char*
5341 sipe_get_status_by_availability(int avail,
5342 const char* activity)
5344 const char *status;
5346 if (avail < 3000)
5347 status = SIPE_STATUS_ID_OFFLINE;
5348 else if (avail < 4500)
5349 status = SIPE_STATUS_ID_AVAILABLE;
5350 else if (avail < 6000)
5351 status = SIPE_STATUS_ID_IDLE;
5352 else if (avail < 7500)
5353 if (activity && !strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].desc)) {
5354 status = SIPE_STATUS_ID_IN_MEETING;
5355 } else if (activity && !strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_IN_CONF].desc)) {
5356 status = SIPE_STATUS_ID_IN_CONF;
5357 } else if (activity && !strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].desc)) {
5358 status = SIPE_STATUS_ID_ON_PHONE;
5359 } else {
5360 status = SIPE_STATUS_ID_BUSY;
5362 else if (avail < 9000)
5363 status = SIPE_STATUS_ID_BUSYIDLE;
5364 else if (avail < 12000)
5365 status = SIPE_STATUS_ID_DND;
5366 else if (avail < 15000)
5367 status = SIPE_STATUS_ID_BRB;
5368 else if (avail < 18000)
5369 status = SIPE_STATUS_ID_AWAY;
5370 else
5371 status = SIPE_STATUS_ID_OFFLINE;
5373 return status;
5377 * Returns 2007-style availability value
5379 * @param sipe_status_id (in)
5380 * @param activity_token (out) Must be g_free()'d after use if consumed.
5382 static int
5383 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5385 int availability;
5386 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5388 if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AWAY) ||
5389 !strcmp(sipe_status_id, SIPE_STATUS_ID_LUNCH)) {
5390 availability = 15500;
5391 activity = SIPE_ACTIVITY_AWAY;
5392 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5393 availability = 12500;
5394 activity = SIPE_ACTIVITY_BRB;
5395 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_DND)) {
5396 availability = 9500;
5397 activity = SIPE_ACTIVITY_DND;
5398 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BUSY) ||
5399 !strcmp(sipe_status_id, SIPE_STATUS_ID_IN_MEETING) ||
5400 !strcmp(sipe_status_id, SIPE_STATUS_ID_IN_CONF) ||
5401 !strcmp(sipe_status_id, SIPE_STATUS_ID_ON_PHONE)) {
5402 availability = 6500;
5403 activity = SIPE_ACTIVITY_BUSY;
5404 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5405 availability = 3500;
5406 activity = SIPE_ACTIVITY_ONLINE;
5407 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5408 availability = 0;
5409 } else {
5410 // Offline or invisible
5411 availability = 18500;
5412 activity = SIPE_ACTIVITY_OFFLINE;
5415 if (activity_token) {
5416 *activity_token = g_strdup(sipe_activity_map[activity].token);
5418 return availability;
5421 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5423 const char *uri;
5424 xmlnode *xn_categories;
5425 xmlnode *xn_category;
5426 xmlnode *xn_node;
5427 const char *status = NULL;
5428 gboolean do_update_status = FALSE;
5430 xn_categories = xmlnode_from_str(data, len);
5431 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5433 for (xn_category = xmlnode_get_child(xn_categories, "category");
5434 xn_category ;
5435 xn_category = xmlnode_get_next_twin(xn_category) )
5437 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5439 /* contactCard */
5440 if (!strcmp(attrVar, "contactCard"))
5442 xmlnode *node;
5443 /* identity - Display Name and email */
5444 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5445 if (node) {
5446 char* display_name = xmlnode_get_data(
5447 xmlnode_get_descendant(node, "name", "displayName", NULL));
5448 char* email = xmlnode_get_data(
5449 xmlnode_get_child(node, "email"));
5451 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5452 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5454 g_free(display_name);
5455 g_free(email);
5457 /* company */
5458 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5459 if (node) {
5460 char* company = xmlnode_get_data(node);
5461 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5462 g_free(company);
5464 /* department */
5465 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5466 if (node) {
5467 char* department = xmlnode_get_data(node);
5468 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5469 g_free(department);
5471 /* title */
5472 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5473 if (node) {
5474 char* title = xmlnode_get_data(node);
5475 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5476 g_free(title);
5478 /* office */
5479 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5480 if (node) {
5481 char* office = xmlnode_get_data(node);
5482 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5483 g_free(office);
5485 /* site (url) */
5486 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5487 if (node) {
5488 char* site = xmlnode_get_data(node);
5489 sipe_update_user_info(sip, uri, SITE_PROP, site);
5490 g_free(site);
5492 /* phone */
5493 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5494 node;
5495 node = xmlnode_get_next_twin(node))
5497 const char *phone_type = xmlnode_get_attrib(node, "type");
5498 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5499 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5501 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5503 g_free(phone);
5504 g_free(phone_display_string);
5506 /* address */
5507 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5508 node;
5509 node = xmlnode_get_next_twin(node))
5511 if (!strcmp(xmlnode_get_attrib(node, "type"), "work")) {
5512 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5513 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5514 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5515 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5516 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5518 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5519 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5520 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5521 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5522 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5524 g_free(street);
5525 g_free(city);
5526 g_free(state);
5527 g_free(zipcode);
5528 g_free(country_code);
5530 break;
5534 /* note */
5535 else if (!strcmp(attrVar, "note"))
5537 if (uri) {
5538 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5540 if (sbuddy) {
5541 /* clean up in case to 'note' element is supplied
5542 * which indicate note removal in client
5544 g_free(sbuddy->annotation);
5545 sbuddy->annotation = NULL;
5546 sbuddy->is_oof_note = FALSE;
5548 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
5549 if (xn_node) {
5550 sbuddy->annotation = xmlnode_get_data(xn_node);
5551 sbuddy->is_oof_note = !strcmp(xmlnode_get_attrib(xn_node, "type"), "OOF");
5553 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",
5554 uri, sbuddy->annotation ? sbuddy->annotation : "");
5557 /* to trigger UI refresh in case no status info is supplied in this update */
5558 do_update_status = TRUE;
5562 /* state */
5563 else if(!strcmp(attrVar, "state"))
5565 char *data;
5566 int availability;
5567 xmlnode *xn_availability;
5568 xmlnode *xn_activity;
5569 xmlnode *xn_meeting_subject;
5570 xmlnode *xn_meeting_location;
5571 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5573 xn_node = xmlnode_get_child(xn_category, "state");
5574 if (!xn_node) continue;
5575 xn_availability = xmlnode_get_child(xn_node, "availability");
5576 if (!xn_availability) continue;
5577 xn_activity = xmlnode_get_child(xn_node, "activity");
5578 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
5579 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
5581 data = xmlnode_get_data(xn_availability);
5582 availability = atoi(data);
5583 g_free(data);
5585 /* activity, meeting_subject, meeting_location */
5586 if (sbuddy) {
5587 /* activity */
5588 g_free(sbuddy->activity);
5589 sbuddy->activity = NULL;
5590 if (xn_activity) {
5591 const char *token = xmlnode_get_attrib(xn_activity, "token");
5592 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
5594 /* from token */
5595 if (!is_empty(token)) {
5596 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
5598 /* form custom element */
5599 if (xn_custom) {
5600 char *custom = xmlnode_get_data(xn_custom);
5602 if (!is_empty(custom)) {
5603 sbuddy->activity = custom;
5604 custom = NULL;
5606 g_free(custom);
5609 /* meeting_subject */
5610 g_free(sbuddy->meeting_subject);
5611 sbuddy->meeting_subject = NULL;
5612 if (xn_meeting_subject) {
5613 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
5615 if (!is_empty(meeting_subject)) {
5616 sbuddy->meeting_subject = meeting_subject;
5617 meeting_subject = NULL;
5619 g_free(meeting_subject);
5621 /* meeting_location */
5622 g_free(sbuddy->meeting_location);
5623 sbuddy->meeting_location = NULL;
5624 if (xn_meeting_location) {
5625 char *meeting_location = xmlnode_get_data(xn_meeting_location);
5627 if (!is_empty(meeting_location)) {
5628 sbuddy->meeting_location = meeting_location;
5629 meeting_location = NULL;
5631 g_free(meeting_location);
5635 status = sipe_get_status_by_availability(availability, sbuddy->activity);
5636 do_update_status = TRUE;
5638 /* calendarData */
5639 else if(!strcmp(attrVar, "calendarData"))
5641 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5642 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
5643 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
5645 if (sbuddy && xn_free_busy) {
5646 g_free(sbuddy->cal_start_time);
5647 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
5649 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
5650 15 : 0;
5652 g_free(sbuddy->cal_free_busy_base64);
5653 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
5655 g_free(sbuddy->cal_free_busy);
5656 sbuddy->cal_free_busy = NULL;
5658 purple_debug_info("sipe", "process_incoming_notify_rlmi: startTime=%s granularity=%d cal_free_busy_base64=\n%s\n", sbuddy->cal_start_time, sbuddy->cal_granularity, sbuddy->cal_free_busy_base64);
5661 if (sbuddy && xn_working_hours) {
5662 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
5667 if (do_update_status) {
5668 if (!status) { /* no status category in this update, using contact's current status */
5669 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
5670 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
5671 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
5672 status = purple_status_get_id(pstatus);
5675 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
5676 sipe_got_user_status(sip, uri, status);
5679 xmlnode_free(xn_categories);
5682 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
5684 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5685 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
5686 payload->host = g_strdup(host);
5687 payload->buddies = server;
5688 sipe_subscribe_presence_batched_routed(sip, payload);
5689 sipe_subscribe_presence_batched_routed_free(payload);
5692 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
5694 xmlnode *xn_list;
5695 xmlnode *xn_resource;
5696 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
5697 g_free, NULL);
5698 GSList *server;
5699 gchar *host;
5701 xn_list = xmlnode_from_str(data, len);
5703 for (xn_resource = xmlnode_get_child(xn_list, "resource");
5704 xn_resource;
5705 xn_resource = xmlnode_get_next_twin(xn_resource) )
5707 const char *uri, *state;
5708 xmlnode *xn_instance;
5710 xn_instance = xmlnode_get_child(xn_resource, "instance");
5711 if (!xn_instance) continue;
5713 uri = xmlnode_get_attrib(xn_resource, "uri");
5714 state = xmlnode_get_attrib(xn_instance, "state");
5715 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
5717 if (strstr(state, "resubscribe")) {
5718 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
5720 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
5721 gchar *user = g_strdup(uri);
5722 host = g_strdup(poolFqdn);
5723 server = g_hash_table_lookup(servers, host);
5724 server = g_slist_append(server, user);
5725 g_hash_table_insert(servers, host, server);
5726 } else {
5727 sipe_subscribe_presence_single(sip, (void *) uri);
5732 /* Send out any deferred poolFqdn subscriptions */
5733 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
5734 g_hash_table_destroy(servers);
5736 xmlnode_free(xn_list);
5739 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
5741 gchar *uri;
5742 gchar *getbasic;
5743 gchar *activity = NULL;
5744 xmlnode *pidf;
5745 xmlnode *basicstatus = NULL, *tuple, *status;
5746 gboolean isonline = FALSE;
5747 xmlnode *display_name_node;
5749 pidf = xmlnode_from_str(data, len);
5750 if (!pidf) {
5751 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
5752 return;
5755 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
5757 if ((tuple = xmlnode_get_child(pidf, "tuple")))
5759 if ((status = xmlnode_get_child(tuple, "status"))) {
5760 basicstatus = xmlnode_get_child(status, "basic");
5764 if (!basicstatus) {
5765 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
5766 xmlnode_free(pidf);
5767 return;
5770 getbasic = xmlnode_get_data(basicstatus);
5771 if (!getbasic) {
5772 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
5773 xmlnode_free(pidf);
5774 return;
5777 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
5778 if (strstr(getbasic, "open")) {
5779 isonline = TRUE;
5781 g_free(getbasic);
5783 display_name_node = xmlnode_get_child(pidf, "display-name");
5784 if (display_name_node) {
5785 char * display_name = xmlnode_get_data(display_name_node);
5787 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5788 g_free(display_name);
5791 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
5792 if ((status = xmlnode_get_child(tuple, "status"))) {
5793 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
5794 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
5795 activity = xmlnode_get_data(basicstatus);
5796 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
5802 if (isonline) {
5803 const gchar * status_id = NULL;
5804 if (activity) {
5805 if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].desc)) {
5806 status_id = SIPE_STATUS_ID_BUSY;
5807 } else if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].desc)) {
5808 status_id = SIPE_STATUS_ID_AWAY;
5812 if (!status_id) {
5813 status_id = SIPE_STATUS_ID_AVAILABLE;
5816 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
5817 sipe_got_user_status(sip, uri, status_id);
5818 } else {
5819 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
5822 g_free(activity);
5823 g_free(uri);
5824 xmlnode_free(pidf);
5827 /** 2005 */
5828 static void
5829 sipe_user_info_has_updated(struct sipe_account_data *sip,
5830 xmlnode *xn_userinfo)
5832 if (sip->user_info) {
5833 xmlnode_free(sip->user_info);
5835 sip->user_info = xmlnode_copy(xn_userinfo);
5837 /* Publish initial state if not yet.
5838 * Assuming this happens on initial responce to self subscription
5839 * so we've already updated our UserInfo.
5841 if (!sip->initial_state_published) {
5842 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
5843 /* dalayed run */
5844 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
5848 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
5850 const char *activity = NULL;
5851 const char *epid;
5852 const char *status_id = NULL;
5853 const char *name;
5854 char *uri;
5855 char *self_uri = sip_uri_self(sip);
5856 int avl;
5857 int act;
5858 const char *device_name = NULL;
5859 const char *cal_start_time = NULL;
5860 const char *cal_granularity = NULL;
5861 char *cal_free_busy_base64 = NULL;
5862 struct sipe_buddy *sbuddy;
5863 xmlnode *node;
5864 xmlnode *xn_presentity;
5865 xmlnode *xn_availability;
5866 xmlnode *xn_activity;
5867 xmlnode *xn_display_name;
5868 xmlnode *xn_email;
5869 xmlnode *xn_phone_number;
5870 xmlnode *xn_userinfo;
5871 xmlnode *xn_note;
5872 xmlnode *xn_oof;
5873 xmlnode *xn_state;
5874 xmlnode *xn_contact;
5875 char *note;
5876 char *free_activity;
5877 int user_avail;
5878 const char *user_avail_nil;
5879 int res_avail;
5880 time_t user_avail_since = 0;
5881 time_t activity_since = 0;
5883 /* fix for Reuters environment on Linux */
5884 if (data && strstr(data, "encoding=\"utf-16\"")) {
5885 char *tmp_data;
5886 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
5887 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
5888 g_free(tmp_data);
5889 } else {
5890 xn_presentity = xmlnode_from_str(data, len);
5893 xn_availability = xmlnode_get_child(xn_presentity, "availability");
5894 xn_activity = xmlnode_get_child(xn_presentity, "activity");
5895 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
5896 xn_email = xmlnode_get_child(xn_presentity, "email");
5897 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
5898 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
5899 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
5900 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
5901 user_avail = xn_state ? atoi(xmlnode_get_attrib(xn_state, "avail")) : 0;
5902 user_avail_since = xn_state ? purple_str_to_time(xmlnode_get_attrib(xn_state, "since"), FALSE, NULL, NULL, NULL) : 0;
5903 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
5904 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
5905 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
5906 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
5908 if (user_avail_nil && !strcmp(user_avail_nil, "true")) { /* null-ed */
5909 user_avail = 0;
5910 user_avail_since = 0;
5913 free_activity = NULL;
5915 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
5916 uri = sip_uri_from_name(name);
5917 avl = atoi(xmlnode_get_attrib(xn_availability, "aggregate"));
5918 epid = xmlnode_get_attrib(xn_availability, "epid");
5919 act = atoi(xmlnode_get_attrib(xn_activity, "aggregate"));
5921 status_id = sipe_get_status_by_act_avail_2005(act, avl);
5922 res_avail = sipe_get_availability_by_status(status_id, NULL);
5923 if (user_avail > res_avail) {
5924 res_avail = user_avail;
5925 status_id = sipe_get_status_by_availability(user_avail, NULL);
5928 if (xn_display_name) {
5929 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
5930 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
5931 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
5932 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
5933 char *tel_uri = sip_to_tel_uri(phone_number);
5935 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5936 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5937 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
5938 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
5940 g_free(tel_uri);
5941 g_free(phone_label);
5942 g_free(phone_number);
5943 g_free(email);
5944 g_free(display_name);
5947 if (xn_contact) {
5948 /* tel */
5949 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
5951 /* Ex.: <tel type="work">tel:+3222220000</tel> */
5952 const char *phone_type = xmlnode_get_attrib(node, "type");
5953 char* phone = xmlnode_get_data(node);
5955 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
5957 g_free(phone);
5961 /* devicePresence */
5962 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
5963 xmlnode *xn_device_name;
5964 xmlnode *xn_calendar_info;
5965 xmlnode *xn_state;
5966 char *state;
5968 /* deviceName */
5969 if (!strcmp(xmlnode_get_attrib(node, "epid"), epid)) {
5970 xn_device_name = xmlnode_get_child(node, "deviceName");
5971 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
5974 /* calendarInfo */
5975 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
5976 if (xn_calendar_info) {
5977 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
5979 if (cal_start_time) {
5980 time_t cal_start_time_t = purple_str_to_time(cal_start_time, FALSE, NULL, NULL, NULL);
5981 time_t cal_start_time_t_tmp = purple_str_to_time(cal_start_time_tmp, FALSE, NULL, NULL, NULL);
5983 if (cal_start_time_t_tmp > cal_start_time_t) {
5984 cal_start_time = cal_start_time_tmp;
5985 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
5986 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
5988 purple_debug_info("sipe", "process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s\n", cal_start_time, cal_granularity, cal_free_busy_base64);
5990 } else {
5991 cal_start_time = cal_start_time_tmp;
5992 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
5993 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
5995 purple_debug_info("sipe", "process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s\n", cal_start_time, cal_granularity, cal_free_busy_base64);
5999 /* state */
6000 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6001 if (xn_state) {
6002 int dev_avail = atoi(xmlnode_get_attrib(xn_state, "avail"));
6003 time_t dev_avail_since = purple_str_to_time(xmlnode_get_attrib(xn_state, "since"), FALSE, NULL, NULL, NULL);
6005 state = xn_state ? xmlnode_get_data(xn_state) : NULL;
6006 if (dev_avail_since > user_avail_since &&
6007 dev_avail >= res_avail)
6009 res_avail = dev_avail;
6010 if (!is_empty(state))
6012 if (!strcmp(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].desc)) {
6013 activity = sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].desc;
6014 } else if (!strcmp(state, "presenting")) {
6015 activity = sipe_activity_map[SIPE_ACTIVITY_IN_CONF].desc;
6016 } else {
6017 activity = free_activity = state;
6018 state = NULL;
6020 activity_since = dev_avail_since;
6022 status_id = sipe_get_status_by_availability(res_avail, activity);
6024 g_free(state);
6028 /* oof */
6029 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6030 activity = sipe_activity_map[SIPE_ACTIVITY_OOF].desc;
6031 activity_since = 0;
6034 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6035 if (sbuddy)
6037 g_free(sbuddy->activity);
6038 sbuddy->activity = NULL;
6039 if (!is_empty(activity)) { sbuddy->activity = g_strdup(activity); }
6041 sbuddy->activity_since = activity_since;
6043 sbuddy->user_avail = user_avail;
6044 sbuddy->user_avail_since = user_avail_since;
6046 g_free(sbuddy->annotation);
6047 sbuddy->annotation = NULL;
6048 if (!is_empty(note)) { sbuddy->annotation = g_strdup(note); }
6050 sbuddy->is_oof_note = (xn_oof != NULL);
6052 g_free(sbuddy->device_name);
6053 sbuddy->device_name = NULL;
6054 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6056 if (!is_empty(cal_free_busy_base64)) {
6057 g_free(sbuddy->cal_start_time);
6058 sbuddy->cal_start_time = g_strdup(cal_start_time);
6060 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6062 g_free(sbuddy->cal_free_busy_base64);
6063 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6065 g_free(sbuddy->cal_free_busy);
6066 sbuddy->cal_free_busy = NULL;
6070 if (free_activity) g_free(free_activity);
6072 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6073 sipe_got_user_status(sip, uri, status_id);
6075 if (!sip->ocs2007 && !strcmp(self_uri, uri)) {
6076 sipe_user_info_has_updated(sip, xn_userinfo);
6079 g_free(note);
6080 xmlnode_free(xn_presentity);
6081 g_free(uri);
6082 g_free(self_uri);
6085 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6087 char *ctype = sipmsg_find_header(msg, "Content-Type");
6089 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6091 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6092 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6094 const char *content = msg->body;
6095 unsigned length = msg->bodylen;
6096 PurpleMimeDocument *mime = NULL;
6098 if (strstr(ctype, "multipart"))
6100 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6101 const char *content_type;
6102 GList* parts;
6103 mime = purple_mime_document_parse(doc);
6104 parts = purple_mime_document_get_parts(mime);
6105 while(parts) {
6106 content = purple_mime_part_get_data(parts->data);
6107 length = purple_mime_part_get_length(parts->data);
6108 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6109 if(content_type && strstr(content_type,"application/rlmi+xml"))
6111 process_incoming_notify_rlmi_resub(sip, content, length);
6113 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6115 process_incoming_notify_msrtc(sip, content, length);
6117 else
6119 process_incoming_notify_rlmi(sip, content, length);
6121 parts = parts->next;
6123 g_free(doc);
6125 if (mime)
6127 purple_mime_document_free(mime);
6130 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6132 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6134 else if(strstr(ctype, "application/rlmi+xml"))
6136 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6139 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6141 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6143 else
6145 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6149 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6151 char *ctype = sipmsg_find_header(msg, "Content-Type");
6152 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6154 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6156 if (ctype &&
6157 strstr(ctype, "multipart") &&
6158 (strstr(ctype, "application/rlmi+xml") ||
6159 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6160 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6161 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6162 GList *parts = purple_mime_document_get_parts(mime);
6163 GSList *buddies = NULL;
6164 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6166 while (parts) {
6167 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6168 purple_mime_part_get_length(parts->data));
6170 if (strcmp(xml->name, "list")) {
6171 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6173 buddies = g_slist_append(buddies, uri);
6175 xmlnode_free(xml);
6177 parts = parts->next;
6179 g_free(doc);
6180 if (mime) purple_mime_document_free(mime);
6182 payload->host = g_strdup(who);
6183 payload->buddies = buddies;
6184 sipe_schedule_action(action_name, timeout,
6185 sipe_subscribe_presence_batched_routed,
6186 sipe_subscribe_presence_batched_routed_free,
6187 sip, payload);
6188 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6190 } else {
6191 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6192 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6194 g_free(action_name);
6198 * Dispatcher for all incoming subscription information
6199 * whether it comes from NOTIFY, BENOTIFY requests or
6200 * piggy-backed to subscription's OK responce.
6202 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6203 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6205 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6207 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6208 gchar *event = sipmsg_find_header(msg, "Event");
6209 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6210 char *tmp;
6211 int timeout = 0;
6213 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6214 event ? event : "",
6215 tmp = fix_newlines(msg->body));
6216 g_free(tmp);
6217 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6219 /* implicit subscriptions */
6220 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6221 sipe_process_imdn(sip, msg);
6224 if (!request)
6226 const gchar *expires_header;
6227 expires_header = sipmsg_find_header(msg, "Expires");
6228 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6229 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6230 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout; // 2 min ahead of expiration
6233 /* for one off subscriptions (send with Expire: 0) */
6234 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6236 sipe_process_provisioning_v2(sip, msg);
6238 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6240 sipe_process_provisioning(sip, msg);
6243 if (!subscription_state || strstr(subscription_state, "active"))
6245 if (event && !g_ascii_strcasecmp(event, "presence"))
6247 sipe_process_presence(sip, msg);
6249 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6251 sipe_process_roaming_contacts(sip, msg);
6253 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6255 sipe_process_roaming_self(sip, msg);
6257 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6259 sipe_process_roaming_acl(sip, msg);
6261 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
6263 sipe_process_presence_wpending(sip, msg);
6265 else if (event && !g_ascii_strcasecmp(event, "conference"))
6267 sipe_process_conference(sip, msg);
6271 /* The server sends status 'terminated' */
6272 if (subscription_state && strstr(subscription_state, "terminated") ) {
6273 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6274 gchar *key = sipe_get_subscription_key(event, who);
6276 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6277 g_free(who);
6279 if (g_hash_table_lookup(sip->subscriptions, key)) {
6280 g_hash_table_remove(sip->subscriptions, key);
6281 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6284 g_free(key);
6287 if (timeout && event) {// For LSC 2005 and OCS 2007
6288 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
6289 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
6291 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
6292 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
6293 g_free(action_name);
6295 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
6296 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
6298 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
6299 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
6300 g_free(action_name);
6302 else*/
6303 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6304 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6306 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6307 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6308 g_free(action_name);
6310 else if (!g_ascii_strcasecmp(event, "presence") &&
6311 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6313 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6314 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6315 if (sip->batched_support) {
6316 sipe_process_presence_timeout(sip, msg, who, timeout);
6318 else {
6319 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6320 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6322 g_free(action_name);
6323 g_free(who);
6327 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
6329 sipe_process_registration_notify(sip, msg);
6332 /* The client responses on received a NOTIFY message */
6333 if (request && !benotify)
6335 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6340 * Whether user manually changed status or
6341 * it was changed automatically due to user
6342 * became inactive/active again
6344 static gboolean
6345 sipe_is_user_state(struct sipe_account_data *sip)
6347 gboolean res = (sip->was_idle == sip->is_idle);
6348 return res;
6351 static void
6352 send_presence_soap0(struct sipe_account_data *sip,
6353 gboolean do_publish_calendar,
6354 gboolean do_reset_status)
6356 struct sipe_ews* ews = sip->ews;
6357 int availability = 0;
6358 int activity = 0;
6359 gchar *body;
6360 gchar *tmp;
6361 gchar *res_note = NULL;
6362 gchar *res_oof = NULL;
6363 const gchar *note_pub = NULL;
6364 gchar *states = NULL;
6365 gchar *calendar_data = NULL;
6366 gchar *epid = get_epid(sip);
6367 time_t now = time(NULL);
6368 gchar *since_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&now)));
6369 const gchar *oof_note = sipe_ews_get_oof_note(ews);
6370 const char *user_input;
6372 if (!sip->initial_state_published) {
6373 g_free(sip->status);
6374 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6377 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6379 /* Note */
6380 if (oof_note) {
6381 note_pub = oof_note;
6382 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6383 } else if (sip->note) {
6384 note_pub = sip->note;
6387 if (note_pub)
6389 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, note_pub);
6391 else if (!(ews && ews->is_updated)) /* preserve existing publication */
6393 xmlnode *xn_note;
6394 if (sip->user_info && (xn_note = xmlnode_get_child(sip->user_info, "note"))) {
6395 res_note = xmlnode_to_str(xn_note, NULL);
6398 if (sip->user_info && xmlnode_get_child(sip->user_info, "oof")) {
6399 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6403 /* User State */
6404 if (!do_reset_status) {
6405 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6407 gchar *activity_token = NULL;
6408 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6410 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6411 avail_2007,
6412 since_time_str,
6413 epid,
6414 activity_token);
6415 g_free(activity_token);
6417 else /* preserve existing publication */
6419 xmlnode *xn_states;
6420 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6421 states = xmlnode_to_str(xn_states, NULL);
6424 } else {
6425 /* do nothing - then User state will be erased */
6427 sip->initial_state_published = TRUE;
6429 /* CalendarInfo */
6430 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6432 char *fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
6433 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6434 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6435 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6436 fb_start_str,
6437 free_busy_base64);
6438 g_free(fb_start_str);
6439 g_free(free_busy_base64);
6442 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6444 /* forming resulting XML */
6445 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6446 sip->username,
6447 availability,
6448 activity,
6449 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6450 res_note ? res_note : "",
6451 res_oof ? res_oof : "",
6452 states ? states : "",
6453 calendar_data ? calendar_data : "",
6454 epid,
6455 since_time_str,
6456 since_time_str,
6457 user_input);
6458 g_free(tmp);
6459 g_free(res_note);
6460 g_free(states);
6461 g_free(calendar_data);
6463 send_soap_request(sip, body);
6465 g_free(body);
6466 g_free(since_time_str);
6467 g_free(epid);
6470 void
6471 send_presence_soap(struct sipe_account_data *sip,
6472 gboolean do_publish_calendar)
6474 return send_presence_soap0(sip, do_publish_calendar, FALSE);
6478 static gboolean
6479 process_send_presence_category_publish_response(struct sipe_account_data *sip,
6480 struct sipmsg *msg,
6481 struct transaction *trans)
6483 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
6485 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
6486 xmlnode *xml;
6487 xmlnode *node;
6488 gchar *fault_code;
6489 GHashTable *faults;
6490 int index_our;
6491 gboolean has_device_publication = FALSE;
6493 xml = xmlnode_from_str(msg->body, msg->bodylen);
6495 /* test if version mismatch fault */
6496 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
6497 if (strcmp(fault_code, "Client.BadCall.WrongDelta")) {
6498 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
6499 g_free(fault_code);
6500 xmlnode_free(xml);
6501 return TRUE;
6503 g_free(fault_code);
6505 /* accumulating information about faulty versions */
6506 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
6507 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
6508 node;
6509 node = xmlnode_get_next_twin(node))
6511 const gchar *index = xmlnode_get_attrib(node, "index");
6512 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
6514 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
6515 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
6517 xmlnode_free(xml);
6519 /* here we are parsing own request to figure out what publication
6520 * referensed here only by index went wrong
6522 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
6524 /* publication */
6525 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
6526 index_our = 1; /* starts with 1 - our first publication */
6527 node;
6528 node = xmlnode_get_next_twin(node), index_our++)
6530 gchar *idx = g_strdup_printf("%d", index_our);
6531 const gchar *curVersion = g_hash_table_lookup(faults, idx);
6532 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
6533 g_free(idx);
6535 if (!strcmp("device", categoryName)) {
6536 has_device_publication = TRUE;
6539 if (curVersion) { /* fault exist on this index */
6540 const gchar *container = xmlnode_get_attrib(node, "container");
6541 const gchar *instance = xmlnode_get_attrib(node, "instance");
6542 /* key is <category><instance><container> */
6543 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
6544 struct sipe_publication *publication =
6545 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
6547 purple_debug_info("sipe", "key is %s\n", key);
6549 if (publication) {
6550 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6551 key, curVersion, publication->version);
6552 /* updating publication's version to the correct one */
6553 publication->version = atoi(curVersion);
6555 g_free(key);
6558 xmlnode_free(xml);
6559 g_hash_table_destroy(faults);
6561 /* rebublishing with right versions */
6562 if (has_device_publication) {
6563 send_publish_category_initial(sip);
6564 } else {
6565 send_presence_status(sip);
6568 return TRUE;
6572 * Returns 'device' XML part for publication.
6573 * Must be g_free'd after use.
6575 static gchar *
6576 sipe_publish_get_category_device(struct sipe_account_data *sip)
6578 gchar *uri;
6579 gchar *doc;
6580 gchar *epid = get_epid(sip);
6581 gchar *uuid = generateUUIDfromEPID(epid);
6582 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
6583 /* key is <category><instance><container> */
6584 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
6585 struct sipe_publication *publication =
6586 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
6588 g_free(key);
6589 g_free(epid);
6591 uri = sip_uri_self(sip);
6592 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
6593 device_instance,
6594 publication ? publication->version : 0,
6595 uuid,
6596 uri,
6597 "00:00:00+01:00", /* @TODO make timezone real*/
6598 sipe_get_host_name()
6601 g_free(uri);
6602 g_free(uuid);
6604 return doc;
6608 * A service method - use
6609 * - send_publish_get_category_state_machine and
6610 * - send_publish_get_category_state_user instead.
6611 * Must be g_free'd after use.
6613 static gchar *
6614 sipe_publish_get_category_state(struct sipe_account_data *sip,
6615 gboolean is_user_state)
6617 int availability = sipe_get_availability_by_status(sip->status, NULL);
6618 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
6619 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
6620 /* key is <category><instance><container> */
6621 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6622 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6623 struct sipe_publication *publication_2 =
6624 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6625 struct sipe_publication *publication_3 =
6626 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6628 g_free(key_2);
6629 g_free(key_3);
6631 if (publication_2 && (publication_2->availability == availability))
6633 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6634 return NULL; /* nothing to update */
6637 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
6638 instance,
6639 publication_2 ? publication_2->version : 0,
6640 availability,
6641 instance,
6642 publication_3 ? publication_3->version : 0,
6643 availability);
6647 * Only Busy and OOF calendar event are published.
6648 * Different instances are used for that.
6650 * Must be g_free'd after use.
6652 static gchar *
6653 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
6654 struct sipe_cal_event *event,
6655 const char *uri,
6656 int cal_satus)
6658 gchar *start_time_str;
6659 int availability = 0;
6660 gchar *res;
6661 guint instance = (cal_satus == SIPE_CAL_OOF) ?
6662 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
6663 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
6665 /* key is <category><instance><container> */
6666 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6667 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6668 struct sipe_publication *publication_2 =
6669 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6670 struct sipe_publication *publication_3 =
6671 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6673 g_free(key_2);
6674 g_free(key_3);
6676 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
6677 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6678 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
6679 return NULL;
6682 if (event &&
6683 publication_3 &&
6684 (publication_3->availability == availability) &&
6685 !strcmp(publication_3->cal_event_hash, sipe_cal_event_hash(event)))
6687 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6688 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
6689 return NULL; /* nothing to update */
6692 if (event &&
6693 (event->cal_status == SIPE_CAL_BUSY ||
6694 event->cal_status == SIPE_CAL_OOF))
6696 gchar *availability_xml_str = NULL;
6697 gchar *activity_xml_str = NULL;
6699 if (event->cal_status == SIPE_CAL_BUSY) {
6700 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
6703 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
6704 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6705 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
6706 "minAvailability=\"6500\"",
6707 "maxAvailability=\"8999\"");
6708 } else if (event->cal_status == SIPE_CAL_OOF) {
6709 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6710 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
6711 "minAvailability=\"12000\"",
6712 "");
6714 start_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&event->start_time)));
6716 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
6717 instance,
6718 publication_2 ? publication_2->version : 0,
6719 uri,
6720 start_time_str,
6721 availability_xml_str ? availability_xml_str : "",
6722 activity_xml_str ? activity_xml_str : "",
6723 event->subject ? event->subject : "",
6724 event->location ? event->location : "",
6726 instance,
6727 publication_3 ? publication_3->version : 0,
6728 uri,
6729 start_time_str,
6730 availability_xml_str ? availability_xml_str : "",
6731 activity_xml_str ? activity_xml_str : "",
6732 event->subject ? event->subject : "",
6733 event->location ? event->location : ""
6735 g_free(start_time_str);
6736 g_free(availability_xml_str);
6737 g_free(activity_xml_str);
6740 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
6742 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
6743 instance,
6744 publication_2 ? publication_2->version : 0,
6746 instance,
6747 publication_3 ? publication_3->version : 0
6751 return res;
6755 * Returns 'machineState' XML part for publication.
6756 * Must be g_free'd after use.
6758 static gchar *
6759 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
6761 return sipe_publish_get_category_state(sip, FALSE);
6765 * Returns 'userState' XML part for publication.
6766 * Must be g_free'd after use.
6768 static gchar *
6769 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
6771 return sipe_publish_get_category_state(sip, TRUE);
6775 * Compares two strings even in case both are NULL/empty
6777 static gboolean
6778 sipe_is_equal(const char* n1, const char* n2) {
6779 return ((!n1 || !strlen(n1)) && (!n2 || !strlen(n2))) /* both empty */
6780 || (n1 && n2 && !strcmp(n1, n2)); /* or not empty and equal */
6784 * Returns 'note' XML part for publication.
6785 * Must be g_free'd after use.
6787 * @param note_type either personal or OOF
6789 static gchar *
6790 sipe_publish_get_category_note(struct sipe_account_data *sip,
6791 const char *note,
6792 const char *note_type)
6794 guint instance = !strcmp("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
6795 /* key is <category><instance><container> */
6796 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
6797 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
6798 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
6800 struct sipe_publication *publication_note_200 =
6801 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
6802 struct sipe_publication *publication_note_300 =
6803 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
6804 struct sipe_publication *publication_note_400 =
6805 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
6807 const char *n1 = note;
6808 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
6810 g_free(key_note_200);
6811 g_free(key_note_300);
6812 g_free(key_note_400);
6814 if (sipe_is_equal(n1, n2))
6816 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
6817 return NULL; /* nothing to update */
6820 return g_markup_printf_escaped(SIPE_PUB_XML_NOTE,
6821 instance,
6822 publication_note_200 ? publication_note_200->version : 0,
6823 note_type,
6824 note ? note : "",
6826 instance,
6827 publication_note_300 ? publication_note_300->version : 0,
6828 note_type,
6829 note ? note : "",
6831 instance,
6832 publication_note_400 ? publication_note_400->version : 0,
6833 note_type,
6834 note ? note : "");
6838 * Returns 'calendarData' XML part with WorkingHours for publication.
6839 * Must be g_free'd after use.
6841 static gchar *
6842 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
6844 struct sipe_ews* ews = sip->ews;
6846 /* key is <category><instance><container> */
6847 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
6848 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
6849 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
6850 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
6851 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
6852 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
6854 struct sipe_publication *publication_cal_1 =
6855 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
6856 struct sipe_publication *publication_cal_100 =
6857 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
6858 struct sipe_publication *publication_cal_200 =
6859 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
6860 struct sipe_publication *publication_cal_300 =
6861 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
6862 struct sipe_publication *publication_cal_400 =
6863 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
6864 struct sipe_publication *publication_cal_32000 =
6865 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
6867 const char *n1 = ews->working_hours_xml_str;
6868 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
6870 g_free(key_cal_1);
6871 g_free(key_cal_100);
6872 g_free(key_cal_200);
6873 g_free(key_cal_300);
6874 g_free(key_cal_400);
6875 g_free(key_cal_32000);
6877 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
6878 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
6879 return NULL;
6882 if (sipe_is_equal(n1, n2))
6884 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
6885 return NULL; /* nothing to update */
6888 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
6889 /* 1 */
6890 publication_cal_1 ? publication_cal_1->version : 0,
6891 ews->email,
6892 ews->working_hours_xml_str,
6893 /* 100 - Public */
6894 publication_cal_100 ? publication_cal_100->version : 0,
6895 /* 200 - Company */
6896 publication_cal_200 ? publication_cal_200->version : 0,
6897 ews->email,
6898 ews->working_hours_xml_str,
6899 /* 300 - Team */
6900 publication_cal_300 ? publication_cal_300->version : 0,
6901 ews->email,
6902 ews->working_hours_xml_str,
6903 /* 400 - Personal */
6904 publication_cal_400 ? publication_cal_400->version : 0,
6905 ews->email,
6906 ews->working_hours_xml_str,
6907 /* 32000 - Blocked */
6908 publication_cal_32000 ? publication_cal_32000->version : 0
6913 * Returns 'calendarData' XML part with FreeBusy for publication.
6914 * Must be g_free'd after use.
6916 static gchar *
6917 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
6919 struct sipe_ews* ews = sip->ews;
6920 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
6921 char *fb_start_str;
6922 char *free_busy_base64;
6923 const char *st;
6924 const char *fb;
6925 char *res;
6927 /* key is <category><instance><container> */
6928 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
6929 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
6930 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
6931 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
6932 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
6933 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
6935 struct sipe_publication *publication_cal_1 =
6936 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
6937 struct sipe_publication *publication_cal_100 =
6938 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
6939 struct sipe_publication *publication_cal_200 =
6940 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
6941 struct sipe_publication *publication_cal_300 =
6942 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
6943 struct sipe_publication *publication_cal_400 =
6944 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
6945 struct sipe_publication *publication_cal_32000 =
6946 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
6948 g_free(key_cal_1);
6949 g_free(key_cal_100);
6950 g_free(key_cal_200);
6951 g_free(key_cal_300);
6952 g_free(key_cal_400);
6953 g_free(key_cal_32000);
6955 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
6956 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
6957 return NULL;
6960 fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
6961 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6963 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
6964 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
6966 if (sipe_is_equal(st, fb_start_str) && sipe_is_equal(fb, free_busy_base64))
6968 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
6969 g_free(fb_start_str);
6970 g_free(free_busy_base64);
6971 return NULL; /* nothing to update */
6974 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
6975 /* 1 */
6976 cal_data_instance,
6977 publication_cal_1 ? publication_cal_1->version : 0,
6978 /* 100 - Public */
6979 cal_data_instance,
6980 publication_cal_100 ? publication_cal_100->version : 0,
6981 /* 200 - Company */
6982 cal_data_instance,
6983 publication_cal_200 ? publication_cal_200->version : 0,
6984 ews->email,
6985 fb_start_str,
6986 free_busy_base64,
6987 /* 300 - Team */
6988 cal_data_instance,
6989 publication_cal_300 ? publication_cal_300->version : 0,
6990 ews->email,
6991 fb_start_str,
6992 free_busy_base64,
6993 /* 400 - Personal */
6994 cal_data_instance,
6995 publication_cal_400 ? publication_cal_400->version : 0,
6996 ews->email,
6997 fb_start_str,
6998 free_busy_base64,
6999 /* 32000 - Blocked */
7000 cal_data_instance,
7001 publication_cal_32000 ? publication_cal_32000->version : 0
7004 g_free(fb_start_str);
7005 g_free(free_busy_base64);
7006 return res;
7009 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7011 gchar *uri;
7012 gchar *doc;
7013 gchar *tmp;
7014 gchar *hdr;
7016 uri = sip_uri_self(sip);
7017 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7018 uri,
7019 publications);
7021 tmp = get_contact(sip);
7022 hdr = g_strdup_printf("Contact: %s\r\n"
7023 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7025 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7027 g_free(tmp);
7028 g_free(hdr);
7029 g_free(uri);
7030 g_free(doc);
7033 static void
7034 send_publish_category_initial(struct sipe_account_data *sip)
7036 gchar *pub_device = sipe_publish_get_category_device(sip);
7037 gchar *pub_machine;
7038 gchar *publications;
7040 g_free(sip->status);
7041 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7043 pub_machine = sipe_publish_get_category_state_machine(sip);
7044 publications = g_strdup_printf("%s%s",
7045 pub_device,
7046 pub_machine ? pub_machine : "");
7047 g_free(pub_device);
7048 g_free(pub_machine);
7050 send_presence_publish(sip, publications);
7051 g_free(publications);
7054 static void
7055 send_presence_category_publish(struct sipe_account_data *sip,
7056 const char *note)
7058 gchar *pub_state = sipe_is_user_state(sip) ?
7059 sipe_publish_get_category_state_user(sip) :
7060 sipe_publish_get_category_state_machine(sip);
7061 gchar *pub_note = sipe_publish_get_category_note(sip, note, "personal");
7062 gchar *publications;
7064 if (!pub_state && !pub_note) {
7065 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7066 return;
7069 publications = g_strdup_printf("%s%s",
7070 pub_state ? pub_state : "",
7071 pub_note ? pub_note : "");
7073 purple_debug_info("sipe", "send_presence_category_publish: sip->status: %s sip->is_idle:%s sip->was_idle:%s\n",
7074 sip->status, sip->is_idle ? "Y" : "N", sip->was_idle ? "Y" : "N");
7076 g_free(pub_state);
7077 g_free(pub_note);
7079 send_presence_publish(sip, publications);
7080 g_free(publications);
7084 * Publishes self status
7085 * based on own calendar information.
7087 * For 2007+
7089 void
7090 publish_calendar_status_self(struct sipe_account_data *sip)
7092 struct sipe_cal_event* event = NULL;
7093 gchar *pub_cal_working_hours = NULL;
7094 gchar *pub_cal_free_busy = NULL;
7095 gchar *pub_calendar = NULL;
7096 gchar *pub_calendar2 = NULL;
7097 gchar *pub_oof_note = NULL;
7098 const gchar *oof_note;
7099 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7101 if (sip->ews && sip->ews->cal_events) {
7102 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7105 if (!event) {
7106 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7107 } else {
7108 char *desc = sipe_cal_event_describe(event);
7109 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7110 g_free(desc);
7113 /* Logic
7114 if OOF
7115 OOF publish, Busy clean
7116 ilse if Busy
7117 OOF clean, Busy publish
7118 else
7119 OOF clean, Busy clean
7121 if (event && event->cal_status == SIPE_CAL_OOF) {
7122 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7123 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7124 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7125 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7126 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7127 } else {
7128 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7129 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7132 if ((oof_note = sipe_ews_get_oof_note(sip->ews))) {
7133 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF");
7136 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7137 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7139 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7140 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7141 } else {
7142 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7143 pub_cal_working_hours ? pub_cal_working_hours : "",
7144 pub_cal_free_busy ? pub_cal_free_busy : "",
7145 pub_calendar ? pub_calendar : "",
7146 pub_calendar2 ? pub_calendar2 : "",
7147 pub_oof_note ? pub_oof_note : "");
7149 send_presence_publish(sip, publications);
7150 g_free(publications);
7153 g_free(pub_cal_working_hours);
7154 g_free(pub_cal_free_busy);
7155 g_free(pub_calendar);
7156 g_free(pub_calendar2);
7157 g_free(pub_oof_note);
7159 /* repeat scheduling */
7160 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7163 static void send_presence_status(struct sipe_account_data *sip)
7165 PurpleStatus * status = purple_account_get_active_status(sip->account);
7166 const gchar *note;
7167 if (!status) return;
7169 note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
7170 purple_debug_info("sipe", "send_presence_status: note: '%s'\n", note ? note : "");
7172 if (sip->ocs2007) {
7173 send_presence_category_publish(sip, note);
7174 } else {
7175 send_presence_soap(sip, FALSE);
7179 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7181 gboolean found = FALSE;
7182 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
7183 if (msg->response == 0) { /* request */
7184 if (!strcmp(msg->method, "MESSAGE")) {
7185 process_incoming_message(sip, msg);
7186 found = TRUE;
7187 } else if (!strcmp(msg->method, "NOTIFY")) {
7188 purple_debug_info("sipe","send->process_incoming_notify\n");
7189 process_incoming_notify(sip, msg, TRUE, FALSE);
7190 found = TRUE;
7191 } else if (!strcmp(msg->method, "BENOTIFY")) {
7192 purple_debug_info("sipe","send->process_incoming_benotify\n");
7193 process_incoming_notify(sip, msg, TRUE, TRUE);
7194 found = TRUE;
7195 } else if (!strcmp(msg->method, "INVITE")) {
7196 process_incoming_invite(sip, msg);
7197 found = TRUE;
7198 } else if (!strcmp(msg->method, "REFER")) {
7199 process_incoming_refer(sip, msg);
7200 found = TRUE;
7201 } else if (!strcmp(msg->method, "OPTIONS")) {
7202 process_incoming_options(sip, msg);
7203 found = TRUE;
7204 } else if (!strcmp(msg->method, "INFO")) {
7205 process_incoming_info(sip, msg);
7206 found = TRUE;
7207 } else if (!strcmp(msg->method, "ACK")) {
7208 // ACK's don't need any response
7209 found = TRUE;
7210 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
7211 // LCS 2005 sends us these - just respond 200 OK
7212 found = TRUE;
7213 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7214 } else if (!strcmp(msg->method, "BYE")) {
7215 process_incoming_bye(sip, msg);
7216 found = TRUE;
7217 } else {
7218 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7220 } else { /* response */
7221 struct transaction *trans = transactions_find(sip, msg);
7222 if (trans) {
7223 if (msg->response == 407) {
7224 gchar *resend, *auth, *ptmp;
7226 if (sip->proxy.retries > 30) return;
7227 sip->proxy.retries++;
7228 /* do proxy authentication */
7230 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7232 fill_auth(ptmp, &sip->proxy);
7233 auth = auth_header(sip, &sip->proxy, trans->msg);
7234 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7235 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7236 g_free(auth);
7237 resend = sipmsg_to_string(trans->msg);
7238 /* resend request */
7239 sendout_pkt(sip->gc, resend);
7240 g_free(resend);
7241 } else {
7242 if (msg->response < 200) {
7243 /* ignore provisional response */
7244 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7245 } else {
7246 sip->proxy.retries = 0;
7247 if (!strcmp(trans->msg->method, "REGISTER")) {
7248 if (msg->response == 401)
7250 sip->registrar.retries++;
7252 else
7254 sip->registrar.retries = 0;
7256 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7257 } else {
7258 if (msg->response == 401) {
7259 gchar *resend, *auth, *ptmp;
7261 if (sip->registrar.retries > 4) return;
7262 sip->registrar.retries++;
7264 #ifdef USE_KERBEROS
7265 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
7266 #endif
7267 ptmp = sipmsg_find_auth_header(msg, "NTLM");
7268 #ifdef USE_KERBEROS
7269 } else {
7270 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
7272 #endif
7274 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp);
7276 fill_auth(ptmp, &sip->registrar);
7277 auth = auth_header(sip, &sip->registrar, trans->msg);
7278 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7279 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7281 //sipmsg_remove_header_now(trans->msg, "Authorization");
7282 //sipmsg_add_header(trans->msg, "Authorization", auth);
7283 g_free(auth);
7284 resend = sipmsg_to_string(trans->msg);
7285 /* resend request */
7286 sendout_pkt(sip->gc, resend);
7287 g_free(resend);
7291 if (trans->callback) {
7292 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7293 /* call the callback to process response*/
7294 (trans->callback)(sip, msg, trans);
7297 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7298 transactions_remove(sip, trans);
7302 found = TRUE;
7303 } else {
7304 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7307 if (!found) {
7308 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
7312 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7314 char *cur;
7315 char *dummy;
7316 char *tmp;
7317 struct sipmsg *msg;
7318 int restlen;
7319 cur = conn->inbuf;
7321 /* according to the RFC remove CRLF at the beginning */
7322 while (*cur == '\r' || *cur == '\n') {
7323 cur++;
7325 if (cur != conn->inbuf) {
7326 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7327 conn->inbufused = strlen(conn->inbuf);
7330 /* Received a full Header? */
7331 sip->processing_input = TRUE;
7332 while (sip->processing_input &&
7333 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7334 time_t currtime = time(NULL);
7335 cur += 2;
7336 cur[0] = '\0';
7337 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7338 g_free(tmp);
7339 msg = sipmsg_parse_header(conn->inbuf);
7340 cur[0] = '\r';
7341 cur += 2;
7342 restlen = conn->inbufused - (cur - conn->inbuf);
7343 if (msg && restlen >= msg->bodylen) {
7344 dummy = g_malloc(msg->bodylen + 1);
7345 memcpy(dummy, cur, msg->bodylen);
7346 dummy[msg->bodylen] = '\0';
7347 msg->body = dummy;
7348 cur += msg->bodylen;
7349 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7350 conn->inbufused = strlen(conn->inbuf);
7351 } else {
7352 if (msg){
7353 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7354 sipmsg_free(msg);
7356 return;
7359 /*if (msg->body) {
7360 purple_debug_info("sipe", "body:\n%s", msg->body);
7363 // Verify the signature before processing it
7364 if (sip->registrar.gssapi_context) {
7365 struct sipmsg_breakdown msgbd;
7366 gchar *signature_input_str;
7367 gchar *rspauth;
7368 msgbd.msg = msg;
7369 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7370 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
7372 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7374 if (rspauth != NULL) {
7375 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7376 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7377 process_input_message(sip, msg);
7378 } else {
7379 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7380 purple_connection_error(sip->gc, _("Invalid message signature received"));
7381 sip->gc->wants_to_die = TRUE;
7383 } else if (msg->response == 401) {
7384 purple_connection_error(sip->gc, _("Wrong password"));
7385 sip->gc->wants_to_die = TRUE;
7387 g_free(signature_input_str);
7389 g_free(rspauth);
7390 sipmsg_breakdown_free(&msgbd);
7391 } else {
7392 process_input_message(sip, msg);
7395 sipmsg_free(msg);
7399 static void sipe_udp_process(gpointer data, gint source,
7400 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
7402 PurpleConnection *gc = data;
7403 struct sipe_account_data *sip = gc->proto_data;
7404 struct sipmsg *msg;
7405 int len;
7406 time_t currtime;
7408 static char buffer[65536];
7409 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
7410 buffer[len] = '\0';
7411 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
7412 msg = sipmsg_parse_msg(buffer);
7413 if (msg) process_input_message(sip, msg);
7417 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
7419 struct sipe_account_data *sip = gc->proto_data;
7420 PurpleSslConnection *gsc = sip->gsc;
7422 purple_debug_error("sipe", "%s",debug);
7423 purple_connection_error(gc, msg);
7425 /* Invalidate this connection. Next send will open a new one */
7426 if (gsc) {
7427 connection_remove(sip, gsc->fd);
7428 purple_ssl_close(gsc);
7430 sip->gsc = NULL;
7431 sip->fd = -1;
7434 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7435 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7437 PurpleConnection *gc = data;
7438 struct sipe_account_data *sip;
7439 struct sip_connection *conn;
7440 int readlen, len;
7441 gboolean firstread = TRUE;
7443 /* NOTE: This check *IS* necessary */
7444 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
7445 purple_ssl_close(gsc);
7446 return;
7449 sip = gc->proto_data;
7450 conn = connection_find(sip, gsc->fd);
7451 if (conn == NULL) {
7452 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7453 gc->wants_to_die = TRUE;
7454 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
7455 return;
7458 /* Read all available data from the SSL connection */
7459 do {
7460 /* Increase input buffer size as needed */
7461 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7462 conn->inbuflen += SIMPLE_BUF_INC;
7463 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7464 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
7467 /* Try to read as much as there is space left in the buffer */
7468 readlen = conn->inbuflen - conn->inbufused - 1;
7469 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
7471 if (len < 0 && errno == EAGAIN) {
7472 /* Try again later */
7473 return;
7474 } else if (len < 0) {
7475 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
7476 return;
7477 } else if (firstread && (len == 0)) {
7478 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
7479 return;
7482 conn->inbufused += len;
7483 firstread = FALSE;
7485 /* Equivalence indicates that there is possibly more data to read */
7486 } while (len == readlen);
7488 conn->inbuf[conn->inbufused] = '\0';
7489 process_input(sip, conn);
7493 static void sipe_input_cb(gpointer data, gint source,
7494 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7496 PurpleConnection *gc = data;
7497 struct sipe_account_data *sip = gc->proto_data;
7498 int len;
7499 struct sip_connection *conn = connection_find(sip, source);
7500 if (!conn) {
7501 purple_debug_error("sipe", "Connection not found!\n");
7502 return;
7505 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7506 conn->inbuflen += SIMPLE_BUF_INC;
7507 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7510 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
7512 if (len < 0 && errno == EAGAIN)
7513 return;
7514 else if (len <= 0) {
7515 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7516 connection_remove(sip, source);
7517 if (sip->fd == source) sip->fd = -1;
7518 return;
7521 conn->inbufused += len;
7522 conn->inbuf[conn->inbufused] = '\0';
7524 process_input(sip, conn);
7527 /* Callback for new connections on incoming TCP port */
7528 static void sipe_newconn_cb(gpointer data, gint source,
7529 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7531 PurpleConnection *gc = data;
7532 struct sipe_account_data *sip = gc->proto_data;
7533 struct sip_connection *conn;
7535 int newfd = accept(source, NULL, NULL);
7537 conn = connection_create(sip, newfd);
7539 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7542 static void login_cb(gpointer data, gint source,
7543 SIPE_UNUSED_PARAMETER const gchar *error_message)
7545 PurpleConnection *gc = data;
7546 struct sipe_account_data *sip;
7547 struct sip_connection *conn;
7549 if (!PURPLE_CONNECTION_IS_VALID(gc))
7551 if (source >= 0)
7552 close(source);
7553 return;
7556 if (source < 0) {
7557 purple_connection_error(gc, _("Could not connect"));
7558 return;
7561 sip = gc->proto_data;
7562 sip->fd = source;
7563 sip->last_keepalive = time(NULL);
7565 conn = connection_create(sip, source);
7567 do_register(sip);
7569 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7572 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7573 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7575 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
7576 if (sip == NULL) return;
7578 do_register(sip);
7581 static guint sipe_ht_hash_nick(const char *nick)
7583 char *lc = g_utf8_strdown(nick, -1);
7584 guint bucket = g_str_hash(lc);
7585 g_free(lc);
7587 return bucket;
7590 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
7592 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
7595 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
7597 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7599 sip->listen_data = NULL;
7601 if (listenfd == -1) {
7602 purple_connection_error(sip->gc, _("Could not create listen socket"));
7603 return;
7606 sip->fd = listenfd;
7608 sip->listenport = purple_network_get_port_from_fd(sip->fd);
7609 sip->listenfd = sip->fd;
7611 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
7613 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
7614 do_register(sip);
7617 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
7618 SIPE_UNUSED_PARAMETER const char *error_message)
7620 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7622 sip->query_data = NULL;
7624 if (!hosts || !hosts->data) {
7625 purple_connection_error(sip->gc, _("Could not resolve hostname"));
7626 return;
7629 hosts = g_slist_remove(hosts, hosts->data);
7630 g_free(sip->serveraddr);
7631 sip->serveraddr = hosts->data;
7632 hosts = g_slist_remove(hosts, hosts->data);
7633 while (hosts) {
7634 hosts = g_slist_remove(hosts, hosts->data);
7635 g_free(hosts->data);
7636 hosts = g_slist_remove(hosts, hosts->data);
7639 /* create socket for incoming connections */
7640 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
7641 sipe_udp_host_resolved_listen_cb, sip);
7642 if (sip->listen_data == NULL) {
7643 purple_connection_error(sip->gc, _("Could not create listen socket"));
7644 return;
7648 static const struct sipe_service_data *current_service = NULL;
7650 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
7651 PurpleSslErrorType error,
7652 gpointer data)
7654 PurpleConnection *gc = data;
7655 struct sipe_account_data *sip;
7657 /* If the connection is already disconnected, we don't need to do anything else */
7658 if (!PURPLE_CONNECTION_IS_VALID(gc))
7659 return;
7661 sip = gc->proto_data;
7662 current_service = sip->service_data;
7663 if (current_service) {
7664 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
7665 current_service->transport ? current_service->transport : "NULL",
7666 current_service->service ? current_service->service : "NULL");
7669 sip->fd = -1;
7670 sip->gsc = NULL;
7672 switch(error) {
7673 case PURPLE_SSL_CONNECT_FAILED:
7674 purple_connection_error(gc, _("Connection failed"));
7675 break;
7676 case PURPLE_SSL_HANDSHAKE_FAILED:
7677 purple_connection_error(gc, _("SSL handshake failed"));
7678 break;
7679 case PURPLE_SSL_CERTIFICATE_INVALID:
7680 purple_connection_error(gc, _("SSL certificate invalid"));
7681 break;
7685 static void
7686 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
7688 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7689 PurpleProxyConnectData *connect_data;
7691 sip->listen_data = NULL;
7693 sip->listenfd = listenfd;
7694 if (sip->listenfd == -1) {
7695 purple_connection_error(sip->gc, _("Could not create listen socket"));
7696 return;
7699 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
7700 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7701 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7702 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
7703 sipe_newconn_cb, sip->gc);
7704 purple_debug_info("sipe", "connecting to %s port %d\n",
7705 sip->realhostname, sip->realport);
7706 /* open tcp connection to the server */
7707 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
7708 sip->realport, login_cb, sip->gc);
7710 if (connect_data == NULL) {
7711 purple_connection_error(sip->gc, _("Could not create socket"));
7715 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
7717 PurpleAccount *account = sip->account;
7718 PurpleConnection *gc = sip->gc;
7720 if (port == 0) {
7721 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
7724 sip->realhostname = hostname;
7725 sip->realport = port;
7727 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
7728 hostname, port);
7730 /* TODO: is there a good default grow size? */
7731 if (sip->transport != SIPE_TRANSPORT_UDP)
7732 sip->txbuf = purple_circ_buffer_new(0);
7734 if (sip->transport == SIPE_TRANSPORT_TLS) {
7735 /* SSL case */
7736 if (!purple_ssl_is_supported()) {
7737 gc->wants_to_die = TRUE;
7738 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
7739 return;
7742 purple_debug_info("sipe", "using SSL\n");
7744 sip->gsc = purple_ssl_connect(account, hostname, port,
7745 login_cb_ssl, sipe_ssl_connect_failure, gc);
7746 if (sip->gsc == NULL) {
7747 purple_connection_error(gc, _("Could not create SSL context"));
7748 return;
7750 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
7751 /* UDP case */
7752 purple_debug_info("sipe", "using UDP\n");
7754 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
7755 if (sip->query_data == NULL) {
7756 purple_connection_error(gc, _("Could not resolve hostname"));
7758 } else {
7759 /* TCP case */
7760 purple_debug_info("sipe", "using TCP\n");
7761 /* create socket for incoming connections */
7762 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
7763 sipe_tcp_connect_listen_cb, sip);
7764 if (sip->listen_data == NULL) {
7765 purple_connection_error(gc, _("Could not create listen socket"));
7766 return;
7771 /* Service list for autodection */
7772 static const struct sipe_service_data service_autodetect[] = {
7773 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
7774 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
7775 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
7776 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
7777 { NULL, NULL, 0 }
7780 /* Service list for SSL/TLS */
7781 static const struct sipe_service_data service_tls[] = {
7782 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
7783 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
7784 { NULL, NULL, 0 }
7787 /* Service list for TCP */
7788 static const struct sipe_service_data service_tcp[] = {
7789 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
7790 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
7791 { NULL, NULL, 0 }
7794 /* Service list for UDP */
7795 static const struct sipe_service_data service_udp[] = {
7796 { "sip", "udp", SIPE_TRANSPORT_UDP },
7797 { NULL, NULL, 0 }
7800 static void srvresolved(PurpleSrvResponse *, int, gpointer);
7801 static void resolve_next_service(struct sipe_account_data *sip,
7802 const struct sipe_service_data *start)
7804 if (start) {
7805 sip->service_data = start;
7806 } else {
7807 sip->service_data++;
7808 if (sip->service_data->service == NULL) {
7809 gchar *hostname;
7810 /* Try connecting to the SIP hostname directly */
7811 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
7812 if (sip->auto_transport) {
7813 // If SSL is supported, default to using it; OCS servers aren't configured
7814 // by default to accept TCP
7815 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
7816 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
7817 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
7820 hostname = g_strdup(sip->sipdomain);
7821 create_connection(sip, hostname, 0);
7822 return;
7826 /* Try to resolve next service */
7827 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
7828 sip->service_data->transport,
7829 sip->sipdomain,
7830 srvresolved, sip);
7833 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
7835 struct sipe_account_data *sip = data;
7837 sip->srv_query_data = NULL;
7839 /* find the host to connect to */
7840 if (results) {
7841 gchar *hostname = g_strdup(resp->hostname);
7842 int port = resp->port;
7843 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
7844 hostname, port);
7845 g_free(resp);
7847 sip->transport = sip->service_data->type;
7849 create_connection(sip, hostname, port);
7850 } else {
7851 resolve_next_service(sip, NULL);
7855 static void sipe_login(PurpleAccount *account)
7857 PurpleConnection *gc;
7858 struct sipe_account_data *sip;
7859 gchar **signinname_login, **userserver;
7860 const char *transport;
7861 const char *email;
7863 const char *username = purple_account_get_username(account);
7864 gc = purple_account_get_connection(account);
7866 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
7868 if (strpbrk(username, "\t\v\r\n") != NULL) {
7869 gc->wants_to_die = TRUE;
7870 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
7871 return;
7874 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
7875 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
7876 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
7877 sip->gc = gc;
7878 sip->account = account;
7879 sip->reregister_set = FALSE;
7880 sip->reauthenticate_set = FALSE;
7881 sip->subscribed = FALSE;
7882 sip->subscribed_buddies = FALSE;
7883 sip->initial_state_published = FALSE;
7885 /* username format: <username>,[<optional login>] */
7886 signinname_login = g_strsplit(username, ",", 2);
7887 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
7889 /* ensure that username format is name@domain */
7890 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
7891 g_strfreev(signinname_login);
7892 gc->wants_to_die = TRUE;
7893 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
7894 return;
7896 sip->username = g_strdup(signinname_login[0]);
7898 /* ensure that email format is name@domain if provided */
7899 email = purple_account_get_string(sip->account, "email", NULL);
7900 if (!is_empty(email) &&
7901 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
7903 gc->wants_to_die = TRUE;
7904 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
7905 return;
7907 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
7909 /* login name specified? */
7910 if (signinname_login[1] && strlen(signinname_login[1])) {
7911 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
7912 gboolean has_domain = domain_user[1] != NULL;
7913 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
7914 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
7915 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
7916 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
7917 sip->authdomain ? sip->authdomain : "", sip->authuser);
7918 g_strfreev(domain_user);
7921 userserver = g_strsplit(signinname_login[0], "@", 2);
7922 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
7923 purple_connection_set_display_name(gc, userserver[0]);
7924 sip->sipdomain = g_strdup(userserver[1]);
7925 g_strfreev(userserver);
7926 g_strfreev(signinname_login);
7928 if (strchr(sip->username, ' ') != NULL) {
7929 gc->wants_to_die = TRUE;
7930 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
7931 return;
7934 sip->password = g_strdup(purple_connection_get_password(gc));
7936 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
7937 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
7938 g_free, (GDestroyNotify)g_hash_table_destroy);
7939 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
7940 g_free, (GDestroyNotify)sipe_subscription_free);
7942 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
7944 g_free(sip->status);
7945 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
7947 sip->auto_transport = FALSE;
7948 transport = purple_account_get_string(account, "transport", "auto");
7949 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
7950 if (userserver[0]) {
7951 /* Use user specified server[:port] */
7952 int port = 0;
7954 if (userserver[1])
7955 port = atoi(userserver[1]);
7957 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
7958 userserver[0], port);
7960 if (strcmp(transport, "auto") == 0) {
7961 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
7962 } else if (strcmp(transport, "tls") == 0) {
7963 sip->transport = SIPE_TRANSPORT_TLS;
7964 } else if (strcmp(transport, "tcp") == 0) {
7965 sip->transport = SIPE_TRANSPORT_TCP;
7966 } else {
7967 sip->transport = SIPE_TRANSPORT_UDP;
7970 create_connection(sip, g_strdup(userserver[0]), port);
7971 } else {
7972 /* Server auto-discovery */
7973 if (strcmp(transport, "auto") == 0) {
7974 sip->auto_transport = TRUE;
7975 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
7976 current_service++;
7977 resolve_next_service(sip, current_service);
7978 } else {
7979 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
7981 } else if (strcmp(transport, "tls") == 0) {
7982 resolve_next_service(sip, service_tls);
7983 } else if (strcmp(transport, "tcp") == 0) {
7984 resolve_next_service(sip, service_tcp);
7985 } else {
7986 resolve_next_service(sip, service_udp);
7989 g_strfreev(userserver);
7992 static void sipe_connection_cleanup(struct sipe_account_data *sip)
7994 connection_free_all(sip);
7996 g_free(sip->epid);
7997 sip->epid = NULL;
7999 if (sip->query_data != NULL)
8000 purple_dnsquery_destroy(sip->query_data);
8001 sip->query_data = NULL;
8003 if (sip->srv_query_data != NULL)
8004 purple_srv_cancel(sip->srv_query_data);
8005 sip->srv_query_data = NULL;
8007 if (sip->listen_data != NULL)
8008 purple_network_listen_cancel(sip->listen_data);
8009 sip->listen_data = NULL;
8011 if (sip->gsc != NULL)
8012 purple_ssl_close(sip->gsc);
8013 sip->gsc = NULL;
8015 sipe_auth_free(&sip->registrar);
8016 sipe_auth_free(&sip->proxy);
8018 if (sip->txbuf)
8019 purple_circ_buffer_destroy(sip->txbuf);
8020 sip->txbuf = NULL;
8022 g_free(sip->realhostname);
8023 sip->realhostname = NULL;
8025 g_free(sip->server_version);
8026 sip->server_version = NULL;
8028 if (sip->listenpa)
8029 purple_input_remove(sip->listenpa);
8030 sip->listenpa = 0;
8031 if (sip->tx_handler)
8032 purple_input_remove(sip->tx_handler);
8033 sip->tx_handler = 0;
8034 if (sip->resendtimeout)
8035 purple_timeout_remove(sip->resendtimeout);
8036 sip->resendtimeout = 0;
8037 if (sip->timeouts) {
8038 GSList *entry = sip->timeouts;
8039 while (entry) {
8040 struct scheduled_action *sched_action = entry->data;
8041 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8042 purple_timeout_remove(sched_action->timeout_handler);
8043 if (sched_action->destroy) {
8044 (*sched_action->destroy)(sched_action->payload);
8046 g_free(sched_action->name);
8047 g_free(sched_action);
8048 entry = entry->next;
8051 g_slist_free(sip->timeouts);
8053 if (sip->allow_events) {
8054 GSList *entry = sip->allow_events;
8055 while (entry) {
8056 g_free(entry->data);
8057 entry = entry->next;
8060 g_slist_free(sip->allow_events);
8062 if (sip->containers) {
8063 GSList *entry = sip->containers;
8064 while (entry) {
8065 free_container((struct sipe_container *)entry->data);
8066 entry = entry->next;
8069 g_slist_free(sip->containers);
8071 if (sip->contact)
8072 g_free(sip->contact);
8073 sip->contact = NULL;
8074 if (sip->regcallid)
8075 g_free(sip->regcallid);
8076 sip->regcallid = NULL;
8078 if (sip->serveraddr)
8079 g_free(sip->serveraddr);
8080 sip->serveraddr = NULL;
8082 if (sip->focus_factory_uri)
8083 g_free(sip->focus_factory_uri);
8084 sip->focus_factory_uri = NULL;
8086 sip->fd = -1;
8087 sip->processing_input = FALSE;
8089 if (sip->ews) {
8090 sipe_ews_free(sip->ews);
8092 sip->ews = NULL;
8096 * A callback for g_hash_table_foreach_remove
8098 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8099 SIPE_UNUSED_PARAMETER gpointer user_data)
8101 sipe_free_buddy((struct sipe_buddy *) buddy);
8103 /* We must return TRUE as the key/value have already been deleted */
8104 return(TRUE);
8107 static void sipe_close(PurpleConnection *gc)
8109 struct sipe_account_data *sip = gc->proto_data;
8111 if (sip) {
8112 /* leave all conversations */
8113 sipe_session_close_all(sip);
8114 sipe_session_remove_all(sip);
8116 if (sip->csta) {
8117 sip_csta_close(sip);
8120 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8121 /* unsubscribe all */
8122 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8124 /* unregister */
8125 do_register_exp(sip, 0);
8128 sipe_connection_cleanup(sip);
8129 g_free(sip->sipdomain);
8130 g_free(sip->username);
8131 g_free(sip->email);
8132 g_free(sip->password);
8133 g_free(sip->authdomain);
8134 g_free(sip->authuser);
8135 g_free(sip->status);
8136 g_free(sip->note);
8138 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8139 g_hash_table_destroy(sip->buddies);
8140 g_hash_table_destroy(sip->our_publications);
8141 g_hash_table_destroy(sip->user_state_publications);
8142 g_hash_table_destroy(sip->subscriptions);
8144 if (sip->groups) {
8145 GSList *entry = sip->groups;
8146 while (entry) {
8147 struct sipe_group *group = entry->data;
8148 g_free(group->name);
8149 g_free(group);
8150 entry = entry->next;
8153 g_slist_free(sip->groups);
8155 if (sip->our_publication_keys) {
8156 GSList *entry = sip->our_publication_keys;
8157 while (entry) {
8158 g_free(entry->data);
8159 entry = entry->next;
8162 g_slist_free(sip->our_publication_keys);
8164 while (sip->transactions)
8165 transactions_remove(sip, sip->transactions->data);
8167 g_free(gc->proto_data);
8168 gc->proto_data = NULL;
8171 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8172 SIPE_UNUSED_PARAMETER void *user_data)
8174 PurpleAccount *acct = purple_connection_get_account(gc);
8175 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8176 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8177 if (conv == NULL)
8178 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8179 purple_conversation_present(conv);
8180 g_free(id);
8183 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8184 SIPE_UNUSED_PARAMETER void *user_data)
8187 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8188 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8191 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8192 SIPE_UNUSED_PARAMETER struct transaction *trans)
8194 PurpleNotifySearchResults *results;
8195 PurpleNotifySearchColumn *column;
8196 xmlnode *searchResults;
8197 xmlnode *mrow;
8198 int match_count = 0;
8199 gboolean more = FALSE;
8200 gchar *secondary;
8202 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8204 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8205 if (!searchResults) {
8206 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8207 return FALSE;
8210 results = purple_notify_searchresults_new();
8212 if (results == NULL) {
8213 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8214 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8216 xmlnode_free(searchResults);
8217 return FALSE;
8220 column = purple_notify_searchresults_column_new(_("User name"));
8221 purple_notify_searchresults_column_add(results, column);
8223 column = purple_notify_searchresults_column_new(_("Name"));
8224 purple_notify_searchresults_column_add(results, column);
8226 column = purple_notify_searchresults_column_new(_("Company"));
8227 purple_notify_searchresults_column_add(results, column);
8229 column = purple_notify_searchresults_column_new(_("Country"));
8230 purple_notify_searchresults_column_add(results, column);
8232 column = purple_notify_searchresults_column_new(_("Email"));
8233 purple_notify_searchresults_column_add(results, column);
8235 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8236 GList *row = NULL;
8238 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8239 row = g_list_append(row, g_strdup(uri_parts[1]));
8240 g_strfreev(uri_parts);
8242 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8243 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8244 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8245 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8247 purple_notify_searchresults_row_add(results, row);
8248 match_count++;
8251 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8252 char *data = xmlnode_get_data_unescaped(mrow);
8253 more = (g_strcasecmp(data, "true") == 0);
8254 g_free(data);
8257 secondary = g_strdup_printf(
8258 dngettext(GETTEXT_PACKAGE,
8259 "Found %d contact%s:",
8260 "Found %d contacts%s:", match_count),
8261 match_count, more ? _(" (more matched your query)") : "");
8263 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8264 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8265 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8267 g_free(secondary);
8268 xmlnode_free(searchResults);
8269 return TRUE;
8272 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8274 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8275 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8276 unsigned i = 0;
8278 do {
8279 PurpleRequestField *field = entries->data;
8280 const char *id = purple_request_field_get_id(field);
8281 const char *value = purple_request_field_string_get_value(field);
8283 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8285 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8286 } while ((entries = g_list_next(entries)) != NULL);
8287 attrs[i] = NULL;
8289 if (i > 0) {
8290 struct sipe_account_data *sip = gc->proto_data;
8291 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8292 gchar *query = g_strjoinv(NULL, attrs);
8293 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8294 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8295 send_soap_request_with_cb(sip, domain_uri, body,
8296 (TransCallback) process_search_contact_response, NULL);
8297 g_free(domain_uri);
8298 g_free(body);
8299 g_free(query);
8302 g_strfreev(attrs);
8305 static void sipe_show_find_contact(PurplePluginAction *action)
8307 PurpleConnection *gc = (PurpleConnection *) action->context;
8308 PurpleRequestFields *fields;
8309 PurpleRequestFieldGroup *group;
8310 PurpleRequestField *field;
8312 fields = purple_request_fields_new();
8313 group = purple_request_field_group_new(NULL);
8314 purple_request_fields_add_group(fields, group);
8316 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8317 purple_request_field_group_add_field(group, field);
8318 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8319 purple_request_field_group_add_field(group, field);
8320 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8321 purple_request_field_group_add_field(group, field);
8322 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8323 purple_request_field_group_add_field(group, field);
8325 purple_request_fields(gc,
8326 _("Search"),
8327 _("Search for a contact"),
8328 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8329 fields,
8330 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8331 _("_Cancel"), NULL,
8332 purple_connection_get_account(gc), NULL, NULL, gc);
8335 static void sipe_show_about_plugin(PurplePluginAction *action)
8337 PurpleConnection *gc = (PurpleConnection *) action->context;
8338 const char *txt =
8339 "<b><font size=\"+1\">Sipe " SIPE_VERSION "</font></b><br/>"
8340 "<br/>"
8341 "A third-party plugin implementing extended version of SIP/SIMPLE used by various products:<br/>"
8342 "<li> - MS Office Communications Server 2007 (R2)</li><br/>"
8343 "<li> - MS Live Communications Server 2005/2003</li><br/>"
8344 "<li> - Reuters Messaging</li><br/>"
8345 "<br/>"
8346 "Home: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8347 "Support: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">Help Forum</a><br/>"
8348 "License: GPLv2<br/>"
8349 "<br/>"
8350 "We support users in the following organizations to mention a few:<br/>"
8351 " - CERN<br/>"
8352 " - Reuters Messaging network<br/>"
8353 " - Deutsche Bank<br/>"
8354 " - Merrill Lynch<br/>"
8355 " - Wachovia<br/>"
8356 " - Siemens<br/>"
8357 " - Alcatel-Lucent<br/>"
8358 " - BT<br/>"
8359 " - Nokia<br/>"
8360 " - HP<br/>"
8361 "<br/>"
8362 "<b>Authors:</b><br/>"
8363 " - Anibal Avelar<br/>"
8364 " - Gabriel Burt<br/>"
8365 " - Stefan Becker<br/>"
8366 " - pier11<br/>";
8368 purple_notify_formatted(gc, NULL, " ", NULL, txt, NULL, NULL);
8371 static void sipe_republish_calendar(PurplePluginAction *action)
8373 PurpleConnection *gc = (PurpleConnection *) action->context;
8374 struct sipe_account_data *sip = gc->proto_data;
8376 sipe_update_calendar(sip);
8379 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
8380 gpointer value,
8381 GString* str)
8383 struct sipe_publication *publication = value;
8385 g_string_append_printf( str,
8386 SIPE_PUB_XML_PUBLICATION_CLEAR,
8387 publication->category,
8388 publication->instance,
8389 publication->container,
8390 publication->version,
8391 "static");
8394 static void sipe_reset_status(PurplePluginAction *action)
8396 PurpleConnection *gc = (PurpleConnection *) action->context;
8397 struct sipe_account_data *sip = gc->proto_data;
8399 if (sip->ocs2007) /* 2007+ */
8401 GString* str = g_string_new(NULL);
8402 gchar *publications;
8404 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
8405 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8406 return;
8409 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
8410 publications = g_string_free(str, FALSE);
8412 send_presence_publish(sip, publications);
8413 g_free(publications);
8415 else /* 2005 */
8417 send_presence_soap0(sip, FALSE, TRUE);
8421 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
8422 gpointer context)
8424 PurpleConnection *gc = (PurpleConnection *)context;
8425 struct sipe_account_data *sip = gc->proto_data;
8426 GList *menu = NULL;
8427 PurplePluginAction *act;
8428 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
8430 act = purple_plugin_action_new(_("About SIPE plugin"), sipe_show_about_plugin);
8431 menu = g_list_prepend(menu, act);
8433 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
8434 menu = g_list_prepend(menu, act);
8436 if (!strcmp(calendar, "EXCH")) {
8437 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
8438 menu = g_list_prepend(menu, act);
8441 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
8442 menu = g_list_prepend(menu, act);
8444 menu = g_list_reverse(menu);
8446 return menu;
8449 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
8453 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8455 return TRUE;
8459 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8461 return TRUE;
8465 static char *sipe_status_text(PurpleBuddy *buddy)
8467 struct sipe_account_data *sip;
8468 struct sipe_buddy *sbuddy;
8469 char *text = NULL;
8471 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
8472 if (sip) //happens on pidgin exit
8474 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8475 if (sbuddy) {
8476 if (!is_empty(sbuddy->activity) && !is_empty(sbuddy->annotation))
8478 text = g_strdup_printf("%s - %s", sbuddy->activity, sbuddy->annotation);
8480 else if (!is_empty(sbuddy->activity))
8482 text = g_strdup(sbuddy->activity);
8484 else
8486 text = g_strdup(sbuddy->annotation);
8491 return text;
8494 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
8496 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8497 const PurpleStatus *status = purple_presence_get_active_status(presence);
8498 struct sipe_account_data *sip;
8499 struct sipe_buddy *sbuddy;
8500 char *annotation = NULL;
8501 gboolean is_oof_note = FALSE;
8502 char *activity = NULL;
8503 char *calendar = NULL;
8504 char *meeting_subject = NULL;
8505 char *meeting_location = NULL;
8507 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
8508 if (sip) //happens on pidgin exit
8510 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8511 if (sbuddy)
8513 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
8514 is_oof_note = sbuddy->is_oof_note;
8515 activity = sbuddy->activity;
8516 calendar = sipe_cal_get_description(sbuddy);
8517 meeting_subject = sbuddy->meeting_subject;
8518 meeting_location = sbuddy->meeting_location;
8522 //Layout
8523 if (purple_presence_is_online(presence))
8525 const char *status_str = activity && status && strcmp(purple_status_get_id(status), SIPE_STATUS_ID_ON_PHONE) ?
8526 activity :
8527 purple_status_get_name(status);
8529 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
8531 if (purple_presence_is_online(presence) &&
8532 !is_empty(calendar))
8534 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
8536 g_free(calendar);
8537 if (!is_empty(meeting_location))
8539 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
8541 if (!is_empty(meeting_subject))
8543 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
8546 if (annotation)
8548 /* Tooltip does not know how to handle markup like <br> */
8549 gchar *s = annotation;
8550 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, annotation);
8551 while ((s = strchr(s, '<')) != NULL) {
8552 if (!g_ascii_strncasecmp(s, "<br>", 4)) {
8553 *s = '\n';
8554 strcpy(s + 1, s + 4);
8556 s++;
8558 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, annotation);
8560 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), annotation);
8561 g_free(annotation);
8566 #if PURPLE_VERSION_CHECK(2,5,0)
8567 static GHashTable *
8568 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
8570 GHashTable *table;
8571 table = g_hash_table_new(g_str_hash, g_str_equal);
8572 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
8573 return table;
8575 #endif
8577 static PurpleBuddy *
8578 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
8580 PurpleBuddy *clone;
8581 const gchar *server_alias, *email;
8582 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
8584 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
8586 purple_blist_add_buddy(clone, NULL, group, NULL);
8588 server_alias = purple_buddy_get_server_alias(buddy);
8589 if (server_alias) {
8590 purple_blist_server_alias_buddy(clone, server_alias);
8593 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8594 if (email) {
8595 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
8598 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
8599 //for UI to update;
8600 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
8601 return clone;
8604 static void
8605 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
8607 PurpleBuddy *buddy, *b;
8608 PurpleConnection *gc;
8609 PurpleGroup * group = purple_find_group(group_name);
8611 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
8613 buddy = (PurpleBuddy *)node;
8615 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
8616 gc = purple_account_get_connection(buddy->account);
8618 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
8619 if (!b){
8620 b = purple_blist_add_buddy_clone(group, buddy);
8623 sipe_group_buddy(gc, buddy->name, NULL, group_name);
8626 static void
8627 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
8629 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8631 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
8633 /* 2007+ conference */
8634 if (sip->ocs2007)
8636 sipe_conf_add(sip, buddy->name);
8638 else /* 2005- multiparty chat */
8640 gchar *self = sip_uri_self(sip);
8641 struct sip_session *session;
8643 session = sipe_session_add_chat(sip);
8644 session->chat_title = sipe_chat_get_name(session->callid);
8645 session->roster_manager = g_strdup(self);
8647 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
8648 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
8649 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
8650 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
8652 g_free(self);
8656 static gboolean
8657 sipe_is_election_finished(struct sip_session *session)
8659 gboolean res = TRUE;
8661 SIPE_DIALOG_FOREACH {
8662 if (dialog->election_vote == 0) {
8663 res = FALSE;
8664 break;
8666 } SIPE_DIALOG_FOREACH_END;
8668 if (res) {
8669 session->is_voting_in_progress = FALSE;
8671 return res;
8674 static void
8675 sipe_election_start(struct sipe_account_data *sip,
8676 struct sip_session *session)
8678 int election_timeout;
8680 if (session->is_voting_in_progress) {
8681 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
8682 return;
8683 } else {
8684 session->is_voting_in_progress = TRUE;
8686 session->bid = rand();
8688 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
8690 SIPE_DIALOG_FOREACH {
8691 /* reset election_vote for each chat participant */
8692 dialog->election_vote = 0;
8694 /* send RequestRM to each chat participant*/
8695 sipe_send_election_request_rm(sip, dialog, session->bid);
8696 } SIPE_DIALOG_FOREACH_END;
8698 election_timeout = 15; /* sec */
8699 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
8703 * @param who a URI to whom to invite to chat
8705 void
8706 sipe_invite_to_chat(struct sipe_account_data *sip,
8707 struct sip_session *session,
8708 const gchar *who)
8710 /* a conference */
8711 if (session->focus_uri)
8713 sipe_invite_conf(sip, session, who);
8715 else /* a multi-party chat */
8717 gchar *self = sip_uri_self(sip);
8718 if (session->roster_manager) {
8719 if (!strcmp(session->roster_manager, self)) {
8720 sipe_invite(sip, session, who, NULL, NULL, FALSE);
8721 } else {
8722 sipe_refer(sip, session, who);
8724 } else {
8725 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
8727 session->pending_invite_queue = slist_insert_unique_sorted(
8728 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
8730 sipe_election_start(sip, session);
8732 g_free(self);
8736 void
8737 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
8738 struct sip_session *session)
8740 gchar *invitee;
8741 GSList *entry = session->pending_invite_queue;
8743 while (entry) {
8744 invitee = entry->data;
8745 sipe_invite_to_chat(sip, session, invitee);
8746 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
8747 g_free(invitee);
8751 static void
8752 sipe_election_result(struct sipe_account_data *sip,
8753 void *sess)
8755 struct sip_session *session = (struct sip_session *)sess;
8756 gchar *rival;
8757 gboolean has_won = TRUE;
8759 if (session->roster_manager) {
8760 purple_debug_info("sipe",
8761 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
8762 return;
8765 session->is_voting_in_progress = FALSE;
8767 SIPE_DIALOG_FOREACH {
8768 if (dialog->election_vote < 0) {
8769 has_won = FALSE;
8770 rival = dialog->with;
8771 break;
8773 } SIPE_DIALOG_FOREACH_END;
8775 if (has_won) {
8776 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
8778 session->roster_manager = sip_uri_self(sip);
8780 SIPE_DIALOG_FOREACH {
8781 /* send SetRM to each chat participant*/
8782 sipe_send_election_set_rm(sip, dialog);
8783 } SIPE_DIALOG_FOREACH_END;
8784 } else {
8785 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
8787 session->bid = 0;
8789 sipe_process_pending_invite_queue(sip, session);
8793 * For 2007+ conference only.
8795 static void
8796 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
8798 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8799 struct sip_session *session;
8801 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
8802 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
8804 session = sipe_session_find_chat_by_title(sip, chat_title);
8806 sipe_conf_modify_user_role(sip, session, buddy->name);
8810 * For 2007+ conference only.
8812 static void
8813 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
8815 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8816 struct sip_session *session;
8818 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
8819 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
8821 session = sipe_session_find_chat_by_title(sip, chat_title);
8823 sipe_conf_delete_user(sip, session, buddy->name);
8826 static void
8827 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
8829 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8830 struct sip_session *session;
8832 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
8833 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
8835 session = sipe_session_find_chat_by_title(sip, chat_title);
8837 sipe_invite_to_chat(sip, session, buddy->name);
8840 static void
8841 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
8843 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8845 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
8846 if (phone) {
8847 char *tel_uri = sip_to_tel_uri(phone);
8849 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
8850 sip_csta_make_call(sip, tel_uri);
8852 g_free(tel_uri);
8856 static void
8857 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
8859 const gchar *email;
8860 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
8862 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8863 if (email)
8865 char *mailto = g_strdup_printf("mailto:%s", email);
8866 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
8867 #ifndef _WIN32
8869 pid_t pid;
8870 char *const parmList[] = {"xdg-email", mailto, NULL};
8871 if ((pid = fork()) == -1)
8873 purple_debug_info("sipe", "fork() error\n");
8875 else if (pid == 0)
8877 execvp(parmList[0], parmList);
8878 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
8881 #else
8883 BOOL ret;
8884 _flushall();
8885 errno = 0;
8886 //@TODO resolve env variable %WINDIR% first
8887 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
8888 if (errno)
8890 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
8893 #endif
8895 g_free(mailto);
8897 else
8899 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
8904 * A menu which appear when right-clicking on buddy in contact list.
8906 static GList *
8907 sipe_buddy_menu(PurpleBuddy *buddy)
8909 PurpleBlistNode *g_node;
8910 PurpleGroup *group, *gr_parent;
8911 PurpleMenuAction *act;
8912 GList *menu = NULL;
8913 GList *menu_groups = NULL;
8914 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8915 const char *email;
8916 const char *phone;
8917 const char *phone_disp_str;
8918 gchar *self = sip_uri_self(sip);
8920 SIPE_SESSION_FOREACH {
8921 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
8923 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
8925 PurpleConvChatBuddyFlags flags;
8926 PurpleConvChatBuddyFlags flags_us;
8928 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
8929 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
8930 if (session->focus_uri
8931 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
8932 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
8934 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
8935 act = purple_menu_action_new(label,
8936 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
8937 session->chat_title, NULL);
8938 g_free(label);
8939 menu = g_list_prepend(menu, act);
8942 if (session->focus_uri
8943 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
8945 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
8946 act = purple_menu_action_new(label,
8947 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
8948 session->chat_title, NULL);
8949 g_free(label);
8950 menu = g_list_prepend(menu, act);
8953 else
8955 if (!session->focus_uri
8956 || (session->focus_uri && !session->locked))
8958 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
8959 act = purple_menu_action_new(label,
8960 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
8961 session->chat_title, NULL);
8962 g_free(label);
8963 menu = g_list_prepend(menu, act);
8967 } SIPE_SESSION_FOREACH_END;
8969 act = purple_menu_action_new(_("New chat"),
8970 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
8971 NULL, NULL);
8972 menu = g_list_prepend(menu, act);
8974 if (sip->csta && !sip->csta->line_status) {
8975 gchar *tmp = NULL;
8976 /* work phone */
8977 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
8978 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
8979 if (phone) {
8980 gchar *label = g_strdup_printf(_("Work %s"),
8981 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8982 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8983 g_free(tmp);
8984 tmp = NULL;
8985 g_free(label);
8986 menu = g_list_prepend(menu, act);
8989 /* mobile phone */
8990 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
8991 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
8992 if (phone) {
8993 gchar *label = g_strdup_printf(_("Mobile %s"),
8994 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8995 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8996 g_free(tmp);
8997 tmp = NULL;
8998 g_free(label);
8999 menu = g_list_prepend(menu, act);
9002 /* home phone */
9003 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9004 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9005 if (phone) {
9006 gchar *label = g_strdup_printf(_("Home %s"),
9007 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9008 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9009 g_free(tmp);
9010 tmp = NULL;
9011 g_free(label);
9012 menu = g_list_prepend(menu, act);
9015 /* other phone */
9016 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9017 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9018 if (phone) {
9019 gchar *label = g_strdup_printf(_("Other %s"),
9020 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9021 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9022 g_free(tmp);
9023 tmp = NULL;
9024 g_free(label);
9025 menu = g_list_prepend(menu, act);
9028 /* custom1 phone */
9029 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9030 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9031 if (phone) {
9032 gchar *label = g_strdup_printf(_("Custom1 %s"),
9033 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9034 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9035 g_free(tmp);
9036 tmp = NULL;
9037 g_free(label);
9038 menu = g_list_prepend(menu, act);
9042 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9043 if (email) {
9044 act = purple_menu_action_new(_("Send email..."),
9045 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9046 NULL, NULL);
9047 menu = g_list_prepend(menu, act);
9050 gr_parent = purple_buddy_get_group(buddy);
9051 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9052 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9053 continue;
9055 group = (PurpleGroup *)g_node;
9056 if (group == gr_parent)
9057 continue;
9059 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9060 continue;
9062 act = purple_menu_action_new(purple_group_get_name(group),
9063 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9064 group->name, NULL);
9065 menu_groups = g_list_prepend(menu_groups, act);
9067 menu_groups = g_list_reverse(menu_groups);
9069 act = purple_menu_action_new(_("Copy to"),
9070 NULL,
9071 NULL, menu_groups);
9072 menu = g_list_prepend(menu, act);
9073 menu = g_list_reverse(menu);
9075 g_free(self);
9076 return menu;
9079 static void
9080 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9082 struct sipe_account_data *sip = chat->account->gc->proto_data;
9083 struct sip_session *session;
9085 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9086 sipe_conf_modify_conference_lock(sip, session, locked);
9089 static void
9090 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9092 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9093 sipe_conf_modify_lock(chat, FALSE);
9096 static void
9097 sipe_chat_menu_lock_cb(PurpleChat *chat)
9099 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9100 sipe_conf_modify_lock(chat, TRUE);
9103 static GList *
9104 sipe_chat_menu(PurpleChat *chat)
9106 PurpleMenuAction *act;
9107 PurpleConvChatBuddyFlags flags_us;
9108 GList *menu = NULL;
9109 struct sipe_account_data *sip = chat->account->gc->proto_data;
9110 struct sip_session *session;
9111 gchar *self;
9113 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9114 if (!session) return NULL;
9116 self = sip_uri_self(sip);
9117 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9119 if (session->focus_uri
9120 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9122 if (session->locked) {
9123 act = purple_menu_action_new(_("Unlock"),
9124 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9125 NULL, NULL);
9126 menu = g_list_prepend(menu, act);
9127 } else {
9128 act = purple_menu_action_new(_("Lock"),
9129 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9130 NULL, NULL);
9131 menu = g_list_prepend(menu, act);
9135 menu = g_list_reverse(menu);
9137 g_free(self);
9138 return menu;
9141 static GList *
9142 sipe_blist_node_menu(PurpleBlistNode *node)
9144 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9145 return sipe_buddy_menu((PurpleBuddy *) node);
9146 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9147 return sipe_chat_menu((PurpleChat *)node);
9148 } else {
9149 return NULL;
9153 static gboolean
9154 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9156 gboolean ret = TRUE;
9157 char *uri = trans->payload->data;
9159 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
9160 PurpleBuddy *pbuddy;
9161 struct sipe_buddy *sbuddy;
9162 const char *alias;
9163 char *device_name = NULL;
9164 char *server_alias = NULL;
9165 char *phone_number = NULL;
9166 char *email = NULL;
9167 const char *site;
9169 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9171 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9172 alias = purple_buddy_get_local_alias(pbuddy);
9174 if (sip)
9176 //will query buddy UA's capabilities and send answer to log
9177 sipe_options_request(sip, uri);
9179 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9180 if (sbuddy)
9182 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9186 if (msg->response != 200) {
9187 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9188 } else {
9189 xmlnode *searchResults;
9190 xmlnode *mrow;
9192 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9193 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9194 if (!searchResults) {
9195 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9196 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9197 const char *value;
9198 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9199 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9200 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9202 /* For 2007 system we will take this from ContactCard -
9203 * it has cleaner tel: URIs at least
9205 if (!sip->ocs2007) {
9206 char *tel_uri = sip_to_tel_uri(phone_number);
9207 /* trims its parameters, so call first */
9208 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9209 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9210 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9211 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9212 g_free(tel_uri);
9215 if (server_alias && strlen(server_alias) > 0) {
9216 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9218 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9219 purple_notify_user_info_add_pair(info, _("Job title"), value);
9221 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9222 purple_notify_user_info_add_pair(info, _("Office"), value);
9224 if (phone_number && strlen(phone_number) > 0) {
9225 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9227 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9228 purple_notify_user_info_add_pair(info, _("Company"), value);
9230 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9231 purple_notify_user_info_add_pair(info, _("City"), value);
9233 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9234 purple_notify_user_info_add_pair(info, _("State"), value);
9236 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9237 purple_notify_user_info_add_pair(info, _("Country"), value);
9239 if (email && strlen(email) > 0) {
9240 purple_notify_user_info_add_pair(info, _("Email address"), email);
9244 xmlnode_free(searchResults);
9247 purple_notify_user_info_add_section_break(info);
9249 if (!server_alias || !strcmp("", server_alias)) {
9250 g_free(server_alias);
9251 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9252 if (server_alias) {
9253 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9257 /* present alias if it differs from server alias */
9258 if (alias && (!server_alias || strcmp(alias, server_alias)))
9260 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9263 if (!email || !strcmp("", email)) {
9264 g_free(email);
9265 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9266 if (email) {
9267 purple_notify_user_info_add_pair(info, _("Email address"), email);
9271 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9272 if (site) {
9273 purple_notify_user_info_add_pair(info, _("Site"), site);
9276 if (device_name) {
9277 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9280 /* show a buddy's user info in a nice dialog box */
9281 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9282 uri, /* buddy's URI */
9283 info, /* body */
9284 NULL, /* callback called when dialog closed */
9285 NULL); /* userdata for callback */
9287 g_free(phone_number);
9288 g_free(server_alias);
9289 g_free(email);
9290 g_free(device_name);
9292 return ret;
9296 * AD search first, LDAP based
9298 static void sipe_get_info(PurpleConnection *gc, const char *username)
9300 struct sipe_account_data *sip = gc->proto_data;
9301 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9302 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9303 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9304 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9306 payload->destroy = g_free;
9307 payload->data = g_strdup(username);
9309 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9310 send_soap_request_with_cb(sip, domain_uri, body,
9311 (TransCallback) process_get_info_response, payload);
9312 g_free(domain_uri);
9313 g_free(body);
9314 g_free(row);
9317 static PurplePlugin *my_protocol = NULL;
9319 static PurplePluginProtocolInfo prpl_info =
9321 OPT_PROTO_CHAT_TOPIC,
9322 NULL, /* user_splits */
9323 NULL, /* protocol_options */
9324 NO_BUDDY_ICONS, /* icon_spec */
9325 sipe_list_icon, /* list_icon */
9326 NULL, /* list_emblems */
9327 sipe_status_text, /* status_text */
9328 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9329 sipe_status_types, /* away_states */
9330 sipe_blist_node_menu, /* blist_node_menu */
9331 NULL, /* chat_info */
9332 NULL, /* chat_info_defaults */
9333 sipe_login, /* login */
9334 sipe_close, /* close */
9335 sipe_im_send, /* send_im */
9336 NULL, /* set_info */ // TODO maybe
9337 sipe_send_typing, /* send_typing */
9338 sipe_get_info, /* get_info */
9339 sipe_set_status, /* set_status */
9340 sipe_set_idle, /* set_idle */
9341 NULL, /* change_passwd */
9342 sipe_add_buddy, /* add_buddy */
9343 NULL, /* add_buddies */
9344 sipe_remove_buddy, /* remove_buddy */
9345 NULL, /* remove_buddies */
9346 sipe_add_permit, /* add_permit */
9347 sipe_add_deny, /* add_deny */
9348 sipe_add_deny, /* rem_permit */
9349 sipe_add_permit, /* rem_deny */
9350 dummy_permit_deny, /* set_permit_deny */
9351 NULL, /* join_chat */
9352 NULL, /* reject_chat */
9353 NULL, /* get_chat_name */
9354 sipe_chat_invite, /* chat_invite */
9355 sipe_chat_leave, /* chat_leave */
9356 NULL, /* chat_whisper */
9357 sipe_chat_send, /* chat_send */
9358 sipe_keep_alive, /* keepalive */
9359 NULL, /* register_user */
9360 NULL, /* get_cb_info */ // deprecated
9361 NULL, /* get_cb_away */ // deprecated
9362 sipe_alias_buddy, /* alias_buddy */
9363 sipe_group_buddy, /* group_buddy */
9364 sipe_rename_group, /* rename_group */
9365 NULL, /* buddy_free */
9366 sipe_convo_closed, /* convo_closed */
9367 purple_normalize_nocase, /* normalize */
9368 NULL, /* set_buddy_icon */
9369 sipe_remove_group, /* remove_group */
9370 NULL, /* get_cb_real_name */ // TODO?
9371 NULL, /* set_chat_topic */
9372 NULL, /* find_blist_chat */
9373 NULL, /* roomlist_get_list */
9374 NULL, /* roomlist_cancel */
9375 NULL, /* roomlist_expand_category */
9376 NULL, /* can_receive_file */
9377 NULL, /* send_file */
9378 NULL, /* new_xfer */
9379 NULL, /* offline_message */
9380 NULL, /* whiteboard_prpl_ops */
9381 sipe_send_raw, /* send_raw */
9382 NULL, /* roomlist_room_serialize */
9383 NULL, /* unregister_user */
9384 NULL, /* send_attention */
9385 NULL, /* get_attention_types */
9386 #if !PURPLE_VERSION_CHECK(2,5,0)
9387 /* Backward compatibility when compiling against 2.4.x API */
9388 (void (*)(void)) /* _purple_reserved4 */
9389 #endif
9390 sizeof(PurplePluginProtocolInfo), /* struct_size */
9391 #if PURPLE_VERSION_CHECK(2,5,0)
9392 sipe_get_account_text_table, /* get_account_text_table */
9393 #if PURPLE_VERSION_CHECK(2,6,0)
9394 NULL, /* initiate_media */
9395 NULL, /* get_media_caps */
9396 #endif
9397 #endif
9401 static PurplePluginInfo info = {
9402 PURPLE_PLUGIN_MAGIC,
9403 PURPLE_MAJOR_VERSION,
9404 PURPLE_MINOR_VERSION,
9405 PURPLE_PLUGIN_PROTOCOL, /**< type */
9406 NULL, /**< ui_requirement */
9407 0, /**< flags */
9408 NULL, /**< dependencies */
9409 PURPLE_PRIORITY_DEFAULT, /**< priority */
9410 "prpl-sipe", /**< id */
9411 "Office Communicator", /**< name */
9412 SIPE_VERSION, /**< version */
9413 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9414 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9415 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9416 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9417 "Gabriel Burt <gburt@novell.com>, " /**< author */
9418 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9419 "pier11 <pier11@operamail.com>", /**< author */
9420 "http://sipe.sourceforge.net/", /**< homepage */
9421 sipe_plugin_load, /**< load */
9422 sipe_plugin_unload, /**< unload */
9423 sipe_plugin_destroy, /**< destroy */
9424 NULL, /**< ui_info */
9425 &prpl_info, /**< extra_info */
9426 NULL,
9427 sipe_actions,
9428 NULL,
9429 NULL,
9430 NULL,
9431 NULL
9434 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9436 GList *entry;
9438 entry = prpl_info.protocol_options;
9439 while (entry) {
9440 purple_account_option_destroy(entry->data);
9441 entry = g_list_delete_link(entry, entry);
9443 prpl_info.protocol_options = NULL;
9445 entry = prpl_info.user_splits;
9446 while (entry) {
9447 purple_account_user_split_destroy(entry->data);
9448 entry = g_list_delete_link(entry, entry);
9450 prpl_info.user_splits = NULL;
9453 static void init_plugin(PurplePlugin *plugin)
9455 PurpleAccountUserSplit *split;
9456 PurpleAccountOption *option;
9458 srand(time(NULL));
9460 #ifdef ENABLE_NLS
9461 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
9462 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
9463 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
9464 textdomain(GETTEXT_PACKAGE);
9465 #endif
9467 purple_plugin_register(plugin);
9469 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
9470 purple_account_user_split_set_reverse(split, FALSE);
9471 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
9473 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
9474 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9476 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
9477 purple_account_option_add_list_item(option, _("Auto"), "auto");
9478 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
9479 purple_account_option_add_list_item(option, _("TCP"), "tcp");
9480 purple_account_option_add_list_item(option, _("UDP"), "udp");
9481 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9483 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
9484 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
9486 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
9487 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9489 #ifdef USE_KERBEROS
9490 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
9491 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9493 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
9494 * No login/password is taken into account if this option present,
9495 * instead used default credentials stored in OS.
9497 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
9498 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9499 #endif
9501 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
9502 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
9503 purple_account_option_add_list_item(option, _("None"), "NONE");
9504 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9506 /** Example: https://server.company.com/EWS/Exchange.asmx */
9507 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
9508 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9510 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
9511 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9513 /** Example: DOMAIN\user or user@company.com */
9514 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
9515 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9517 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
9518 purple_account_option_set_masked(option, TRUE);
9519 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9521 my_protocol = plugin;
9524 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
9527 Local Variables:
9528 mode: c
9529 c-file-style: "bsd"
9530 indent-tabs-mode: t
9531 tab-width: 8
9532 End: