better presentation of non-primary statuses
[siplcs.git] / src / core / sipe.c
blobd5b1bdca1588816a9e914b6d0e6ba5ac7881c73c
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 "savedstatuses.h"
65 #include "privacy.h"
66 #include "prpl.h"
67 #include "plugin.h"
68 #include "util.h"
69 #include "version.h"
70 #include "network.h"
71 #include "xmlnode.h"
72 #include "mime.h"
73 #include "core.h"
75 #include "sipe.h"
76 #include "sipe-cal.h"
77 #include "sipe-ews.h"
78 #include "sipe-chat.h"
79 #include "sipe-conf.h"
80 #include "sip-csta.h"
81 #include "sipe-dialog.h"
82 #include "sipe-nls.h"
83 #include "sipe-session.h"
84 #include "sipe-utils.h"
85 #include "sipmsg.h"
86 #include "sipe-sign.h"
87 #include "dnssrv.h"
88 #include "request.h"
90 /* Backward compatibility when compiling against 2.4.x API */
91 #if !PURPLE_VERSION_CHECK(2,5,0)
92 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
93 #endif
95 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
97 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
98 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
100 /* Keep in sync with sipe_transport_type! */
101 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
102 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
104 /* Status identifiers (see also: sipe_status_types()) */
105 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
106 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
107 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
108 /* PURPLE_STATUS_UNAVAILABLE: */
109 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
110 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
111 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
112 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
113 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
114 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
115 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
116 /* PURPLE_STATUS_AWAY: */
117 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
118 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
119 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
120 /** Reuters status (user settable) */
121 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
122 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
123 /* ??? PURPLE_STATUS_MOBILE */
124 /* ??? PURPLE_STATUS_TUNE */
126 /* Status attributes (see also sipe_status_types() */
127 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
129 static struct sipe_activity_map_struct
131 sipe_activity type;
132 const char *token;
133 const char *desc;
134 const char *status_id;
136 } const sipe_activity_map[] =
138 /* This has nothing to do with Availability numbers, like 3500 (online).
139 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
141 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
142 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
143 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
144 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
145 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("BusyIdle") , NULL },
146 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
147 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
148 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
149 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
150 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
151 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
152 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
153 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
154 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
155 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
157 /** @param x is sipe_activity */
158 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
161 /* Action name templates */
162 #define ACTION_NAME_PRESENCE "<presence><%s>"
164 static sipe_activity
165 sipe_get_activity_by_token(const char *token)
167 int i;
169 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
171 if (!strcmp(token, sipe_activity_map[i].token))
172 return sipe_activity_map[i].type;
175 return sipe_activity_map[0].type;
178 static const char *
179 sipe_get_activity_desc_by_token(const char *token)
181 if (!token) return NULL;
183 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
186 /** Allows to send typed messages from chat window again after account reinstantiation. */
187 static void
188 sipe_rejoin_chat(PurpleConversation *conv)
190 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
191 PURPLE_CONV_CHAT(conv)->left)
193 PURPLE_CONV_CHAT(conv)->left = FALSE;
194 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
198 static char *genbranch()
200 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
201 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
202 rand() & 0xFFFF, rand() & 0xFFFF);
206 static char *default_ua = NULL;
207 static const char*
208 sipe_get_useragent(struct sipe_account_data *sip)
210 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
211 if (is_empty(useragent)) {
212 if (!default_ua) {
213 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
214 /* ref: lzodefs.h */
215 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
216 #define SIPE_TARGET_PLATFORM "linux"
217 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
218 #define SIPE_TARGET_PLATFORM "bsd"
219 # elif defined(__APPLE__) || defined(__MACOS__)
220 #define SIPE_TARGET_PLATFORM "macosx"
221 #elif defined(__solaris__) || defined(__sun)
222 #define SIPE_TARGET_PLATFORM "sun"
223 #elif defined(_WIN32)
224 #define SIPE_TARGET_PLATFORM "win"
225 #else
226 #define SIPE_TARGET_PLATFORM "generic"
227 #endif
229 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
230 #define SIPE_TARGET_ARCH "x86_64"
231 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
232 #define SIPE_TARGET_ARCH "i386"
233 #elif defined(__ppc64__)
234 #define SIPE_TARGET_ARCH "ppc64"
235 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
236 #define SIPE_TARGET_ARCH "ppc"
237 #else
238 #define SIPE_TARGET_ARCH "other"
239 #endif
241 default_ua = g_strdup_printf("Purple/%s Sipe/%s (%s-%s; %s)",
242 purple_core_get_version(),
243 SIPE_VERSION,
244 SIPE_TARGET_PLATFORM,
245 SIPE_TARGET_ARCH,
246 sip->server_version ? sip->server_version : "");
248 useragent = default_ua;
250 return useragent;
253 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
254 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
256 return "sipe";
259 static void sipe_plugin_destroy(PurplePlugin *plugin);
261 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
263 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
264 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
265 gpointer data);
267 static void sipe_close(PurpleConnection *gc);
269 static void send_presence_status(struct sipe_account_data *sip);
271 static void sendout_pkt(PurpleConnection *gc, const char *buf);
273 static void sipe_keep_alive(PurpleConnection *gc)
275 struct sipe_account_data *sip = gc->proto_data;
276 if (sip->transport == SIPE_TRANSPORT_UDP) {
277 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
278 gchar buf[2] = {0, 0};
279 purple_debug_info("sipe", "sending keep alive\n");
280 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
281 } else {
282 time_t now = time(NULL);
283 if ((sip->keepalive_timeout > 0) &&
284 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
285 #if PURPLE_VERSION_CHECK(2,4,0)
286 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
287 #endif
289 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
290 sendout_pkt(gc, "\r\n\r\n");
291 sip->last_keepalive = now;
296 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
298 struct sip_connection *ret = NULL;
299 GSList *entry = sip->openconns;
300 while (entry) {
301 ret = entry->data;
302 if (ret->fd == fd) return ret;
303 entry = entry->next;
305 return NULL;
308 static void sipe_auth_free(struct sip_auth *auth)
310 g_free(auth->opaque);
311 auth->opaque = NULL;
312 g_free(auth->realm);
313 auth->realm = NULL;
314 g_free(auth->target);
315 auth->target = NULL;
316 auth->type = AUTH_TYPE_UNSET;
317 auth->retries = 0;
318 auth->expires = 0;
319 g_free(auth->gssapi_data);
320 auth->gssapi_data = NULL;
321 sip_sec_destroy_context(auth->gssapi_context);
322 auth->gssapi_context = NULL;
325 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
327 struct sip_connection *ret = g_new0(struct sip_connection, 1);
328 ret->fd = fd;
329 sip->openconns = g_slist_append(sip->openconns, ret);
330 return ret;
333 static void connection_remove(struct sipe_account_data *sip, int fd)
335 struct sip_connection *conn = connection_find(sip, fd);
336 if (conn) {
337 sip->openconns = g_slist_remove(sip->openconns, conn);
338 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
339 g_free(conn->inbuf);
340 g_free(conn);
344 static void connection_free_all(struct sipe_account_data *sip)
346 struct sip_connection *ret = NULL;
347 GSList *entry = sip->openconns;
348 while (entry) {
349 ret = entry->data;
350 connection_remove(sip, ret->fd);
351 entry = sip->openconns;
355 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
357 gchar noncecount[9];
358 const char *authuser = sip->authuser;
359 gchar *response;
360 gchar *ret;
362 if (!authuser || strlen(authuser) < 1) {
363 authuser = sip->username;
366 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
367 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
369 // If we have a signature for the message, include that
370 if (msg->signature) {
371 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);
374 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
375 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
376 gchar *gssapi_data;
377 gchar *opaque;
379 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
380 &(auth->expires),
381 auth->type,
382 purple_account_get_bool(sip->account, "sso", TRUE),
383 sip->authdomain ? sip->authdomain : "",
384 authuser,
385 sip->password,
386 auth->target,
387 auth->gssapi_data);
388 if (!gssapi_data || !auth->gssapi_context) {
389 sip->gc->wants_to_die = TRUE;
390 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
391 return NULL;
394 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
395 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
396 g_free(opaque);
397 g_free(gssapi_data);
398 return ret;
401 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
403 } else { /* Digest */
405 /* Calculate new session key */
406 if (!auth->opaque) {
407 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
408 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
409 authuser, auth->realm, sip->password,
410 auth->gssapi_data, NULL);
413 sprintf(noncecount, "%08d", auth->nc++);
414 response = purple_cipher_http_digest_calculate_response("md5",
415 msg->method, msg->target, NULL, NULL,
416 auth->gssapi_data, noncecount, NULL,
417 auth->opaque);
418 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
420 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);
421 g_free(response);
422 return ret;
426 static char *parse_attribute(const char *attrname, const char *source)
428 const char *tmp, *tmp2;
429 char *retval = NULL;
430 int len = strlen(attrname);
432 if (!strncmp(source, attrname, len)) {
433 tmp = source + len;
434 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
435 if (tmp2)
436 retval = g_strndup(tmp, tmp2 - tmp);
437 else
438 retval = g_strdup(tmp);
441 return retval;
444 static void fill_auth(gchar *hdr, struct sip_auth *auth)
446 int i;
447 gchar **parts;
449 if (!hdr) {
450 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
451 return;
454 if (!g_strncasecmp(hdr, "NTLM", 4)) {
455 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
456 auth->type = AUTH_TYPE_NTLM;
457 hdr += 5;
458 auth->nc = 1;
459 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
460 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
461 auth->type = AUTH_TYPE_KERBEROS;
462 hdr += 9;
463 auth->nc = 3;
464 } else {
465 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
466 auth->type = AUTH_TYPE_DIGEST;
467 hdr += 7;
470 parts = g_strsplit(hdr, "\", ", 0);
471 for (i = 0; parts[i]; i++) {
472 char *tmp;
474 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
476 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
477 g_free(auth->gssapi_data);
478 auth->gssapi_data = tmp;
480 if (auth->type == AUTH_TYPE_NTLM) {
481 /* NTLM module extracts nonce from gssapi-data */
482 auth->nc = 3;
485 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
486 /* Only used with AUTH_TYPE_DIGEST */
487 g_free(auth->gssapi_data);
488 auth->gssapi_data = tmp;
489 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
490 g_free(auth->opaque);
491 auth->opaque = tmp;
492 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
493 g_free(auth->realm);
494 auth->realm = tmp;
496 if (auth->type == AUTH_TYPE_DIGEST) {
497 /* Throw away old session key */
498 g_free(auth->opaque);
499 auth->opaque = NULL;
500 auth->nc = 1;
503 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
504 g_free(auth->target);
505 auth->target = tmp;
508 g_strfreev(parts);
510 return;
513 static void sipe_canwrite_cb(gpointer data,
514 SIPE_UNUSED_PARAMETER gint source,
515 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
517 PurpleConnection *gc = data;
518 struct sipe_account_data *sip = gc->proto_data;
519 gsize max_write;
520 gssize written;
522 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
524 if (max_write == 0) {
525 if (sip->tx_handler != 0){
526 purple_input_remove(sip->tx_handler);
527 sip->tx_handler = 0;
529 return;
532 written = write(sip->fd, sip->txbuf->outptr, max_write);
534 if (written < 0 && errno == EAGAIN)
535 written = 0;
536 else if (written <= 0) {
537 /*TODO: do we really want to disconnect on a failure to write?*/
538 purple_connection_error(gc, _("Could not write"));
539 return;
542 purple_circ_buffer_mark_read(sip->txbuf, written);
545 static void sipe_canwrite_cb_ssl(gpointer data,
546 SIPE_UNUSED_PARAMETER gint src,
547 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
549 PurpleConnection *gc = data;
550 struct sipe_account_data *sip = gc->proto_data;
551 gsize max_write;
552 gssize written;
554 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
556 if (max_write == 0) {
557 if (sip->tx_handler != 0) {
558 purple_input_remove(sip->tx_handler);
559 sip->tx_handler = 0;
560 return;
564 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
566 if (written < 0 && errno == EAGAIN)
567 written = 0;
568 else if (written <= 0) {
569 /*TODO: do we really want to disconnect on a failure to write?*/
570 purple_connection_error(gc, _("Could not write"));
571 return;
574 purple_circ_buffer_mark_read(sip->txbuf, written);
577 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
579 static void send_later_cb(gpointer data, gint source,
580 SIPE_UNUSED_PARAMETER const gchar *error)
582 PurpleConnection *gc = data;
583 struct sipe_account_data *sip;
584 struct sip_connection *conn;
586 if (!PURPLE_CONNECTION_IS_VALID(gc))
588 if (source >= 0)
589 close(source);
590 return;
593 if (source < 0) {
594 purple_connection_error(gc, _("Could not connect"));
595 return;
598 sip = gc->proto_data;
599 sip->fd = source;
600 sip->connecting = FALSE;
601 sip->last_keepalive = time(NULL);
603 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
605 /* If there is more to write now, we need to register a handler */
606 if (sip->txbuf->bufused > 0)
607 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
609 conn = connection_create(sip, source);
610 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
613 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
615 struct sipe_account_data *sip;
617 if (!PURPLE_CONNECTION_IS_VALID(gc))
619 if (gsc) purple_ssl_close(gsc);
620 return NULL;
623 sip = gc->proto_data;
624 sip->fd = gsc->fd;
625 sip->gsc = gsc;
626 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
627 sip->connecting = FALSE;
628 sip->last_keepalive = time(NULL);
630 connection_create(sip, gsc->fd);
632 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
634 return sip;
637 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
638 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
640 PurpleConnection *gc = data;
641 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
642 if (sip == NULL) return;
644 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
646 /* If there is more to write now */
647 if (sip->txbuf->bufused > 0) {
648 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
653 static void sendlater(PurpleConnection *gc, const char *buf)
655 struct sipe_account_data *sip = gc->proto_data;
657 if (!sip->connecting) {
658 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
659 if (sip->transport == SIPE_TRANSPORT_TLS){
660 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
661 } else {
662 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
663 purple_connection_error(gc, _("Could not create socket"));
666 sip->connecting = TRUE;
669 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
670 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
672 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
675 static void sendout_pkt(PurpleConnection *gc, const char *buf)
677 struct sipe_account_data *sip = gc->proto_data;
678 time_t currtime = time(NULL);
679 int writelen = strlen(buf);
680 char *tmp;
682 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
683 g_free(tmp);
684 if (sip->transport == SIPE_TRANSPORT_UDP) {
685 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
686 purple_debug_info("sipe", "could not send packet\n");
688 } else {
689 int ret;
690 if (sip->fd < 0) {
691 sendlater(gc, buf);
692 return;
695 if (sip->tx_handler) {
696 ret = -1;
697 errno = EAGAIN;
698 } else{
699 if (sip->gsc){
700 ret = purple_ssl_write(sip->gsc, buf, writelen);
701 }else{
702 ret = write(sip->fd, buf, writelen);
706 if (ret < 0 && errno == EAGAIN)
707 ret = 0;
708 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
709 sendlater(gc, buf);
710 return;
713 if (ret < writelen) {
714 if (!sip->tx_handler){
715 if (sip->gsc){
716 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
718 else{
719 sip->tx_handler = purple_input_add(sip->fd,
720 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
721 gc);
725 /* XXX: is it OK to do this? You might get part of a request sent
726 with part of another. */
727 if (sip->txbuf->bufused > 0)
728 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
730 purple_circ_buffer_append(sip->txbuf, buf + ret,
731 writelen - ret);
736 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
738 sendout_pkt(gc, buf);
739 return len;
742 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
744 GSList *tmp = msg->headers;
745 gchar *name;
746 gchar *value;
747 GString *outstr = g_string_new("");
748 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
749 while (tmp) {
750 name = ((struct siphdrelement*) (tmp->data))->name;
751 value = ((struct siphdrelement*) (tmp->data))->value;
752 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
753 tmp = g_slist_next(tmp);
755 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
756 sendout_pkt(sip->gc, outstr->str);
757 g_string_free(outstr, TRUE);
760 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
762 gchar * buf;
764 if (sip->registrar.type == AUTH_TYPE_UNSET) {
765 return;
768 if (sip->registrar.gssapi_context) {
769 struct sipmsg_breakdown msgbd;
770 gchar *signature_input_str;
771 msgbd.msg = msg;
772 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
773 msgbd.rand = g_strdup_printf("%08x", g_random_int());
774 sip->registrar.ntlm_num++;
775 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
776 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
777 if (signature_input_str != NULL) {
778 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
779 msg->signature = signature_hex;
780 msg->rand = g_strdup(msgbd.rand);
781 msg->num = g_strdup(msgbd.num);
782 g_free(signature_input_str);
784 sipmsg_breakdown_free(&msgbd);
787 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
788 buf = auth_header(sip, &sip->registrar, msg);
789 if (buf) {
790 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
792 g_free(buf);
793 } 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")) {
794 sip->registrar.nc = 3;
795 #ifdef USE_KERBEROS
796 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
797 #endif
798 sip->registrar.type = AUTH_TYPE_NTLM;
799 #ifdef USE_KERBEROS
800 } else {
801 sip->registrar.type = AUTH_TYPE_KERBEROS;
803 #endif
806 buf = auth_header(sip, &sip->registrar, msg);
807 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
808 g_free(buf);
809 } else {
810 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
814 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
815 const char *text, const char *body)
817 gchar *name;
818 gchar *value;
819 GString *outstr = g_string_new("");
820 struct sipe_account_data *sip = gc->proto_data;
821 gchar *contact;
822 GSList *tmp;
823 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
825 contact = get_contact(sip);
826 sipmsg_add_header(msg, "Contact", contact);
827 g_free(contact);
829 if (body) {
830 gchar len[12];
831 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
832 sipmsg_add_header(msg, "Content-Length", len);
833 } else {
834 sipmsg_add_header(msg, "Content-Length", "0");
837 msg->response = code;
839 sipmsg_strip_headers(msg, keepers);
840 sipmsg_merge_new_headers(msg);
841 sign_outgoing_message(msg, sip, msg->method);
843 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
844 tmp = msg->headers;
845 while (tmp) {
846 name = ((struct siphdrelement*) (tmp->data))->name;
847 value = ((struct siphdrelement*) (tmp->data))->value;
849 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
850 tmp = g_slist_next(tmp);
852 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
853 sendout_pkt(gc, outstr->str);
854 g_string_free(outstr, TRUE);
857 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
859 if (sip->transactions) {
860 sip->transactions = g_slist_remove(sip->transactions, trans);
861 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
863 if (trans->msg) sipmsg_free(trans->msg);
864 if (trans->payload) {
865 (*trans->payload->destroy)(trans->payload->data);
866 g_free(trans->payload);
868 g_free(trans->key);
869 g_free(trans);
873 static struct transaction *
874 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
876 gchar *call_id = NULL;
877 gchar *cseq = NULL;
878 struct transaction *trans = g_new0(struct transaction, 1);
880 trans->time = time(NULL);
881 trans->msg = (struct sipmsg *)msg;
882 call_id = sipmsg_find_header(trans->msg, "Call-ID");
883 cseq = sipmsg_find_header(trans->msg, "CSeq");
884 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
885 trans->callback = callback;
886 sip->transactions = g_slist_append(sip->transactions, trans);
887 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
888 return trans;
891 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
893 struct transaction *trans;
894 GSList *transactions = sip->transactions;
895 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
896 gchar *cseq = sipmsg_find_header(msg, "CSeq");
897 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
899 while (transactions) {
900 trans = transactions->data;
901 if (!g_strcasecmp(trans->key, key)) {
902 g_free(key);
903 return trans;
905 transactions = transactions->next;
908 g_free(key);
909 return NULL;
912 struct transaction *
913 send_sip_request(PurpleConnection *gc, const gchar *method,
914 const gchar *url, const gchar *to, const gchar *addheaders,
915 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
917 struct sipe_account_data *sip = gc->proto_data;
918 const char *addh = "";
919 char *buf;
920 struct sipmsg *msg;
921 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
922 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
923 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
924 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
925 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
926 gchar *route = g_strdup("");
927 gchar *epid = get_epid(sip);
928 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
929 struct transaction *trans = NULL;
931 if (dialog && dialog->routes)
933 GSList *iter = dialog->routes;
935 while(iter)
937 char *tmp = route;
938 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
939 g_free(tmp);
940 iter = g_slist_next(iter);
944 if (!ourtag && !dialog) {
945 ourtag = gentag();
948 if (!strcmp(method, "REGISTER")) {
949 if (sip->regcallid) {
950 g_free(callid);
951 callid = g_strdup(sip->regcallid);
952 } else {
953 sip->regcallid = g_strdup(callid);
955 cseq = ++sip->cseq;
958 if (addheaders) addh = addheaders;
960 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
961 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
962 "From: <sip:%s>%s%s;epid=%s\r\n"
963 "To: <%s>%s%s%s%s\r\n"
964 "Max-Forwards: 70\r\n"
965 "CSeq: %d %s\r\n"
966 "User-Agent: %s\r\n"
967 "Call-ID: %s\r\n"
968 "%s%s"
969 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
970 method,
971 dialog && dialog->request ? dialog->request : url,
972 TRANSPORT_DESCRIPTOR,
973 purple_network_get_my_ip(-1),
974 sip->listenport,
975 branch ? ";branch=" : "",
976 branch ? branch : "",
977 sip->username,
978 ourtag ? ";tag=" : "",
979 ourtag ? ourtag : "",
980 epid,
982 theirtag ? ";tag=" : "",
983 theirtag ? theirtag : "",
984 theirepid ? ";epid=" : "",
985 theirepid ? theirepid : "",
986 cseq,
987 method,
988 sipe_get_useragent(sip),
989 callid,
990 route,
991 addh,
992 body ? (gsize) strlen(body) : 0,
993 body ? body : "");
996 //printf ("parsing msg buf:\n%s\n\n", buf);
997 msg = sipmsg_parse_msg(buf);
999 g_free(buf);
1000 g_free(ourtag);
1001 g_free(theirtag);
1002 g_free(theirepid);
1003 g_free(branch);
1004 g_free(callid);
1005 g_free(route);
1006 g_free(epid);
1008 sign_outgoing_message (msg, sip, method);
1010 buf = sipmsg_to_string (msg);
1012 /* add to ongoing transactions */
1013 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1014 if (strcmp(method, "ACK")) {
1015 trans = transactions_add_buf(sip, msg, tc);
1016 } else {
1017 sipmsg_free(msg);
1019 sendout_pkt(gc, buf);
1020 g_free(buf);
1022 return trans;
1026 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1028 static void
1029 send_soap_request_with_cb(struct sipe_account_data *sip,
1030 gchar *from0,
1031 gchar *body,
1032 TransCallback callback,
1033 struct transaction_payload *payload)
1035 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1036 gchar *contact = get_contact(sip);
1037 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1038 "Content-Type: application/SOAP+xml\r\n",contact);
1040 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1041 trans->payload = payload;
1043 g_free(from);
1044 g_free(contact);
1045 g_free(hdr);
1048 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1050 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1053 static char *get_contact_register(struct sipe_account_data *sip)
1055 char *epid = get_epid(sip);
1056 char *uuid = generateUUIDfromEPID(epid);
1057 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);
1058 g_free(uuid);
1059 g_free(epid);
1060 return(buf);
1063 static void do_register_exp(struct sipe_account_data *sip, int expire)
1065 char *uri;
1066 char *expires;
1067 char *to;
1068 char *contact;
1069 char *hdr;
1071 if (!sip->sipdomain) return;
1073 uri = sip_uri_from_name(sip->sipdomain);
1074 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1075 to = sip_uri_self(sip);
1076 contact = get_contact_register(sip);
1077 hdr = g_strdup_printf("Contact: %s\r\n"
1078 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1079 "Event: registration\r\n"
1080 "Allow-Events: presence\r\n"
1081 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1082 "%s", contact, expires);
1083 g_free(contact);
1084 g_free(expires);
1086 sip->registerstatus = 1;
1088 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1089 process_register_response);
1091 g_free(hdr);
1092 g_free(uri);
1093 g_free(to);
1096 static void do_register_cb(struct sipe_account_data *sip,
1097 SIPE_UNUSED_PARAMETER void *unused)
1099 do_register_exp(sip, -1);
1100 sip->reregister_set = FALSE;
1103 static void do_register(struct sipe_account_data *sip)
1105 do_register_exp(sip, -1);
1108 static void
1109 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1111 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1112 send_soap_request(sip, body);
1113 g_free(body);
1116 static void
1117 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1119 if (allow) {
1120 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1121 } else {
1122 purple_debug_info("sipe", "Blocking contact %s\n", who);
1125 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1128 static
1129 void sipe_auth_user_cb(void * data)
1131 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1132 if (!job) return;
1134 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1135 g_free(job);
1138 static
1139 void sipe_deny_user_cb(void * data)
1141 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1142 if (!job) return;
1144 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1145 g_free(job);
1148 static void
1149 sipe_add_permit(PurpleConnection *gc, const char *name)
1151 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1152 sipe_contact_allow_deny(sip, name, TRUE);
1155 static void
1156 sipe_add_deny(PurpleConnection *gc, const char *name)
1158 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1159 sipe_contact_allow_deny(sip, name, FALSE);
1162 /*static void
1163 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1165 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1166 sipe_contact_set_acl(sip, name, "");
1169 static void
1170 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1172 xmlnode *watchers;
1173 xmlnode *watcher;
1174 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1175 if (msg->response != 0 && msg->response != 200) return;
1177 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1179 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1180 if (!watchers) return;
1182 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1183 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1184 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1185 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1187 // TODO pull out optional displayName to pass as alias
1188 if (remote_user) {
1189 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1190 job->who = remote_user;
1191 job->sip = sip;
1192 purple_account_request_authorization(
1193 sip->account,
1194 remote_user,
1195 _("you"), /* id */
1196 alias,
1197 NULL, /* message */
1198 on_list,
1199 sipe_auth_user_cb,
1200 sipe_deny_user_cb,
1201 (void *) job);
1206 xmlnode_free(watchers);
1207 return;
1210 static void
1211 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1213 PurpleGroup * purple_group = purple_find_group(group->name);
1214 if (!purple_group) {
1215 purple_group = purple_group_new(group->name);
1216 purple_blist_add_group(purple_group, NULL);
1219 if (purple_group) {
1220 group->purple_group = purple_group;
1221 sip->groups = g_slist_append(sip->groups, group);
1222 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1223 } else {
1224 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1228 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1230 struct sipe_group *group;
1231 GSList *entry;
1232 if (sip == NULL) {
1233 return NULL;
1236 entry = sip->groups;
1237 while (entry) {
1238 group = entry->data;
1239 if (group->id == id) {
1240 return group;
1242 entry = entry->next;
1244 return NULL;
1247 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1249 struct sipe_group *group;
1250 GSList *entry;
1251 if (!sip || !name) {
1252 return NULL;
1255 entry = sip->groups;
1256 while (entry) {
1257 group = entry->data;
1258 if (!strcmp(group->name, name)) {
1259 return group;
1261 entry = entry->next;
1263 return NULL;
1266 static void
1267 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1269 gchar *body;
1270 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1271 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1272 send_soap_request(sip, body);
1273 g_free(body);
1274 g_free(group->name);
1275 group->name = g_strdup(name);
1279 * Only appends if no such value already stored.
1280 * Like Set in Java.
1282 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1283 GSList * res = list;
1284 if (!g_slist_find_custom(list, data, func)) {
1285 res = g_slist_insert_sorted(list, data, func);
1287 return res;
1290 static int
1291 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1292 return group1->id - group2->id;
1296 * Returns string like "2 4 7 8" - group ids buddy belong to.
1298 static gchar *
1299 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1300 int i = 0;
1301 gchar *res;
1302 //creating array from GList, converting int to gchar*
1303 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1304 GSList *entry = buddy->groups;
1305 while (entry) {
1306 struct sipe_group * group = entry->data;
1307 ids_arr[i] = g_strdup_printf("%d", group->id);
1308 entry = entry->next;
1309 i++;
1311 ids_arr[i] = NULL;
1312 res = g_strjoinv(" ", ids_arr);
1313 g_strfreev(ids_arr);
1314 return res;
1318 * Sends buddy update to server
1320 static void
1321 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1323 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1324 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1326 if (buddy && purple_buddy) {
1327 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1328 gchar *body;
1329 gchar *groups = sipe_get_buddy_groups_string(buddy);
1330 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1332 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1333 alias, groups, "true", buddy->name, sip->contacts_delta++
1335 send_soap_request(sip, body);
1336 g_free(groups);
1337 g_free(body);
1341 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1343 if (msg->response == 200) {
1344 struct sipe_group *group;
1345 struct group_user_context *ctx = trans->payload->data;
1346 xmlnode *xml;
1347 xmlnode *node;
1348 char *group_id;
1349 struct sipe_buddy *buddy;
1351 xml = xmlnode_from_str(msg->body, msg->bodylen);
1352 if (!xml) {
1353 return FALSE;
1356 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1357 if (!node) {
1358 xmlnode_free(xml);
1359 return FALSE;
1362 group_id = xmlnode_get_data(node);
1363 if (!group_id) {
1364 xmlnode_free(xml);
1365 return FALSE;
1368 group = g_new0(struct sipe_group, 1);
1369 group->id = (int)g_ascii_strtod(group_id, NULL);
1370 g_free(group_id);
1371 group->name = g_strdup(ctx->group_name);
1373 sipe_group_add(sip, group);
1375 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1376 if (buddy) {
1377 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1380 sipe_group_set_user(sip, ctx->user_name);
1382 xmlnode_free(xml);
1383 return TRUE;
1385 return FALSE;
1388 static void sipe_group_context_destroy(gpointer data)
1390 struct group_user_context *ctx = data;
1391 g_free(ctx->group_name);
1392 g_free(ctx->user_name);
1393 g_free(ctx);
1396 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1398 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1399 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1400 gchar *body;
1401 ctx->group_name = g_strdup(name);
1402 ctx->user_name = g_strdup(who);
1403 payload->destroy = sipe_group_context_destroy;
1404 payload->data = ctx;
1406 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1407 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1408 g_free(body);
1412 * Data structure for scheduled actions
1415 struct scheduled_action {
1417 * Name of action.
1418 * Format is <Event>[<Data>...]
1419 * Example: <presence><sip:user@domain.com> or <registration>
1421 gchar *name;
1422 guint timeout_handler;
1423 gboolean repetitive;
1424 Action action;
1425 GDestroyNotify destroy;
1426 struct sipe_account_data *sip;
1427 void *payload;
1431 * A timer callback
1432 * Should return FALSE if repetitive action is not needed
1434 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1436 gboolean ret;
1437 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1438 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1439 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1440 (sched_action->action)(sched_action->sip, sched_action->payload);
1441 ret = sched_action->repetitive;
1442 if (sched_action->destroy) {
1443 (*sched_action->destroy)(sched_action->payload);
1445 g_free(sched_action->name);
1446 g_free(sched_action);
1447 return ret;
1451 * Kills action timer effectively cancelling
1452 * scheduled action
1454 * @param name of action
1456 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1458 GSList *entry;
1460 if (!sip->timeouts || !name) return;
1462 entry = sip->timeouts;
1463 while (entry) {
1464 struct scheduled_action *sched_action = entry->data;
1465 if(!strcmp(sched_action->name, name)) {
1466 GSList *to_delete = entry;
1467 entry = entry->next;
1468 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1469 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1470 purple_timeout_remove(sched_action->timeout_handler);
1471 if (sched_action->destroy) {
1472 (*sched_action->destroy)(sched_action->payload);
1474 g_free(sched_action->name);
1475 g_free(sched_action);
1476 } else {
1477 entry = entry->next;
1482 static void
1483 sipe_schedule_action0(const gchar *name,
1484 int timeout,
1485 gboolean isSeconds,
1486 Action action,
1487 GDestroyNotify destroy,
1488 struct sipe_account_data *sip,
1489 void *payload)
1491 struct scheduled_action *sched_action;
1493 /* Make sure each action only exists once */
1494 sipe_cancel_scheduled_action(sip, name);
1496 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1497 sched_action = g_new0(struct scheduled_action, 1);
1498 sched_action->repetitive = FALSE;
1499 sched_action->name = g_strdup(name);
1500 sched_action->action = action;
1501 sched_action->destroy = destroy;
1502 sched_action->sip = sip;
1503 sched_action->payload = payload;
1504 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1505 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1506 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1507 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1510 void
1511 sipe_schedule_action(const gchar *name,
1512 int timeout,
1513 Action action,
1514 GDestroyNotify destroy,
1515 struct sipe_account_data *sip,
1516 void *payload)
1518 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1522 * Same as sipe_schedule_action() but timeout is in milliseconds.
1524 static void
1525 sipe_schedule_action_msec(const gchar *name,
1526 int timeout,
1527 Action action,
1528 GDestroyNotify destroy,
1529 struct sipe_account_data *sip,
1530 void *payload)
1532 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1535 static void
1536 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1537 time_t calculate_from);
1539 static int
1540 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1542 static const char*
1543 sipe_get_status_by_availability(int avail,
1544 char** activity);
1546 static void
1547 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1548 const char *status_id,
1549 const char *message,
1550 time_t do_not_publish[]);
1552 static void
1553 sipe_apply_calendar_status(struct sipe_account_data *sip,
1554 struct sipe_buddy *sbuddy,
1555 const char *status_id)
1557 time_t cal_avail_since;
1558 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1559 int avail;
1560 gchar *self_uri = sip_uri_self(sip);
1562 if (!sbuddy) return;
1564 if (cal_status < SIPE_CAL_NO_DATA) {
1565 purple_debug_info("sipe", "update_calendar_status_cb: cal_status : %d for %s\n", cal_status, sbuddy->name);
1566 purple_debug_info("sipe", "update_calendar_status_cb: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1569 /* scheduled Cal update call */
1570 if (!status_id) {
1571 status_id = sbuddy->last_non_cal_status_id;
1572 g_free(sbuddy->activity);
1573 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1576 /* adjust to calendar status */
1577 if (cal_status != SIPE_CAL_NO_DATA) {
1578 purple_debug_info("sipe", "update_calendar_status_cb: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1580 if (cal_status == SIPE_CAL_BUSY
1581 && cal_avail_since > sbuddy->user_avail_since
1582 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1584 status_id = SIPE_STATUS_ID_BUSY;
1585 g_free(sbuddy->activity);
1586 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1588 avail = sipe_get_availability_by_status(status_id, NULL);
1590 purple_debug_info("sipe", "update_calendar_status_cb: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1591 if (cal_avail_since > sbuddy->activity_since) {
1592 if (cal_status == SIPE_CAL_OOF
1593 && avail >= 15000) /* 12000 in 2007 */
1595 g_free(sbuddy->activity);
1596 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1601 /* then set status_id actually */
1602 purple_debug_info("sipe", "sipe_got_user_status: to %s for %s\n", status_id, sbuddy->name);
1603 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1605 /* set our account state to the one in roaming (including calendar info) */
1606 if (sip->initial_state_published && !strcmp(sbuddy->name, self_uri)) {
1607 if (!strcmp(status_id, SIPE_STATUS_ID_OFFLINE)) {
1608 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1611 purple_debug_info("sipe", "sipe_got_user_status: to %s for the account\n", sip->status);
1612 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1614 g_free(self_uri);
1617 static void
1618 sipe_got_user_status(struct sipe_account_data *sip,
1619 const char* uri,
1620 const char *status_id)
1622 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1624 if (!sbuddy) return;
1626 /* Check if on 2005 system contact's calendar,
1627 * then set/preserve it.
1629 if (!sip->ocs2007) {
1630 sipe_apply_calendar_status(sip, sbuddy, status_id);
1631 } else {
1632 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1636 static void
1637 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1638 struct sipe_buddy *sbuddy,
1639 struct sipe_account_data *sip)
1641 sipe_apply_calendar_status(sip, sbuddy, NULL);
1645 * Updates contact's status
1646 * based on their calendar information.
1648 * Applicability: 2005 systems
1650 static void
1651 update_calendar_status(struct sipe_account_data *sip)
1653 purple_debug_info("sipe", "update_calendar_status() started.\n");
1654 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1656 /* repeat scheduling */
1657 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1661 * Schedules process of contacts' status update
1662 * based on their calendar information.
1663 * Should be scheduled to the beginning of every
1664 * 15 min interval, like:
1665 * 13:00, 13:15, 13:30, 13:45, etc.
1667 * Applicability: 2005 systems
1669 static void
1670 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1671 time_t calculate_from)
1673 int interval = 15*60;
1674 /** start of the beginning of closest 15 min interval. */
1675 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1677 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1678 asctime(localtime(&calculate_from)));
1679 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1680 asctime(localtime(&next_start)));
1682 sipe_schedule_action("<+2005-cal-status>",
1683 (int)(next_start - time(NULL)),
1684 (Action)update_calendar_status,
1685 NULL,
1686 sip,
1687 NULL);
1691 * Schedules process of self status publish
1692 * based on own calendar information.
1693 * Should be scheduled to the beginning of every
1694 * 15 min interval, like:
1695 * 13:00, 13:15, 13:30, 13:45, etc.
1697 * Applicability: 2007+ systems
1699 static void
1700 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1701 time_t calculate_from)
1703 int interval = 5*60;
1704 /** start of the beginning of closest 5 min interval. */
1705 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1707 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1708 asctime(localtime(&calculate_from)));
1709 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1710 asctime(localtime(&next_start)));
1712 sipe_schedule_action("<+2007-cal-status>",
1713 (int)(next_start - time(NULL)),
1714 (Action)publish_calendar_status_self,
1715 NULL,
1716 sip,
1717 NULL);
1720 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1722 /** Should be g_free()'d
1724 static gchar *
1725 sipe_get_subscription_key(gchar *event,
1726 gchar *with)
1728 gchar *key = NULL;
1730 if (is_empty(event)) return NULL;
1732 if (event && !g_ascii_strcasecmp(event, "presence")) {
1733 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1734 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1736 /* @TODO drop participated buddies' just_added flag */
1737 } else if (event) {
1738 /* Subscription is identified by <event> key */
1739 key = g_strdup_printf("<%s>", event);
1742 return key;
1745 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1746 SIPE_UNUSED_PARAMETER struct transaction *trans)
1748 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1749 gchar *event = sipmsg_find_header(msg, "Event");
1750 gchar *key;
1752 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1753 if (!event) {
1754 struct sipmsg *request_msg = trans->msg;
1755 event = sipmsg_find_header(request_msg, "Event");
1758 key = sipe_get_subscription_key(event, with);
1760 /* 200 OK; 481 Call Leg Does Not Exist */
1761 if (key && (msg->response == 200 || msg->response == 481)) {
1762 if (g_hash_table_lookup(sip->subscriptions, key)) {
1763 g_hash_table_remove(sip->subscriptions, key);
1764 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1768 /* create/store subscription dialog if not yet */
1769 if (msg->response == 200) {
1770 gchar *callid = sipmsg_find_header(msg, "Call-ID");
1771 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1773 if (key) {
1774 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1775 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1777 subscription->dialog.callid = g_strdup(callid);
1778 subscription->dialog.cseq = atoi(cseq);
1779 subscription->dialog.with = g_strdup(with);
1780 subscription->event = g_strdup(event);
1781 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1783 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1786 g_free(cseq);
1789 g_free(key);
1790 g_free(with);
1792 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1794 process_incoming_notify(sip, msg, FALSE, FALSE);
1796 return TRUE;
1799 static void sipe_subscribe_resource_uri(const char *name,
1800 SIPE_UNUSED_PARAMETER gpointer value,
1801 gchar **resources_uri)
1803 gchar *tmp = *resources_uri;
1804 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1805 g_free(tmp);
1808 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1810 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1811 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1812 gchar *tmp = *resources_uri;
1814 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1816 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1817 g_free(tmp);
1821 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1822 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1823 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1824 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1825 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1828 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1830 gchar *key;
1831 gchar *contact = get_contact(sip);
1832 gchar *request;
1833 gchar *content;
1834 gchar *require = "";
1835 gchar *accept = "";
1836 gchar *autoextend = "";
1837 gchar *content_type;
1838 struct sip_dialog *dialog;
1840 if (sip->ocs2007) {
1841 require = ", categoryList";
1842 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1843 content_type = "application/msrtc-adrl-categorylist+xml";
1844 content = g_strdup_printf(
1845 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1846 "<action name=\"subscribe\" id=\"63792024\">\n"
1847 "<adhocList>\n%s</adhocList>\n"
1848 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1849 "<category name=\"calendarData\"/>\n"
1850 "<category name=\"contactCard\"/>\n"
1851 "<category name=\"note\"/>\n"
1852 "<category name=\"state\"/>\n"
1853 "</categoryList>\n"
1854 "</action>\n"
1855 "</batchSub>", sip->username, resources_uri);
1856 } else {
1857 autoextend = "Supported: com.microsoft.autoextend\r\n";
1858 content_type = "application/adrl+xml";
1859 content = g_strdup_printf(
1860 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1861 "<create xmlns=\"\">\n%s</create>\n"
1862 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1864 g_free(resources_uri);
1866 request = g_strdup_printf(
1867 "Require: adhoclist%s\r\n"
1868 "Supported: eventlist\r\n"
1869 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1870 "Supported: ms-piggyback-first-notify\r\n"
1871 "%sSupported: ms-benotify\r\n"
1872 "Proxy-Require: ms-benotify\r\n"
1873 "Event: presence\r\n"
1874 "Content-Type: %s\r\n"
1875 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1876 g_free(contact);
1878 /* subscribe to buddy presence */
1879 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1880 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1881 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1882 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1884 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1886 g_free(content);
1887 g_free(to);
1888 g_free(request);
1889 g_free(key);
1892 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1893 SIPE_UNUSED_PARAMETER void *unused)
1895 gchar *to = sip_uri_self(sip);
1896 gchar *resources_uri = g_strdup("");
1897 if (sip->ocs2007) {
1898 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1899 } else {
1900 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1903 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1906 struct presence_batched_routed {
1907 gchar *host;
1908 GSList *buddies;
1911 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1913 struct presence_batched_routed *data = payload;
1914 GSList *buddies = data->buddies;
1915 while (buddies) {
1916 g_free(buddies->data);
1917 buddies = buddies->next;
1919 g_slist_free(data->buddies);
1920 g_free(data->host);
1921 g_free(payload);
1924 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1926 struct presence_batched_routed *data = payload;
1927 GSList *buddies = data->buddies;
1928 gchar *resources_uri = g_strdup("");
1929 while (buddies) {
1930 gchar *tmp = resources_uri;
1931 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1932 g_free(tmp);
1933 buddies = buddies->next;
1935 sipe_subscribe_presence_batched_to(sip, resources_uri,
1936 g_strdup(data->host));
1940 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1941 * The user sends a single SUBSCRIBE request to the subscribed contact.
1942 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1946 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1949 gchar *key;
1950 gchar *to = sip_uri((char *)buddy_name);
1951 gchar *tmp = get_contact(sip);
1952 gchar *request;
1953 gchar *content = NULL;
1954 gchar *autoextend = "";
1955 gchar *content_type = "";
1956 struct sip_dialog *dialog;
1957 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
1958 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1960 if (sbuddy) sbuddy->just_added = FALSE;
1962 if (sip->ocs2007) {
1963 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1964 } else {
1965 autoextend = "Supported: com.microsoft.autoextend\r\n";
1968 request = g_strdup_printf(
1969 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1970 "Supported: ms-piggyback-first-notify\r\n"
1971 "%s%sSupported: ms-benotify\r\n"
1972 "Proxy-Require: ms-benotify\r\n"
1973 "Event: presence\r\n"
1974 "Contact: %s\r\n", autoextend, content_type, tmp);
1976 if (sip->ocs2007) {
1977 content = g_strdup_printf(
1978 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1979 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1980 "<resource uri=\"%s\"%s\n"
1981 "</adhocList>\n"
1982 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1983 "<category name=\"calendarData\"/>\n"
1984 "<category name=\"contactCard\"/>\n"
1985 "<category name=\"note\"/>\n"
1986 "<category name=\"state\"/>\n"
1987 "</categoryList>\n"
1988 "</action>\n"
1989 "</batchSub>", sip->username, to, context);
1992 g_free(tmp);
1994 /* subscribe to buddy presence */
1995 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1996 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1997 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1998 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2000 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2002 g_free(content);
2003 g_free(to);
2004 g_free(request);
2005 g_free(key);
2008 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2010 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2012 if (!purple_status_is_active(status))
2013 return;
2015 if (account->gc) {
2016 struct sipe_account_data *sip = account->gc->proto_data;
2018 if (sip) {
2019 gchar *action_name;
2020 time_t now = time(NULL);
2021 const char *status_id = purple_status_get_id(status);
2022 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2023 sipe_activity activity = sipe_get_activity_by_token(status_id);
2024 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2027 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2028 status_id, (int)sip->do_not_publish[activity], (int)now);
2030 sip->do_not_publish[activity] = 0;
2031 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2032 status_id, (int)sip->do_not_publish[activity]);
2034 if (do_not_publish)
2036 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2037 return;
2040 g_free(sip->status);
2041 sip->status = g_strdup(status_id);
2043 /* this will preserve OOF flag as well */
2044 if (!(note && sip->note && !strcmp(note, sip->note))) {
2045 sip->is_oof_note = FALSE;
2046 g_free(sip->note);
2047 sip->note = g_strdup(note);
2048 sip->note_since = time(NULL);
2051 /* schedule 2 sec to capture idle flag */
2052 action_name = g_strdup_printf("<%s>", "+set-status");
2053 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2054 g_free(action_name);
2058 static void
2059 sipe_set_idle(PurpleConnection * gc,
2060 int interval)
2062 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2064 if (gc) {
2065 struct sipe_account_data *sip = gc->proto_data;
2067 if (sip) {
2068 sip->idle_switch = time(NULL);
2069 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2074 static void
2075 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2076 SIPE_UNUSED_PARAMETER const char *alias)
2078 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2079 sipe_group_set_user(sip, name);
2082 static void
2083 sipe_group_buddy(PurpleConnection *gc,
2084 const char *who,
2085 const char *old_group_name,
2086 const char *new_group_name)
2088 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2089 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2090 struct sipe_group * old_group = NULL;
2091 struct sipe_group * new_group;
2093 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2094 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2096 if(!buddy) { // buddy not in roaming list
2097 return;
2100 if (old_group_name) {
2101 old_group = sipe_group_find_by_name(sip, old_group_name);
2103 new_group = sipe_group_find_by_name(sip, new_group_name);
2105 if (old_group) {
2106 buddy->groups = g_slist_remove(buddy->groups, old_group);
2107 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2110 if (!new_group) {
2111 sipe_group_create(sip, new_group_name, who);
2112 } else {
2113 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2114 sipe_group_set_user(sip, who);
2118 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2120 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2122 /* libpurple can call us with undefined buddy or group */
2123 if (buddy && group) {
2124 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2126 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2127 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2128 purple_blist_rename_buddy(buddy, buddy_name);
2129 g_free(buddy_name);
2131 /* Prepend sip: if needed */
2132 if (strncmp("sip:", buddy->name, 4)) {
2133 gchar *buf = sip_uri_from_name(buddy->name);
2134 purple_blist_rename_buddy(buddy, buf);
2135 g_free(buf);
2138 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2139 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2140 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2141 b->name = g_strdup(buddy->name);
2142 b->just_added = TRUE;
2143 g_hash_table_insert(sip->buddies, b->name, b);
2144 sipe_group_buddy(gc, b->name, NULL, group->name);
2145 /* @TODO should go to callback */
2146 sipe_subscribe_presence_single(sip, b->name);
2147 } else {
2148 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2153 static void sipe_free_buddy(struct sipe_buddy *buddy)
2155 #ifndef _WIN32
2157 * We are calling g_hash_table_foreach_steal(). That means that no
2158 * key/value deallocation functions are called. Therefore the glib
2159 * hash code does not touch the key (buddy->name) or value (buddy)
2160 * of the to-be-deleted hash node at all. It follows that we
2162 * - MUST free the memory for the key ourselves and
2163 * - ARE allowed to do it in this function
2165 * Conclusion: glib must be broken on the Windows platform if sipe
2166 * crashes with SIGTRAP when closing. You'll have to live
2167 * with the memory leak until this is fixed.
2169 g_free(buddy->name);
2170 #endif
2171 g_free(buddy->activity);
2172 g_free(buddy->meeting_subject);
2173 g_free(buddy->meeting_location);
2174 g_free(buddy->note);
2176 g_free(buddy->cal_start_time);
2177 g_free(buddy->cal_free_busy_base64);
2178 g_free(buddy->cal_free_busy);
2179 g_free(buddy->last_non_cal_activity);
2181 sipe_cal_free_working_hours(buddy->cal_working_hours);
2183 g_free(buddy->device_name);
2184 g_slist_free(buddy->groups);
2185 g_free(buddy);
2189 * Unassociates buddy from group first.
2190 * Then see if no groups left, removes buddy completely.
2191 * Otherwise updates buddy groups on server.
2193 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2195 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2196 struct sipe_buddy *b;
2197 struct sipe_group *g = NULL;
2199 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2200 if (!buddy) return;
2202 b = g_hash_table_lookup(sip->buddies, buddy->name);
2203 if (!b) return;
2205 if (group) {
2206 g = sipe_group_find_by_name(sip, group->name);
2209 if (g) {
2210 b->groups = g_slist_remove(b->groups, g);
2211 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2214 if (g_slist_length(b->groups) < 1) {
2215 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2216 sipe_cancel_scheduled_action(sip, action_name);
2217 g_free(action_name);
2219 g_hash_table_remove(sip->buddies, buddy->name);
2221 if (b->name) {
2222 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2223 send_soap_request(sip, body);
2224 g_free(body);
2227 sipe_free_buddy(b);
2228 } else {
2229 //updates groups on server
2230 sipe_group_set_user(sip, b->name);
2235 static void
2236 sipe_rename_group(PurpleConnection *gc,
2237 const char *old_name,
2238 PurpleGroup *group,
2239 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2241 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2242 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2243 if (s_group) {
2244 sipe_group_rename(sip, s_group, group->name);
2245 } else {
2246 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2250 static void
2251 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2253 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2254 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2255 if (s_group) {
2256 gchar *body;
2257 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2258 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2259 send_soap_request(sip, body);
2260 g_free(body);
2262 sip->groups = g_slist_remove(sip->groups, s_group);
2263 g_free(s_group->name);
2264 g_free(s_group);
2265 } else {
2266 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2270 /** All statuses need message attribute to pass Note */
2271 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2273 PurpleStatusType *type;
2274 GList *types = NULL;
2276 /* Macros to reduce code repetition.
2277 Translators: noun */
2278 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2279 prim, id, name, \
2280 TRUE, user, FALSE, \
2281 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2282 NULL); \
2283 types = g_list_append(types, type);
2285 /* Online */
2286 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2287 NULL,
2288 NULL,
2289 TRUE);
2291 /* Busy */
2292 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2293 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2294 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2295 TRUE);
2297 /* Do Not Disturb */
2298 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2299 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2300 NULL,
2301 TRUE);
2303 /* Away */
2304 /* Goes first in the list as
2305 * purple picks the first status with the AWAY type
2306 * for idle.
2308 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2309 NULL,
2310 NULL,
2311 TRUE);
2313 /* Be Right Back */
2314 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2315 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2316 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2317 TRUE);
2319 /* Appear Offline */
2320 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2321 NULL,
2322 NULL,
2323 TRUE);
2325 /* Offline (not user settable) */
2326 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE,
2327 NULL,
2328 NULL,
2329 FALSE);
2331 return types;
2335 * A callback for g_hash_table_foreach
2337 static void
2338 sipe_buddy_subscribe_cb(char *buddy_name,
2339 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2340 struct sipe_account_data *sip)
2342 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2343 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2344 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2345 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2347 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2348 g_free(action_name);
2352 * Removes entries from purple buddy list
2353 * that does not correspond ones in the roaming contact list.
2355 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2356 GSList *buddies = purple_find_buddies(sip->account, NULL);
2357 GSList *entry = buddies;
2358 struct sipe_buddy *buddy;
2359 PurpleBuddy *b;
2360 PurpleGroup *g;
2362 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2363 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2364 while (entry) {
2365 b = entry->data;
2366 g = purple_buddy_get_group(b);
2367 buddy = g_hash_table_lookup(sip->buddies, b->name);
2368 if(buddy) {
2369 gboolean in_sipe_groups = FALSE;
2370 GSList *entry2 = buddy->groups;
2371 while (entry2) {
2372 struct sipe_group *group = entry2->data;
2373 if (!strcmp(group->name, g->name)) {
2374 in_sipe_groups = TRUE;
2375 break;
2377 entry2 = entry2->next;
2379 if(!in_sipe_groups) {
2380 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2381 purple_blist_remove_buddy(b);
2383 } else {
2384 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2385 purple_blist_remove_buddy(b);
2387 entry = entry->next;
2389 g_slist_free(buddies);
2392 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2394 int len = msg->bodylen;
2396 gchar *tmp = sipmsg_find_header(msg, "Event");
2397 xmlnode *item;
2398 xmlnode *isc;
2399 const gchar *contacts_delta;
2400 xmlnode *group_node;
2401 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
2402 return FALSE;
2405 /* Convert the contact from XML to Purple Buddies */
2406 isc = xmlnode_from_str(msg->body, len);
2407 if (!isc) {
2408 return FALSE;
2411 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2412 if (contacts_delta) {
2413 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2416 if (!strcmp(isc->name, "contactList")) {
2418 /* Parse groups */
2419 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2420 struct sipe_group * group = g_new0(struct sipe_group, 1);
2421 const char *name = xmlnode_get_attrib(group_node, "name");
2423 if (!strncmp(name, "~", 1)) {
2424 name = _("Other Contacts");
2426 group->name = g_strdup(name);
2427 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2429 sipe_group_add(sip, group);
2432 // Make sure we have at least one group
2433 if (g_slist_length(sip->groups) == 0) {
2434 struct sipe_group * group = g_new0(struct sipe_group, 1);
2435 PurpleGroup *purple_group;
2436 group->name = g_strdup(_("Other Contacts"));
2437 group->id = 1;
2438 purple_group = purple_group_new(group->name);
2439 purple_blist_add_group(purple_group, NULL);
2440 sip->groups = g_slist_append(sip->groups, group);
2443 /* Parse contacts */
2444 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2445 const gchar *uri = xmlnode_get_attrib(item, "uri");
2446 const gchar *name = xmlnode_get_attrib(item, "name");
2447 gchar *buddy_name;
2448 struct sipe_buddy *buddy = NULL;
2449 gchar *tmp;
2450 gchar **item_groups;
2451 int i = 0;
2453 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2454 tmp = sip_uri_from_name(uri);
2455 buddy_name = g_ascii_strdown(tmp, -1);
2456 g_free(tmp);
2458 /* assign to group Other Contacts if nothing else received */
2459 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2460 if(!tmp || !strcmp("", tmp) ) {
2461 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2462 g_free(tmp);
2463 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2465 item_groups = g_strsplit(tmp, " ", 0);
2466 g_free(tmp);
2468 while (item_groups[i]) {
2469 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2471 // If couldn't find the right group for this contact, just put them in the first group we have
2472 if (group == NULL && g_slist_length(sip->groups) > 0) {
2473 group = sip->groups->data;
2476 if (group != NULL) {
2477 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2478 if (!b){
2479 b = purple_buddy_new(sip->account, buddy_name, uri);
2480 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2482 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2485 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2486 if (name != NULL && strlen(name) != 0) {
2487 purple_blist_alias_buddy(b, name);
2489 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2493 if (!buddy) {
2494 buddy = g_new0(struct sipe_buddy, 1);
2495 buddy->name = g_strdup(b->name);
2496 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2499 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2501 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2502 } else {
2503 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2504 name);
2507 i++;
2508 } // while, contact groups
2509 g_strfreev(item_groups);
2510 g_free(buddy_name);
2512 } // for, contacts
2514 sipe_cleanup_local_blist(sip);
2516 /* Add self-contact if not there yet. 2005 systems. */
2517 /* This will resemble subscription to roaming_self in 2007 systems */
2518 if (!sip->ocs2007) {
2519 gchar *self_uri = sip_uri_self(sip);
2520 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2522 if (!buddy) {
2523 buddy = g_new0(struct sipe_buddy, 1);
2524 buddy->name = g_strdup(self_uri);
2525 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2527 g_free(self_uri);
2530 xmlnode_free(isc);
2532 /* subscribe to buddies */
2533 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2534 if (sip->batched_support) {
2535 sipe_subscribe_presence_batched(sip, NULL);
2536 } else {
2537 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2539 sip->subscribed_buddies = TRUE;
2541 /* for 2005 systems schedule contacts' status update
2542 * based on their calendar information
2544 if (!sip->ocs2007) {
2545 sipe_sched_calendar_status_update(sip, time(NULL));
2548 return 0;
2552 * Subscribe roaming contacts
2554 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2556 gchar *to = sip_uri_self(sip);
2557 gchar *tmp = get_contact(sip);
2558 gchar *hdr = g_strdup_printf(
2559 "Event: vnd-microsoft-roaming-contacts\r\n"
2560 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2561 "Supported: com.microsoft.autoextend\r\n"
2562 "Supported: ms-benotify\r\n"
2563 "Proxy-Require: ms-benotify\r\n"
2564 "Supported: ms-piggyback-first-notify\r\n"
2565 "Contact: %s\r\n", tmp);
2566 g_free(tmp);
2568 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2569 g_free(to);
2570 g_free(hdr);
2573 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2574 SIPE_UNUSED_PARAMETER void *unused)
2576 gchar *key;
2577 struct sip_dialog *dialog;
2578 gchar *to = sip_uri_self(sip);
2579 gchar *tmp = get_contact(sip);
2580 gchar *hdr = g_strdup_printf(
2581 "Event: presence.wpending\r\n"
2582 "Accept: text/xml+msrtc.wpending\r\n"
2583 "Supported: com.microsoft.autoextend\r\n"
2584 "Supported: ms-benotify\r\n"
2585 "Proxy-Require: ms-benotify\r\n"
2586 "Supported: ms-piggyback-first-notify\r\n"
2587 "Contact: %s\r\n", tmp);
2588 g_free(tmp);
2590 /* Subscription is identified by <event> key */
2591 key = g_strdup_printf("<%s>", "presence.wpending");
2592 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2593 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2595 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2597 g_free(to);
2598 g_free(hdr);
2599 g_free(key);
2603 * Fires on deregistration event initiated by server.
2604 * [MS-SIPREGE] SIP extension.
2607 // 2007 Example
2609 // Content-Type: text/registration-event
2610 // subscription-state: terminated;expires=0
2611 // ms-diagnostics-public: 4141;reason="User disabled"
2613 // deregistered;event=rejected
2615 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2617 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2618 gchar *event = NULL;
2619 gchar *reason = NULL;
2620 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2622 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2623 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2625 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2626 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2627 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2628 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2629 } else {
2630 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2631 return;
2634 if (warning != NULL) {
2635 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2636 } else { // for LCS2005
2637 int error_id = 0;
2638 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2639 error_id = 4140; // [MS-SIPREGE]
2640 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2641 reason = g_strdup(_("you are already signed in at another location"));
2642 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2643 error_id = 4141;
2644 reason = g_strdup(_("user disabled")); // [MS-OCER]
2645 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2646 error_id = 4142;
2647 reason = g_strdup(_("user moved")); // [MS-OCER]
2650 g_free(event);
2651 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2652 g_free(reason);
2654 sip->gc->wants_to_die = TRUE;
2655 purple_connection_error(sip->gc, warning);
2656 g_free(warning);
2660 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2662 xmlnode *xn_provision_group_list;
2663 xmlnode *node;
2665 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2667 /* provisionGroup */
2668 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2669 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2670 g_free(sip->focus_factory_uri);
2671 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2672 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2673 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2674 break;
2677 xmlnode_free(xn_provision_group_list);
2680 /** for 2005 system */
2681 static void
2682 sipe_process_provisioning(struct sipe_account_data *sip,
2683 struct sipmsg *msg)
2685 xmlnode *xn_provision;
2686 xmlnode *node;
2688 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2689 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2690 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2691 if ((node = xmlnode_get_child(node, "line"))) {
2692 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2693 const gchar *server = xmlnode_get_attrib(node, "server");
2694 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2695 sip_csta_open(sip, line_uri, server);
2698 xmlnode_free(xn_provision);
2701 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2703 const gchar *contacts_delta;
2704 xmlnode *xml;
2706 xml = xmlnode_from_str(msg->body, msg->bodylen);
2707 if (!xml)
2709 return;
2712 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2713 if (contacts_delta)
2715 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2718 xmlnode_free(xml);
2721 static void
2722 free_container(struct sipe_container *container)
2724 GSList *entry;
2726 if (!container) return;
2728 entry = container->members;
2729 while (entry) {
2730 g_free(entry->data);
2731 entry = g_slist_remove(entry, entry->data);
2733 g_free(container);
2737 * Finds locally stored MS-PRES container member
2739 static struct sipe_container_member *
2740 sipe_find_container_member(struct sipe_container *container,
2741 const gchar *type,
2742 const gchar *value)
2744 struct sipe_container_member *member;
2745 GSList *entry;
2747 if (container == NULL || type == NULL) {
2748 return NULL;
2751 entry = container->members;
2752 while (entry) {
2753 member = entry->data;
2754 if (!g_strcasecmp(member->type, type)
2755 && ((!member->value && !value)
2756 || (value && member->value && !g_strcasecmp(member->value, value)))
2758 return member;
2760 entry = entry->next;
2762 return NULL;
2766 * Finds locally stored MS-PRES container by id
2768 static struct sipe_container *
2769 sipe_find_container(struct sipe_account_data *sip,
2770 guint id)
2772 struct sipe_container *container;
2773 GSList *entry;
2775 if (sip == NULL) {
2776 return NULL;
2779 entry = sip->containers;
2780 while (entry) {
2781 container = entry->data;
2782 if (id == container->id) {
2783 return container;
2785 entry = entry->next;
2787 return NULL;
2791 * Access Levels
2792 * 32000 - Blocked
2793 * 400 - Personal
2794 * 300 - Team
2795 * 200 - Company
2796 * 100 - Public
2798 static int
2799 sipe_find_access_level(struct sipe_account_data *sip,
2800 const gchar *type,
2801 const gchar *value)
2803 guint containers[] = {32000, 400, 300, 200, 100};
2804 int i = 0;
2806 for (i = 0; i < 5; i++) {
2807 struct sipe_container_member *member;
2808 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2809 if (!container) continue;
2811 member = sipe_find_container_member(container, type, value);
2812 if (member) {
2813 return containers[i];
2817 return -1;
2820 static void
2821 sipe_send_set_container_members(struct sipe_account_data *sip,
2822 guint container_id,
2823 guint container_version,
2824 const gchar* action,
2825 const gchar* type,
2826 const gchar* value)
2828 gchar *self = sip_uri_self(sip);
2829 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2830 gchar *contact;
2831 gchar *hdr;
2832 gchar *body = g_strdup_printf(
2833 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2834 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2835 "</setContainerMembers>",
2836 container_id,
2837 container_version,
2838 action,
2839 type,
2840 value_str);
2841 g_free(value_str);
2843 contact = get_contact(sip);
2844 hdr = g_strdup_printf("Contact: %s\r\n"
2845 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2846 g_free(contact);
2848 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2850 g_free(hdr);
2851 g_free(body);
2852 g_free(self);
2855 static void
2856 free_publication(struct sipe_publication *publication)
2858 g_free(publication->category);
2859 g_free(publication->cal_event_hash);
2860 g_free(publication->note);
2862 g_free(publication->working_hours_xml_str);
2863 g_free(publication->fb_start_str);
2864 g_free(publication->free_busy_base64);
2866 g_free(publication);
2869 /* key is <category><instance><container> */
2870 static gboolean
2871 sipe_is_our_publication(struct sipe_account_data *sip,
2872 const gchar *key)
2874 GSList *entry;
2876 /* filling keys for our publications if not yet cached */
2877 if (!sip->our_publication_keys) {
2878 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2879 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2880 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2881 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2882 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2883 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2884 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2886 /* device */
2887 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2888 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2890 /* state:machineState */
2891 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2892 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2893 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2894 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2896 /* state:userState */
2897 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2898 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2899 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2900 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2902 /* state:calendarState */
2903 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2904 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2905 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2906 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2908 /* state:calendarState OOF */
2909 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2910 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2911 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2912 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2914 /* note */
2915 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2916 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2917 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2918 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2919 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2920 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2922 /* note OOF */
2923 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2924 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2925 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2926 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2927 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2928 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2930 /* calendarData:WorkingHours */
2931 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2932 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2933 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2934 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2935 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2936 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2937 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2938 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2939 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2940 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2941 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2942 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2944 /* calendarData:FreeBusy */
2945 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2946 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2947 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2948 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2949 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2950 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2951 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2952 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2953 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2954 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2955 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2956 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2958 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2959 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2962 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
2964 entry = sip->our_publication_keys;
2965 while (entry) {
2966 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
2967 if (!strcmp(entry->data, key)) {
2968 return TRUE;
2970 entry = entry->next;
2972 return FALSE;
2975 /** Property names to store in blist.xml */
2976 #define ALIAS_PROP "alias"
2977 #define EMAIL_PROP "email"
2978 #define PHONE_PROP "phone"
2979 #define PHONE_DISPLAY_PROP "phone-display"
2980 #define PHONE_MOBILE_PROP "phone-mobile"
2981 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2982 #define PHONE_HOME_PROP "phone-home"
2983 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2984 #define PHONE_OTHER_PROP "phone-other"
2985 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2986 #define PHONE_CUSTOM1_PROP "phone-custom1"
2987 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2988 #define SITE_PROP "site"
2989 #define COMPANY_PROP "company"
2990 #define DEPARTMENT_PROP "department"
2991 #define TITLE_PROP "title"
2992 #define OFFICE_PROP "office"
2993 /** implies work address */
2994 #define ADDRESS_STREET_PROP "address-street"
2995 #define ADDRESS_CITY_PROP "address-city"
2996 #define ADDRESS_STATE_PROP "address-state"
2997 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2998 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3000 * Update user information
3002 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3003 * @param property_name
3004 * @param property_value may be modified to strip white space
3006 static void
3007 sipe_update_user_info(struct sipe_account_data *sip,
3008 const char *uri,
3009 const char *property_name,
3010 char *property_value)
3012 GSList *buddies, *entry;
3014 if (!property_name || strlen(property_name) == 0) return;
3016 if (property_value)
3017 property_value = g_strstrip(property_value);
3019 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3020 while (entry) {
3021 const char *prop_str;
3022 const char *server_alias;
3023 PurpleBuddy *p_buddy = entry->data;
3025 /* for Display Name */
3026 if (!strcmp(property_name, ALIAS_PROP)) {
3027 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3028 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3029 purple_blist_alias_buddy(p_buddy, property_value);
3032 server_alias = purple_buddy_get_server_alias(p_buddy);
3033 if (property_value && strlen(property_value) > 0 &&
3034 ( (server_alias && strcmp(property_value, server_alias))
3035 || !server_alias || strlen(server_alias) == 0 )
3037 purple_blist_server_alias_buddy(p_buddy, property_value);
3040 /* for other properties */
3041 else {
3042 if (property_value && strlen(property_value) > 0) {
3043 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3044 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3045 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3050 entry = entry->next;
3052 g_slist_free(buddies);
3056 * Update user phone
3057 * Suitable for both 2005 and 2007 systems.
3059 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3060 * @param phone_type
3061 * @param phone may be modified to strip white space
3062 * @param phone_display_string may be modified to strip white space
3064 static void
3065 sipe_update_user_phone(struct sipe_account_data *sip,
3066 const char *uri,
3067 const gchar *phone_type,
3068 gchar *phone,
3069 gchar *phone_display_string)
3071 const char *phone_node = PHONE_PROP; /* work phone by default */
3072 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3074 if(!phone || strlen(phone) == 0) return;
3076 if (phone_type && (!strcmp(phone_type, "mobile") || !strcmp(phone_type, "cell"))) {
3077 phone_node = PHONE_MOBILE_PROP;
3078 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3079 } else if (phone_type && !strcmp(phone_type, "home")) {
3080 phone_node = PHONE_HOME_PROP;
3081 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3082 } else if (phone_type && !strcmp(phone_type, "other")) {
3083 phone_node = PHONE_OTHER_PROP;
3084 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3085 } else if (phone_type && !strcmp(phone_type, "custom1")) {
3086 phone_node = PHONE_CUSTOM1_PROP;
3087 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3090 sipe_update_user_info(sip, uri, phone_node, phone);
3091 if (phone_display_string) {
3092 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3096 static void
3097 sipe_update_calendar(struct sipe_account_data *sip)
3099 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3101 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3103 if (!strcmp(calendar, "EXCH")) {
3104 sipe_ews_update_calendar(sip);
3107 /* schedule repeat */
3108 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3110 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3114 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3115 * by using standard Purple's means of signals and saved statuses.
3117 * Thus all UI elements get updated: Status Button with Note, docklet.
3118 * This is ablolutely important as both our status and note can come
3119 * inbound (roaming) or be updated programmatically (e.g. based on our
3120 * calendar data).
3122 static void
3123 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3124 const char *status_id,
3125 const char *message,
3126 time_t do_not_publish[])
3128 PurpleStatus *status = purple_account_get_active_status(account);
3129 gboolean changed = TRUE;
3131 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3132 purple_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3134 changed = FALSE;
3137 if (changed) {
3138 PurpleSavedStatus *saved_status;
3139 const PurpleStatusType *acct_status_type =
3140 purple_status_type_find_with_id(account->status_types, status_id);
3141 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3142 sipe_activity activity = sipe_get_activity_by_token(status_id);
3144 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3145 if (saved_status) {
3146 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3149 /* If this type+message is unique then create a new transient saved status
3150 * Ref: gtkstatusbox.c
3152 if (!saved_status) {
3153 GList *tmp;
3154 GList *active_accts = purple_accounts_get_all_active();
3156 saved_status = purple_savedstatus_new(NULL, primitive);
3157 purple_savedstatus_set_message(saved_status, message);
3159 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3160 purple_savedstatus_set_substatus(saved_status,
3161 (PurpleAccount *)tmp->data, acct_status_type, message);
3163 g_list_free(active_accts);
3166 do_not_publish[activity] = time(NULL);
3167 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3168 status_id, (int)do_not_publish[activity]);
3170 /* Set the status for each account */
3171 purple_savedstatus_activate(saved_status);
3175 static void
3176 send_publish_category_initial(struct sipe_account_data *sip);
3179 * When we receive some self (BE) NOTIFY with a new subscriber
3180 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3183 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3185 gchar *contact;
3186 gchar *to;
3187 xmlnode *xml;
3188 xmlnode *node;
3189 xmlnode *node2;
3190 char *display_name = NULL;
3191 char *uri;
3192 GSList *category_names = NULL;
3193 int aggreg_avail = 0;
3194 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3195 gboolean do_update_status = FALSE;
3197 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3199 xml = xmlnode_from_str(msg->body, msg->bodylen);
3200 if (!xml) return;
3202 contact = get_contact(sip);
3203 to = sip_uri_self(sip);
3206 /* categories */
3207 /* set list of categories participating in this XML */
3208 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3209 const gchar *name = xmlnode_get_attrib(node, "name");
3210 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3212 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3213 category_names ? (int) g_slist_length(category_names) : -1);
3214 /* drop category information */
3215 if (category_names) {
3216 GSList *entry = category_names;
3217 while (entry) {
3218 GHashTable *cat_publications;
3219 const gchar *category = entry->data;
3220 entry = entry->next;
3221 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3222 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3223 if (cat_publications) {
3224 g_hash_table_remove(sip->our_publications, category);
3225 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3229 g_slist_free(category_names);
3230 /* filling our categories reflected in roaming data */
3231 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3232 const gchar *name = xmlnode_get_attrib(node, "name");
3233 const gchar *container = xmlnode_get_attrib(node, "container");
3234 const gchar *instance = xmlnode_get_attrib(node, "instance");
3235 const gchar *version = xmlnode_get_attrib(node, "version");
3236 guint version_int = version ? atoi(version) : 0;
3237 gchar *key;
3239 if (!container || !instance) continue;
3241 /* key is <category><instance><container> */
3242 key = g_strdup_printf("<%s><%s><%s>", name, instance, container);
3243 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version_int);
3245 /* capture all userState publication for later clean up if required */
3246 if (!strcmp(name, "state") && (atoi(container) == 2 || atoi(container) == 3)) {
3247 xmlnode *xn_state = xmlnode_get_child(node, "state");
3249 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3250 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3251 publication->category = g_strdup(name);
3252 publication->instance = atoi(instance);
3253 publication->container = atoi(container);
3254 publication->version = version_int;
3256 if (!sip->user_state_publications) {
3257 sip->user_state_publications = g_hash_table_new_full(
3258 g_str_hash, g_str_equal,
3259 g_free, (GDestroyNotify)free_publication);
3261 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3262 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3263 key, version_int);
3267 if (sipe_is_our_publication(sip, key)) {
3268 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3270 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3271 publication->category = g_strdup(name);
3272 publication->instance = atoi(instance);
3273 publication->container = atoi(container);
3274 publication->version = version_int;
3275 /* filling publication->availability */
3276 if (!strcmp(name, "state")) {
3277 xmlnode *xn_state = xmlnode_get_child(node, "state");
3278 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3280 if (xn_avail) {
3281 gchar *avail_str = xmlnode_get_data(xn_avail);
3282 if (avail_str) {
3283 publication->availability = atoi(avail_str);
3285 g_free(avail_str);
3287 /* for calendarState */
3288 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3289 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3290 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3292 event->start_time = purple_str_to_time(xmlnode_get_attrib(xn_state, "startTime"),
3293 FALSE, NULL, NULL, NULL);
3294 if (xn_activity) {
3295 if (!strcmp(xmlnode_get_attrib(xn_activity, "token"),
3296 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3298 event->is_meeting = TRUE;
3301 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3302 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3304 publication->cal_event_hash = sipe_cal_event_hash(event);
3305 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3306 publication->cal_event_hash);
3307 sipe_cal_event_free(event);
3310 /* filling publication->note */
3311 if (!strcmp(name, "note")) {
3312 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3314 g_free(sip->note);
3315 if (xn_body) {
3316 char *tmp;
3318 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3319 g_free(tmp);
3320 sip->note = g_strdup(publication->note);
3322 do_update_status = TRUE;
3325 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3326 if (!strcmp(name, "calendarData") && (publication->container == 300)) {
3327 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3328 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3329 if (xn_free_busy) {
3330 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3331 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3333 if (xn_working_hours) {
3334 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3338 if (!cat_publications) {
3339 cat_publications = g_hash_table_new_full(
3340 g_str_hash, g_str_equal,
3341 g_free, (GDestroyNotify)free_publication);
3342 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3343 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3345 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3346 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version_int);
3348 g_free(key);
3350 /* aggregateState (not an our publication) from 2-nd container */
3351 if (!strcmp(name, "state") && atoi(container) == 2) {
3352 xmlnode *xn_state = xmlnode_get_child(node, "state");
3354 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3355 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3356 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3358 if (xn_avail) {
3359 gchar *avail_str = xmlnode_get_data(xn_avail);
3360 if (avail_str) {
3361 aggreg_avail = atoi(avail_str);
3363 g_free(avail_str);
3366 if (xn_activity) {
3367 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3369 aggreg_activity = sipe_get_activity_by_token(activity_token);
3372 do_update_status = TRUE;
3376 /* userProperties published by server from AD */
3377 if (!sip->csta && !strcmp(name, "userProperties")) {
3378 xmlnode *line;
3379 /* line, for Remote Call Control (RCC) */
3380 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3381 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3382 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3383 gchar *line_uri;
3385 if (!line_server || (strcmp(line_type, "Rcc") && strcmp(line_type, "Dual"))) continue;
3387 line_uri = xmlnode_get_data(line);
3388 if (line_uri) {
3389 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3390 sip_csta_open(sip, line_uri, line_server);
3392 g_free(line_uri);
3394 break;
3398 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3399 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3401 /* containers */
3402 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3403 guint id = atoi(xmlnode_get_attrib(node, "id"));
3404 struct sipe_container *container = sipe_find_container(sip, id);
3406 if (container) {
3407 sip->containers = g_slist_remove(sip->containers, container);
3408 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3409 free_container(container);
3411 container = g_new0(struct sipe_container, 1);
3412 container->id = id;
3413 container->version = atoi(xmlnode_get_attrib(node, "version"));
3414 sip->containers = g_slist_append(sip->containers, container);
3415 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3417 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3418 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3419 member->type = xmlnode_get_attrib(node2, "type");
3420 member->value = xmlnode_get_attrib(node2, "value");
3421 container->members = g_slist_append(container->members, member);
3422 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3423 member->type, member->value ? member->value : "");
3427 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3428 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3429 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3430 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3431 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3432 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3433 /* initial set-up to let counterparties see your status */
3434 if (sameEnterpriseAL < 0) {
3435 struct sipe_container *container = sipe_find_container(sip, 200);
3436 guint version = container ? container->version : 0;
3437 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3439 if (federatedAL < 0) {
3440 struct sipe_container *container = sipe_find_container(sip, 100);
3441 guint version = container ? container->version : 0;
3442 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3444 sip->access_level_set = TRUE;
3447 /* subscribers */
3448 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3449 const char *user;
3450 const char *acknowledged;
3451 gchar *hdr;
3452 gchar *body;
3454 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3455 if (!user) continue;
3456 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3457 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3458 uri = sip_uri_from_name(user);
3460 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3462 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3463 if(!g_ascii_strcasecmp(acknowledged,"false")){
3464 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3465 if (!purple_find_buddy(sip->account, uri)) {
3466 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3469 hdr = g_strdup_printf(
3470 "Contact: %s\r\n"
3471 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3473 body = g_strdup_printf(
3474 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3475 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3476 "</setSubscribers>", user);
3478 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3479 g_free(body);
3480 g_free(hdr);
3482 g_free(display_name);
3483 g_free(uri);
3486 g_free(contact);
3487 xmlnode_free(xml);
3489 /* Publish initial state if not yet.
3490 * Assuming this happens on initial responce to subscription to roaming-self
3491 * so we've already updated our roaming data in full.
3492 * Only for 2007+
3494 if (!sip->initial_state_published) {
3495 send_publish_category_initial(sip);
3496 sip->initial_state_published = TRUE;
3497 /* dalayed run */
3498 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3499 do_update_status = FALSE;
3500 } else if (aggreg_avail) {
3502 g_free(sip->status);
3503 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3504 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3505 } else {
3506 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3510 if (do_update_status) {
3511 purple_debug_info("sipe", "sipe_process_roaming_self: to %s for the account\n", sip->status);
3512 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3515 g_free(to);
3518 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3520 gchar *to = sip_uri_self(sip);
3521 gchar *tmp = get_contact(sip);
3522 gchar *hdr = g_strdup_printf(
3523 "Event: vnd-microsoft-roaming-ACL\r\n"
3524 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3525 "Supported: com.microsoft.autoextend\r\n"
3526 "Supported: ms-benotify\r\n"
3527 "Proxy-Require: ms-benotify\r\n"
3528 "Supported: ms-piggyback-first-notify\r\n"
3529 "Contact: %s\r\n", tmp);
3530 g_free(tmp);
3532 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3533 g_free(to);
3534 g_free(hdr);
3538 * To request for presence information about the user, access level settings that have already been configured by the user
3539 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3540 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3543 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3545 gchar *to = sip_uri_self(sip);
3546 gchar *tmp = get_contact(sip);
3547 gchar *hdr = g_strdup_printf(
3548 "Event: vnd-microsoft-roaming-self\r\n"
3549 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3550 "Supported: ms-benotify\r\n"
3551 "Proxy-Require: ms-benotify\r\n"
3552 "Supported: ms-piggyback-first-notify\r\n"
3553 "Contact: %s\r\n"
3554 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3556 gchar *body=g_strdup(
3557 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3558 "<roaming type=\"categories\"/>"
3559 "<roaming type=\"containers\"/>"
3560 "<roaming type=\"subscribers\"/></roamingList>");
3562 g_free(tmp);
3563 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3564 g_free(body);
3565 g_free(to);
3566 g_free(hdr);
3570 * For 2005 version
3572 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3574 gchar *to = sip_uri_self(sip);
3575 gchar *tmp = get_contact(sip);
3576 gchar *hdr = g_strdup_printf(
3577 "Event: vnd-microsoft-provisioning\r\n"
3578 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3579 "Supported: com.microsoft.autoextend\r\n"
3580 "Supported: ms-benotify\r\n"
3581 "Proxy-Require: ms-benotify\r\n"
3582 "Supported: ms-piggyback-first-notify\r\n"
3583 "Expires: 0\r\n"
3584 "Contact: %s\r\n", tmp);
3586 g_free(tmp);
3587 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3588 g_free(to);
3589 g_free(hdr);
3592 /** Subscription for provisioning information to help with initial
3593 * configuration. This subscription is a one-time query (denoted by the Expires header,
3594 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3595 * configuration, meeting policies, and policy settings that Communicator must enforce.
3596 * TODO: for what we need this information.
3599 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3601 gchar *to = sip_uri_self(sip);
3602 gchar *tmp = get_contact(sip);
3603 gchar *hdr = g_strdup_printf(
3604 "Event: vnd-microsoft-provisioning-v2\r\n"
3605 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3606 "Supported: com.microsoft.autoextend\r\n"
3607 "Supported: ms-benotify\r\n"
3608 "Proxy-Require: ms-benotify\r\n"
3609 "Supported: ms-piggyback-first-notify\r\n"
3610 "Expires: 0\r\n"
3611 "Contact: %s\r\n"
3612 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3613 gchar *body = g_strdup(
3614 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3615 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3616 "<provisioningGroup name=\"ucPolicy\"/>"
3617 "</provisioningGroupList>");
3619 g_free(tmp);
3620 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3621 g_free(body);
3622 g_free(to);
3623 g_free(hdr);
3626 static void
3627 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3628 gpointer value, gpointer user_data)
3630 struct sip_subscription *subscription = value;
3631 struct sip_dialog *dialog = &subscription->dialog;
3632 struct sipe_account_data *sip = user_data;
3633 gchar *tmp = get_contact(sip);
3634 gchar *hdr = g_strdup_printf(
3635 "Event: %s\r\n"
3636 "Expires: 0\r\n"
3637 "Contact: %s\r\n", subscription->event, tmp);
3638 g_free(tmp);
3640 /* Rate limit to max. 25 requests per seconds */
3641 g_usleep(1000000 / 25);
3643 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3644 g_free(hdr);
3647 /* IM Session (INVITE and MESSAGE methods) */
3649 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3650 static gchar *
3651 get_end_points (struct sipe_account_data *sip,
3652 struct sip_session *session)
3654 gchar *res;
3656 if (session == NULL) {
3657 return NULL;
3660 res = g_strdup_printf("<sip:%s>", sip->username);
3662 SIPE_DIALOG_FOREACH {
3663 gchar *tmp = res;
3664 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3665 g_free(tmp);
3667 if (dialog->theirepid) {
3668 tmp = res;
3669 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3670 g_free(tmp);
3672 } SIPE_DIALOG_FOREACH_END;
3674 return res;
3677 static gboolean
3678 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3679 struct sipmsg *msg,
3680 SIPE_UNUSED_PARAMETER struct transaction *trans)
3682 gboolean ret = TRUE;
3684 if (msg->response != 200) {
3685 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3686 return FALSE;
3689 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3691 return ret;
3695 * Asks UA/proxy about its capabilities.
3697 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3699 gchar *to = sip_uri(who);
3700 gchar *contact = get_contact(sip);
3701 gchar *request = g_strdup_printf(
3702 "Accept: application/sdp\r\n"
3703 "Contact: %s\r\n", contact);
3704 g_free(contact);
3706 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3708 g_free(to);
3709 g_free(request);
3712 static void
3713 sipe_notify_user(struct sipe_account_data *sip,
3714 struct sip_session *session,
3715 PurpleMessageFlags flags,
3716 const gchar *message)
3718 PurpleConversation *conv;
3720 if (!session->conv) {
3721 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3722 } else {
3723 conv = session->conv;
3725 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3728 void
3729 sipe_present_info(struct sipe_account_data *sip,
3730 struct sip_session *session,
3731 const gchar *message)
3733 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3736 static void
3737 sipe_present_err(struct sipe_account_data *sip,
3738 struct sip_session *session,
3739 const gchar *message)
3741 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3744 void
3745 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3746 struct sip_session *session,
3747 int sip_error,
3748 const gchar *who,
3749 const gchar *message)
3751 char *msg, *msg_tmp, *msg_tmp2;
3752 const char *label;
3754 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
3755 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3756 g_free(msg_tmp);
3757 /* Service unavailable; Server Internal Error; Server Time-out */
3758 if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3759 label = _("This message was not delivered to %s because the service is not available");
3760 } else if (sip_error == 486) { /* Busy Here */
3761 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3762 } else {
3763 label = _("This message was not delivered to %s because one or more recipients are offline");
3766 msg_tmp = g_strdup_printf( "%s:\n%s" ,
3767 msg_tmp2 = g_strdup_printf(label, who ? who : ""), msg ? msg : "");
3768 sipe_present_err(sip, session, msg_tmp);
3769 g_free(msg_tmp2);
3770 g_free(msg_tmp);
3771 g_free(msg);
3775 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
3777 static gboolean
3778 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
3779 SIPE_UNUSED_PARAMETER struct transaction *trans)
3781 gboolean ret = TRUE;
3782 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3783 struct sip_session *session = sipe_session_find_im(sip, with);
3784 struct sip_dialog *dialog;
3785 gchar *cseq;
3786 char *key;
3787 gchar *message;
3789 if (!session) {
3790 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3791 g_free(with);
3792 return FALSE;
3795 dialog = sipe_dialog_find(session, with);
3796 if (!dialog) {
3797 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3798 g_free(with);
3799 return FALSE;
3802 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3803 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3804 g_free(cseq);
3805 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3807 if (msg->response >= 400) {
3808 PurpleBuddy *pbuddy;
3809 gchar *alias = with;
3811 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3813 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3814 alias = (gchar *)purple_buddy_get_alias(pbuddy);
3817 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
3818 ret = FALSE;
3819 } else {
3820 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3821 if (message_id) {
3822 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
3823 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
3824 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3827 g_hash_table_remove(session->unconfirmed_messages, key);
3828 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
3829 key, g_hash_table_size(session->unconfirmed_messages));
3832 g_free(key);
3833 g_free(with);
3835 if (ret) sipe_im_process_queue(sip, session);
3836 return ret;
3839 static gboolean
3840 sipe_is_election_finished(struct sip_session *session);
3842 static void
3843 sipe_election_result(struct sipe_account_data *sip,
3844 void *sess);
3846 static gboolean
3847 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
3848 SIPE_UNUSED_PARAMETER struct transaction *trans)
3850 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3851 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3852 struct sip_dialog *dialog;
3853 struct sip_session *session;
3855 session = sipe_session_find_chat_by_callid(sip, callid);
3856 if (!session) {
3857 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
3858 return FALSE;
3861 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
3862 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3863 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
3864 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
3866 if (xn_request_rm_response) {
3867 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
3868 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
3870 dialog = sipe_dialog_find(session, with);
3871 if (!dialog) {
3872 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
3873 return FALSE;
3876 if (allow && !g_strcasecmp(allow, "true")) {
3877 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
3878 dialog->election_vote = 1;
3879 } else if (allow && !g_strcasecmp(allow, "false")) {
3880 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
3881 dialog->election_vote = -1;
3884 if (sipe_is_election_finished(session)) {
3885 sipe_election_result(sip, session);
3888 } else if (xn_set_rm_response) {
3891 xmlnode_free(xn_action);
3895 return TRUE;
3898 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
3900 gchar *hdr;
3901 gchar *tmp;
3902 char *msgformat;
3903 char *msgtext;
3904 gchar *msgr_value;
3905 gchar *msgr;
3907 sipe_parse_html(msg, &msgformat, &msgtext);
3908 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
3910 msgr_value = sipmsg_get_msgr_string(msgformat);
3911 g_free(msgformat);
3912 if (msgr_value) {
3913 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3914 g_free(msgr_value);
3915 } else {
3916 msgr = g_strdup("");
3919 tmp = get_contact(sip);
3920 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3921 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3922 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3923 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
3924 g_free(tmp);
3925 g_free(msgr);
3927 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
3928 g_free(msgtext);
3929 g_free(hdr);
3933 static void
3934 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
3936 GSList *entry2 = session->outgoing_message_queue;
3937 while (entry2) {
3938 char *queued_msg = entry2->data;
3940 /* for multiparty chat or conference */
3941 if (session->is_multiparty || session->focus_uri) {
3942 gchar *who = sip_uri_self(sip);
3943 serv_got_chat_in(sip->gc, session->chat_id, who,
3944 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
3945 g_free(who);
3948 SIPE_DIALOG_FOREACH {
3949 char *key;
3951 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3953 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3954 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
3955 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
3956 key, g_hash_table_size(session->unconfirmed_messages));
3957 g_free(key);
3959 sipe_send_message(sip, dialog, queued_msg);
3960 } SIPE_DIALOG_FOREACH_END;
3962 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3963 g_free(queued_msg);
3967 static void
3968 sipe_refer_notify(struct sipe_account_data *sip,
3969 struct sip_session *session,
3970 const gchar *who,
3971 int status,
3972 const gchar *desc)
3974 gchar *hdr;
3975 gchar *body;
3976 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3978 hdr = g_strdup_printf(
3979 "Event: refer\r\n"
3980 "Subscription-State: %s\r\n"
3981 "Content-Type: message/sipfrag\r\n",
3982 status >= 200 ? "terminated" : "active");
3984 body = g_strdup_printf(
3985 "SIP/2.0 %d %s\r\n",
3986 status, desc);
3988 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
3990 g_free(hdr);
3991 g_free(body);
3994 static gboolean
3995 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
3997 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3998 struct sip_session *session;
3999 struct sip_dialog *dialog;
4000 char *cseq;
4001 char *key;
4002 gchar *message;
4003 struct sipmsg *request_msg = trans->msg;
4005 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4006 gchar *referred_by;
4008 session = sipe_session_find_chat_by_callid(sip, callid);
4009 if (!session) {
4010 session = sipe_session_find_im(sip, with);
4012 if (!session) {
4013 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4014 g_free(with);
4015 return FALSE;
4018 dialog = sipe_dialog_find(session, with);
4019 if (!dialog) {
4020 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4021 g_free(with);
4022 return FALSE;
4025 sipe_dialog_parse(dialog, msg, TRUE);
4027 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4028 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4029 g_free(cseq);
4030 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4032 if (msg->response != 200) {
4033 PurpleBuddy *pbuddy;
4034 gchar *alias = with;
4036 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4038 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4039 alias = (gchar *)purple_buddy_get_alias(pbuddy);
4042 if (message) {
4043 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
4044 } else {
4045 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4046 sipe_present_err(sip, session, tmp_msg);
4047 g_free(tmp_msg);
4050 sipe_dialog_remove(session, with);
4052 g_free(key);
4053 g_free(with);
4054 return FALSE;
4057 dialog->cseq = 0;
4058 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4059 dialog->outgoing_invite = NULL;
4060 dialog->is_established = TRUE;
4062 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4063 if (referred_by) {
4064 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4065 g_free(referred_by);
4068 /* add user to chat if it is a multiparty session */
4069 if (session->is_multiparty) {
4070 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4071 with, NULL,
4072 PURPLE_CBFLAGS_NONE, TRUE);
4075 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4076 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4077 if (session->outgoing_message_queue) {
4078 char *queued_msg = session->outgoing_message_queue->data;
4079 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
4080 g_free(queued_msg);
4084 sipe_im_process_queue(sip, session);
4086 g_hash_table_remove(session->unconfirmed_messages, key);
4087 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4088 key, g_hash_table_size(session->unconfirmed_messages));
4090 g_free(key);
4091 g_free(with);
4092 return TRUE;
4096 void
4097 sipe_invite(struct sipe_account_data *sip,
4098 struct sip_session *session,
4099 const gchar *who,
4100 const gchar *msg_body,
4101 const gchar *referred_by,
4102 const gboolean is_triggered)
4104 gchar *hdr;
4105 gchar *to;
4106 gchar *contact;
4107 gchar *body;
4108 gchar *self;
4109 char *ms_text_format = NULL;
4110 gchar *roster_manager;
4111 gchar *end_points;
4112 gchar *referred_by_str;
4113 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4115 if (dialog && dialog->is_established) {
4116 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4117 return;
4120 if (!dialog) {
4121 dialog = sipe_dialog_add(session);
4122 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4123 dialog->with = g_strdup(who);
4126 if (!(dialog->ourtag)) {
4127 dialog->ourtag = gentag();
4130 to = sip_uri(who);
4132 if (msg_body) {
4133 char *msgformat;
4134 char *msgtext;
4135 char *base64_msg;
4136 gchar *msgr_value;
4137 gchar *msgr;
4138 char *key;
4140 sipe_parse_html(msg_body, &msgformat, &msgtext);
4141 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4143 msgr_value = sipmsg_get_msgr_string(msgformat);
4144 g_free(msgformat);
4145 msgr = "";
4146 if (msgr_value) {
4147 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4148 g_free(msgr_value);
4151 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4152 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
4153 g_free(msgtext);
4154 g_free(msgr);
4155 g_free(base64_msg);
4157 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4158 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
4159 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4160 key, g_hash_table_size(session->unconfirmed_messages));
4161 g_free(key);
4164 contact = get_contact(sip);
4165 end_points = get_end_points(sip, session);
4166 self = sip_uri_self(sip);
4167 roster_manager = g_strdup_printf(
4168 "Roster-Manager: %s\r\n"
4169 "EndPoints: %s\r\n",
4170 self,
4171 end_points);
4172 referred_by_str = referred_by ?
4173 g_strdup_printf(
4174 "Referred-By: %s\r\n",
4175 referred_by)
4176 : g_strdup("");
4177 hdr = g_strdup_printf(
4178 "Supported: ms-sender\r\n"
4179 "%s"
4180 "%s"
4181 "%s"
4182 "%s"
4183 "Contact: %s\r\n%s"
4184 "Content-Type: application/sdp\r\n",
4185 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
4186 referred_by_str,
4187 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4188 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4189 contact,
4190 ms_text_format ? ms_text_format : "");
4191 g_free(ms_text_format);
4192 g_free(self);
4194 body = g_strdup_printf(
4195 "v=0\r\n"
4196 "o=- 0 0 IN IP4 %s\r\n"
4197 "s=session\r\n"
4198 "c=IN IP4 %s\r\n"
4199 "t=0 0\r\n"
4200 "m=%s %d sip null\r\n"
4201 "a=accept-types:text/plain text/html image/gif "
4202 "multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4203 purple_network_get_my_ip(-1),
4204 purple_network_get_my_ip(-1),
4205 sip->ocs2007 ? "message" : "x-ms-message",
4206 sip->realport);
4208 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4209 to, to, hdr, body, dialog, process_invite_response);
4211 g_free(to);
4212 g_free(roster_manager);
4213 g_free(end_points);
4214 g_free(referred_by_str);
4215 g_free(body);
4216 g_free(hdr);
4217 g_free(contact);
4220 static void
4221 sipe_refer(struct sipe_account_data *sip,
4222 struct sip_session *session,
4223 const gchar *who)
4225 gchar *hdr;
4226 gchar *contact;
4227 gchar *epid = get_epid(sip);
4228 struct sip_dialog *dialog = sipe_dialog_find(session,
4229 session->roster_manager);
4230 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4232 contact = get_contact(sip);
4233 hdr = g_strdup_printf(
4234 "Contact: %s\r\n"
4235 "Refer-to: <%s>\r\n"
4236 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4237 "Require: com.microsoft.rtc-multiparty\r\n",
4238 contact,
4239 who,
4240 sip->username,
4241 ourtag ? ";tag=" : "",
4242 ourtag ? ourtag : "",
4243 epid);
4244 g_free(epid);
4246 send_sip_request(sip->gc, "REFER",
4247 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4249 g_free(hdr);
4250 g_free(contact);
4253 static void
4254 sipe_send_election_request_rm(struct sipe_account_data *sip,
4255 struct sip_dialog *dialog,
4256 int bid)
4258 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4260 gchar *body = g_strdup_printf(
4261 "<?xml version=\"1.0\"?>\r\n"
4262 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4263 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4264 sip->username, bid);
4266 send_sip_request(sip->gc, "INFO",
4267 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4269 g_free(body);
4272 static void
4273 sipe_send_election_set_rm(struct sipe_account_data *sip,
4274 struct sip_dialog *dialog)
4276 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4278 gchar *body = g_strdup_printf(
4279 "<?xml version=\"1.0\"?>\r\n"
4280 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4281 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4282 sip->username);
4284 send_sip_request(sip->gc, "INFO",
4285 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4287 g_free(body);
4290 static void
4291 sipe_session_close(struct sipe_account_data *sip,
4292 struct sip_session * session)
4294 if (session && session->focus_uri) {
4295 sipe_conf_immcu_closed(sip, session);
4296 conf_session_close(sip, session);
4299 if (session) {
4300 SIPE_DIALOG_FOREACH {
4301 /* @TODO slow down BYE message sending rate */
4302 /* @see single subscription code */
4303 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4304 } SIPE_DIALOG_FOREACH_END;
4306 sipe_session_remove(sip, session);
4310 static void
4311 sipe_session_close_all(struct sipe_account_data *sip)
4313 GSList *entry;
4314 while ((entry = sip->sessions) != NULL) {
4315 sipe_session_close(sip, entry->data);
4319 static void
4320 sipe_convo_closed(PurpleConnection * gc, const char *who)
4322 struct sipe_account_data *sip = gc->proto_data;
4324 purple_debug_info("sipe", "conversation with %s closed\n", who);
4325 sipe_session_close(sip, sipe_session_find_im(sip, who));
4328 static void
4329 sipe_chat_leave (PurpleConnection *gc, int id)
4331 struct sipe_account_data *sip = gc->proto_data;
4332 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4334 sipe_session_close(sip, session);
4337 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4338 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4340 struct sipe_account_data *sip = gc->proto_data;
4341 struct sip_session *session;
4342 struct sip_dialog *dialog;
4343 gchar *uri = sip_uri(who);
4345 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4347 session = sipe_session_find_or_add_im(sip, uri);
4348 dialog = sipe_dialog_find(session, uri);
4350 // Queue the message
4351 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
4353 if (dialog && !dialog->outgoing_invite) {
4354 sipe_im_process_queue(sip, session);
4355 } else if (!dialog || !dialog->outgoing_invite) {
4356 // Need to send the INVITE to get the outgoing dialog setup
4357 sipe_invite(sip, session, uri, what, NULL, FALSE);
4360 g_free(uri);
4361 return 1;
4364 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4365 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4367 struct sipe_account_data *sip = gc->proto_data;
4368 struct sip_session *session;
4370 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4372 session = sipe_session_find_chat_by_id(sip, id);
4374 // Queue the message
4375 if (session && session->dialogs) {
4376 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4377 g_strdup(what));
4378 sipe_im_process_queue(sip, session);
4379 } else if (sip) {
4380 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4381 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4383 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4384 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4386 if (sip->ocs2007) {
4387 struct sip_session *session = sipe_session_add_chat(sip);
4389 session->is_multiparty = FALSE;
4390 session->focus_uri = g_strdup(proto_chat_id);
4391 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4392 g_strdup(what));
4393 sipe_invite_conf_focus(sip, session);
4397 return 1;
4400 /* End IM Session (INVITE and MESSAGE methods) */
4402 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4404 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4405 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4406 gchar *from;
4407 struct sip_session *session;
4409 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4411 /* Call Control protocol */
4412 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4414 process_incoming_info_csta(sip, msg);
4415 return;
4418 from = parse_from(sipmsg_find_header(msg, "From"));
4419 session = sipe_session_find_chat_by_callid(sip, callid);
4420 if (!session) {
4421 session = sipe_session_find_im(sip, from);
4423 if (!session) {
4424 g_free(from);
4425 return;
4428 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4430 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4431 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4432 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4434 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4436 if (xn_request_rm) {
4437 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4438 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
4439 gchar *body = g_strdup_printf(
4440 "<?xml version=\"1.0\"?>\r\n"
4441 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4442 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4443 sip->username,
4444 session->bid < bid ? "true" : "false");
4445 send_sip_response(sip->gc, msg, 200, "OK", body);
4446 g_free(body);
4447 } else if (xn_set_rm) {
4448 gchar *body;
4449 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4450 g_free(session->roster_manager);
4451 session->roster_manager = g_strdup(rm);
4453 body = g_strdup_printf(
4454 "<?xml version=\"1.0\"?>\r\n"
4455 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4456 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4457 sip->username);
4458 send_sip_response(sip->gc, msg, 200, "OK", body);
4459 g_free(body);
4461 xmlnode_free(xn_action);
4464 else
4466 /* looks like purple lacks typing notification for chat */
4467 if (!session->is_multiparty && !session->focus_uri) {
4468 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4469 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4470 "status");
4471 if (status && !strcmp(status, "type")) {
4472 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4473 } else if (status && !strcmp(status, "idle")) {
4474 serv_got_typing_stopped(sip->gc, from);
4476 xmlnode_free(xn_keyboard_activity);
4479 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4481 g_free(from);
4484 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4486 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4487 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4488 struct sip_session *session;
4489 struct sip_dialog *dialog;
4491 /* collect dialog identification
4492 * we need callid, ourtag and theirtag to unambiguously identify dialog
4494 /* take data before 'msg' will be modified by send_sip_response */
4495 dialog = g_new0(struct sip_dialog, 1);
4496 dialog->callid = g_strdup(callid);
4497 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4498 dialog->with = g_strdup(from);
4499 sipe_dialog_parse(dialog, msg, FALSE);
4501 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4503 session = sipe_session_find_chat_by_callid(sip, callid);
4504 if (!session) {
4505 session = sipe_session_find_im(sip, from);
4507 if (!session) {
4508 g_free(from);
4509 return;
4512 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4513 g_free(session->roster_manager);
4514 session->roster_manager = NULL;
4517 /* This what BYE is essentially for - terminating dialog */
4518 sipe_dialog_remove_3(session, dialog);
4519 sipe_dialog_free(dialog);
4520 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4521 sipe_conf_immcu_closed(sip, session);
4522 } else if (session->is_multiparty) {
4523 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4526 g_free(from);
4529 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4531 gchar *self = sip_uri_self(sip);
4532 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4533 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4534 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4535 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4536 struct sip_session *session;
4537 struct sip_dialog *dialog;
4539 session = sipe_session_find_chat_by_callid(sip, callid);
4540 dialog = sipe_dialog_find(session, from);
4542 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
4543 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4544 } else {
4545 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4547 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
4550 g_free(self);
4551 g_free(from);
4552 g_free(refer_to);
4553 g_free(referred_by);
4556 static unsigned int
4557 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4559 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4560 struct sip_session *session;
4561 struct sip_dialog *dialog;
4563 if (state == PURPLE_NOT_TYPING)
4564 return 0;
4566 session = sipe_session_find_im(sip, who);
4567 dialog = sipe_dialog_find(session, who);
4569 if (session && dialog && dialog->is_established) {
4570 send_sip_request(gc, "INFO", who, who,
4571 "Content-Type: application/xml\r\n",
4572 SIPE_SEND_TYPING, dialog, NULL);
4574 return SIPE_TYPING_SEND_TIMEOUT;
4577 static gboolean resend_timeout(struct sipe_account_data *sip)
4579 GSList *tmp = sip->transactions;
4580 time_t currtime = time(NULL);
4581 while (tmp) {
4582 struct transaction *trans = tmp->data;
4583 tmp = tmp->next;
4584 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4585 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4586 /* TODO 408 */
4587 } else {
4588 if ((currtime - trans->time > 2) && trans->retries == 0) {
4589 trans->retries++;
4590 sendout_sipmsg(sip, trans->msg);
4594 return TRUE;
4597 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4598 SIPE_UNUSED_PARAMETER void *unused)
4600 /* register again when security token expires */
4601 /* we have to start a new authentication as the security token
4602 * is almost expired by sending a not signed REGISTER message */
4603 purple_debug_info("sipe", "do a full reauthentication\n");
4604 sipe_auth_free(&sip->registrar);
4605 sipe_auth_free(&sip->proxy);
4606 sip->registerstatus = 0;
4607 do_register(sip);
4608 sip->reauthenticate_set = FALSE;
4611 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4613 gchar *from;
4614 gchar *contenttype;
4615 gboolean found = FALSE;
4617 from = parse_from(sipmsg_find_header(msg, "From"));
4619 if (!from) return;
4621 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4623 contenttype = sipmsg_find_header(msg, "Content-Type");
4624 if (!strncmp(contenttype, "text/plain", 10)
4625 || !strncmp(contenttype, "text/html", 9)
4626 || !strncmp(contenttype, "multipart/related", 17)
4627 || !strncmp(contenttype, "multipart/alternative", 21))
4629 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4630 gchar *html = get_html_message(contenttype, msg->body);
4632 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4633 if (!session) {
4634 session = sipe_session_find_im(sip, from);
4637 if (session && session->focus_uri) { /* a conference */
4638 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4639 gchar *sender = parse_from(tmp);
4640 g_free(tmp);
4641 serv_got_chat_in(sip->gc, session->chat_id, sender,
4642 PURPLE_MESSAGE_RECV, html, time(NULL));
4643 g_free(sender);
4644 } else if (session && session->is_multiparty) { /* a multiparty chat */
4645 serv_got_chat_in(sip->gc, session->chat_id, from,
4646 PURPLE_MESSAGE_RECV, html, time(NULL));
4647 } else {
4648 serv_got_im(sip->gc, from, html, 0, time(NULL));
4650 g_free(html);
4651 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4652 found = TRUE;
4654 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
4655 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4656 xmlnode *state;
4657 gchar *statedata;
4659 if (!isc) {
4660 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4661 return;
4664 state = xmlnode_get_child(isc, "state");
4666 if (!state) {
4667 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4668 xmlnode_free(isc);
4669 return;
4672 statedata = xmlnode_get_data(state);
4673 if (statedata) {
4674 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4675 else serv_got_typing_stopped(sip->gc, from);
4677 g_free(statedata);
4679 xmlnode_free(isc);
4680 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4681 found = TRUE;
4683 if (!found) {
4684 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4685 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4686 if (!session) {
4687 session = sipe_session_find_im(sip, from);
4689 if (session) {
4690 gchar *msg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4691 from);
4692 sipe_present_err(sip, session, msg);
4693 g_free(msg);
4696 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
4697 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
4699 g_free(from);
4702 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
4704 gchar *body;
4705 gchar *newTag;
4706 gchar *oldHeader;
4707 gchar *newHeader;
4708 gboolean is_multiparty = FALSE;
4709 gboolean is_triggered = FALSE;
4710 gboolean was_multiparty = TRUE;
4711 gboolean just_joined = FALSE;
4712 gchar *from;
4713 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4714 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4715 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4716 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4717 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4718 GSList *end_points = NULL;
4719 char *tmp = NULL;
4720 struct sip_session *session;
4722 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
4723 g_free(tmp);
4725 /* Invitation to join conference */
4726 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
4727 process_incoming_invite_conf(sip, msg);
4728 return;
4731 /* Only accept text invitations */
4732 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4733 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4734 return;
4737 // TODO There *must* be a better way to clean up the To header to add a tag...
4738 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4739 oldHeader = sipmsg_find_header(msg, "To");
4740 newTag = gentag();
4741 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4742 sipmsg_remove_header_now(msg, "To");
4743 sipmsg_add_header_now(msg, "To", newHeader);
4744 g_free(newHeader);
4746 if (end_points_hdr) {
4747 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4749 if (g_slist_length(end_points) > 2) {
4750 is_multiparty = TRUE;
4753 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4754 is_triggered = TRUE;
4755 is_multiparty = TRUE;
4758 session = sipe_session_find_chat_by_callid(sip, callid);
4759 /* Convert to multiparty */
4760 if (session && is_multiparty && !session->is_multiparty) {
4761 g_free(session->with);
4762 session->with = NULL;
4763 was_multiparty = FALSE;
4764 session->is_multiparty = TRUE;
4765 session->chat_id = rand();
4768 if (!session && is_multiparty) {
4769 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
4771 /* IM session */
4772 from = parse_from(sipmsg_find_header(msg, "From"));
4773 if (!session) {
4774 session = sipe_session_find_or_add_im(sip, from);
4777 if (session) {
4778 g_free(session->callid);
4779 session->callid = g_strdup(callid);
4781 session->is_multiparty = is_multiparty;
4782 if (roster_manager) {
4783 session->roster_manager = g_strdup(roster_manager);
4787 if (is_multiparty && end_points) {
4788 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
4789 GSList *entry = end_points;
4790 while (entry) {
4791 struct sip_dialog *dialog;
4792 struct sipendpoint *end_point = entry->data;
4793 entry = entry->next;
4795 if (!g_strcasecmp(from, end_point->contact) ||
4796 !g_strcasecmp(to, end_point->contact))
4797 continue;
4799 dialog = sipe_dialog_find(session, end_point->contact);
4800 if (dialog) {
4801 g_free(dialog->theirepid);
4802 dialog->theirepid = end_point->epid;
4803 end_point->epid = NULL;
4804 } else {
4805 dialog = sipe_dialog_add(session);
4807 dialog->callid = g_strdup(session->callid);
4808 dialog->with = end_point->contact;
4809 end_point->contact = NULL;
4810 dialog->theirepid = end_point->epid;
4811 end_point->epid = NULL;
4813 just_joined = TRUE;
4815 /* send triggered INVITE */
4816 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
4819 g_free(to);
4822 if (end_points) {
4823 GSList *entry = end_points;
4824 while (entry) {
4825 struct sipendpoint *end_point = entry->data;
4826 entry = entry->next;
4827 g_free(end_point->contact);
4828 g_free(end_point->epid);
4829 g_free(end_point);
4831 g_slist_free(end_points);
4834 if (session) {
4835 struct sip_dialog *dialog = sipe_dialog_find(session, from);
4836 if (dialog) {
4837 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
4838 } else {
4839 dialog = sipe_dialog_add(session);
4841 dialog->callid = g_strdup(session->callid);
4842 dialog->with = g_strdup(from);
4843 sipe_dialog_parse(dialog, msg, FALSE);
4845 if (!dialog->ourtag) {
4846 dialog->ourtag = newTag;
4847 newTag = NULL;
4850 just_joined = TRUE;
4852 } else {
4853 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
4855 g_free(newTag);
4857 if (is_multiparty && !session->conv) {
4858 gchar *chat_title = sipe_chat_get_name(callid);
4859 gchar *self = sip_uri_self(sip);
4860 /* create prpl chat */
4861 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
4862 session->chat_title = g_strdup(chat_title);
4863 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
4864 /* add self */
4865 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4866 self, NULL,
4867 PURPLE_CBFLAGS_NONE, FALSE);
4868 g_free(chat_title);
4869 g_free(self);
4872 if (is_multiparty && !was_multiparty) {
4873 /* add current IM counterparty to chat */
4874 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4875 sipe_dialog_first(session)->with, NULL,
4876 PURPLE_CBFLAGS_NONE, FALSE);
4879 /* add inviting party to chat */
4880 if (just_joined && session->conv) {
4881 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4882 from, NULL,
4883 PURPLE_CBFLAGS_NONE, TRUE);
4886 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4888 /* This used only in 2005 official client, not 2007 or Reuters.
4889 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4890 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4892 if (is_multiparty) {
4893 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4894 gchar *ms_text_format = sipmsg_find_header(msg, "ms-text-format");
4895 if (ms_text_format) {
4896 if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html")) {
4898 gchar *html = get_html_message(ms_text_format, NULL);
4899 if (html) {
4900 if (is_multiparty) {
4901 serv_got_chat_in(sip->gc, session->chat_id, from,
4902 PURPLE_MESSAGE_RECV, html, time(NULL));
4903 } else {
4904 serv_got_im(sip->gc, from, html, 0, time(NULL));
4906 g_free(html);
4907 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
4914 g_free(from);
4916 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
4917 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4918 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4920 body = g_strdup_printf(
4921 "v=0\r\n"
4922 "o=- 0 0 IN IP4 %s\r\n"
4923 "s=session\r\n"
4924 "c=IN IP4 %s\r\n"
4925 "t=0 0\r\n"
4926 "m=%s %d sip sip:%s\r\n"
4927 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4928 purple_network_get_my_ip(-1),
4929 purple_network_get_my_ip(-1),
4930 sip->ocs2007 ? "message" : "x-ms-message",
4931 sip->realport,
4932 sip->username);
4933 send_sip_response(sip->gc, msg, 200, "OK", body);
4934 g_free(body);
4937 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
4939 gchar *body;
4941 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
4942 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4943 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4945 body = g_strdup_printf(
4946 "v=0\r\n"
4947 "o=- 0 0 IN IP4 0.0.0.0\r\n"
4948 "s=session\r\n"
4949 "c=IN IP4 0.0.0.0\r\n"
4950 "t=0 0\r\n"
4951 "m=%s %d sip sip:%s\r\n"
4952 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4953 sip->ocs2007 ? "message" : "x-ms-message",
4954 sip->realport,
4955 sip->username);
4956 send_sip_response(sip->gc, msg, 200, "OK", body);
4957 g_free(body);
4960 static void sipe_connection_cleanup(struct sipe_account_data *);
4961 static void create_connection(struct sipe_account_data *, gchar *, int);
4963 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
4964 SIPE_UNUSED_PARAMETER struct transaction *trans)
4966 gchar *tmp;
4967 const gchar *expires_header;
4968 int expires, i;
4969 GSList *hdr = msg->headers;
4970 struct siphdrelement *elem;
4972 expires_header = sipmsg_find_header(msg, "Expires");
4973 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
4974 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
4976 switch (msg->response) {
4977 case 200:
4978 if (expires == 0) {
4979 sip->registerstatus = 0;
4980 } else {
4981 gchar *contact_hdr = NULL;
4982 gchar *gruu = NULL;
4983 gchar *epid;
4984 gchar *uuid;
4985 gchar *timeout;
4986 gchar *server_hdr = sipmsg_find_header(msg, "Server");
4988 if (!sip->reregister_set) {
4989 gchar *action_name = g_strdup_printf("<%s>", "registration");
4990 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
4991 g_free(action_name);
4992 sip->reregister_set = TRUE;
4995 sip->registerstatus = 3;
4997 if (server_hdr && !sip->server_version) {
4998 sip->server_version = g_strdup(server_hdr);
4999 g_free(default_ua);
5000 default_ua = NULL;
5003 #ifdef USE_KERBEROS
5004 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5005 #endif
5006 tmp = sipmsg_find_auth_header(msg, "NTLM");
5007 #ifdef USE_KERBEROS
5008 } else {
5009 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5011 #endif
5012 if (tmp) {
5013 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5014 fill_auth(tmp, &sip->registrar);
5017 if (!sip->reauthenticate_set) {
5018 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5019 guint reauth_timeout;
5020 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5021 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5022 reauth_timeout = sip->registrar.expires - 300;
5023 } else {
5024 /* NTLM: we have to reauthenticate as our security token expires
5025 after eight hours (be five minutes early) */
5026 reauth_timeout = (8 * 3600) - 300;
5028 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5029 g_free(action_name);
5030 sip->reauthenticate_set = TRUE;
5033 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5035 epid = get_epid(sip);
5036 uuid = generateUUIDfromEPID(epid);
5037 g_free(epid);
5039 // There can be multiple Contact headers (one per location where the user is logged in) so
5040 // make sure to only get the one for this uuid
5041 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5042 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5043 if (valid_contact) {
5044 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5045 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5046 g_free(valid_contact);
5047 break;
5048 } else {
5049 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5052 g_free(uuid);
5054 g_free(sip->contact);
5055 if(gruu) {
5056 sip->contact = g_strdup_printf("<%s>", gruu);
5057 g_free(gruu);
5058 } else {
5059 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5060 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);
5062 sip->ocs2007 = FALSE;
5063 sip->batched_support = FALSE;
5065 while(hdr)
5067 elem = hdr->data;
5068 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5069 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5070 /* We interpret this as OCS2007+ indicator */
5071 sip->ocs2007 = TRUE;
5072 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5074 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5075 sip->batched_support = TRUE;
5076 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5079 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5080 gchar **caps = g_strsplit(elem->value,",",0);
5081 i = 0;
5082 while (caps[i]) {
5083 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5084 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5085 i++;
5087 g_strfreev(caps);
5089 hdr = g_slist_next(hdr);
5092 /* rejoin open chats to be able to use them by continue to send messages */
5093 purple_conversation_foreach(sipe_rejoin_chat);
5095 /* subscriptions */
5096 if (!sip->subscribed) { //do it just once, not every re-register
5098 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5099 (GCompareFunc)g_ascii_strcasecmp)) {
5100 sipe_subscribe_roaming_contacts(sip);
5103 /* For 2007+ it does not make sence to subscribe to:
5104 * vnd-microsoft-roaming-ACL
5105 * vnd-microsoft-provisioning (not v2)
5106 * presence.wpending
5107 * These are for backward compatibility.
5109 if (sip->ocs2007)
5111 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5112 (GCompareFunc)g_ascii_strcasecmp)) {
5113 sipe_subscribe_roaming_self(sip);
5115 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5116 (GCompareFunc)g_ascii_strcasecmp)) {
5117 sipe_subscribe_roaming_provisioning_v2(sip);
5120 /* For 2005- servers */
5121 else
5123 //sipe_options_request(sip, sip->sipdomain);
5125 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5126 (GCompareFunc)g_ascii_strcasecmp)) {
5127 sipe_subscribe_roaming_acl(sip);
5129 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5130 (GCompareFunc)g_ascii_strcasecmp)) {
5131 sipe_subscribe_roaming_provisioning(sip);
5133 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5134 (GCompareFunc)g_ascii_strcasecmp)) {
5135 sipe_subscribe_presence_wpending(sip, msg);
5138 /* For 2007+ we publish our initial statuses and calendar data only after
5139 * received our existing publications in sipe_process_roaming_self()
5140 * Only in this case we know versions of current publications made
5141 * on our behalf.
5143 /* For 2005- we publish our initial statuses only after
5144 * received our existing UserInfo data in response to
5145 * self subscription.
5146 * Only in this case we won't override existing UserInfo data
5147 * set earlier or by other client on our behalf.
5151 sip->subscribed = TRUE;
5154 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5155 "timeout=", ";", NULL);
5156 if (timeout != NULL) {
5157 sscanf(timeout, "%u", &sip->keepalive_timeout);
5158 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5159 sip->keepalive_timeout);
5160 g_free(timeout);
5163 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5165 break;
5166 case 301:
5168 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5170 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5171 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5172 gchar **tmp;
5173 gchar *hostname;
5174 int port = 0;
5175 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5176 int i = 1;
5178 tmp = g_strsplit(parts[0], ":", 0);
5179 hostname = g_strdup(tmp[0]);
5180 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5181 g_strfreev(tmp);
5183 while (parts[i]) {
5184 tmp = g_strsplit(parts[i], "=", 0);
5185 if (tmp[1]) {
5186 if (g_strcasecmp("transport", tmp[0]) == 0) {
5187 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5188 transport = SIPE_TRANSPORT_TCP;
5189 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5190 transport = SIPE_TRANSPORT_UDP;
5194 g_strfreev(tmp);
5195 i++;
5197 g_strfreev(parts);
5199 /* Close old connection */
5200 sipe_connection_cleanup(sip);
5202 /* Create new connection */
5203 sip->transport = transport;
5204 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5205 hostname, port, TRANSPORT_DESCRIPTOR);
5206 create_connection(sip, hostname, port);
5208 g_free(redirect);
5210 break;
5211 case 401:
5212 if (sip->registerstatus != 2) {
5213 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5214 if (sip->registrar.retries > 3) {
5215 sip->gc->wants_to_die = TRUE;
5216 purple_connection_error(sip->gc, _("Wrong password"));
5217 return TRUE;
5219 #ifdef USE_KERBEROS
5220 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5221 #endif
5222 tmp = sipmsg_find_auth_header(msg, "NTLM");
5223 #ifdef USE_KERBEROS
5224 } else {
5225 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5227 #endif
5228 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5229 fill_auth(tmp, &sip->registrar);
5230 sip->registerstatus = 2;
5231 if (sip->account->disconnecting) {
5232 do_register_exp(sip, 0);
5233 } else {
5234 do_register(sip);
5237 break;
5238 case 403:
5240 gchar *warning = sipmsg_find_header(msg, "Warning");
5241 gchar **reason = NULL;
5242 if (warning != NULL) {
5243 /* Example header:
5244 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5246 reason = g_strsplit(warning, "\"", 0);
5248 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5249 (reason && reason[1]) ? reason[1] : _("no reason given"));
5250 g_strfreev(reason);
5252 sip->gc->wants_to_die = TRUE;
5253 purple_connection_error(sip->gc, warning);
5254 g_free(warning);
5255 return TRUE;
5257 break;
5258 case 404:
5260 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5261 gchar *reason = NULL;
5262 if (warning != NULL) {
5263 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5265 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5266 warning ? (reason ? reason : _("no reason given")) :
5267 _("SIP is either not enabled for the destination URI or it does not exist"));
5268 g_free(reason);
5270 sip->gc->wants_to_die = TRUE;
5271 purple_connection_error(sip->gc, warning);
5272 g_free(warning);
5273 return TRUE;
5275 break;
5276 case 503:
5277 case 504: /* Server time-out */
5279 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5280 gchar *reason = NULL;
5281 if (warning != NULL) {
5282 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5284 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : "<a href=\"http://www.reuters.com\">http://www.reuters.com</a>"/*_("no reason given")*/);
5285 g_free(reason);
5287 sip->gc->wants_to_die = TRUE;
5288 purple_connection_error(sip->gc, warning);
5289 g_free(warning);
5290 return TRUE;
5292 break;
5294 return TRUE;
5298 * Returns 2005-style activity and Availability.
5300 * @param status Sipe statis id.
5302 static void
5303 sipe_get_act_avail_by_status_2005(const char *status,
5304 int *activity,
5305 int *availability)
5307 int avail = 300; /* online */
5308 int act = 400; /* Available */
5310 if (!strcmp(status, SIPE_STATUS_ID_AWAY)) {
5311 act = 100;
5312 //} else if (!strcmp(status, SIPE_STATUS_ID_LUNCH)) {
5313 // act = 150;
5314 } else if (!strcmp(status, SIPE_STATUS_ID_BRB)) {
5315 act = 300;
5316 } else if (!strcmp(status, SIPE_STATUS_ID_AVAILABLE)) {
5317 act = 400;
5318 //} else if (!strcmp(status, SIPE_STATUS_ID_ON_PHONE)) {
5319 // act = 500;
5320 } else if (!strcmp(status, SIPE_STATUS_ID_BUSY) ||
5321 !strcmp(status, SIPE_STATUS_ID_DND)) {
5322 act = 600;
5323 } else if (!strcmp(status, SIPE_STATUS_ID_INVISIBLE) ||
5324 !strcmp(status, SIPE_STATUS_ID_OFFLINE)) {
5325 avail = 0; /* offline */
5326 act = 100;
5327 } else {
5328 act = 400; /* Available */
5331 if (activity) *activity = act;
5332 if (availability) *availability = avail;
5336 * [MS-SIP] 2.2.1
5338 * @param activity 2005 aggregated activity. Ex.: 600
5339 * @param availablity 2005 aggregated availablity. Ex.: 300
5341 static const char *
5342 sipe_get_status_by_act_avail_2005(const int activity,
5343 const int availablity,
5344 char **activity_desc)
5346 const char *status_id = NULL;
5347 const char *act = NULL;
5349 if (activity < 150) {
5350 status_id = SIPE_STATUS_ID_AWAY;
5351 } else if (activity < 200) {
5352 //status_id = SIPE_STATUS_ID_LUNCH;
5353 status_id = SIPE_STATUS_ID_AWAY;
5354 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5355 } else if (activity < 300) {
5356 //status_id = SIPE_STATUS_ID_IDLE;
5357 status_id = SIPE_STATUS_ID_AWAY;
5358 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5359 } else if (activity < 400) {
5360 status_id = SIPE_STATUS_ID_BRB;
5361 } else if (activity < 500) {
5362 status_id = SIPE_STATUS_ID_AVAILABLE;
5363 } else if (activity < 600) {
5364 status_id = SIPE_STATUS_ID_ON_PHONE;
5365 } else if (activity < 700) {
5366 status_id = SIPE_STATUS_ID_BUSY;
5367 } else if (activity < 800) {
5368 status_id = SIPE_STATUS_ID_AWAY;
5369 } else {
5370 status_id = SIPE_STATUS_ID_AVAILABLE;
5373 if (availablity < 100)
5374 status_id = SIPE_STATUS_ID_OFFLINE;
5376 if (activity_desc && act) {
5377 g_free(*activity_desc);
5378 *activity_desc = g_strdup(act);
5381 return status_id;
5385 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5387 static const char*
5388 sipe_get_status_by_availability(int avail,
5389 char** activity_desc)
5391 const char *status;
5392 const char *act = NULL;
5394 if (avail < 3000) {
5395 status = SIPE_STATUS_ID_OFFLINE;
5396 } else if (avail < 4500) {
5397 status = SIPE_STATUS_ID_AVAILABLE;
5398 } else if (avail < 6000) {
5399 //status = SIPE_STATUS_ID_IDLE;
5400 status = SIPE_STATUS_ID_AVAILABLE;
5401 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5402 } else if (avail < 7500) {
5403 status = SIPE_STATUS_ID_BUSY;
5404 } else if (avail < 9000) {
5405 //status = SIPE_STATUS_ID_BUSYIDLE;
5406 status = SIPE_STATUS_ID_BUSY;
5407 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5408 } else if (avail < 12000) {
5409 status = SIPE_STATUS_ID_DND;
5410 } else if (avail < 15000) {
5411 status = SIPE_STATUS_ID_BRB;
5412 } else if (avail < 18000) {
5413 status = SIPE_STATUS_ID_AWAY;
5414 } else {
5415 status = SIPE_STATUS_ID_OFFLINE;
5418 if (activity_desc && act) {
5419 g_free(*activity_desc);
5420 *activity_desc = g_strdup(act);
5423 return status;
5427 * Returns 2007-style availability value
5429 * @param sipe_status_id (in)
5430 * @param activity_token (out) Must be g_free()'d after use if consumed.
5432 static int
5433 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5435 int availability;
5436 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5438 if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5439 availability = 15500;
5440 if (!activity_token || !(*activity_token)) {
5441 activity = SIPE_ACTIVITY_AWAY;
5443 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5444 availability = 12500;
5445 activity = SIPE_ACTIVITY_BRB;
5446 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_DND)) {
5447 availability = 9500;
5448 activity = SIPE_ACTIVITY_DND;
5449 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5450 availability = 6500;
5451 if (!activity_token || !(*activity_token)) {
5452 activity = SIPE_ACTIVITY_BUSY;
5454 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5455 availability = 3500;
5456 activity = SIPE_ACTIVITY_ONLINE;
5457 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5458 availability = 0;
5459 } else {
5460 // Offline or invisible
5461 availability = 18500;
5462 activity = SIPE_ACTIVITY_OFFLINE;
5465 if (activity_token) {
5466 *activity_token = g_strdup(sipe_activity_map[activity].token);
5468 return availability;
5471 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5473 const char *uri;
5474 xmlnode *xn_categories;
5475 xmlnode *xn_category;
5476 xmlnode *xn_node;
5477 const char *status = NULL;
5478 gboolean do_update_status = FALSE;
5480 xn_categories = xmlnode_from_str(data, len);
5481 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5483 for (xn_category = xmlnode_get_child(xn_categories, "category");
5484 xn_category ;
5485 xn_category = xmlnode_get_next_twin(xn_category) )
5487 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5489 /* contactCard */
5490 if (!strcmp(attrVar, "contactCard"))
5492 xmlnode *node;
5493 /* identity - Display Name and email */
5494 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5495 if (node) {
5496 char* display_name = xmlnode_get_data(
5497 xmlnode_get_descendant(node, "name", "displayName", NULL));
5498 char* email = xmlnode_get_data(
5499 xmlnode_get_child(node, "email"));
5501 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5502 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5504 g_free(display_name);
5505 g_free(email);
5507 /* company */
5508 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5509 if (node) {
5510 char* company = xmlnode_get_data(node);
5511 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5512 g_free(company);
5514 /* department */
5515 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5516 if (node) {
5517 char* department = xmlnode_get_data(node);
5518 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5519 g_free(department);
5521 /* title */
5522 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5523 if (node) {
5524 char* title = xmlnode_get_data(node);
5525 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5526 g_free(title);
5528 /* office */
5529 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5530 if (node) {
5531 char* office = xmlnode_get_data(node);
5532 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5533 g_free(office);
5535 /* site (url) */
5536 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5537 if (node) {
5538 char* site = xmlnode_get_data(node);
5539 sipe_update_user_info(sip, uri, SITE_PROP, site);
5540 g_free(site);
5542 /* phone */
5543 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5544 node;
5545 node = xmlnode_get_next_twin(node))
5547 const char *phone_type = xmlnode_get_attrib(node, "type");
5548 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5549 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5551 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5553 g_free(phone);
5554 g_free(phone_display_string);
5556 /* address */
5557 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5558 node;
5559 node = xmlnode_get_next_twin(node))
5561 if (!strcmp(xmlnode_get_attrib(node, "type"), "work")) {
5562 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5563 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5564 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5565 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5566 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5568 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5569 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5570 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5571 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5572 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5574 g_free(street);
5575 g_free(city);
5576 g_free(state);
5577 g_free(zipcode);
5578 g_free(country_code);
5580 break;
5584 /* note */
5585 else if (!strcmp(attrVar, "note"))
5587 if (uri) {
5588 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5590 if (sbuddy) {
5591 /* clean up in case no 'note' element is supplied
5592 * which indicate note removal in client
5594 g_free(sbuddy->note);
5595 sbuddy->note = NULL;
5596 sbuddy->is_oof_note = FALSE;
5598 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
5599 if (xn_node) {
5600 char *tmp;
5601 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
5602 g_free(tmp);
5603 sbuddy->is_oof_note = !strcmp(xmlnode_get_attrib(xn_node, "type"), "OOF");
5605 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",
5606 uri, sbuddy->note ? sbuddy->note : "");
5609 /* to trigger UI refresh in case no status info is supplied in this update */
5610 do_update_status = TRUE;
5614 /* state */
5615 else if(!strcmp(attrVar, "state"))
5617 char *data;
5618 int availability;
5619 xmlnode *xn_availability;
5620 xmlnode *xn_activity;
5621 xmlnode *xn_meeting_subject;
5622 xmlnode *xn_meeting_location;
5623 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5625 xn_node = xmlnode_get_child(xn_category, "state");
5626 if (!xn_node) continue;
5627 xn_availability = xmlnode_get_child(xn_node, "availability");
5628 if (!xn_availability) continue;
5629 xn_activity = xmlnode_get_child(xn_node, "activity");
5630 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
5631 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
5633 data = xmlnode_get_data(xn_availability);
5634 availability = atoi(data);
5635 g_free(data);
5637 /* activity, meeting_subject, meeting_location */
5638 if (sbuddy) {
5639 /* activity */
5640 g_free(sbuddy->activity);
5641 sbuddy->activity = NULL;
5642 if (xn_activity) {
5643 const char *token = xmlnode_get_attrib(xn_activity, "token");
5644 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
5646 /* from token */
5647 if (!is_empty(token)) {
5648 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
5650 /* from custom element */
5651 if (xn_custom) {
5652 char *custom = xmlnode_get_data(xn_custom);
5654 if (!is_empty(custom)) {
5655 sbuddy->activity = custom;
5656 custom = NULL;
5658 g_free(custom);
5661 /* meeting_subject */
5662 g_free(sbuddy->meeting_subject);
5663 sbuddy->meeting_subject = NULL;
5664 if (xn_meeting_subject) {
5665 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
5667 if (!is_empty(meeting_subject)) {
5668 sbuddy->meeting_subject = meeting_subject;
5669 meeting_subject = NULL;
5671 g_free(meeting_subject);
5673 /* meeting_location */
5674 g_free(sbuddy->meeting_location);
5675 sbuddy->meeting_location = NULL;
5676 if (xn_meeting_location) {
5677 char *meeting_location = xmlnode_get_data(xn_meeting_location);
5679 if (!is_empty(meeting_location)) {
5680 sbuddy->meeting_location = meeting_location;
5681 meeting_location = NULL;
5683 g_free(meeting_location);
5686 status = sipe_get_status_by_availability(availability, &(sbuddy->activity));
5689 do_update_status = TRUE;
5691 /* calendarData */
5692 else if(!strcmp(attrVar, "calendarData"))
5694 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5695 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
5696 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
5698 if (sbuddy && xn_free_busy) {
5699 g_free(sbuddy->cal_start_time);
5700 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
5702 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
5703 15 : 0;
5705 g_free(sbuddy->cal_free_busy_base64);
5706 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
5708 g_free(sbuddy->cal_free_busy);
5709 sbuddy->cal_free_busy = NULL;
5711 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);
5714 if (sbuddy && xn_working_hours) {
5715 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
5720 if (do_update_status) {
5721 if (!status) { /* no status category in this update, using contact's current status */
5722 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
5723 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
5724 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
5725 status = purple_status_get_id(pstatus);
5728 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
5729 sipe_got_user_status(sip, uri, status);
5732 xmlnode_free(xn_categories);
5735 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
5737 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5738 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
5739 payload->host = g_strdup(host);
5740 payload->buddies = server;
5741 sipe_subscribe_presence_batched_routed(sip, payload);
5742 sipe_subscribe_presence_batched_routed_free(payload);
5745 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
5747 xmlnode *xn_list;
5748 xmlnode *xn_resource;
5749 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
5750 g_free, NULL);
5751 GSList *server;
5752 gchar *host;
5754 xn_list = xmlnode_from_str(data, len);
5756 for (xn_resource = xmlnode_get_child(xn_list, "resource");
5757 xn_resource;
5758 xn_resource = xmlnode_get_next_twin(xn_resource) )
5760 const char *uri, *state;
5761 xmlnode *xn_instance;
5763 xn_instance = xmlnode_get_child(xn_resource, "instance");
5764 if (!xn_instance) continue;
5766 uri = xmlnode_get_attrib(xn_resource, "uri");
5767 state = xmlnode_get_attrib(xn_instance, "state");
5768 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
5770 if (strstr(state, "resubscribe")) {
5771 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
5773 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
5774 gchar *user = g_strdup(uri);
5775 host = g_strdup(poolFqdn);
5776 server = g_hash_table_lookup(servers, host);
5777 server = g_slist_append(server, user);
5778 g_hash_table_insert(servers, host, server);
5779 } else {
5780 sipe_subscribe_presence_single(sip, (void *) uri);
5785 /* Send out any deferred poolFqdn subscriptions */
5786 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
5787 g_hash_table_destroy(servers);
5789 xmlnode_free(xn_list);
5792 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
5794 gchar *uri;
5795 gchar *getbasic;
5796 gchar *activity = NULL;
5797 xmlnode *pidf;
5798 xmlnode *basicstatus = NULL, *tuple, *status;
5799 gboolean isonline = FALSE;
5800 xmlnode *display_name_node;
5802 pidf = xmlnode_from_str(data, len);
5803 if (!pidf) {
5804 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
5805 return;
5808 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
5810 if ((tuple = xmlnode_get_child(pidf, "tuple")))
5812 if ((status = xmlnode_get_child(tuple, "status"))) {
5813 basicstatus = xmlnode_get_child(status, "basic");
5817 if (!basicstatus) {
5818 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
5819 xmlnode_free(pidf);
5820 return;
5823 getbasic = xmlnode_get_data(basicstatus);
5824 if (!getbasic) {
5825 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
5826 xmlnode_free(pidf);
5827 return;
5830 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
5831 if (strstr(getbasic, "open")) {
5832 isonline = TRUE;
5834 g_free(getbasic);
5836 display_name_node = xmlnode_get_child(pidf, "display-name");
5837 if (display_name_node) {
5838 char * display_name = xmlnode_get_data(display_name_node);
5840 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5841 g_free(display_name);
5844 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
5845 if ((status = xmlnode_get_child(tuple, "status"))) {
5846 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
5847 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
5848 activity = xmlnode_get_data(basicstatus);
5849 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
5855 if (isonline) {
5856 const gchar * status_id = NULL;
5857 if (activity) {
5858 if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
5859 status_id = SIPE_STATUS_ID_BUSY;
5860 } else if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
5861 status_id = SIPE_STATUS_ID_AWAY;
5865 if (!status_id) {
5866 status_id = SIPE_STATUS_ID_AVAILABLE;
5869 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
5870 sipe_got_user_status(sip, uri, status_id);
5871 } else {
5872 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
5875 g_free(activity);
5876 g_free(uri);
5877 xmlnode_free(pidf);
5880 /** 2005 */
5881 static void
5882 sipe_user_info_has_updated(struct sipe_account_data *sip,
5883 xmlnode *xn_userinfo)
5885 if (sip->user_info) {
5886 xmlnode_free(sip->user_info);
5888 sip->user_info = xmlnode_copy(xn_userinfo);
5890 /* Publish initial state if not yet.
5891 * Assuming this happens on initial responce to self subscription
5892 * so we've already updated our UserInfo.
5894 if (!sip->initial_state_published) {
5895 send_presence_soap(sip, FALSE);
5896 /* dalayed run */
5897 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
5901 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
5903 char *activity = NULL;
5904 const char *epid;
5905 const char *status_id = NULL;
5906 const char *name;
5907 char *uri;
5908 char *self_uri = sip_uri_self(sip);
5909 int avl;
5910 int act;
5911 const char *device_name = NULL;
5912 const char *cal_start_time = NULL;
5913 const char *cal_granularity = NULL;
5914 char *cal_free_busy_base64 = NULL;
5915 struct sipe_buddy *sbuddy;
5916 xmlnode *node;
5917 xmlnode *xn_presentity;
5918 xmlnode *xn_availability;
5919 xmlnode *xn_activity;
5920 xmlnode *xn_display_name;
5921 xmlnode *xn_email;
5922 xmlnode *xn_phone_number;
5923 xmlnode *xn_userinfo;
5924 xmlnode *xn_note;
5925 xmlnode *xn_oof;
5926 xmlnode *xn_state;
5927 xmlnode *xn_contact;
5928 char *note;
5929 char *free_activity;
5930 int user_avail;
5931 const char *user_avail_nil;
5932 int res_avail;
5933 time_t user_avail_since = 0;
5934 time_t activity_since = 0;
5936 /* fix for Reuters environment on Linux */
5937 if (data && strstr(data, "encoding=\"utf-16\"")) {
5938 char *tmp_data;
5939 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
5940 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
5941 g_free(tmp_data);
5942 } else {
5943 xn_presentity = xmlnode_from_str(data, len);
5946 xn_availability = xmlnode_get_child(xn_presentity, "availability");
5947 xn_activity = xmlnode_get_child(xn_presentity, "activity");
5948 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
5949 xn_email = xmlnode_get_child(xn_presentity, "email");
5950 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
5951 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
5952 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
5953 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
5954 user_avail = xn_state ? atoi(xmlnode_get_attrib(xn_state, "avail")) : 0;
5955 user_avail_since = xn_state ? purple_str_to_time(xmlnode_get_attrib(xn_state, "since"), FALSE, NULL, NULL, NULL) : 0;
5956 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
5957 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
5958 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
5959 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
5961 if (user_avail_nil && !strcmp(user_avail_nil, "true")) { /* null-ed */
5962 user_avail = 0;
5963 user_avail_since = 0;
5966 free_activity = NULL;
5968 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
5969 uri = sip_uri_from_name(name);
5970 avl = atoi(xmlnode_get_attrib(xn_availability, "aggregate"));
5971 epid = xmlnode_get_attrib(xn_availability, "epid");
5972 act = atoi(xmlnode_get_attrib(xn_activity, "aggregate"));
5974 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
5975 res_avail = sipe_get_availability_by_status(status_id, NULL);
5976 if (user_avail > res_avail) {
5977 res_avail = user_avail;
5978 status_id = sipe_get_status_by_availability(user_avail, NULL);
5981 if (xn_display_name) {
5982 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
5983 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
5984 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
5985 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
5986 char *tel_uri = sip_to_tel_uri(phone_number);
5988 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5989 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5990 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
5991 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
5993 g_free(tel_uri);
5994 g_free(phone_label);
5995 g_free(phone_number);
5996 g_free(email);
5997 g_free(display_name);
6000 if (xn_contact) {
6001 /* tel */
6002 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6004 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6005 const char *phone_type = xmlnode_get_attrib(node, "type");
6006 char* phone = xmlnode_get_data(node);
6008 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6010 g_free(phone);
6014 /* devicePresence */
6015 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6016 xmlnode *xn_device_name;
6017 xmlnode *xn_calendar_info;
6018 xmlnode *xn_state;
6019 char *state;
6021 /* deviceName */
6022 if (!strcmp(xmlnode_get_attrib(node, "epid"), epid)) {
6023 xn_device_name = xmlnode_get_child(node, "deviceName");
6024 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6027 /* calendarInfo */
6028 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6029 if (xn_calendar_info) {
6030 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6032 if (cal_start_time) {
6033 time_t cal_start_time_t = purple_str_to_time(cal_start_time, FALSE, NULL, NULL, NULL);
6034 time_t cal_start_time_t_tmp = purple_str_to_time(cal_start_time_tmp, FALSE, NULL, NULL, NULL);
6036 if (cal_start_time_t_tmp > cal_start_time_t) {
6037 cal_start_time = cal_start_time_tmp;
6038 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6039 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6041 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);
6043 } else {
6044 cal_start_time = cal_start_time_tmp;
6045 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6046 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6048 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);
6052 /* state */
6053 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6054 if (xn_state) {
6055 int dev_avail = atoi(xmlnode_get_attrib(xn_state, "avail"));
6056 time_t dev_avail_since = purple_str_to_time(xmlnode_get_attrib(xn_state, "since"), FALSE, NULL, NULL, NULL);
6058 state = xmlnode_get_data(xn_state);
6059 if (dev_avail_since > user_avail_since &&
6060 dev_avail >= res_avail)
6062 res_avail = dev_avail;
6063 if (!is_empty(state))
6065 if (!strcmp(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6066 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6067 } else if (!strcmp(state, "presenting")) {
6068 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6069 } else {
6070 activity = state;
6072 activity_since = dev_avail_since;
6074 status_id = sipe_get_status_by_availability(res_avail, &activity);
6076 g_free(state);
6080 /* oof */
6081 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6082 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6083 activity_since = 0;
6086 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6087 if (sbuddy)
6089 g_free(sbuddy->activity);
6090 sbuddy->activity = activity;
6092 sbuddy->activity_since = activity_since;
6094 sbuddy->user_avail = user_avail;
6095 sbuddy->user_avail_since = user_avail_since;
6097 g_free(sbuddy->note);
6098 sbuddy->note = NULL;
6099 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6101 sbuddy->is_oof_note = (xn_oof != NULL);
6103 g_free(sbuddy->device_name);
6104 sbuddy->device_name = NULL;
6105 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6107 if (!is_empty(cal_free_busy_base64)) {
6108 g_free(sbuddy->cal_start_time);
6109 sbuddy->cal_start_time = g_strdup(cal_start_time);
6111 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6113 g_free(sbuddy->cal_free_busy_base64);
6114 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6116 g_free(sbuddy->cal_free_busy);
6117 sbuddy->cal_free_busy = NULL;
6120 sbuddy->last_non_cal_status_id = status_id;
6121 g_free(sbuddy->last_non_cal_activity);
6122 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6124 if (!strcmp(sbuddy->name, self_uri)) {
6125 if (!(sbuddy->note && sip->note && !strcmp(sbuddy->note, sip->note))) /* not same */
6127 sip->is_oof_note = sbuddy->is_oof_note;
6129 g_free(sip->note);
6130 sip->note = g_strdup(sbuddy->note);
6132 sip->note_since = time(NULL);
6135 g_free(sip->status);
6136 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6140 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6141 sipe_got_user_status(sip, uri, status_id);
6143 if (!sip->ocs2007 && !strcmp(self_uri, uri)) {
6144 sipe_user_info_has_updated(sip, xn_userinfo);
6147 g_free(note);
6148 xmlnode_free(xn_presentity);
6149 g_free(uri);
6150 g_free(self_uri);
6153 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6155 char *ctype = sipmsg_find_header(msg, "Content-Type");
6157 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6159 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6160 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6162 const char *content = msg->body;
6163 unsigned length = msg->bodylen;
6164 PurpleMimeDocument *mime = NULL;
6166 if (strstr(ctype, "multipart"))
6168 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6169 const char *content_type;
6170 GList* parts;
6171 mime = purple_mime_document_parse(doc);
6172 parts = purple_mime_document_get_parts(mime);
6173 while(parts) {
6174 content = purple_mime_part_get_data(parts->data);
6175 length = purple_mime_part_get_length(parts->data);
6176 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6177 if(content_type && strstr(content_type,"application/rlmi+xml"))
6179 process_incoming_notify_rlmi_resub(sip, content, length);
6181 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6183 process_incoming_notify_msrtc(sip, content, length);
6185 else
6187 process_incoming_notify_rlmi(sip, content, length);
6189 parts = parts->next;
6191 g_free(doc);
6193 if (mime)
6195 purple_mime_document_free(mime);
6198 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6200 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6202 else if(strstr(ctype, "application/rlmi+xml"))
6204 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6207 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6209 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6211 else
6213 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6217 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6219 char *ctype = sipmsg_find_header(msg, "Content-Type");
6220 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6222 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6224 if (ctype &&
6225 strstr(ctype, "multipart") &&
6226 (strstr(ctype, "application/rlmi+xml") ||
6227 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6228 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6229 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6230 GList *parts = purple_mime_document_get_parts(mime);
6231 GSList *buddies = NULL;
6232 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6234 while (parts) {
6235 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6236 purple_mime_part_get_length(parts->data));
6238 if (strcmp(xml->name, "list")) {
6239 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6241 buddies = g_slist_append(buddies, uri);
6243 xmlnode_free(xml);
6245 parts = parts->next;
6247 g_free(doc);
6248 if (mime) purple_mime_document_free(mime);
6250 payload->host = g_strdup(who);
6251 payload->buddies = buddies;
6252 sipe_schedule_action(action_name, timeout,
6253 sipe_subscribe_presence_batched_routed,
6254 sipe_subscribe_presence_batched_routed_free,
6255 sip, payload);
6256 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6258 } else {
6259 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6260 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6262 g_free(action_name);
6266 * Dispatcher for all incoming subscription information
6267 * whether it comes from NOTIFY, BENOTIFY requests or
6268 * piggy-backed to subscription's OK responce.
6270 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6271 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6273 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6275 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6276 gchar *event = sipmsg_find_header(msg, "Event");
6277 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6278 char *tmp;
6279 int timeout = 0;
6281 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6282 event ? event : "",
6283 tmp = fix_newlines(msg->body));
6284 g_free(tmp);
6285 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6287 /* implicit subscriptions */
6288 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6289 sipe_process_imdn(sip, msg);
6292 if (!request)
6294 const gchar *expires_header;
6295 expires_header = sipmsg_find_header(msg, "Expires");
6296 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6297 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6298 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout; // 2 min ahead of expiration
6301 /* for one off subscriptions (send with Expire: 0) */
6302 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6304 sipe_process_provisioning_v2(sip, msg);
6306 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6308 sipe_process_provisioning(sip, msg);
6311 if (!subscription_state || strstr(subscription_state, "active"))
6313 if (event && !g_ascii_strcasecmp(event, "presence"))
6315 sipe_process_presence(sip, msg);
6317 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6319 sipe_process_roaming_contacts(sip, msg);
6321 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6323 sipe_process_roaming_self(sip, msg);
6325 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6327 sipe_process_roaming_acl(sip, msg);
6329 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
6331 sipe_process_presence_wpending(sip, msg);
6333 else if (event && !g_ascii_strcasecmp(event, "conference"))
6335 sipe_process_conference(sip, msg);
6339 /* The server sends status 'terminated' */
6340 if (subscription_state && strstr(subscription_state, "terminated") ) {
6341 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6342 gchar *key = sipe_get_subscription_key(event, who);
6344 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6345 g_free(who);
6347 if (g_hash_table_lookup(sip->subscriptions, key)) {
6348 g_hash_table_remove(sip->subscriptions, key);
6349 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6352 g_free(key);
6355 if (timeout && event) {// For LSC 2005 and OCS 2007
6356 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
6357 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
6359 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
6360 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
6361 g_free(action_name);
6363 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
6364 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
6366 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
6367 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
6368 g_free(action_name);
6370 else*/
6371 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6372 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6374 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6375 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6376 g_free(action_name);
6378 else if (!g_ascii_strcasecmp(event, "presence") &&
6379 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6381 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6382 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6383 if (sip->batched_support) {
6384 sipe_process_presence_timeout(sip, msg, who, timeout);
6386 else {
6387 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6388 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6390 g_free(action_name);
6391 g_free(who);
6395 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
6397 sipe_process_registration_notify(sip, msg);
6400 /* The client responses on received a NOTIFY message */
6401 if (request && !benotify)
6403 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6408 * Whether user manually changed status or
6409 * it was changed automatically due to user
6410 * became inactive/active again
6412 static gboolean
6413 sipe_is_user_state(struct sipe_account_data *sip)
6415 gboolean res;
6416 time_t now = time(NULL);
6418 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6419 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6421 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6423 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6424 return res;
6427 static void
6428 send_presence_soap0(struct sipe_account_data *sip,
6429 gboolean do_publish_calendar,
6430 gboolean do_reset_status)
6432 struct sipe_ews* ews = sip->ews;
6433 int availability = 0;
6434 int activity = 0;
6435 gchar *body;
6436 gchar *tmp;
6437 gchar *tmp2 = NULL;
6438 gchar *res_note = NULL;
6439 gchar *res_oof = NULL;
6440 const gchar *note_pub = NULL;
6441 gchar *states = NULL;
6442 gchar *calendar_data = NULL;
6443 gchar *epid = get_epid(sip);
6444 time_t now = time(NULL);
6445 gchar *since_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&now)));
6446 const gchar *oof_note = sipe_ews_get_oof_note(ews);
6447 const char *user_input;
6448 gboolean pub_oof = oof_note && (!sip->note || sip->note_since < ews->oof_start);
6450 if (oof_note && sip->note) {
6451 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6452 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6455 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6457 if (!sip->initial_state_published ||
6458 do_reset_status)
6460 g_free(sip->status);
6461 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6464 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6466 /* Note */
6467 if (pub_oof) {
6468 note_pub = oof_note;
6469 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6470 } else if (sip->note) {
6471 if (sip->is_oof_note) { /* stale OOF note, as it's not present in ews already (oof_note == NULL) */
6472 g_free(sip->note);
6473 sip->note = NULL;
6474 sip->is_oof_note = FALSE;
6475 sip->note_since = 0;
6476 } else {
6477 note_pub = sip->note;
6481 if (note_pub)
6483 /* to protocol internal plain text format */
6484 tmp = purple_markup_strip_html(note_pub);
6485 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6486 g_free(tmp);
6489 /* User State */
6490 if (!do_reset_status) {
6491 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6493 gchar *activity_token = NULL;
6494 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6496 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6497 avail_2007,
6498 since_time_str,
6499 epid,
6500 activity_token);
6501 g_free(activity_token);
6503 else /* preserve existing publication */
6505 xmlnode *xn_states;
6506 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6507 states = xmlnode_to_str(xn_states, NULL);
6508 /* this is a hack-around to remove added newline after inner element,
6509 * state in this case, where it shouldn't be.
6510 * After several use of xmlnode_to_str, amount of added newlines
6511 * grows significantly.
6513 purple_str_strip_char(states, '\n');
6514 //purple_str_strip_char(states, '\r');
6517 } else {
6518 /* do nothing - then User state will be erased */
6520 sip->initial_state_published = TRUE;
6522 /* CalendarInfo */
6523 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6525 char *fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
6526 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6527 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6528 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6529 fb_start_str,
6530 free_busy_base64);
6531 g_free(fb_start_str);
6532 g_free(free_busy_base64);
6535 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6537 /* forming resulting XML */
6538 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6539 sip->username,
6540 availability,
6541 activity,
6542 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6543 res_note ? res_note : "",
6544 res_oof ? res_oof : "",
6545 states ? states : "",
6546 calendar_data ? calendar_data : "",
6547 epid,
6548 since_time_str,
6549 since_time_str,
6550 user_input);
6551 g_free(tmp);
6552 g_free(tmp2);
6553 g_free(res_note);
6554 g_free(states);
6555 g_free(calendar_data);
6557 send_soap_request(sip, body);
6559 g_free(body);
6560 g_free(since_time_str);
6561 g_free(epid);
6564 void
6565 send_presence_soap(struct sipe_account_data *sip,
6566 gboolean do_publish_calendar)
6568 return send_presence_soap0(sip, do_publish_calendar, FALSE);
6572 static gboolean
6573 process_send_presence_category_publish_response(struct sipe_account_data *sip,
6574 struct sipmsg *msg,
6575 struct transaction *trans)
6577 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
6579 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
6580 xmlnode *xml;
6581 xmlnode *node;
6582 gchar *fault_code;
6583 GHashTable *faults;
6584 int index_our;
6585 gboolean has_device_publication = FALSE;
6587 xml = xmlnode_from_str(msg->body, msg->bodylen);
6589 /* test if version mismatch fault */
6590 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
6591 if (strcmp(fault_code, "Client.BadCall.WrongDelta")) {
6592 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
6593 g_free(fault_code);
6594 xmlnode_free(xml);
6595 return TRUE;
6597 g_free(fault_code);
6599 /* accumulating information about faulty versions */
6600 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
6601 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
6602 node;
6603 node = xmlnode_get_next_twin(node))
6605 const gchar *index = xmlnode_get_attrib(node, "index");
6606 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
6608 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
6609 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
6611 xmlnode_free(xml);
6613 /* here we are parsing own request to figure out what publication
6614 * referensed here only by index went wrong
6616 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
6618 /* publication */
6619 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
6620 index_our = 1; /* starts with 1 - our first publication */
6621 node;
6622 node = xmlnode_get_next_twin(node), index_our++)
6624 gchar *idx = g_strdup_printf("%d", index_our);
6625 const gchar *curVersion = g_hash_table_lookup(faults, idx);
6626 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
6627 g_free(idx);
6629 if (!strcmp("device", categoryName)) {
6630 has_device_publication = TRUE;
6633 if (curVersion) { /* fault exist on this index */
6634 const gchar *container = xmlnode_get_attrib(node, "container");
6635 const gchar *instance = xmlnode_get_attrib(node, "instance");
6636 /* key is <category><instance><container> */
6637 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
6638 struct sipe_publication *publication =
6639 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
6641 purple_debug_info("sipe", "key is %s\n", key);
6643 if (publication) {
6644 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6645 key, curVersion, publication->version);
6646 /* updating publication's version to the correct one */
6647 publication->version = atoi(curVersion);
6649 g_free(key);
6652 xmlnode_free(xml);
6653 g_hash_table_destroy(faults);
6655 /* rebublishing with right versions */
6656 if (has_device_publication) {
6657 send_publish_category_initial(sip);
6658 } else {
6659 send_presence_status(sip);
6662 return TRUE;
6666 * Returns 'device' XML part for publication.
6667 * Must be g_free'd after use.
6669 static gchar *
6670 sipe_publish_get_category_device(struct sipe_account_data *sip)
6672 gchar *uri;
6673 gchar *doc;
6674 gchar *epid = get_epid(sip);
6675 gchar *uuid = generateUUIDfromEPID(epid);
6676 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
6677 /* key is <category><instance><container> */
6678 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
6679 struct sipe_publication *publication =
6680 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
6682 g_free(key);
6683 g_free(epid);
6685 uri = sip_uri_self(sip);
6686 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
6687 device_instance,
6688 publication ? publication->version : 0,
6689 uuid,
6690 uri,
6691 "00:00:00+01:00", /* @TODO make timezone real*/
6692 sipe_get_host_name()
6695 g_free(uri);
6696 g_free(uuid);
6698 return doc;
6702 * A service method - use
6703 * - send_publish_get_category_state_machine and
6704 * - send_publish_get_category_state_user instead.
6705 * Must be g_free'd after use.
6707 static gchar *
6708 sipe_publish_get_category_state(struct sipe_account_data *sip,
6709 gboolean is_user_state)
6711 int availability = sipe_get_availability_by_status(sip->status, NULL);
6712 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
6713 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
6714 /* key is <category><instance><container> */
6715 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6716 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6717 struct sipe_publication *publication_2 =
6718 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6719 struct sipe_publication *publication_3 =
6720 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6722 g_free(key_2);
6723 g_free(key_3);
6725 if (publication_2 && (publication_2->availability == availability))
6727 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6728 return NULL; /* nothing to update */
6731 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
6732 instance,
6733 publication_2 ? publication_2->version : 0,
6734 availability,
6735 instance,
6736 publication_3 ? publication_3->version : 0,
6737 availability);
6741 * Only Busy and OOF calendar event are published.
6742 * Different instances are used for that.
6744 * Must be g_free'd after use.
6746 static gchar *
6747 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
6748 struct sipe_cal_event *event,
6749 const char *uri,
6750 int cal_satus)
6752 gchar *start_time_str;
6753 int availability = 0;
6754 gchar *res;
6755 guint instance = (cal_satus == SIPE_CAL_OOF) ?
6756 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
6757 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
6759 /* key is <category><instance><container> */
6760 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6761 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6762 struct sipe_publication *publication_2 =
6763 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6764 struct sipe_publication *publication_3 =
6765 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6767 g_free(key_2);
6768 g_free(key_3);
6770 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
6771 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6772 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
6773 return NULL;
6776 if (event &&
6777 publication_3 &&
6778 (publication_3->availability == availability) &&
6779 !strcmp(publication_3->cal_event_hash, sipe_cal_event_hash(event)))
6781 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6782 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
6783 return NULL; /* nothing to update */
6786 if (event &&
6787 (event->cal_status == SIPE_CAL_BUSY ||
6788 event->cal_status == SIPE_CAL_OOF))
6790 gchar *availability_xml_str = NULL;
6791 gchar *activity_xml_str = NULL;
6793 if (event->cal_status == SIPE_CAL_BUSY) {
6794 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
6797 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
6798 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6799 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
6800 "minAvailability=\"6500\"",
6801 "maxAvailability=\"8999\"");
6802 } else if (event->cal_status == SIPE_CAL_OOF) {
6803 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6804 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
6805 "minAvailability=\"12000\"",
6806 "");
6808 start_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&event->start_time)));
6810 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
6811 instance,
6812 publication_2 ? publication_2->version : 0,
6813 uri,
6814 start_time_str,
6815 availability_xml_str ? availability_xml_str : "",
6816 activity_xml_str ? activity_xml_str : "",
6817 event->subject ? event->subject : "",
6818 event->location ? event->location : "",
6820 instance,
6821 publication_3 ? publication_3->version : 0,
6822 uri,
6823 start_time_str,
6824 availability_xml_str ? availability_xml_str : "",
6825 activity_xml_str ? activity_xml_str : "",
6826 event->subject ? event->subject : "",
6827 event->location ? event->location : ""
6829 g_free(start_time_str);
6830 g_free(availability_xml_str);
6831 g_free(activity_xml_str);
6834 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
6836 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
6837 instance,
6838 publication_2 ? publication_2->version : 0,
6840 instance,
6841 publication_3 ? publication_3->version : 0
6845 return res;
6849 * Returns 'machineState' XML part for publication.
6850 * Must be g_free'd after use.
6852 static gchar *
6853 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
6855 return sipe_publish_get_category_state(sip, FALSE);
6859 * Returns 'userState' XML part for publication.
6860 * Must be g_free'd after use.
6862 static gchar *
6863 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
6865 return sipe_publish_get_category_state(sip, TRUE);
6869 * Compares two strings even in case both are NULL/empty
6871 static gboolean
6872 sipe_is_equal(const char* n1, const char* n2) {
6873 return ((!n1 || !strlen(n1)) && (!n2 || !strlen(n2))) /* both empty */
6874 || (n1 && n2 && !strcmp(n1, n2)); /* or not empty and equal */
6878 * Returns 'note' XML part for publication.
6879 * Must be g_free'd after use.
6881 * Protocol format for Note is plain text.
6883 * @param note a note in Sipe internal HTML format
6884 * @param note_type either personal or OOF
6886 static gchar *
6887 sipe_publish_get_category_note(struct sipe_account_data *sip,
6888 const char *note, /* html */
6889 const char *note_type)
6891 guint instance = !strcmp("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
6892 /* key is <category><instance><container> */
6893 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
6894 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
6895 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
6897 struct sipe_publication *publication_note_200 =
6898 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
6899 struct sipe_publication *publication_note_300 =
6900 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
6901 struct sipe_publication *publication_note_400 =
6902 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
6904 char *n1 = note ? purple_markup_strip_html(note) : NULL;
6905 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
6907 g_free(key_note_200);
6908 g_free(key_note_300);
6909 g_free(key_note_400);
6911 if (sipe_is_equal(n1, n2))
6913 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
6914 return NULL; /* nothing to update */
6917 return g_markup_printf_escaped(SIPE_PUB_XML_NOTE,
6918 instance,
6919 publication_note_200 ? publication_note_200->version : 0,
6920 note_type,
6921 n1 ? n1 : "",
6923 instance,
6924 publication_note_300 ? publication_note_300->version : 0,
6925 note_type,
6926 n1 ? n1 : "",
6928 instance,
6929 publication_note_400 ? publication_note_400->version : 0,
6930 note_type,
6931 n1 ? n1 : "");
6932 g_free(n1);
6936 * Returns 'calendarData' XML part with WorkingHours for publication.
6937 * Must be g_free'd after use.
6939 static gchar *
6940 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
6942 struct sipe_ews* ews = sip->ews;
6944 /* key is <category><instance><container> */
6945 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
6946 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
6947 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
6948 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
6949 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
6950 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
6952 struct sipe_publication *publication_cal_1 =
6953 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
6954 struct sipe_publication *publication_cal_100 =
6955 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
6956 struct sipe_publication *publication_cal_200 =
6957 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
6958 struct sipe_publication *publication_cal_300 =
6959 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
6960 struct sipe_publication *publication_cal_400 =
6961 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
6962 struct sipe_publication *publication_cal_32000 =
6963 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
6965 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
6966 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
6968 g_free(key_cal_1);
6969 g_free(key_cal_100);
6970 g_free(key_cal_200);
6971 g_free(key_cal_300);
6972 g_free(key_cal_400);
6973 g_free(key_cal_32000);
6975 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
6976 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
6977 return NULL;
6980 if (sipe_is_equal(n1, n2))
6982 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
6983 return NULL; /* nothing to update */
6986 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
6987 /* 1 */
6988 publication_cal_1 ? publication_cal_1->version : 0,
6989 ews->email,
6990 ews->working_hours_xml_str,
6991 /* 100 - Public */
6992 publication_cal_100 ? publication_cal_100->version : 0,
6993 /* 200 - Company */
6994 publication_cal_200 ? publication_cal_200->version : 0,
6995 ews->email,
6996 ews->working_hours_xml_str,
6997 /* 300 - Team */
6998 publication_cal_300 ? publication_cal_300->version : 0,
6999 ews->email,
7000 ews->working_hours_xml_str,
7001 /* 400 - Personal */
7002 publication_cal_400 ? publication_cal_400->version : 0,
7003 ews->email,
7004 ews->working_hours_xml_str,
7005 /* 32000 - Blocked */
7006 publication_cal_32000 ? publication_cal_32000->version : 0
7011 * Returns 'calendarData' XML part with FreeBusy for publication.
7012 * Must be g_free'd after use.
7014 static gchar *
7015 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7017 struct sipe_ews* ews = sip->ews;
7018 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7019 char *fb_start_str;
7020 char *free_busy_base64;
7021 const char *st;
7022 const char *fb;
7023 char *res;
7025 /* key is <category><instance><container> */
7026 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7027 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7028 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7029 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7030 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7031 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7033 struct sipe_publication *publication_cal_1 =
7034 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7035 struct sipe_publication *publication_cal_100 =
7036 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7037 struct sipe_publication *publication_cal_200 =
7038 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7039 struct sipe_publication *publication_cal_300 =
7040 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7041 struct sipe_publication *publication_cal_400 =
7042 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7043 struct sipe_publication *publication_cal_32000 =
7044 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7046 g_free(key_cal_1);
7047 g_free(key_cal_100);
7048 g_free(key_cal_200);
7049 g_free(key_cal_300);
7050 g_free(key_cal_400);
7051 g_free(key_cal_32000);
7053 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7054 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7055 return NULL;
7058 fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
7059 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7061 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7062 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7064 if (sipe_is_equal(st, fb_start_str) && sipe_is_equal(fb, free_busy_base64))
7066 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7067 g_free(fb_start_str);
7068 g_free(free_busy_base64);
7069 return NULL; /* nothing to update */
7072 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7073 /* 1 */
7074 cal_data_instance,
7075 publication_cal_1 ? publication_cal_1->version : 0,
7076 /* 100 - Public */
7077 cal_data_instance,
7078 publication_cal_100 ? publication_cal_100->version : 0,
7079 /* 200 - Company */
7080 cal_data_instance,
7081 publication_cal_200 ? publication_cal_200->version : 0,
7082 ews->email,
7083 fb_start_str,
7084 free_busy_base64,
7085 /* 300 - Team */
7086 cal_data_instance,
7087 publication_cal_300 ? publication_cal_300->version : 0,
7088 ews->email,
7089 fb_start_str,
7090 free_busy_base64,
7091 /* 400 - Personal */
7092 cal_data_instance,
7093 publication_cal_400 ? publication_cal_400->version : 0,
7094 ews->email,
7095 fb_start_str,
7096 free_busy_base64,
7097 /* 32000 - Blocked */
7098 cal_data_instance,
7099 publication_cal_32000 ? publication_cal_32000->version : 0
7102 g_free(fb_start_str);
7103 g_free(free_busy_base64);
7104 return res;
7107 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7109 gchar *uri;
7110 gchar *doc;
7111 gchar *tmp;
7112 gchar *hdr;
7114 uri = sip_uri_self(sip);
7115 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7116 uri,
7117 publications);
7119 tmp = get_contact(sip);
7120 hdr = g_strdup_printf("Contact: %s\r\n"
7121 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7123 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7125 g_free(tmp);
7126 g_free(hdr);
7127 g_free(uri);
7128 g_free(doc);
7131 static void
7132 send_publish_category_initial(struct sipe_account_data *sip)
7134 gchar *pub_device = sipe_publish_get_category_device(sip);
7135 gchar *pub_machine;
7136 gchar *publications;
7138 g_free(sip->status);
7139 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7141 pub_machine = sipe_publish_get_category_state_machine(sip);
7142 publications = g_strdup_printf("%s%s",
7143 pub_device,
7144 pub_machine ? pub_machine : "");
7145 g_free(pub_device);
7146 g_free(pub_machine);
7148 send_presence_publish(sip, publications);
7149 g_free(publications);
7152 static void
7153 send_presence_category_publish(struct sipe_account_data *sip,
7154 const char *note)
7156 gchar *pub_state = sipe_is_user_state(sip) ?
7157 sipe_publish_get_category_state_user(sip) :
7158 sipe_publish_get_category_state_machine(sip);
7159 gchar *pub_note = sipe_publish_get_category_note(sip, note, "personal");
7160 gchar *publications;
7162 if (!pub_state && !pub_note) {
7163 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7164 return;
7167 publications = g_strdup_printf("%s%s",
7168 pub_state ? pub_state : "",
7169 pub_note ? pub_note : "");
7171 g_free(pub_state);
7172 g_free(pub_note);
7174 send_presence_publish(sip, publications);
7175 g_free(publications);
7179 * Publishes self status
7180 * based on own calendar information.
7182 * For 2007+
7184 void
7185 publish_calendar_status_self(struct sipe_account_data *sip)
7187 struct sipe_cal_event* event = NULL;
7188 gchar *pub_cal_working_hours = NULL;
7189 gchar *pub_cal_free_busy = NULL;
7190 gchar *pub_calendar = NULL;
7191 gchar *pub_calendar2 = NULL;
7192 gchar *pub_oof_note = NULL;
7193 const gchar *oof_note;
7195 if (!sip->ews) {
7196 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7197 return;
7200 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7201 if (sip->ews->cal_events) {
7202 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7205 if (!event) {
7206 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7207 } else {
7208 char *desc = sipe_cal_event_describe(event);
7209 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7210 g_free(desc);
7213 /* Logic
7214 if OOF
7215 OOF publish, Busy clean
7216 ilse if Busy
7217 OOF clean, Busy publish
7218 else
7219 OOF clean, Busy clean
7221 if (event && event->cal_status == SIPE_CAL_OOF) {
7222 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7223 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7224 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7225 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7226 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7227 } else {
7228 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7229 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7232 if ((oof_note = sipe_ews_get_oof_note(sip->ews))) {
7233 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF");
7236 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7237 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7239 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7240 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7241 } else {
7242 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7243 pub_cal_working_hours ? pub_cal_working_hours : "",
7244 pub_cal_free_busy ? pub_cal_free_busy : "",
7245 pub_calendar ? pub_calendar : "",
7246 pub_calendar2 ? pub_calendar2 : "",
7247 pub_oof_note ? pub_oof_note : "");
7249 send_presence_publish(sip, publications);
7250 g_free(publications);
7253 g_free(pub_cal_working_hours);
7254 g_free(pub_cal_free_busy);
7255 g_free(pub_calendar);
7256 g_free(pub_calendar2);
7257 g_free(pub_oof_note);
7259 /* repeat scheduling */
7260 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7263 static void send_presence_status(struct sipe_account_data *sip)
7265 PurpleStatus * status = purple_account_get_active_status(sip->account);
7266 const gchar *note;
7267 if (!status) return;
7269 note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
7270 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7271 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7272 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7273 purple_debug_info("sipe", "send_presence_status: note: '%s'\n", note ? note : "");
7275 if (sip->ocs2007) {
7276 send_presence_category_publish(sip, note);
7277 } else {
7278 send_presence_soap(sip, FALSE);
7282 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7284 gboolean found = FALSE;
7285 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
7286 if (msg->response == 0) { /* request */
7287 if (!strcmp(msg->method, "MESSAGE")) {
7288 process_incoming_message(sip, msg);
7289 found = TRUE;
7290 } else if (!strcmp(msg->method, "NOTIFY")) {
7291 purple_debug_info("sipe","send->process_incoming_notify\n");
7292 process_incoming_notify(sip, msg, TRUE, FALSE);
7293 found = TRUE;
7294 } else if (!strcmp(msg->method, "BENOTIFY")) {
7295 purple_debug_info("sipe","send->process_incoming_benotify\n");
7296 process_incoming_notify(sip, msg, TRUE, TRUE);
7297 found = TRUE;
7298 } else if (!strcmp(msg->method, "INVITE")) {
7299 process_incoming_invite(sip, msg);
7300 found = TRUE;
7301 } else if (!strcmp(msg->method, "REFER")) {
7302 process_incoming_refer(sip, msg);
7303 found = TRUE;
7304 } else if (!strcmp(msg->method, "OPTIONS")) {
7305 process_incoming_options(sip, msg);
7306 found = TRUE;
7307 } else if (!strcmp(msg->method, "INFO")) {
7308 process_incoming_info(sip, msg);
7309 found = TRUE;
7310 } else if (!strcmp(msg->method, "ACK")) {
7311 // ACK's don't need any response
7312 found = TRUE;
7313 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
7314 // LCS 2005 sends us these - just respond 200 OK
7315 found = TRUE;
7316 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7317 } else if (!strcmp(msg->method, "BYE")) {
7318 process_incoming_bye(sip, msg);
7319 found = TRUE;
7320 } else {
7321 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7323 } else { /* response */
7324 struct transaction *trans = transactions_find(sip, msg);
7325 if (trans) {
7326 if (msg->response == 407) {
7327 gchar *resend, *auth, *ptmp;
7329 if (sip->proxy.retries > 30) return;
7330 sip->proxy.retries++;
7331 /* do proxy authentication */
7333 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7335 fill_auth(ptmp, &sip->proxy);
7336 auth = auth_header(sip, &sip->proxy, trans->msg);
7337 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7338 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7339 g_free(auth);
7340 resend = sipmsg_to_string(trans->msg);
7341 /* resend request */
7342 sendout_pkt(sip->gc, resend);
7343 g_free(resend);
7344 } else {
7345 if (msg->response < 200) {
7346 /* ignore provisional response */
7347 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7348 } else {
7349 sip->proxy.retries = 0;
7350 if (!strcmp(trans->msg->method, "REGISTER")) {
7351 if (msg->response == 401)
7353 sip->registrar.retries++;
7355 else
7357 sip->registrar.retries = 0;
7359 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7360 } else {
7361 if (msg->response == 401) {
7362 gchar *resend, *auth, *ptmp;
7364 if (sip->registrar.retries > 4) return;
7365 sip->registrar.retries++;
7367 #ifdef USE_KERBEROS
7368 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
7369 #endif
7370 ptmp = sipmsg_find_auth_header(msg, "NTLM");
7371 #ifdef USE_KERBEROS
7372 } else {
7373 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
7375 #endif
7377 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp);
7379 fill_auth(ptmp, &sip->registrar);
7380 auth = auth_header(sip, &sip->registrar, trans->msg);
7381 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7382 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7384 //sipmsg_remove_header_now(trans->msg, "Authorization");
7385 //sipmsg_add_header(trans->msg, "Authorization", auth);
7386 g_free(auth);
7387 resend = sipmsg_to_string(trans->msg);
7388 /* resend request */
7389 sendout_pkt(sip->gc, resend);
7390 g_free(resend);
7394 if (trans->callback) {
7395 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7396 /* call the callback to process response*/
7397 (trans->callback)(sip, msg, trans);
7400 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7401 transactions_remove(sip, trans);
7405 found = TRUE;
7406 } else {
7407 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7410 if (!found) {
7411 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
7415 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7417 char *cur;
7418 char *dummy;
7419 char *tmp;
7420 struct sipmsg *msg;
7421 int restlen;
7422 cur = conn->inbuf;
7424 /* according to the RFC remove CRLF at the beginning */
7425 while (*cur == '\r' || *cur == '\n') {
7426 cur++;
7428 if (cur != conn->inbuf) {
7429 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7430 conn->inbufused = strlen(conn->inbuf);
7433 /* Received a full Header? */
7434 sip->processing_input = TRUE;
7435 while (sip->processing_input &&
7436 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7437 time_t currtime = time(NULL);
7438 cur += 2;
7439 cur[0] = '\0';
7440 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7441 g_free(tmp);
7442 msg = sipmsg_parse_header(conn->inbuf);
7443 cur[0] = '\r';
7444 cur += 2;
7445 restlen = conn->inbufused - (cur - conn->inbuf);
7446 if (msg && restlen >= msg->bodylen) {
7447 dummy = g_malloc(msg->bodylen + 1);
7448 memcpy(dummy, cur, msg->bodylen);
7449 dummy[msg->bodylen] = '\0';
7450 msg->body = dummy;
7451 cur += msg->bodylen;
7452 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7453 conn->inbufused = strlen(conn->inbuf);
7454 } else {
7455 if (msg){
7456 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7457 sipmsg_free(msg);
7459 return;
7462 /*if (msg->body) {
7463 purple_debug_info("sipe", "body:\n%s", msg->body);
7466 // Verify the signature before processing it
7467 if (sip->registrar.gssapi_context) {
7468 struct sipmsg_breakdown msgbd;
7469 gchar *signature_input_str;
7470 gchar *rspauth;
7471 msgbd.msg = msg;
7472 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7473 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
7475 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7477 if (rspauth != NULL) {
7478 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7479 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7480 process_input_message(sip, msg);
7481 } else {
7482 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7483 purple_connection_error(sip->gc, _("Invalid message signature received"));
7484 sip->gc->wants_to_die = TRUE;
7486 } else if (msg->response == 401) {
7487 purple_connection_error(sip->gc, _("Wrong password"));
7488 sip->gc->wants_to_die = TRUE;
7490 g_free(signature_input_str);
7492 g_free(rspauth);
7493 sipmsg_breakdown_free(&msgbd);
7494 } else {
7495 process_input_message(sip, msg);
7498 sipmsg_free(msg);
7502 static void sipe_udp_process(gpointer data, gint source,
7503 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
7505 PurpleConnection *gc = data;
7506 struct sipe_account_data *sip = gc->proto_data;
7507 int len;
7509 static char buffer[65536];
7510 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
7511 time_t currtime = time(NULL);
7512 struct sipmsg *msg;
7513 buffer[len] = '\0';
7514 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
7515 msg = sipmsg_parse_msg(buffer);
7516 if (msg) process_input_message(sip, msg);
7520 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
7522 struct sipe_account_data *sip = gc->proto_data;
7523 PurpleSslConnection *gsc = sip->gsc;
7525 purple_debug_error("sipe", "%s",debug);
7526 purple_connection_error(gc, msg);
7528 /* Invalidate this connection. Next send will open a new one */
7529 if (gsc) {
7530 connection_remove(sip, gsc->fd);
7531 purple_ssl_close(gsc);
7533 sip->gsc = NULL;
7534 sip->fd = -1;
7537 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7538 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7540 PurpleConnection *gc = data;
7541 struct sipe_account_data *sip;
7542 struct sip_connection *conn;
7543 int readlen, len;
7544 gboolean firstread = TRUE;
7546 /* NOTE: This check *IS* necessary */
7547 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
7548 purple_ssl_close(gsc);
7549 return;
7552 sip = gc->proto_data;
7553 conn = connection_find(sip, gsc->fd);
7554 if (conn == NULL) {
7555 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7556 gc->wants_to_die = TRUE;
7557 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
7558 return;
7561 /* Read all available data from the SSL connection */
7562 do {
7563 /* Increase input buffer size as needed */
7564 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7565 conn->inbuflen += SIMPLE_BUF_INC;
7566 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7567 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
7570 /* Try to read as much as there is space left in the buffer */
7571 readlen = conn->inbuflen - conn->inbufused - 1;
7572 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
7574 if (len < 0 && errno == EAGAIN) {
7575 /* Try again later */
7576 return;
7577 } else if (len < 0) {
7578 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
7579 return;
7580 } else if (firstread && (len == 0)) {
7581 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
7582 return;
7585 conn->inbufused += len;
7586 firstread = FALSE;
7588 /* Equivalence indicates that there is possibly more data to read */
7589 } while (len == readlen);
7591 conn->inbuf[conn->inbufused] = '\0';
7592 process_input(sip, conn);
7596 static void sipe_input_cb(gpointer data, gint source,
7597 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7599 PurpleConnection *gc = data;
7600 struct sipe_account_data *sip = gc->proto_data;
7601 int len;
7602 struct sip_connection *conn = connection_find(sip, source);
7603 if (!conn) {
7604 purple_debug_error("sipe", "Connection not found!\n");
7605 return;
7608 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7609 conn->inbuflen += SIMPLE_BUF_INC;
7610 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7613 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
7615 if (len < 0 && errno == EAGAIN)
7616 return;
7617 else if (len <= 0) {
7618 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7619 connection_remove(sip, source);
7620 if (sip->fd == source) sip->fd = -1;
7621 return;
7624 conn->inbufused += len;
7625 conn->inbuf[conn->inbufused] = '\0';
7627 process_input(sip, conn);
7630 /* Callback for new connections on incoming TCP port */
7631 static void sipe_newconn_cb(gpointer data, gint source,
7632 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7634 PurpleConnection *gc = data;
7635 struct sipe_account_data *sip = gc->proto_data;
7636 struct sip_connection *conn;
7638 int newfd = accept(source, NULL, NULL);
7640 conn = connection_create(sip, newfd);
7642 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7645 static void login_cb(gpointer data, gint source,
7646 SIPE_UNUSED_PARAMETER const gchar *error_message)
7648 PurpleConnection *gc = data;
7649 struct sipe_account_data *sip;
7650 struct sip_connection *conn;
7652 if (!PURPLE_CONNECTION_IS_VALID(gc))
7654 if (source >= 0)
7655 close(source);
7656 return;
7659 if (source < 0) {
7660 purple_connection_error(gc, _("Could not connect"));
7661 return;
7664 sip = gc->proto_data;
7665 sip->fd = source;
7666 sip->last_keepalive = time(NULL);
7668 conn = connection_create(sip, source);
7670 do_register(sip);
7672 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7675 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7676 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7678 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
7679 if (sip == NULL) return;
7681 do_register(sip);
7684 static guint sipe_ht_hash_nick(const char *nick)
7686 char *lc = g_utf8_strdown(nick, -1);
7687 guint bucket = g_str_hash(lc);
7688 g_free(lc);
7690 return bucket;
7693 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
7695 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
7698 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
7700 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7702 sip->listen_data = NULL;
7704 if (listenfd == -1) {
7705 purple_connection_error(sip->gc, _("Could not create listen socket"));
7706 return;
7709 sip->fd = listenfd;
7711 sip->listenport = purple_network_get_port_from_fd(sip->fd);
7712 sip->listenfd = sip->fd;
7714 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
7716 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
7717 do_register(sip);
7720 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
7721 SIPE_UNUSED_PARAMETER const char *error_message)
7723 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7725 sip->query_data = NULL;
7727 if (!hosts || !hosts->data) {
7728 purple_connection_error(sip->gc, _("Could not resolve hostname"));
7729 return;
7732 hosts = g_slist_remove(hosts, hosts->data);
7733 g_free(sip->serveraddr);
7734 sip->serveraddr = hosts->data;
7735 hosts = g_slist_remove(hosts, hosts->data);
7736 while (hosts) {
7737 hosts = g_slist_remove(hosts, hosts->data);
7738 g_free(hosts->data);
7739 hosts = g_slist_remove(hosts, hosts->data);
7742 /* create socket for incoming connections */
7743 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
7744 sipe_udp_host_resolved_listen_cb, sip);
7745 if (sip->listen_data == NULL) {
7746 purple_connection_error(sip->gc, _("Could not create listen socket"));
7747 return;
7751 static const struct sipe_service_data *current_service = NULL;
7753 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
7754 PurpleSslErrorType error,
7755 gpointer data)
7757 PurpleConnection *gc = data;
7758 struct sipe_account_data *sip;
7760 /* If the connection is already disconnected, we don't need to do anything else */
7761 if (!PURPLE_CONNECTION_IS_VALID(gc))
7762 return;
7764 sip = gc->proto_data;
7765 current_service = sip->service_data;
7766 if (current_service) {
7767 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
7768 current_service->transport ? current_service->transport : "NULL",
7769 current_service->service ? current_service->service : "NULL");
7772 sip->fd = -1;
7773 sip->gsc = NULL;
7775 switch(error) {
7776 case PURPLE_SSL_CONNECT_FAILED:
7777 purple_connection_error(gc, _("Connection failed"));
7778 break;
7779 case PURPLE_SSL_HANDSHAKE_FAILED:
7780 purple_connection_error(gc, _("SSL handshake failed"));
7781 break;
7782 case PURPLE_SSL_CERTIFICATE_INVALID:
7783 purple_connection_error(gc, _("SSL certificate invalid"));
7784 break;
7788 static void
7789 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
7791 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7792 PurpleProxyConnectData *connect_data;
7794 sip->listen_data = NULL;
7796 sip->listenfd = listenfd;
7797 if (sip->listenfd == -1) {
7798 purple_connection_error(sip->gc, _("Could not create listen socket"));
7799 return;
7802 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
7803 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7804 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7805 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
7806 sipe_newconn_cb, sip->gc);
7807 purple_debug_info("sipe", "connecting to %s port %d\n",
7808 sip->realhostname, sip->realport);
7809 /* open tcp connection to the server */
7810 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
7811 sip->realport, login_cb, sip->gc);
7813 if (connect_data == NULL) {
7814 purple_connection_error(sip->gc, _("Could not create socket"));
7818 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
7820 PurpleAccount *account = sip->account;
7821 PurpleConnection *gc = sip->gc;
7823 if (port == 0) {
7824 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
7827 sip->realhostname = hostname;
7828 sip->realport = port;
7830 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
7831 hostname, port);
7833 /* TODO: is there a good default grow size? */
7834 if (sip->transport != SIPE_TRANSPORT_UDP)
7835 sip->txbuf = purple_circ_buffer_new(0);
7837 if (sip->transport == SIPE_TRANSPORT_TLS) {
7838 /* SSL case */
7839 if (!purple_ssl_is_supported()) {
7840 gc->wants_to_die = TRUE;
7841 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
7842 return;
7845 purple_debug_info("sipe", "using SSL\n");
7847 sip->gsc = purple_ssl_connect(account, hostname, port,
7848 login_cb_ssl, sipe_ssl_connect_failure, gc);
7849 if (sip->gsc == NULL) {
7850 purple_connection_error(gc, _("Could not create SSL context"));
7851 return;
7853 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
7854 /* UDP case */
7855 purple_debug_info("sipe", "using UDP\n");
7857 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
7858 if (sip->query_data == NULL) {
7859 purple_connection_error(gc, _("Could not resolve hostname"));
7861 } else {
7862 /* TCP case */
7863 purple_debug_info("sipe", "using TCP\n");
7864 /* create socket for incoming connections */
7865 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
7866 sipe_tcp_connect_listen_cb, sip);
7867 if (sip->listen_data == NULL) {
7868 purple_connection_error(gc, _("Could not create listen socket"));
7869 return;
7874 /* Service list for autodection */
7875 static const struct sipe_service_data service_autodetect[] = {
7876 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
7877 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
7878 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
7879 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
7880 { NULL, NULL, 0 }
7883 /* Service list for SSL/TLS */
7884 static const struct sipe_service_data service_tls[] = {
7885 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
7886 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
7887 { NULL, NULL, 0 }
7890 /* Service list for TCP */
7891 static const struct sipe_service_data service_tcp[] = {
7892 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
7893 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
7894 { NULL, NULL, 0 }
7897 /* Service list for UDP */
7898 static const struct sipe_service_data service_udp[] = {
7899 { "sip", "udp", SIPE_TRANSPORT_UDP },
7900 { NULL, NULL, 0 }
7903 static void srvresolved(PurpleSrvResponse *, int, gpointer);
7904 static void resolve_next_service(struct sipe_account_data *sip,
7905 const struct sipe_service_data *start)
7907 if (start) {
7908 sip->service_data = start;
7909 } else {
7910 sip->service_data++;
7911 if (sip->service_data->service == NULL) {
7912 gchar *hostname;
7913 /* Try connecting to the SIP hostname directly */
7914 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
7915 if (sip->auto_transport) {
7916 // If SSL is supported, default to using it; OCS servers aren't configured
7917 // by default to accept TCP
7918 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
7919 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
7920 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
7923 hostname = g_strdup(sip->sipdomain);
7924 create_connection(sip, hostname, 0);
7925 return;
7929 /* Try to resolve next service */
7930 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
7931 sip->service_data->transport,
7932 sip->sipdomain,
7933 srvresolved, sip);
7936 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
7938 struct sipe_account_data *sip = data;
7940 sip->srv_query_data = NULL;
7942 /* find the host to connect to */
7943 if (results) {
7944 gchar *hostname = g_strdup(resp->hostname);
7945 int port = resp->port;
7946 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
7947 hostname, port);
7948 g_free(resp);
7950 sip->transport = sip->service_data->type;
7952 create_connection(sip, hostname, port);
7953 } else {
7954 resolve_next_service(sip, NULL);
7958 static void sipe_login(PurpleAccount *account)
7960 PurpleConnection *gc;
7961 struct sipe_account_data *sip;
7962 gchar **signinname_login, **userserver;
7963 const char *transport;
7964 const char *email;
7966 const char *username = purple_account_get_username(account);
7967 gc = purple_account_get_connection(account);
7969 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
7971 if (strpbrk(username, "\t\v\r\n") != NULL) {
7972 gc->wants_to_die = TRUE;
7973 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
7974 return;
7977 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
7978 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
7979 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
7980 sip->gc = gc;
7981 sip->account = account;
7982 sip->reregister_set = FALSE;
7983 sip->reauthenticate_set = FALSE;
7984 sip->subscribed = FALSE;
7985 sip->subscribed_buddies = FALSE;
7986 sip->initial_state_published = FALSE;
7988 /* username format: <username>,[<optional login>] */
7989 signinname_login = g_strsplit(username, ",", 2);
7990 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
7992 /* ensure that username format is name@domain */
7993 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
7994 g_strfreev(signinname_login);
7995 gc->wants_to_die = TRUE;
7996 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
7997 return;
7999 sip->username = g_strdup(signinname_login[0]);
8001 /* ensure that email format is name@domain if provided */
8002 email = purple_account_get_string(sip->account, "email", NULL);
8003 if (!is_empty(email) &&
8004 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8006 gc->wants_to_die = TRUE;
8007 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8008 return;
8010 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8012 /* login name specified? */
8013 if (signinname_login[1] && strlen(signinname_login[1])) {
8014 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8015 gboolean has_domain = domain_user[1] != NULL;
8016 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8017 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8018 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8019 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8020 sip->authdomain ? sip->authdomain : "", sip->authuser);
8021 g_strfreev(domain_user);
8024 userserver = g_strsplit(signinname_login[0], "@", 2);
8025 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8026 purple_connection_set_display_name(gc, userserver[0]);
8027 sip->sipdomain = g_strdup(userserver[1]);
8028 g_strfreev(userserver);
8029 g_strfreev(signinname_login);
8031 if (strchr(sip->username, ' ') != NULL) {
8032 gc->wants_to_die = TRUE;
8033 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8034 return;
8037 sip->password = g_strdup(purple_connection_get_password(gc));
8039 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8040 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8041 g_free, (GDestroyNotify)g_hash_table_destroy);
8042 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8043 g_free, (GDestroyNotify)sipe_subscription_free);
8045 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8047 g_free(sip->status);
8048 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8050 sip->auto_transport = FALSE;
8051 transport = purple_account_get_string(account, "transport", "auto");
8052 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8053 if (userserver[0]) {
8054 /* Use user specified server[:port] */
8055 int port = 0;
8057 if (userserver[1])
8058 port = atoi(userserver[1]);
8060 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8061 userserver[0], port);
8063 if (strcmp(transport, "auto") == 0) {
8064 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8065 } else if (strcmp(transport, "tls") == 0) {
8066 sip->transport = SIPE_TRANSPORT_TLS;
8067 } else if (strcmp(transport, "tcp") == 0) {
8068 sip->transport = SIPE_TRANSPORT_TCP;
8069 } else {
8070 sip->transport = SIPE_TRANSPORT_UDP;
8073 create_connection(sip, g_strdup(userserver[0]), port);
8074 } else {
8075 /* Server auto-discovery */
8076 if (strcmp(transport, "auto") == 0) {
8077 sip->auto_transport = TRUE;
8078 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8079 current_service++;
8080 resolve_next_service(sip, current_service);
8081 } else {
8082 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8084 } else if (strcmp(transport, "tls") == 0) {
8085 resolve_next_service(sip, service_tls);
8086 } else if (strcmp(transport, "tcp") == 0) {
8087 resolve_next_service(sip, service_tcp);
8088 } else {
8089 resolve_next_service(sip, service_udp);
8092 g_strfreev(userserver);
8095 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8097 connection_free_all(sip);
8099 g_free(sip->epid);
8100 sip->epid = NULL;
8102 if (sip->query_data != NULL)
8103 purple_dnsquery_destroy(sip->query_data);
8104 sip->query_data = NULL;
8106 if (sip->srv_query_data != NULL)
8107 purple_srv_cancel(sip->srv_query_data);
8108 sip->srv_query_data = NULL;
8110 if (sip->listen_data != NULL)
8111 purple_network_listen_cancel(sip->listen_data);
8112 sip->listen_data = NULL;
8114 if (sip->gsc != NULL)
8115 purple_ssl_close(sip->gsc);
8116 sip->gsc = NULL;
8118 sipe_auth_free(&sip->registrar);
8119 sipe_auth_free(&sip->proxy);
8121 if (sip->txbuf)
8122 purple_circ_buffer_destroy(sip->txbuf);
8123 sip->txbuf = NULL;
8125 g_free(sip->realhostname);
8126 sip->realhostname = NULL;
8128 g_free(sip->server_version);
8129 sip->server_version = NULL;
8131 if (sip->listenpa)
8132 purple_input_remove(sip->listenpa);
8133 sip->listenpa = 0;
8134 if (sip->tx_handler)
8135 purple_input_remove(sip->tx_handler);
8136 sip->tx_handler = 0;
8137 if (sip->resendtimeout)
8138 purple_timeout_remove(sip->resendtimeout);
8139 sip->resendtimeout = 0;
8140 if (sip->timeouts) {
8141 GSList *entry = sip->timeouts;
8142 while (entry) {
8143 struct scheduled_action *sched_action = entry->data;
8144 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8145 purple_timeout_remove(sched_action->timeout_handler);
8146 if (sched_action->destroy) {
8147 (*sched_action->destroy)(sched_action->payload);
8149 g_free(sched_action->name);
8150 g_free(sched_action);
8151 entry = entry->next;
8154 g_slist_free(sip->timeouts);
8156 if (sip->allow_events) {
8157 GSList *entry = sip->allow_events;
8158 while (entry) {
8159 g_free(entry->data);
8160 entry = entry->next;
8163 g_slist_free(sip->allow_events);
8165 if (sip->containers) {
8166 GSList *entry = sip->containers;
8167 while (entry) {
8168 free_container((struct sipe_container *)entry->data);
8169 entry = entry->next;
8172 g_slist_free(sip->containers);
8174 if (sip->contact)
8175 g_free(sip->contact);
8176 sip->contact = NULL;
8177 if (sip->regcallid)
8178 g_free(sip->regcallid);
8179 sip->regcallid = NULL;
8181 if (sip->serveraddr)
8182 g_free(sip->serveraddr);
8183 sip->serveraddr = NULL;
8185 if (sip->focus_factory_uri)
8186 g_free(sip->focus_factory_uri);
8187 sip->focus_factory_uri = NULL;
8189 sip->fd = -1;
8190 sip->processing_input = FALSE;
8192 if (sip->ews) {
8193 sipe_ews_free(sip->ews);
8195 sip->ews = NULL;
8199 * A callback for g_hash_table_foreach_remove
8201 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8202 SIPE_UNUSED_PARAMETER gpointer user_data)
8204 sipe_free_buddy((struct sipe_buddy *) buddy);
8206 /* We must return TRUE as the key/value have already been deleted */
8207 return(TRUE);
8210 static void sipe_close(PurpleConnection *gc)
8212 struct sipe_account_data *sip = gc->proto_data;
8214 if (sip) {
8215 /* leave all conversations */
8216 sipe_session_close_all(sip);
8217 sipe_session_remove_all(sip);
8219 if (sip->csta) {
8220 sip_csta_close(sip);
8223 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8224 /* unsubscribe all */
8225 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8227 /* unregister */
8228 do_register_exp(sip, 0);
8231 sipe_connection_cleanup(sip);
8232 g_free(sip->sipdomain);
8233 g_free(sip->username);
8234 g_free(sip->email);
8235 g_free(sip->password);
8236 g_free(sip->authdomain);
8237 g_free(sip->authuser);
8238 g_free(sip->status);
8239 g_free(sip->note);
8241 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8242 g_hash_table_destroy(sip->buddies);
8243 g_hash_table_destroy(sip->our_publications);
8244 g_hash_table_destroy(sip->user_state_publications);
8245 g_hash_table_destroy(sip->subscriptions);
8247 if (sip->groups) {
8248 GSList *entry = sip->groups;
8249 while (entry) {
8250 struct sipe_group *group = entry->data;
8251 g_free(group->name);
8252 g_free(group);
8253 entry = entry->next;
8256 g_slist_free(sip->groups);
8258 if (sip->our_publication_keys) {
8259 GSList *entry = sip->our_publication_keys;
8260 while (entry) {
8261 g_free(entry->data);
8262 entry = entry->next;
8265 g_slist_free(sip->our_publication_keys);
8267 while (sip->transactions)
8268 transactions_remove(sip, sip->transactions->data);
8270 g_free(gc->proto_data);
8271 gc->proto_data = NULL;
8274 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8275 SIPE_UNUSED_PARAMETER void *user_data)
8277 PurpleAccount *acct = purple_connection_get_account(gc);
8278 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8279 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8280 if (conv == NULL)
8281 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8282 purple_conversation_present(conv);
8283 g_free(id);
8286 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8287 SIPE_UNUSED_PARAMETER void *user_data)
8290 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8291 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8294 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8295 SIPE_UNUSED_PARAMETER struct transaction *trans)
8297 PurpleNotifySearchResults *results;
8298 PurpleNotifySearchColumn *column;
8299 xmlnode *searchResults;
8300 xmlnode *mrow;
8301 int match_count = 0;
8302 gboolean more = FALSE;
8303 gchar *secondary;
8305 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8307 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8308 if (!searchResults) {
8309 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8310 return FALSE;
8313 results = purple_notify_searchresults_new();
8315 if (results == NULL) {
8316 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8317 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8319 xmlnode_free(searchResults);
8320 return FALSE;
8323 column = purple_notify_searchresults_column_new(_("User name"));
8324 purple_notify_searchresults_column_add(results, column);
8326 column = purple_notify_searchresults_column_new(_("Name"));
8327 purple_notify_searchresults_column_add(results, column);
8329 column = purple_notify_searchresults_column_new(_("Company"));
8330 purple_notify_searchresults_column_add(results, column);
8332 column = purple_notify_searchresults_column_new(_("Country"));
8333 purple_notify_searchresults_column_add(results, column);
8335 column = purple_notify_searchresults_column_new(_("Email"));
8336 purple_notify_searchresults_column_add(results, column);
8338 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8339 GList *row = NULL;
8341 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8342 row = g_list_append(row, g_strdup(uri_parts[1]));
8343 g_strfreev(uri_parts);
8345 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8346 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8347 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8348 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8350 purple_notify_searchresults_row_add(results, row);
8351 match_count++;
8354 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8355 char *data = xmlnode_get_data_unescaped(mrow);
8356 more = (g_strcasecmp(data, "true") == 0);
8357 g_free(data);
8360 secondary = g_strdup_printf(
8361 dngettext(GETTEXT_PACKAGE,
8362 "Found %d contact%s:",
8363 "Found %d contacts%s:", match_count),
8364 match_count, more ? _(" (more matched your query)") : "");
8366 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8367 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8368 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8370 g_free(secondary);
8371 xmlnode_free(searchResults);
8372 return TRUE;
8375 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8377 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8378 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8379 unsigned i = 0;
8381 do {
8382 PurpleRequestField *field = entries->data;
8383 const char *id = purple_request_field_get_id(field);
8384 const char *value = purple_request_field_string_get_value(field);
8386 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8388 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8389 } while ((entries = g_list_next(entries)) != NULL);
8390 attrs[i] = NULL;
8392 if (i > 0) {
8393 struct sipe_account_data *sip = gc->proto_data;
8394 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8395 gchar *query = g_strjoinv(NULL, attrs);
8396 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8397 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8398 send_soap_request_with_cb(sip, domain_uri, body,
8399 (TransCallback) process_search_contact_response, NULL);
8400 g_free(domain_uri);
8401 g_free(body);
8402 g_free(query);
8405 g_strfreev(attrs);
8408 static void sipe_show_find_contact(PurplePluginAction *action)
8410 PurpleConnection *gc = (PurpleConnection *) action->context;
8411 PurpleRequestFields *fields;
8412 PurpleRequestFieldGroup *group;
8413 PurpleRequestField *field;
8415 fields = purple_request_fields_new();
8416 group = purple_request_field_group_new(NULL);
8417 purple_request_fields_add_group(fields, group);
8419 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8420 purple_request_field_group_add_field(group, field);
8421 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8422 purple_request_field_group_add_field(group, field);
8423 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8424 purple_request_field_group_add_field(group, field);
8425 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8426 purple_request_field_group_add_field(group, field);
8428 purple_request_fields(gc,
8429 _("Search"),
8430 _("Search for a contact"),
8431 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8432 fields,
8433 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8434 _("_Cancel"), NULL,
8435 purple_connection_get_account(gc), NULL, NULL, gc);
8438 static void sipe_show_about_plugin(PurplePluginAction *action)
8440 PurpleConnection *gc = (PurpleConnection *) action->context;
8441 const char *txt =
8442 "<b><font size=\"+1\">Sipe " SIPE_VERSION "</font></b><br/>"
8443 "<br/>"
8444 "A third-party plugin implementing extended version of SIP/SIMPLE used by various products:<br/>"
8445 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8446 "<li> - MS Office Communications Server 2007</li><br/>"
8447 "<li> - MS Live Communications Server 2005</li><br/>"
8448 "<li> - MS Live Communications Server 2003</li><br/>"
8449 "<li> - Reuters Messaging</li><br/>"
8450 "<br/>"
8451 "Home: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8452 "Support: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">Help Forum</a><br/>"
8453 "License: GPLv2+<br/>"
8454 "<br/>"
8455 "We support users in the following organizations to mention a few:<br/>"
8456 " - CERN<br/>"
8457 " - Reuters Messaging network<br/>"
8458 " - Deutsche Bank<br/>"
8459 " - Merrill Lynch<br/>"
8460 " - Wachovia<br/>"
8461 " - Intel<br/>"
8462 " - Nokia<br/>"
8463 " - HP<br/>"
8464 " - Siemens<br/>"
8465 " - Alcatel-Lucent<br/>"
8466 " - BT<br/>"
8467 "<br/>"
8468 "<b>Authors:</b><br/>"
8469 " - Anibal Avelar<br/>"
8470 " - Gabriel Burt<br/>"
8471 " - Stefan Becker<br/>"
8472 " - pier11<br/>";
8474 purple_notify_formatted(gc, NULL, " ", NULL, txt, NULL, NULL);
8477 static void sipe_republish_calendar(PurplePluginAction *action)
8479 PurpleConnection *gc = (PurpleConnection *) action->context;
8480 struct sipe_account_data *sip = gc->proto_data;
8482 sipe_update_calendar(sip);
8485 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
8486 gpointer value,
8487 GString* str)
8489 struct sipe_publication *publication = value;
8491 g_string_append_printf( str,
8492 SIPE_PUB_XML_PUBLICATION_CLEAR,
8493 publication->category,
8494 publication->instance,
8495 publication->container,
8496 publication->version,
8497 "static");
8500 static void sipe_reset_status(PurplePluginAction *action)
8502 PurpleConnection *gc = (PurpleConnection *) action->context;
8503 struct sipe_account_data *sip = gc->proto_data;
8505 if (sip->ocs2007) /* 2007+ */
8507 GString* str = g_string_new(NULL);
8508 gchar *publications;
8510 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
8511 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8512 return;
8515 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
8516 publications = g_string_free(str, FALSE);
8518 send_presence_publish(sip, publications);
8519 g_free(publications);
8521 else /* 2005 */
8523 send_presence_soap0(sip, FALSE, TRUE);
8527 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
8528 gpointer context)
8530 PurpleConnection *gc = (PurpleConnection *)context;
8531 struct sipe_account_data *sip = gc->proto_data;
8532 GList *menu = NULL;
8533 PurplePluginAction *act;
8534 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
8536 act = purple_plugin_action_new(_("About SIPE plugin"), sipe_show_about_plugin);
8537 menu = g_list_prepend(menu, act);
8539 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
8540 menu = g_list_prepend(menu, act);
8542 if (!strcmp(calendar, "EXCH")) {
8543 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
8544 menu = g_list_prepend(menu, act);
8547 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
8548 menu = g_list_prepend(menu, act);
8550 menu = g_list_reverse(menu);
8552 return menu;
8555 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
8559 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8561 return TRUE;
8565 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8567 return TRUE;
8571 static char *sipe_status_text(PurpleBuddy *buddy)
8573 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8574 const PurpleStatus *status = purple_presence_get_active_status(presence);
8575 const char *status_id = purple_status_get_id(status);
8576 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
8577 struct sipe_buddy *sbuddy;
8578 char *text = NULL;
8580 if (sip) //happens on pidgin exit
8582 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8583 if (sbuddy) {
8584 const char *activity_str = sbuddy->activity ?
8585 sbuddy->activity :
8586 !strcmp(status_id, SIPE_STATUS_ID_DND) || !strcmp(status_id, SIPE_STATUS_ID_BRB) ?
8587 purple_status_get_name(status) : NULL;
8589 if (activity_str && sbuddy->note)
8591 text = g_strdup_printf("%s - %s", activity_str, sbuddy->note);
8593 else if (activity_str)
8595 text = g_strdup(activity_str);
8597 else
8599 text = g_strdup(sbuddy->note);
8604 return text;
8607 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
8609 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8610 const PurpleStatus *status = purple_presence_get_active_status(presence);
8611 struct sipe_account_data *sip;
8612 struct sipe_buddy *sbuddy;
8613 char *note = NULL;
8614 gboolean is_oof_note = FALSE;
8615 char *activity = NULL;
8616 char *calendar = NULL;
8617 char *meeting_subject = NULL;
8618 char *meeting_location = NULL;
8620 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
8621 if (sip) //happens on pidgin exit
8623 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8624 if (sbuddy)
8626 note = sbuddy->note;
8627 is_oof_note = sbuddy->is_oof_note;
8628 activity = sbuddy->activity;
8629 calendar = sipe_cal_get_description(sbuddy);
8630 meeting_subject = sbuddy->meeting_subject;
8631 meeting_location = sbuddy->meeting_location;
8635 //Layout
8636 if (purple_presence_is_online(presence))
8638 const char *status_str = activity ? activity : purple_status_get_name(status);
8640 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
8642 if (purple_presence_is_online(presence) &&
8643 !is_empty(calendar))
8645 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
8647 g_free(calendar);
8648 if (!is_empty(meeting_location))
8650 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
8652 if (!is_empty(meeting_subject))
8654 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
8657 if (note)
8659 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
8661 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), note);
8666 #if PURPLE_VERSION_CHECK(2,5,0)
8667 static GHashTable *
8668 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
8670 GHashTable *table;
8671 table = g_hash_table_new(g_str_hash, g_str_equal);
8672 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
8673 return table;
8675 #endif
8677 static PurpleBuddy *
8678 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
8680 PurpleBuddy *clone;
8681 const gchar *server_alias, *email;
8682 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
8684 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
8686 purple_blist_add_buddy(clone, NULL, group, NULL);
8688 server_alias = purple_buddy_get_server_alias(buddy);
8689 if (server_alias) {
8690 purple_blist_server_alias_buddy(clone, server_alias);
8693 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8694 if (email) {
8695 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
8698 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
8699 //for UI to update;
8700 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
8701 return clone;
8704 static void
8705 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
8707 PurpleBuddy *buddy, *b;
8708 PurpleConnection *gc;
8709 PurpleGroup * group = purple_find_group(group_name);
8711 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
8713 buddy = (PurpleBuddy *)node;
8715 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
8716 gc = purple_account_get_connection(buddy->account);
8718 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
8719 if (!b){
8720 purple_blist_add_buddy_clone(group, buddy);
8723 sipe_group_buddy(gc, buddy->name, NULL, group_name);
8726 static void
8727 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
8729 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8731 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
8733 /* 2007+ conference */
8734 if (sip->ocs2007)
8736 sipe_conf_add(sip, buddy->name);
8738 else /* 2005- multiparty chat */
8740 gchar *self = sip_uri_self(sip);
8741 struct sip_session *session;
8743 session = sipe_session_add_chat(sip);
8744 session->chat_title = sipe_chat_get_name(session->callid);
8745 session->roster_manager = g_strdup(self);
8747 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
8748 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
8749 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
8750 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
8752 g_free(self);
8756 static gboolean
8757 sipe_is_election_finished(struct sip_session *session)
8759 gboolean res = TRUE;
8761 SIPE_DIALOG_FOREACH {
8762 if (dialog->election_vote == 0) {
8763 res = FALSE;
8764 break;
8766 } SIPE_DIALOG_FOREACH_END;
8768 if (res) {
8769 session->is_voting_in_progress = FALSE;
8771 return res;
8774 static void
8775 sipe_election_start(struct sipe_account_data *sip,
8776 struct sip_session *session)
8778 int election_timeout;
8780 if (session->is_voting_in_progress) {
8781 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
8782 return;
8783 } else {
8784 session->is_voting_in_progress = TRUE;
8786 session->bid = rand();
8788 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
8790 SIPE_DIALOG_FOREACH {
8791 /* reset election_vote for each chat participant */
8792 dialog->election_vote = 0;
8794 /* send RequestRM to each chat participant*/
8795 sipe_send_election_request_rm(sip, dialog, session->bid);
8796 } SIPE_DIALOG_FOREACH_END;
8798 election_timeout = 15; /* sec */
8799 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
8803 * @param who a URI to whom to invite to chat
8805 void
8806 sipe_invite_to_chat(struct sipe_account_data *sip,
8807 struct sip_session *session,
8808 const gchar *who)
8810 /* a conference */
8811 if (session->focus_uri)
8813 sipe_invite_conf(sip, session, who);
8815 else /* a multi-party chat */
8817 gchar *self = sip_uri_self(sip);
8818 if (session->roster_manager) {
8819 if (!strcmp(session->roster_manager, self)) {
8820 sipe_invite(sip, session, who, NULL, NULL, FALSE);
8821 } else {
8822 sipe_refer(sip, session, who);
8824 } else {
8825 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
8827 session->pending_invite_queue = slist_insert_unique_sorted(
8828 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
8830 sipe_election_start(sip, session);
8832 g_free(self);
8836 void
8837 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
8838 struct sip_session *session)
8840 gchar *invitee;
8841 GSList *entry = session->pending_invite_queue;
8843 while (entry) {
8844 invitee = entry->data;
8845 sipe_invite_to_chat(sip, session, invitee);
8846 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
8847 g_free(invitee);
8851 static void
8852 sipe_election_result(struct sipe_account_data *sip,
8853 void *sess)
8855 struct sip_session *session = (struct sip_session *)sess;
8856 gchar *rival;
8857 gboolean has_won = TRUE;
8859 if (session->roster_manager) {
8860 purple_debug_info("sipe",
8861 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
8862 return;
8865 session->is_voting_in_progress = FALSE;
8867 SIPE_DIALOG_FOREACH {
8868 if (dialog->election_vote < 0) {
8869 has_won = FALSE;
8870 rival = dialog->with;
8871 break;
8873 } SIPE_DIALOG_FOREACH_END;
8875 if (has_won) {
8876 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
8878 session->roster_manager = sip_uri_self(sip);
8880 SIPE_DIALOG_FOREACH {
8881 /* send SetRM to each chat participant*/
8882 sipe_send_election_set_rm(sip, dialog);
8883 } SIPE_DIALOG_FOREACH_END;
8884 } else {
8885 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
8887 session->bid = 0;
8889 sipe_process_pending_invite_queue(sip, session);
8893 * For 2007+ conference only.
8895 static void
8896 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
8898 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8899 struct sip_session *session;
8901 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
8902 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
8904 session = sipe_session_find_chat_by_title(sip, chat_title);
8906 sipe_conf_modify_user_role(sip, session, buddy->name);
8910 * For 2007+ conference only.
8912 static void
8913 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
8915 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8916 struct sip_session *session;
8918 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
8919 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
8921 session = sipe_session_find_chat_by_title(sip, chat_title);
8923 sipe_conf_delete_user(sip, session, buddy->name);
8926 static void
8927 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
8929 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8930 struct sip_session *session;
8932 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
8933 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
8935 session = sipe_session_find_chat_by_title(sip, chat_title);
8937 sipe_invite_to_chat(sip, session, buddy->name);
8940 static void
8941 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
8943 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8945 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
8946 if (phone) {
8947 char *tel_uri = sip_to_tel_uri(phone);
8949 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
8950 sip_csta_make_call(sip, tel_uri);
8952 g_free(tel_uri);
8956 static void
8957 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
8959 const gchar *email;
8960 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
8962 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8963 if (email)
8965 char *mailto = g_strdup_printf("mailto:%s", email);
8966 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
8967 #ifndef _WIN32
8969 pid_t pid;
8970 char *const parmList[] = {"xdg-email", mailto, NULL};
8971 if ((pid = fork()) == -1)
8973 purple_debug_info("sipe", "fork() error\n");
8975 else if (pid == 0)
8977 execvp(parmList[0], parmList);
8978 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
8981 #else
8983 BOOL ret;
8984 _flushall();
8985 errno = 0;
8986 //@TODO resolve env variable %WINDIR% first
8987 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
8988 if (errno)
8990 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
8993 #endif
8995 g_free(mailto);
8997 else
8999 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9004 * A menu which appear when right-clicking on buddy in contact list.
9006 static GList *
9007 sipe_buddy_menu(PurpleBuddy *buddy)
9009 PurpleBlistNode *g_node;
9010 PurpleGroup *group, *gr_parent;
9011 PurpleMenuAction *act;
9012 GList *menu = NULL;
9013 GList *menu_groups = NULL;
9014 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9015 const char *email;
9016 const char *phone;
9017 const char *phone_disp_str;
9018 gchar *self = sip_uri_self(sip);
9020 SIPE_SESSION_FOREACH {
9021 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9023 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9025 PurpleConvChatBuddyFlags flags;
9026 PurpleConvChatBuddyFlags flags_us;
9028 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9029 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9030 if (session->focus_uri
9031 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9032 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9034 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9035 act = purple_menu_action_new(label,
9036 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9037 session->chat_title, NULL);
9038 g_free(label);
9039 menu = g_list_prepend(menu, act);
9042 if (session->focus_uri
9043 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9045 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9046 act = purple_menu_action_new(label,
9047 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9048 session->chat_title, NULL);
9049 g_free(label);
9050 menu = g_list_prepend(menu, act);
9053 else
9055 if (!session->focus_uri
9056 || (session->focus_uri && !session->locked))
9058 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9059 act = purple_menu_action_new(label,
9060 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9061 session->chat_title, NULL);
9062 g_free(label);
9063 menu = g_list_prepend(menu, act);
9067 } SIPE_SESSION_FOREACH_END;
9069 act = purple_menu_action_new(_("New chat"),
9070 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9071 NULL, NULL);
9072 menu = g_list_prepend(menu, act);
9074 if (sip->csta && !sip->csta->line_status) {
9075 gchar *tmp = NULL;
9076 /* work phone */
9077 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9078 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9079 if (phone) {
9080 gchar *label = g_strdup_printf(_("Work %s"),
9081 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9082 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9083 g_free(tmp);
9084 tmp = NULL;
9085 g_free(label);
9086 menu = g_list_prepend(menu, act);
9089 /* mobile phone */
9090 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9091 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9092 if (phone) {
9093 gchar *label = g_strdup_printf(_("Mobile %s"),
9094 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9095 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9096 g_free(tmp);
9097 tmp = NULL;
9098 g_free(label);
9099 menu = g_list_prepend(menu, act);
9102 /* home phone */
9103 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9104 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9105 if (phone) {
9106 gchar *label = g_strdup_printf(_("Home %s"),
9107 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9108 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9109 g_free(tmp);
9110 tmp = NULL;
9111 g_free(label);
9112 menu = g_list_prepend(menu, act);
9115 /* other phone */
9116 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9117 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9118 if (phone) {
9119 gchar *label = g_strdup_printf(_("Other %s"),
9120 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9121 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9122 g_free(tmp);
9123 tmp = NULL;
9124 g_free(label);
9125 menu = g_list_prepend(menu, act);
9128 /* custom1 phone */
9129 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9130 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9131 if (phone) {
9132 gchar *label = g_strdup_printf(_("Custom1 %s"),
9133 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9134 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9135 g_free(tmp);
9136 tmp = NULL;
9137 g_free(label);
9138 menu = g_list_prepend(menu, act);
9142 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9143 if (email) {
9144 act = purple_menu_action_new(_("Send email..."),
9145 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9146 NULL, NULL);
9147 menu = g_list_prepend(menu, act);
9150 gr_parent = purple_buddy_get_group(buddy);
9151 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9152 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9153 continue;
9155 group = (PurpleGroup *)g_node;
9156 if (group == gr_parent)
9157 continue;
9159 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9160 continue;
9162 act = purple_menu_action_new(purple_group_get_name(group),
9163 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9164 group->name, NULL);
9165 menu_groups = g_list_prepend(menu_groups, act);
9167 menu_groups = g_list_reverse(menu_groups);
9169 act = purple_menu_action_new(_("Copy to"),
9170 NULL,
9171 NULL, menu_groups);
9172 menu = g_list_prepend(menu, act);
9173 menu = g_list_reverse(menu);
9175 g_free(self);
9176 return menu;
9179 static void
9180 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9182 struct sipe_account_data *sip = chat->account->gc->proto_data;
9183 struct sip_session *session;
9185 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9186 sipe_conf_modify_conference_lock(sip, session, locked);
9189 static void
9190 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9192 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9193 sipe_conf_modify_lock(chat, FALSE);
9196 static void
9197 sipe_chat_menu_lock_cb(PurpleChat *chat)
9199 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9200 sipe_conf_modify_lock(chat, TRUE);
9203 static GList *
9204 sipe_chat_menu(PurpleChat *chat)
9206 PurpleMenuAction *act;
9207 PurpleConvChatBuddyFlags flags_us;
9208 GList *menu = NULL;
9209 struct sipe_account_data *sip = chat->account->gc->proto_data;
9210 struct sip_session *session;
9211 gchar *self;
9213 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9214 if (!session) return NULL;
9216 self = sip_uri_self(sip);
9217 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9219 if (session->focus_uri
9220 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9222 if (session->locked) {
9223 act = purple_menu_action_new(_("Unlock"),
9224 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9225 NULL, NULL);
9226 menu = g_list_prepend(menu, act);
9227 } else {
9228 act = purple_menu_action_new(_("Lock"),
9229 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9230 NULL, NULL);
9231 menu = g_list_prepend(menu, act);
9235 menu = g_list_reverse(menu);
9237 g_free(self);
9238 return menu;
9241 static GList *
9242 sipe_blist_node_menu(PurpleBlistNode *node)
9244 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9245 return sipe_buddy_menu((PurpleBuddy *) node);
9246 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9247 return sipe_chat_menu((PurpleChat *)node);
9248 } else {
9249 return NULL;
9253 static gboolean
9254 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9256 char *uri = trans->payload->data;
9258 PurpleNotifyUserInfo *info;
9259 PurpleBuddy *pbuddy = NULL;
9260 struct sipe_buddy *sbuddy;
9261 const char *alias = NULL;
9262 char *device_name = NULL;
9263 char *server_alias = NULL;
9264 char *phone_number = NULL;
9265 char *email = NULL;
9266 const char *site;
9268 if (!sip) return FALSE;
9270 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9272 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9273 alias = purple_buddy_get_local_alias(pbuddy);
9275 //will query buddy UA's capabilities and send answer to log
9276 sipe_options_request(sip, uri);
9278 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9279 if (sbuddy) {
9280 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9283 info = purple_notify_user_info_new();
9285 if (msg->response != 200) {
9286 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9287 } else {
9288 xmlnode *searchResults;
9289 xmlnode *mrow;
9291 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9292 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9293 if (!searchResults) {
9294 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9295 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9296 const char *value;
9297 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9298 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9299 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9301 /* For 2007 system we will take this from ContactCard -
9302 * it has cleaner tel: URIs at least
9304 if (!sip->ocs2007) {
9305 char *tel_uri = sip_to_tel_uri(phone_number);
9306 /* trims its parameters, so call first */
9307 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9308 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9309 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9310 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9311 g_free(tel_uri);
9314 if (server_alias && strlen(server_alias) > 0) {
9315 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9317 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9318 purple_notify_user_info_add_pair(info, _("Job title"), value);
9320 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9321 purple_notify_user_info_add_pair(info, _("Office"), value);
9323 if (phone_number && strlen(phone_number) > 0) {
9324 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9326 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9327 purple_notify_user_info_add_pair(info, _("Company"), value);
9329 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9330 purple_notify_user_info_add_pair(info, _("City"), value);
9332 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9333 purple_notify_user_info_add_pair(info, _("State"), value);
9335 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9336 purple_notify_user_info_add_pair(info, _("Country"), value);
9338 if (email && strlen(email) > 0) {
9339 purple_notify_user_info_add_pair(info, _("Email address"), email);
9343 xmlnode_free(searchResults);
9346 purple_notify_user_info_add_section_break(info);
9348 if (!server_alias || !strcmp("", server_alias)) {
9349 g_free(server_alias);
9350 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9351 if (server_alias) {
9352 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9356 /* present alias if it differs from server alias */
9357 if (alias && (!server_alias || strcmp(alias, server_alias)))
9359 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9362 if (!email || !strcmp("", email)) {
9363 g_free(email);
9364 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9365 if (email) {
9366 purple_notify_user_info_add_pair(info, _("Email address"), email);
9370 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9371 if (site) {
9372 purple_notify_user_info_add_pair(info, _("Site"), site);
9375 if (device_name) {
9376 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9379 /* show a buddy's user info in a nice dialog box */
9380 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9381 uri, /* buddy's URI */
9382 info, /* body */
9383 NULL, /* callback called when dialog closed */
9384 NULL); /* userdata for callback */
9386 g_free(phone_number);
9387 g_free(server_alias);
9388 g_free(email);
9389 g_free(device_name);
9391 return TRUE;
9395 * AD search first, LDAP based
9397 static void sipe_get_info(PurpleConnection *gc, const char *username)
9399 struct sipe_account_data *sip = gc->proto_data;
9400 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9401 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9402 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9403 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9405 payload->destroy = g_free;
9406 payload->data = g_strdup(username);
9408 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9409 send_soap_request_with_cb(sip, domain_uri, body,
9410 (TransCallback) process_get_info_response, payload);
9411 g_free(domain_uri);
9412 g_free(body);
9413 g_free(row);
9416 static PurplePlugin *my_protocol = NULL;
9418 static PurplePluginProtocolInfo prpl_info =
9420 OPT_PROTO_CHAT_TOPIC,
9421 NULL, /* user_splits */
9422 NULL, /* protocol_options */
9423 NO_BUDDY_ICONS, /* icon_spec */
9424 sipe_list_icon, /* list_icon */
9425 NULL, /* list_emblems */
9426 sipe_status_text, /* status_text */
9427 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9428 sipe_status_types, /* away_states */
9429 sipe_blist_node_menu, /* blist_node_menu */
9430 NULL, /* chat_info */
9431 NULL, /* chat_info_defaults */
9432 sipe_login, /* login */
9433 sipe_close, /* close */
9434 sipe_im_send, /* send_im */
9435 NULL, /* set_info */ // TODO maybe
9436 sipe_send_typing, /* send_typing */
9437 sipe_get_info, /* get_info */
9438 sipe_set_status, /* set_status */
9439 sipe_set_idle, /* set_idle */
9440 NULL, /* change_passwd */
9441 sipe_add_buddy, /* add_buddy */
9442 NULL, /* add_buddies */
9443 sipe_remove_buddy, /* remove_buddy */
9444 NULL, /* remove_buddies */
9445 sipe_add_permit, /* add_permit */
9446 sipe_add_deny, /* add_deny */
9447 sipe_add_deny, /* rem_permit */
9448 sipe_add_permit, /* rem_deny */
9449 dummy_permit_deny, /* set_permit_deny */
9450 NULL, /* join_chat */
9451 NULL, /* reject_chat */
9452 NULL, /* get_chat_name */
9453 sipe_chat_invite, /* chat_invite */
9454 sipe_chat_leave, /* chat_leave */
9455 NULL, /* chat_whisper */
9456 sipe_chat_send, /* chat_send */
9457 sipe_keep_alive, /* keepalive */
9458 NULL, /* register_user */
9459 NULL, /* get_cb_info */ // deprecated
9460 NULL, /* get_cb_away */ // deprecated
9461 sipe_alias_buddy, /* alias_buddy */
9462 sipe_group_buddy, /* group_buddy */
9463 sipe_rename_group, /* rename_group */
9464 NULL, /* buddy_free */
9465 sipe_convo_closed, /* convo_closed */
9466 purple_normalize_nocase, /* normalize */
9467 NULL, /* set_buddy_icon */
9468 sipe_remove_group, /* remove_group */
9469 NULL, /* get_cb_real_name */ // TODO?
9470 NULL, /* set_chat_topic */
9471 NULL, /* find_blist_chat */
9472 NULL, /* roomlist_get_list */
9473 NULL, /* roomlist_cancel */
9474 NULL, /* roomlist_expand_category */
9475 NULL, /* can_receive_file */
9476 NULL, /* send_file */
9477 NULL, /* new_xfer */
9478 NULL, /* offline_message */
9479 NULL, /* whiteboard_prpl_ops */
9480 sipe_send_raw, /* send_raw */
9481 NULL, /* roomlist_room_serialize */
9482 NULL, /* unregister_user */
9483 NULL, /* send_attention */
9484 NULL, /* get_attention_types */
9485 #if !PURPLE_VERSION_CHECK(2,5,0)
9486 /* Backward compatibility when compiling against 2.4.x API */
9487 (void (*)(void)) /* _purple_reserved4 */
9488 #endif
9489 sizeof(PurplePluginProtocolInfo), /* struct_size */
9490 #if PURPLE_VERSION_CHECK(2,5,0)
9491 sipe_get_account_text_table, /* get_account_text_table */
9492 #if PURPLE_VERSION_CHECK(2,6,0)
9493 NULL, /* initiate_media */
9494 NULL, /* get_media_caps */
9495 #endif
9496 #endif
9500 static PurplePluginInfo info = {
9501 PURPLE_PLUGIN_MAGIC,
9502 PURPLE_MAJOR_VERSION,
9503 PURPLE_MINOR_VERSION,
9504 PURPLE_PLUGIN_PROTOCOL, /**< type */
9505 NULL, /**< ui_requirement */
9506 0, /**< flags */
9507 NULL, /**< dependencies */
9508 PURPLE_PRIORITY_DEFAULT, /**< priority */
9509 "prpl-sipe", /**< id */
9510 "Office Communicator", /**< name */
9511 SIPE_VERSION, /**< version */
9512 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9513 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9514 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9515 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9516 "Gabriel Burt <gburt@novell.com>, " /**< author */
9517 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9518 "pier11 <pier11@operamail.com>", /**< author */
9519 "http://sipe.sourceforge.net/", /**< homepage */
9520 sipe_plugin_load, /**< load */
9521 sipe_plugin_unload, /**< unload */
9522 sipe_plugin_destroy, /**< destroy */
9523 NULL, /**< ui_info */
9524 &prpl_info, /**< extra_info */
9525 NULL,
9526 sipe_actions,
9527 NULL,
9528 NULL,
9529 NULL,
9530 NULL
9533 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9535 GList *entry;
9537 entry = prpl_info.protocol_options;
9538 while (entry) {
9539 purple_account_option_destroy(entry->data);
9540 entry = g_list_delete_link(entry, entry);
9542 prpl_info.protocol_options = NULL;
9544 entry = prpl_info.user_splits;
9545 while (entry) {
9546 purple_account_user_split_destroy(entry->data);
9547 entry = g_list_delete_link(entry, entry);
9549 prpl_info.user_splits = NULL;
9552 static void init_plugin(PurplePlugin *plugin)
9554 PurpleAccountUserSplit *split;
9555 PurpleAccountOption *option;
9557 srand(time(NULL));
9559 #ifdef ENABLE_NLS
9560 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
9561 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
9562 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
9563 textdomain(GETTEXT_PACKAGE);
9564 #endif
9566 purple_plugin_register(plugin);
9568 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
9569 purple_account_user_split_set_reverse(split, FALSE);
9570 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
9572 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
9573 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9575 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
9576 purple_account_option_add_list_item(option, _("Auto"), "auto");
9577 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
9578 purple_account_option_add_list_item(option, _("TCP"), "tcp");
9579 purple_account_option_add_list_item(option, _("UDP"), "udp");
9580 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9582 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
9583 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
9585 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
9586 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9588 #ifdef USE_KERBEROS
9589 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
9590 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9592 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
9593 * No login/password is taken into account if this option present,
9594 * instead used default credentials stored in OS.
9596 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
9597 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9598 #endif
9600 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
9601 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
9602 purple_account_option_add_list_item(option, _("None"), "NONE");
9603 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9605 /** Example: https://server.company.com/EWS/Exchange.asmx */
9606 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
9607 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9609 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
9610 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9612 /** Example: DOMAIN\user or user@company.com */
9613 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
9614 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9616 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
9617 purple_account_option_set_masked(option, TRUE);
9618 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9620 my_protocol = plugin;
9623 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
9626 Local Variables:
9627 mode: c
9628 c-file-style: "bsd"
9629 indent-tabs-mode: t
9630 tab-width: 8
9631 End: