added "Find on LinkedIn" link to User Info screen.
[siplcs.git] / src / core / sipe.c
blob29c155273fcaf6e3c1467e936a0d437bd137ee07
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_("Busy-Idle") , 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 sip->registrar.type = AUTH_TYPE_NTLM;
796 #ifdef USE_KERBEROS
797 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
798 sip->registrar.type = AUTH_TYPE_KERBEROS;
800 #endif
803 buf = auth_header(sip, &sip->registrar, msg);
804 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
805 g_free(buf);
806 } else {
807 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
811 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
812 const char *text, const char *body)
814 gchar *name;
815 gchar *value;
816 GString *outstr = g_string_new("");
817 struct sipe_account_data *sip = gc->proto_data;
818 gchar *contact;
819 GSList *tmp;
820 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
822 /* Can return NULL! */
823 contact = get_contact(sip);
824 if (contact) {
825 sipmsg_add_header(msg, "Contact", contact);
826 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;
899 if (!call_id || !cseq) {
900 purple_debug(PURPLE_DEBUG_ERROR, "sipe", "transaction_find: no Call-ID or CSeq!\n");
901 return NULL;
904 key = g_strdup_printf("<%s><%s>", call_id, cseq);
905 while (transactions) {
906 trans = transactions->data;
907 if (!g_strcasecmp(trans->key, key)) {
908 g_free(key);
909 return trans;
911 transactions = transactions->next;
914 g_free(key);
915 return NULL;
918 struct transaction *
919 send_sip_request(PurpleConnection *gc, const gchar *method,
920 const gchar *url, const gchar *to, const gchar *addheaders,
921 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
923 struct sipe_account_data *sip = gc->proto_data;
924 const char *addh = "";
925 char *buf;
926 struct sipmsg *msg;
927 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
928 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
929 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
930 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
931 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
932 gchar *route = g_strdup("");
933 gchar *epid = get_epid(sip);
934 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
935 struct transaction *trans = NULL;
937 if (dialog && dialog->routes)
939 GSList *iter = dialog->routes;
941 while(iter)
943 char *tmp = route;
944 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
945 g_free(tmp);
946 iter = g_slist_next(iter);
950 if (!ourtag && !dialog) {
951 ourtag = gentag();
954 if (!strcmp(method, "REGISTER")) {
955 if (sip->regcallid) {
956 g_free(callid);
957 callid = g_strdup(sip->regcallid);
958 } else {
959 sip->regcallid = g_strdup(callid);
961 cseq = ++sip->cseq;
964 if (addheaders) addh = addheaders;
966 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
967 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
968 "From: <sip:%s>%s%s;epid=%s\r\n"
969 "To: <%s>%s%s%s%s\r\n"
970 "Max-Forwards: 70\r\n"
971 "CSeq: %d %s\r\n"
972 "User-Agent: %s\r\n"
973 "Call-ID: %s\r\n"
974 "%s%s"
975 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
976 method,
977 dialog && dialog->request ? dialog->request : url,
978 TRANSPORT_DESCRIPTOR,
979 purple_network_get_my_ip(-1),
980 sip->listenport,
981 branch ? ";branch=" : "",
982 branch ? branch : "",
983 sip->username,
984 ourtag ? ";tag=" : "",
985 ourtag ? ourtag : "",
986 epid,
988 theirtag ? ";tag=" : "",
989 theirtag ? theirtag : "",
990 theirepid ? ";epid=" : "",
991 theirepid ? theirepid : "",
992 cseq,
993 method,
994 sipe_get_useragent(sip),
995 callid,
996 route,
997 addh,
998 body ? (gsize) strlen(body) : 0,
999 body ? body : "");
1002 //printf ("parsing msg buf:\n%s\n\n", buf);
1003 msg = sipmsg_parse_msg(buf);
1005 g_free(buf);
1006 g_free(ourtag);
1007 g_free(theirtag);
1008 g_free(theirepid);
1009 g_free(branch);
1010 g_free(callid);
1011 g_free(route);
1012 g_free(epid);
1014 sign_outgoing_message (msg, sip, method);
1016 buf = sipmsg_to_string (msg);
1018 /* add to ongoing transactions */
1019 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1020 if (strcmp(method, "ACK")) {
1021 trans = transactions_add_buf(sip, msg, tc);
1022 } else {
1023 sipmsg_free(msg);
1025 sendout_pkt(gc, buf);
1026 g_free(buf);
1028 return trans;
1032 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1034 static void
1035 send_soap_request_with_cb(struct sipe_account_data *sip,
1036 gchar *from0,
1037 gchar *body,
1038 TransCallback callback,
1039 struct transaction_payload *payload)
1041 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1042 gchar *contact = get_contact(sip);
1043 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1044 "Content-Type: application/SOAP+xml\r\n",contact);
1046 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1047 trans->payload = payload;
1049 g_free(from);
1050 g_free(contact);
1051 g_free(hdr);
1054 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1056 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1059 static char *get_contact_register(struct sipe_account_data *sip)
1061 char *epid = get_epid(sip);
1062 char *uuid = generateUUIDfromEPID(epid);
1063 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);
1064 g_free(uuid);
1065 g_free(epid);
1066 return(buf);
1069 static void do_register_exp(struct sipe_account_data *sip, int expire)
1071 char *uri;
1072 char *expires;
1073 char *to;
1074 char *contact;
1075 char *hdr;
1077 if (!sip->sipdomain) return;
1079 uri = sip_uri_from_name(sip->sipdomain);
1080 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1081 to = sip_uri_self(sip);
1082 contact = get_contact_register(sip);
1083 hdr = g_strdup_printf("Contact: %s\r\n"
1084 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1085 "Event: registration\r\n"
1086 "Allow-Events: presence\r\n"
1087 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1088 "%s", contact, expires);
1089 g_free(contact);
1090 g_free(expires);
1092 sip->registerstatus = 1;
1094 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1095 process_register_response);
1097 g_free(hdr);
1098 g_free(uri);
1099 g_free(to);
1102 static void do_register_cb(struct sipe_account_data *sip,
1103 SIPE_UNUSED_PARAMETER void *unused)
1105 do_register_exp(sip, -1);
1106 sip->reregister_set = FALSE;
1109 static void do_register(struct sipe_account_data *sip)
1111 do_register_exp(sip, -1);
1114 static void
1115 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1117 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1118 send_soap_request(sip, body);
1119 g_free(body);
1122 static void
1123 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1125 if (allow) {
1126 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1127 } else {
1128 purple_debug_info("sipe", "Blocking contact %s\n", who);
1131 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1134 static
1135 void sipe_auth_user_cb(void * data)
1137 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1138 if (!job) return;
1140 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1141 g_free(job);
1144 static
1145 void sipe_deny_user_cb(void * data)
1147 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1148 if (!job) return;
1150 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1151 g_free(job);
1154 static void
1155 sipe_add_permit(PurpleConnection *gc, const char *name)
1157 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1158 sipe_contact_allow_deny(sip, name, TRUE);
1161 static void
1162 sipe_add_deny(PurpleConnection *gc, const char *name)
1164 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1165 sipe_contact_allow_deny(sip, name, FALSE);
1168 /*static void
1169 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1171 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1172 sipe_contact_set_acl(sip, name, "");
1175 static void
1176 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1178 xmlnode *watchers;
1179 xmlnode *watcher;
1180 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1181 if (msg->response != 0 && msg->response != 200) return;
1183 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1185 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1186 if (!watchers) return;
1188 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1189 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1190 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1191 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1193 // TODO pull out optional displayName to pass as alias
1194 if (remote_user) {
1195 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1196 job->who = remote_user;
1197 job->sip = sip;
1198 purple_account_request_authorization(
1199 sip->account,
1200 remote_user,
1201 _("you"), /* id */
1202 alias,
1203 NULL, /* message */
1204 on_list,
1205 sipe_auth_user_cb,
1206 sipe_deny_user_cb,
1207 (void *) job);
1212 xmlnode_free(watchers);
1213 return;
1216 static void
1217 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1219 PurpleGroup * purple_group = purple_find_group(group->name);
1220 if (!purple_group) {
1221 purple_group = purple_group_new(group->name);
1222 purple_blist_add_group(purple_group, NULL);
1225 if (purple_group) {
1226 group->purple_group = purple_group;
1227 sip->groups = g_slist_append(sip->groups, group);
1228 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1229 } else {
1230 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1234 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1236 struct sipe_group *group;
1237 GSList *entry;
1238 if (sip == NULL) {
1239 return NULL;
1242 entry = sip->groups;
1243 while (entry) {
1244 group = entry->data;
1245 if (group->id == id) {
1246 return group;
1248 entry = entry->next;
1250 return NULL;
1253 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1255 struct sipe_group *group;
1256 GSList *entry;
1257 if (!sip || !name) {
1258 return NULL;
1261 entry = sip->groups;
1262 while (entry) {
1263 group = entry->data;
1264 if (!strcmp(group->name, name)) {
1265 return group;
1267 entry = entry->next;
1269 return NULL;
1272 static void
1273 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1275 gchar *body;
1276 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1277 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1278 send_soap_request(sip, body);
1279 g_free(body);
1280 g_free(group->name);
1281 group->name = g_strdup(name);
1285 * Only appends if no such value already stored.
1286 * Like Set in Java.
1288 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1289 GSList * res = list;
1290 if (!g_slist_find_custom(list, data, func)) {
1291 res = g_slist_insert_sorted(list, data, func);
1293 return res;
1296 static int
1297 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1298 return group1->id - group2->id;
1302 * Returns string like "2 4 7 8" - group ids buddy belong to.
1304 static gchar *
1305 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1306 int i = 0;
1307 gchar *res;
1308 //creating array from GList, converting int to gchar*
1309 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1310 GSList *entry = buddy->groups;
1311 while (entry) {
1312 struct sipe_group * group = entry->data;
1313 ids_arr[i] = g_strdup_printf("%d", group->id);
1314 entry = entry->next;
1315 i++;
1317 ids_arr[i] = NULL;
1318 res = g_strjoinv(" ", ids_arr);
1319 g_strfreev(ids_arr);
1320 return res;
1324 * Sends buddy update to server
1326 static void
1327 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1329 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1330 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1332 if (buddy && purple_buddy) {
1333 const char *alias = purple_buddy_get_alias(purple_buddy);
1334 gchar *body;
1335 gchar *groups = sipe_get_buddy_groups_string(buddy);
1336 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1338 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1339 alias, groups, "true", buddy->name, sip->contacts_delta++
1341 send_soap_request(sip, body);
1342 g_free(groups);
1343 g_free(body);
1347 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1349 if (msg->response == 200) {
1350 struct sipe_group *group;
1351 struct group_user_context *ctx = trans->payload->data;
1352 xmlnode *xml;
1353 xmlnode *node;
1354 char *group_id;
1355 struct sipe_buddy *buddy;
1357 xml = xmlnode_from_str(msg->body, msg->bodylen);
1358 if (!xml) {
1359 return FALSE;
1362 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1363 if (!node) {
1364 xmlnode_free(xml);
1365 return FALSE;
1368 group_id = xmlnode_get_data(node);
1369 if (!group_id) {
1370 xmlnode_free(xml);
1371 return FALSE;
1374 group = g_new0(struct sipe_group, 1);
1375 group->id = (int)g_ascii_strtod(group_id, NULL);
1376 g_free(group_id);
1377 group->name = g_strdup(ctx->group_name);
1379 sipe_group_add(sip, group);
1381 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1382 if (buddy) {
1383 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1386 sipe_group_set_user(sip, ctx->user_name);
1388 xmlnode_free(xml);
1389 return TRUE;
1391 return FALSE;
1394 static void sipe_group_context_destroy(gpointer data)
1396 struct group_user_context *ctx = data;
1397 g_free(ctx->group_name);
1398 g_free(ctx->user_name);
1399 g_free(ctx);
1402 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1404 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1405 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1406 gchar *body;
1407 ctx->group_name = g_strdup(name);
1408 ctx->user_name = g_strdup(who);
1409 payload->destroy = sipe_group_context_destroy;
1410 payload->data = ctx;
1412 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1413 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1414 g_free(body);
1418 * Data structure for scheduled actions
1421 struct scheduled_action {
1423 * Name of action.
1424 * Format is <Event>[<Data>...]
1425 * Example: <presence><sip:user@domain.com> or <registration>
1427 gchar *name;
1428 guint timeout_handler;
1429 gboolean repetitive;
1430 Action action;
1431 GDestroyNotify destroy;
1432 struct sipe_account_data *sip;
1433 void *payload;
1437 * A timer callback
1438 * Should return FALSE if repetitive action is not needed
1440 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1442 gboolean ret;
1443 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1444 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1445 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1446 (sched_action->action)(sched_action->sip, sched_action->payload);
1447 ret = sched_action->repetitive;
1448 if (sched_action->destroy) {
1449 (*sched_action->destroy)(sched_action->payload);
1451 g_free(sched_action->name);
1452 g_free(sched_action);
1453 return ret;
1457 * Kills action timer effectively cancelling
1458 * scheduled action
1460 * @param name of action
1462 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1464 GSList *entry;
1466 if (!sip->timeouts || !name) return;
1468 entry = sip->timeouts;
1469 while (entry) {
1470 struct scheduled_action *sched_action = entry->data;
1471 if(!strcmp(sched_action->name, name)) {
1472 GSList *to_delete = entry;
1473 entry = entry->next;
1474 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1475 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1476 purple_timeout_remove(sched_action->timeout_handler);
1477 if (sched_action->destroy) {
1478 (*sched_action->destroy)(sched_action->payload);
1480 g_free(sched_action->name);
1481 g_free(sched_action);
1482 } else {
1483 entry = entry->next;
1488 static void
1489 sipe_schedule_action0(const gchar *name,
1490 int timeout,
1491 gboolean isSeconds,
1492 Action action,
1493 GDestroyNotify destroy,
1494 struct sipe_account_data *sip,
1495 void *payload)
1497 struct scheduled_action *sched_action;
1499 /* Make sure each action only exists once */
1500 sipe_cancel_scheduled_action(sip, name);
1502 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1503 sched_action = g_new0(struct scheduled_action, 1);
1504 sched_action->repetitive = FALSE;
1505 sched_action->name = g_strdup(name);
1506 sched_action->action = action;
1507 sched_action->destroy = destroy;
1508 sched_action->sip = sip;
1509 sched_action->payload = payload;
1510 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1511 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1512 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1513 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1516 void
1517 sipe_schedule_action(const gchar *name,
1518 int timeout,
1519 Action action,
1520 GDestroyNotify destroy,
1521 struct sipe_account_data *sip,
1522 void *payload)
1524 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1528 * Same as sipe_schedule_action() but timeout is in milliseconds.
1530 static void
1531 sipe_schedule_action_msec(const gchar *name,
1532 int timeout,
1533 Action action,
1534 GDestroyNotify destroy,
1535 struct sipe_account_data *sip,
1536 void *payload)
1538 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1541 static void
1542 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1543 time_t calculate_from);
1545 static int
1546 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1548 static const char*
1549 sipe_get_status_by_availability(int avail,
1550 char** activity);
1552 static void
1553 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1554 const char *status_id,
1555 const char *message,
1556 time_t do_not_publish[]);
1558 static void
1559 sipe_apply_calendar_status(struct sipe_account_data *sip,
1560 struct sipe_buddy *sbuddy,
1561 const char *status_id)
1563 time_t cal_avail_since;
1564 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1565 int avail;
1566 gchar *self_uri = sip_uri_self(sip);
1568 if (!sbuddy) return;
1570 if (cal_status < SIPE_CAL_NO_DATA) {
1571 purple_debug_info("sipe", "update_calendar_status_cb: cal_status : %d for %s\n", cal_status, sbuddy->name);
1572 purple_debug_info("sipe", "update_calendar_status_cb: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1575 /* scheduled Cal update call */
1576 if (!status_id) {
1577 status_id = sbuddy->last_non_cal_status_id;
1578 g_free(sbuddy->activity);
1579 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1582 /* adjust to calendar status */
1583 if (cal_status != SIPE_CAL_NO_DATA) {
1584 purple_debug_info("sipe", "update_calendar_status_cb: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1586 if (cal_status == SIPE_CAL_BUSY
1587 && cal_avail_since > sbuddy->user_avail_since
1588 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1590 status_id = SIPE_STATUS_ID_BUSY;
1591 g_free(sbuddy->activity);
1592 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1594 avail = sipe_get_availability_by_status(status_id, NULL);
1596 purple_debug_info("sipe", "update_calendar_status_cb: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1597 if (cal_avail_since > sbuddy->activity_since) {
1598 if (cal_status == SIPE_CAL_OOF
1599 && avail >= 15000) /* 12000 in 2007 */
1601 g_free(sbuddy->activity);
1602 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1607 /* then set status_id actually */
1608 purple_debug_info("sipe", "sipe_got_user_status: to %s for %s\n", status_id ? status_id : "", sbuddy->name ? sbuddy->name : "" );
1609 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1611 /* set our account state to the one in roaming (including calendar info) */
1612 if (sip->initial_state_published && !strcmp(sbuddy->name, self_uri)) {
1613 if (!strcmp(status_id, SIPE_STATUS_ID_OFFLINE)) {
1614 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1617 purple_debug_info("sipe", "sipe_got_user_status: switch to '%s' for the account\n", sip->status);
1618 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1620 g_free(self_uri);
1623 static void
1624 sipe_got_user_status(struct sipe_account_data *sip,
1625 const char* uri,
1626 const char *status_id)
1628 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1630 if (!sbuddy) return;
1632 /* Check if on 2005 system contact's calendar,
1633 * then set/preserve it.
1635 if (!sip->ocs2007) {
1636 sipe_apply_calendar_status(sip, sbuddy, status_id);
1637 } else {
1638 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1642 static void
1643 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1644 struct sipe_buddy *sbuddy,
1645 struct sipe_account_data *sip)
1647 sipe_apply_calendar_status(sip, sbuddy, NULL);
1651 * Updates contact's status
1652 * based on their calendar information.
1654 * Applicability: 2005 systems
1656 static void
1657 update_calendar_status(struct sipe_account_data *sip)
1659 purple_debug_info("sipe", "update_calendar_status() started.\n");
1660 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1662 /* repeat scheduling */
1663 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1667 * Schedules process of contacts' status update
1668 * based on their calendar information.
1669 * Should be scheduled to the beginning of every
1670 * 15 min interval, like:
1671 * 13:00, 13:15, 13:30, 13:45, etc.
1673 * Applicability: 2005 systems
1675 static void
1676 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1677 time_t calculate_from)
1679 int interval = 15*60;
1680 /** start of the beginning of closest 15 min interval. */
1681 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1683 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1684 asctime(localtime(&calculate_from)));
1685 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1686 asctime(localtime(&next_start)));
1688 sipe_schedule_action("<+2005-cal-status>",
1689 (int)(next_start - time(NULL)),
1690 (Action)update_calendar_status,
1691 NULL,
1692 sip,
1693 NULL);
1697 * Schedules process of self status publish
1698 * based on own calendar information.
1699 * Should be scheduled to the beginning of every
1700 * 15 min interval, like:
1701 * 13:00, 13:15, 13:30, 13:45, etc.
1703 * Applicability: 2007+ systems
1705 static void
1706 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1707 time_t calculate_from)
1709 int interval = 5*60;
1710 /** start of the beginning of closest 5 min interval. */
1711 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1713 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1714 asctime(localtime(&calculate_from)));
1715 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1716 asctime(localtime(&next_start)));
1718 sipe_schedule_action("<+2007-cal-status>",
1719 (int)(next_start - time(NULL)),
1720 (Action)publish_calendar_status_self,
1721 NULL,
1722 sip,
1723 NULL);
1726 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1728 /** Should be g_free()'d
1730 static gchar *
1731 sipe_get_subscription_key(gchar *event,
1732 gchar *with)
1734 gchar *key = NULL;
1736 if (is_empty(event)) return NULL;
1738 if (event && !g_ascii_strcasecmp(event, "presence")) {
1739 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1740 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1742 /* @TODO drop participated buddies' just_added flag */
1743 } else if (event) {
1744 /* Subscription is identified by <event> key */
1745 key = g_strdup_printf("<%s>", event);
1748 return key;
1751 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1752 SIPE_UNUSED_PARAMETER struct transaction *trans)
1754 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1755 gchar *event = sipmsg_find_header(msg, "Event");
1756 gchar *key;
1758 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1759 if (!event) {
1760 struct sipmsg *request_msg = trans->msg;
1761 event = sipmsg_find_header(request_msg, "Event");
1764 key = sipe_get_subscription_key(event, with);
1766 /* 200 OK; 481 Call Leg Does Not Exist */
1767 if (key && (msg->response == 200 || msg->response == 481)) {
1768 if (g_hash_table_lookup(sip->subscriptions, key)) {
1769 g_hash_table_remove(sip->subscriptions, key);
1770 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1774 /* create/store subscription dialog if not yet */
1775 if (msg->response == 200) {
1776 gchar *callid = sipmsg_find_header(msg, "Call-ID");
1777 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1779 if (key) {
1780 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1781 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1783 subscription->dialog.callid = g_strdup(callid);
1784 subscription->dialog.cseq = atoi(cseq);
1785 subscription->dialog.with = g_strdup(with);
1786 subscription->event = g_strdup(event);
1787 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1789 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1792 g_free(cseq);
1795 g_free(key);
1796 g_free(with);
1798 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1800 process_incoming_notify(sip, msg, FALSE, FALSE);
1802 return TRUE;
1805 static void sipe_subscribe_resource_uri(const char *name,
1806 SIPE_UNUSED_PARAMETER gpointer value,
1807 gchar **resources_uri)
1809 gchar *tmp = *resources_uri;
1810 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1811 g_free(tmp);
1814 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1816 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1817 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1818 gchar *tmp = *resources_uri;
1820 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1822 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1823 g_free(tmp);
1827 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1828 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1829 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1830 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1831 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1834 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1836 gchar *key;
1837 gchar *contact = get_contact(sip);
1838 gchar *request;
1839 gchar *content;
1840 gchar *require = "";
1841 gchar *accept = "";
1842 gchar *autoextend = "";
1843 gchar *content_type;
1844 struct sip_dialog *dialog;
1846 if (sip->ocs2007) {
1847 require = ", categoryList";
1848 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1849 content_type = "application/msrtc-adrl-categorylist+xml";
1850 content = g_strdup_printf(
1851 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1852 "<action name=\"subscribe\" id=\"63792024\">\n"
1853 "<adhocList>\n%s</adhocList>\n"
1854 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1855 "<category name=\"calendarData\"/>\n"
1856 "<category name=\"contactCard\"/>\n"
1857 "<category name=\"note\"/>\n"
1858 "<category name=\"state\"/>\n"
1859 "</categoryList>\n"
1860 "</action>\n"
1861 "</batchSub>", sip->username, resources_uri);
1862 } else {
1863 autoextend = "Supported: com.microsoft.autoextend\r\n";
1864 content_type = "application/adrl+xml";
1865 content = g_strdup_printf(
1866 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1867 "<create xmlns=\"\">\n%s</create>\n"
1868 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1870 g_free(resources_uri);
1872 request = g_strdup_printf(
1873 "Require: adhoclist%s\r\n"
1874 "Supported: eventlist\r\n"
1875 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1876 "Supported: ms-piggyback-first-notify\r\n"
1877 "%sSupported: ms-benotify\r\n"
1878 "Proxy-Require: ms-benotify\r\n"
1879 "Event: presence\r\n"
1880 "Content-Type: %s\r\n"
1881 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1882 g_free(contact);
1884 /* subscribe to buddy presence */
1885 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1886 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1887 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1888 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1890 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1892 g_free(content);
1893 g_free(to);
1894 g_free(request);
1895 g_free(key);
1898 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1899 SIPE_UNUSED_PARAMETER void *unused)
1901 gchar *to = sip_uri_self(sip);
1902 gchar *resources_uri = g_strdup("");
1903 if (sip->ocs2007) {
1904 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1905 } else {
1906 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1909 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1912 struct presence_batched_routed {
1913 gchar *host;
1914 GSList *buddies;
1917 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1919 struct presence_batched_routed *data = payload;
1920 GSList *buddies = data->buddies;
1921 while (buddies) {
1922 g_free(buddies->data);
1923 buddies = buddies->next;
1925 g_slist_free(data->buddies);
1926 g_free(data->host);
1927 g_free(payload);
1930 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1932 struct presence_batched_routed *data = payload;
1933 GSList *buddies = data->buddies;
1934 gchar *resources_uri = g_strdup("");
1935 while (buddies) {
1936 gchar *tmp = resources_uri;
1937 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1938 g_free(tmp);
1939 buddies = buddies->next;
1941 sipe_subscribe_presence_batched_to(sip, resources_uri,
1942 g_strdup(data->host));
1946 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1947 * The user sends a single SUBSCRIBE request to the subscribed contact.
1948 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1952 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1955 gchar *key;
1956 gchar *to = sip_uri((char *)buddy_name);
1957 gchar *tmp = get_contact(sip);
1958 gchar *request;
1959 gchar *content = NULL;
1960 gchar *autoextend = "";
1961 gchar *content_type = "";
1962 struct sip_dialog *dialog;
1963 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
1964 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1966 if (sbuddy) sbuddy->just_added = FALSE;
1968 if (sip->ocs2007) {
1969 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1970 } else {
1971 autoextend = "Supported: com.microsoft.autoextend\r\n";
1974 request = g_strdup_printf(
1975 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1976 "Supported: ms-piggyback-first-notify\r\n"
1977 "%s%sSupported: ms-benotify\r\n"
1978 "Proxy-Require: ms-benotify\r\n"
1979 "Event: presence\r\n"
1980 "Contact: %s\r\n", autoextend, content_type, tmp);
1982 if (sip->ocs2007) {
1983 content = g_strdup_printf(
1984 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1985 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1986 "<resource uri=\"%s\"%s\n"
1987 "</adhocList>\n"
1988 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1989 "<category name=\"calendarData\"/>\n"
1990 "<category name=\"contactCard\"/>\n"
1991 "<category name=\"note\"/>\n"
1992 "<category name=\"state\"/>\n"
1993 "</categoryList>\n"
1994 "</action>\n"
1995 "</batchSub>", sip->username, to, context);
1998 g_free(tmp);
2000 /* subscribe to buddy presence */
2001 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2002 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2003 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2004 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2006 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2008 g_free(content);
2009 g_free(to);
2010 g_free(request);
2011 g_free(key);
2014 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2016 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2018 if (!purple_status_is_active(status))
2019 return;
2021 if (account->gc) {
2022 struct sipe_account_data *sip = account->gc->proto_data;
2024 if (sip) {
2025 gchar *action_name;
2026 gchar *tmp;
2027 time_t now = time(NULL);
2028 const char *status_id = purple_status_get_id(status);
2029 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2030 sipe_activity activity = sipe_get_activity_by_token(status_id);
2031 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2033 /* when other point of presence clears note, but we are keeping
2034 * state if OOF note.
2036 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2037 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2038 do_not_publish = FALSE;
2041 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2042 status_id, (int)sip->do_not_publish[activity], (int)now);
2044 sip->do_not_publish[activity] = 0;
2045 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2046 status_id, (int)sip->do_not_publish[activity]);
2048 if (do_not_publish)
2050 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2051 return;
2054 g_free(sip->status);
2055 sip->status = g_strdup(status_id);
2057 /* hack to escape apostrof before comparison */
2058 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2060 /* this will preserve OOF flag as well */
2061 if (!(tmp && sip->note && !strcmp(tmp, sip->note))) {
2062 sip->is_oof_note = FALSE;
2063 g_free(sip->note);
2064 sip->note = g_strdup(note);
2065 sip->note_since = time(NULL);
2067 g_free(tmp);
2069 /* schedule 2 sec to capture idle flag */
2070 action_name = g_strdup_printf("<%s>", "+set-status");
2071 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2072 g_free(action_name);
2076 static void
2077 sipe_set_idle(PurpleConnection * gc,
2078 int interval)
2080 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2082 if (gc) {
2083 struct sipe_account_data *sip = gc->proto_data;
2085 if (sip) {
2086 sip->idle_switch = time(NULL);
2087 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2092 static void
2093 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2094 SIPE_UNUSED_PARAMETER const char *alias)
2096 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2097 sipe_group_set_user(sip, name);
2100 static void
2101 sipe_group_buddy(PurpleConnection *gc,
2102 const char *who,
2103 const char *old_group_name,
2104 const char *new_group_name)
2106 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2107 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2108 struct sipe_group * old_group = NULL;
2109 struct sipe_group * new_group;
2111 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2112 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2114 if(!buddy) { // buddy not in roaming list
2115 return;
2118 if (old_group_name) {
2119 old_group = sipe_group_find_by_name(sip, old_group_name);
2121 new_group = sipe_group_find_by_name(sip, new_group_name);
2123 if (old_group) {
2124 buddy->groups = g_slist_remove(buddy->groups, old_group);
2125 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2128 if (!new_group) {
2129 sipe_group_create(sip, new_group_name, who);
2130 } else {
2131 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2132 sipe_group_set_user(sip, who);
2136 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2138 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2140 /* libpurple can call us with undefined buddy or group */
2141 if (buddy && group) {
2142 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2144 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2145 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2146 purple_blist_rename_buddy(buddy, buddy_name);
2147 g_free(buddy_name);
2149 /* Prepend sip: if needed */
2150 if (strncmp("sip:", buddy->name, 4)) {
2151 gchar *buf = sip_uri_from_name(buddy->name);
2152 purple_blist_rename_buddy(buddy, buf);
2153 g_free(buf);
2156 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2157 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2158 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2159 b->name = g_strdup(buddy->name);
2160 b->just_added = TRUE;
2161 g_hash_table_insert(sip->buddies, b->name, b);
2162 sipe_group_buddy(gc, b->name, NULL, group->name);
2163 /* @TODO should go to callback */
2164 sipe_subscribe_presence_single(sip, b->name);
2165 } else {
2166 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2171 static void sipe_free_buddy(struct sipe_buddy *buddy)
2173 #ifndef _WIN32
2175 * We are calling g_hash_table_foreach_steal(). That means that no
2176 * key/value deallocation functions are called. Therefore the glib
2177 * hash code does not touch the key (buddy->name) or value (buddy)
2178 * of the to-be-deleted hash node at all. It follows that we
2180 * - MUST free the memory for the key ourselves and
2181 * - ARE allowed to do it in this function
2183 * Conclusion: glib must be broken on the Windows platform if sipe
2184 * crashes with SIGTRAP when closing. You'll have to live
2185 * with the memory leak until this is fixed.
2187 g_free(buddy->name);
2188 #endif
2189 g_free(buddy->activity);
2190 g_free(buddy->meeting_subject);
2191 g_free(buddy->meeting_location);
2192 g_free(buddy->note);
2194 g_free(buddy->cal_start_time);
2195 g_free(buddy->cal_free_busy_base64);
2196 g_free(buddy->cal_free_busy);
2197 g_free(buddy->last_non_cal_activity);
2199 sipe_cal_free_working_hours(buddy->cal_working_hours);
2201 g_free(buddy->device_name);
2202 g_slist_free(buddy->groups);
2203 g_free(buddy);
2207 * Unassociates buddy from group first.
2208 * Then see if no groups left, removes buddy completely.
2209 * Otherwise updates buddy groups on server.
2211 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2213 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2214 struct sipe_buddy *b;
2215 struct sipe_group *g = NULL;
2217 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2218 if (!buddy) return;
2220 b = g_hash_table_lookup(sip->buddies, buddy->name);
2221 if (!b) return;
2223 if (group) {
2224 g = sipe_group_find_by_name(sip, group->name);
2227 if (g) {
2228 b->groups = g_slist_remove(b->groups, g);
2229 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2232 if (g_slist_length(b->groups) < 1) {
2233 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2234 sipe_cancel_scheduled_action(sip, action_name);
2235 g_free(action_name);
2237 g_hash_table_remove(sip->buddies, buddy->name);
2239 if (b->name) {
2240 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2241 send_soap_request(sip, body);
2242 g_free(body);
2245 sipe_free_buddy(b);
2246 } else {
2247 //updates groups on server
2248 sipe_group_set_user(sip, b->name);
2253 static void
2254 sipe_rename_group(PurpleConnection *gc,
2255 const char *old_name,
2256 PurpleGroup *group,
2257 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2259 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2260 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2261 if (s_group) {
2262 sipe_group_rename(sip, s_group, group->name);
2263 } else {
2264 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2268 static void
2269 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2271 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2272 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2273 if (s_group) {
2274 gchar *body;
2275 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2276 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2277 send_soap_request(sip, body);
2278 g_free(body);
2280 sip->groups = g_slist_remove(sip->groups, s_group);
2281 g_free(s_group->name);
2282 g_free(s_group);
2283 } else {
2284 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2288 /** All statuses need message attribute to pass Note */
2289 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2291 PurpleStatusType *type;
2292 GList *types = NULL;
2294 /* Macros to reduce code repetition.
2295 Translators: noun */
2296 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2297 prim, id, name, \
2298 TRUE, user, FALSE, \
2299 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2300 NULL); \
2301 types = g_list_append(types, type);
2303 /* Online */
2304 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2305 NULL,
2306 NULL,
2307 TRUE);
2309 /* Busy */
2310 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2311 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2312 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2313 TRUE);
2315 /* Do Not Disturb */
2316 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2317 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2318 NULL,
2319 TRUE);
2321 /* Away */
2322 /* Goes first in the list as
2323 * purple picks the first status with the AWAY type
2324 * for idle.
2326 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2327 NULL,
2328 NULL,
2329 TRUE);
2331 /* Be Right Back */
2332 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2333 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2334 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2335 TRUE);
2337 /* Appear Offline */
2338 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2339 NULL,
2340 NULL,
2341 TRUE);
2343 /* Offline (not user settable) */
2344 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE,
2345 NULL,
2346 NULL,
2347 FALSE);
2349 return types;
2353 * A callback for g_hash_table_foreach
2355 static void
2356 sipe_buddy_subscribe_cb(char *buddy_name,
2357 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2358 struct sipe_account_data *sip)
2360 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2361 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2362 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2363 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2365 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2366 g_free(action_name);
2370 * Removes entries from purple buddy list
2371 * that does not correspond ones in the roaming contact list.
2373 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2374 GSList *buddies = purple_find_buddies(sip->account, NULL);
2375 GSList *entry = buddies;
2376 struct sipe_buddy *buddy;
2377 PurpleBuddy *b;
2378 PurpleGroup *g;
2380 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2381 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2382 while (entry) {
2383 b = entry->data;
2384 g = purple_buddy_get_group(b);
2385 buddy = g_hash_table_lookup(sip->buddies, b->name);
2386 if(buddy) {
2387 gboolean in_sipe_groups = FALSE;
2388 GSList *entry2 = buddy->groups;
2389 while (entry2) {
2390 struct sipe_group *group = entry2->data;
2391 if (!strcmp(group->name, g->name)) {
2392 in_sipe_groups = TRUE;
2393 break;
2395 entry2 = entry2->next;
2397 if(!in_sipe_groups) {
2398 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2399 purple_blist_remove_buddy(b);
2401 } else {
2402 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2403 purple_blist_remove_buddy(b);
2405 entry = entry->next;
2407 g_slist_free(buddies);
2410 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2412 int len = msg->bodylen;
2414 gchar *tmp = sipmsg_find_header(msg, "Event");
2415 xmlnode *item;
2416 xmlnode *isc;
2417 const gchar *contacts_delta;
2418 xmlnode *group_node;
2419 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
2420 return FALSE;
2423 /* Convert the contact from XML to Purple Buddies */
2424 isc = xmlnode_from_str(msg->body, len);
2425 if (!isc) {
2426 return FALSE;
2429 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2430 if (contacts_delta) {
2431 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2434 if (!strcmp(isc->name, "contactList")) {
2436 /* Parse groups */
2437 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2438 struct sipe_group * group = g_new0(struct sipe_group, 1);
2439 const char *name = xmlnode_get_attrib(group_node, "name");
2441 if (!strncmp(name, "~", 1)) {
2442 name = _("Other Contacts");
2444 group->name = g_strdup(name);
2445 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2447 sipe_group_add(sip, group);
2450 // Make sure we have at least one group
2451 if (g_slist_length(sip->groups) == 0) {
2452 struct sipe_group * group = g_new0(struct sipe_group, 1);
2453 PurpleGroup *purple_group;
2454 group->name = g_strdup(_("Other Contacts"));
2455 group->id = 1;
2456 purple_group = purple_group_new(group->name);
2457 purple_blist_add_group(purple_group, NULL);
2458 sip->groups = g_slist_append(sip->groups, group);
2461 /* Parse contacts */
2462 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2463 const gchar *uri = xmlnode_get_attrib(item, "uri");
2464 const gchar *name = xmlnode_get_attrib(item, "name");
2465 gchar *buddy_name;
2466 struct sipe_buddy *buddy = NULL;
2467 gchar *tmp;
2468 gchar **item_groups;
2469 int i = 0;
2471 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2472 tmp = sip_uri_from_name(uri);
2473 buddy_name = g_ascii_strdown(tmp, -1);
2474 g_free(tmp);
2476 /* assign to group Other Contacts if nothing else received */
2477 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2478 if(!tmp || !strcmp("", tmp) ) {
2479 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2480 g_free(tmp);
2481 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2483 item_groups = g_strsplit(tmp, " ", 0);
2484 g_free(tmp);
2486 while (item_groups[i]) {
2487 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2489 // If couldn't find the right group for this contact, just put them in the first group we have
2490 if (group == NULL && g_slist_length(sip->groups) > 0) {
2491 group = sip->groups->data;
2494 if (group != NULL) {
2495 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2496 if (!b){
2497 b = purple_buddy_new(sip->account, buddy_name, uri);
2498 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2500 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2503 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2504 if (name != NULL && strlen(name) != 0) {
2505 purple_blist_alias_buddy(b, name);
2507 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2511 if (!buddy) {
2512 buddy = g_new0(struct sipe_buddy, 1);
2513 buddy->name = g_strdup(b->name);
2514 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2517 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2519 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2520 } else {
2521 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2522 name);
2525 i++;
2526 } // while, contact groups
2527 g_strfreev(item_groups);
2528 g_free(buddy_name);
2530 } // for, contacts
2532 sipe_cleanup_local_blist(sip);
2534 /* Add self-contact if not there yet. 2005 systems. */
2535 /* This will resemble subscription to roaming_self in 2007 systems */
2536 if (!sip->ocs2007) {
2537 gchar *self_uri = sip_uri_self(sip);
2538 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2540 if (!buddy) {
2541 buddy = g_new0(struct sipe_buddy, 1);
2542 buddy->name = g_strdup(self_uri);
2543 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2545 g_free(self_uri);
2548 xmlnode_free(isc);
2550 /* subscribe to buddies */
2551 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2552 if (sip->batched_support) {
2553 sipe_subscribe_presence_batched(sip, NULL);
2554 } else {
2555 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2557 sip->subscribed_buddies = TRUE;
2559 /* for 2005 systems schedule contacts' status update
2560 * based on their calendar information
2562 if (!sip->ocs2007) {
2563 sipe_sched_calendar_status_update(sip, time(NULL));
2566 return 0;
2570 * Subscribe roaming contacts
2572 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2574 gchar *to = sip_uri_self(sip);
2575 gchar *tmp = get_contact(sip);
2576 gchar *hdr = g_strdup_printf(
2577 "Event: vnd-microsoft-roaming-contacts\r\n"
2578 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2579 "Supported: com.microsoft.autoextend\r\n"
2580 "Supported: ms-benotify\r\n"
2581 "Proxy-Require: ms-benotify\r\n"
2582 "Supported: ms-piggyback-first-notify\r\n"
2583 "Contact: %s\r\n", tmp);
2584 g_free(tmp);
2586 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2587 g_free(to);
2588 g_free(hdr);
2591 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2592 SIPE_UNUSED_PARAMETER void *unused)
2594 gchar *key;
2595 struct sip_dialog *dialog;
2596 gchar *to = sip_uri_self(sip);
2597 gchar *tmp = get_contact(sip);
2598 gchar *hdr = g_strdup_printf(
2599 "Event: presence.wpending\r\n"
2600 "Accept: text/xml+msrtc.wpending\r\n"
2601 "Supported: com.microsoft.autoextend\r\n"
2602 "Supported: ms-benotify\r\n"
2603 "Proxy-Require: ms-benotify\r\n"
2604 "Supported: ms-piggyback-first-notify\r\n"
2605 "Contact: %s\r\n", tmp);
2606 g_free(tmp);
2608 /* Subscription is identified by <event> key */
2609 key = g_strdup_printf("<%s>", "presence.wpending");
2610 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2611 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2613 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2615 g_free(to);
2616 g_free(hdr);
2617 g_free(key);
2621 * Fires on deregistration event initiated by server.
2622 * [MS-SIPREGE] SIP extension.
2625 // 2007 Example
2627 // Content-Type: text/registration-event
2628 // subscription-state: terminated;expires=0
2629 // ms-diagnostics-public: 4141;reason="User disabled"
2631 // deregistered;event=rejected
2633 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2635 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2636 gchar *event = NULL;
2637 gchar *reason = NULL;
2638 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2640 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2641 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2643 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2644 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2645 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2646 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2647 } else {
2648 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2649 return;
2652 if (warning != NULL) {
2653 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2654 } else { // for LCS2005
2655 int error_id = 0;
2656 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2657 error_id = 4140; // [MS-SIPREGE]
2658 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2659 reason = g_strdup(_("you are already signed in at another location"));
2660 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2661 error_id = 4141;
2662 reason = g_strdup(_("user disabled")); // [MS-OCER]
2663 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2664 error_id = 4142;
2665 reason = g_strdup(_("user moved")); // [MS-OCER]
2668 g_free(event);
2669 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2670 g_free(reason);
2672 sip->gc->wants_to_die = TRUE;
2673 purple_connection_error(sip->gc, warning);
2674 g_free(warning);
2678 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2680 xmlnode *xn_provision_group_list;
2681 xmlnode *node;
2683 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2685 /* provisionGroup */
2686 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2687 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2688 g_free(sip->focus_factory_uri);
2689 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2690 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2691 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2692 break;
2695 xmlnode_free(xn_provision_group_list);
2698 /** for 2005 system */
2699 static void
2700 sipe_process_provisioning(struct sipe_account_data *sip,
2701 struct sipmsg *msg)
2703 xmlnode *xn_provision;
2704 xmlnode *node;
2706 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2707 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2708 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2709 if ((node = xmlnode_get_child(node, "line"))) {
2710 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2711 const gchar *server = xmlnode_get_attrib(node, "server");
2712 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2713 sip_csta_open(sip, line_uri, server);
2716 xmlnode_free(xn_provision);
2719 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2721 const gchar *contacts_delta;
2722 xmlnode *xml;
2724 xml = xmlnode_from_str(msg->body, msg->bodylen);
2725 if (!xml)
2727 return;
2730 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2731 if (contacts_delta)
2733 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2736 xmlnode_free(xml);
2739 static void
2740 free_container(struct sipe_container *container)
2742 GSList *entry;
2744 if (!container) return;
2746 entry = container->members;
2747 while (entry) {
2748 g_free(entry->data);
2749 entry = g_slist_remove(entry, entry->data);
2751 g_free(container);
2755 * Finds locally stored MS-PRES container member
2757 static struct sipe_container_member *
2758 sipe_find_container_member(struct sipe_container *container,
2759 const gchar *type,
2760 const gchar *value)
2762 struct sipe_container_member *member;
2763 GSList *entry;
2765 if (container == NULL || type == NULL) {
2766 return NULL;
2769 entry = container->members;
2770 while (entry) {
2771 member = entry->data;
2772 if (!g_strcasecmp(member->type, type)
2773 && ((!member->value && !value)
2774 || (value && member->value && !g_strcasecmp(member->value, value)))
2776 return member;
2778 entry = entry->next;
2780 return NULL;
2784 * Finds locally stored MS-PRES container by id
2786 static struct sipe_container *
2787 sipe_find_container(struct sipe_account_data *sip,
2788 guint id)
2790 struct sipe_container *container;
2791 GSList *entry;
2793 if (sip == NULL) {
2794 return NULL;
2797 entry = sip->containers;
2798 while (entry) {
2799 container = entry->data;
2800 if (id == container->id) {
2801 return container;
2803 entry = entry->next;
2805 return NULL;
2809 * Access Levels
2810 * 32000 - Blocked
2811 * 400 - Personal
2812 * 300 - Team
2813 * 200 - Company
2814 * 100 - Public
2816 static int
2817 sipe_find_access_level(struct sipe_account_data *sip,
2818 const gchar *type,
2819 const gchar *value)
2821 guint containers[] = {32000, 400, 300, 200, 100};
2822 int i = 0;
2824 for (i = 0; i < 5; i++) {
2825 struct sipe_container_member *member;
2826 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2827 if (!container) continue;
2829 member = sipe_find_container_member(container, type, value);
2830 if (member) {
2831 return containers[i];
2835 return -1;
2838 static void
2839 sipe_send_set_container_members(struct sipe_account_data *sip,
2840 guint container_id,
2841 guint container_version,
2842 const gchar* action,
2843 const gchar* type,
2844 const gchar* value)
2846 gchar *self = sip_uri_self(sip);
2847 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2848 gchar *contact;
2849 gchar *hdr;
2850 gchar *body = g_strdup_printf(
2851 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2852 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2853 "</setContainerMembers>",
2854 container_id,
2855 container_version,
2856 action,
2857 type,
2858 value_str);
2859 g_free(value_str);
2861 contact = get_contact(sip);
2862 hdr = g_strdup_printf("Contact: %s\r\n"
2863 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2864 g_free(contact);
2866 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2868 g_free(hdr);
2869 g_free(body);
2870 g_free(self);
2873 static void
2874 free_publication(struct sipe_publication *publication)
2876 g_free(publication->category);
2877 g_free(publication->cal_event_hash);
2878 g_free(publication->note);
2880 g_free(publication->working_hours_xml_str);
2881 g_free(publication->fb_start_str);
2882 g_free(publication->free_busy_base64);
2884 g_free(publication);
2887 /* key is <category><instance><container> */
2888 static gboolean
2889 sipe_is_our_publication(struct sipe_account_data *sip,
2890 const gchar *key)
2892 GSList *entry;
2894 /* filling keys for our publications if not yet cached */
2895 if (!sip->our_publication_keys) {
2896 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2897 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2898 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2899 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2900 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2901 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2902 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2904 purple_debug_info("sipe", "* Our Publication Instances *\n");
2905 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2906 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2907 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2908 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2909 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2910 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2911 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2912 purple_debug_info("sipe", "\tNote : %u\n", 0);
2913 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2915 /* device */
2916 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2917 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2919 /* state:machineState */
2920 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2921 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2922 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2923 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2925 /* state:userState */
2926 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2927 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2928 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2929 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2931 /* state:calendarState */
2932 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2933 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2934 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2935 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2937 /* state:calendarState OOF */
2938 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2939 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2940 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2941 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2943 /* note */
2944 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2945 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2946 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2947 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2948 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2949 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2951 /* note OOF */
2952 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2953 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2954 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2955 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2956 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2957 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2959 /* calendarData:WorkingHours */
2960 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2961 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2962 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2963 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2964 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2965 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2966 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2967 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2968 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2969 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2970 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2971 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2973 /* calendarData:FreeBusy */
2974 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2975 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2976 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2977 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2978 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2979 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2980 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2981 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2982 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2983 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2984 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2985 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2987 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2988 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2991 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
2993 entry = sip->our_publication_keys;
2994 while (entry) {
2995 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
2996 if (!strcmp(entry->data, key)) {
2997 return TRUE;
2999 entry = entry->next;
3001 return FALSE;
3004 /** Property names to store in blist.xml */
3005 #define ALIAS_PROP "alias"
3006 #define EMAIL_PROP "email"
3007 #define PHONE_PROP "phone"
3008 #define PHONE_DISPLAY_PROP "phone-display"
3009 #define PHONE_MOBILE_PROP "phone-mobile"
3010 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3011 #define PHONE_HOME_PROP "phone-home"
3012 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3013 #define PHONE_OTHER_PROP "phone-other"
3014 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3015 #define PHONE_CUSTOM1_PROP "phone-custom1"
3016 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3017 #define SITE_PROP "site"
3018 #define COMPANY_PROP "company"
3019 #define DEPARTMENT_PROP "department"
3020 #define TITLE_PROP "title"
3021 #define OFFICE_PROP "office"
3022 /** implies work address */
3023 #define ADDRESS_STREET_PROP "address-street"
3024 #define ADDRESS_CITY_PROP "address-city"
3025 #define ADDRESS_STATE_PROP "address-state"
3026 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3027 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3030 * Tries to figure out user first and last name
3031 * based on Display Name and email properties.
3033 * Allocates memory - must be g_free()'d
3035 * Examples to parse:
3036 * First Last
3037 * First Last - Company Name
3038 * Last, First
3039 * Last, First M.
3040 * Last, First (C)(STP) (Company)
3041 * first.last@company.com (preprocessed as "first last")
3042 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3044 * Unusable examples:
3045 * user@company.com (preprocessed as "user")
3046 * first.m.last@company.com (preprocessed as "first m last")
3047 * user.company.com@reuters.net (preprocessed as "user company com")
3049 static void
3050 sipe_get_first_last_names(struct sipe_account_data *sip,
3051 const char *uri,
3052 char **first_name,
3053 char **last_name)
3055 PurpleBuddy *p_buddy;
3056 char *display_name;
3057 const char *email;
3058 const char *first, *last;
3059 char *tmp;
3060 char **parts;
3061 gboolean has_comma = FALSE;
3063 if (!sip || !uri) return;
3065 p_buddy = purple_find_buddy(sip->account, uri);
3067 if (!p_buddy) return;
3069 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3070 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3072 if (!display_name && !email) return;
3074 /* if no display name, make "first last anything_else" out of email */
3075 if (email && !display_name) {
3076 display_name = g_strndup(email, strstr(email, "@") - email);
3077 display_name = purple_strreplace((tmp = display_name), ".", " ");
3078 g_free(tmp);
3081 has_comma = (strstr(display_name, ",") != NULL);
3083 if (display_name) {
3084 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3085 g_free(tmp);
3086 display_name = purple_strreplace((tmp = display_name), ",", " ");
3087 g_free(tmp);
3090 parts = g_strsplit(display_name, " ", 0);
3092 if (!parts[0] || !parts[1]) {
3093 g_free(display_name);
3094 g_strfreev(parts);
3095 return;
3098 if (has_comma) {
3099 last = parts[0];
3100 first = parts[1];
3101 } else {
3102 first = parts[0];
3103 last = parts[1];
3106 if (first_name) {
3107 *first_name = g_strstrip(g_strdup(first));
3110 if (last_name) {
3111 *last_name = g_strstrip(g_strdup(last));
3114 g_free(display_name);
3115 g_strfreev(parts);
3119 * Update user information
3121 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3122 * @param property_name
3123 * @param property_value may be modified to strip white space
3125 static void
3126 sipe_update_user_info(struct sipe_account_data *sip,
3127 const char *uri,
3128 const char *property_name,
3129 char *property_value)
3131 GSList *buddies, *entry;
3133 if (!property_name || strlen(property_name) == 0) return;
3135 if (property_value)
3136 property_value = g_strstrip(property_value);
3138 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3139 while (entry) {
3140 const char *prop_str;
3141 const char *server_alias;
3142 PurpleBuddy *p_buddy = entry->data;
3144 /* for Display Name */
3145 if (!strcmp(property_name, ALIAS_PROP)) {
3146 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3147 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3148 purple_blist_alias_buddy(p_buddy, property_value);
3151 server_alias = purple_buddy_get_server_alias(p_buddy);
3152 if (property_value && strlen(property_value) > 0 &&
3153 ( (server_alias && strcmp(property_value, server_alias))
3154 || !server_alias || strlen(server_alias) == 0 )
3156 purple_blist_server_alias_buddy(p_buddy, property_value);
3159 /* for other properties */
3160 else {
3161 if (property_value && strlen(property_value) > 0) {
3162 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3163 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3164 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3169 entry = entry->next;
3171 g_slist_free(buddies);
3175 * Update user phone
3176 * Suitable for both 2005 and 2007 systems.
3178 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3179 * @param phone_type
3180 * @param phone may be modified to strip white space
3181 * @param phone_display_string may be modified to strip white space
3183 static void
3184 sipe_update_user_phone(struct sipe_account_data *sip,
3185 const char *uri,
3186 const gchar *phone_type,
3187 gchar *phone,
3188 gchar *phone_display_string)
3190 const char *phone_node = PHONE_PROP; /* work phone by default */
3191 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3193 if(!phone || strlen(phone) == 0) return;
3195 if (phone_type && (!strcmp(phone_type, "mobile") || !strcmp(phone_type, "cell"))) {
3196 phone_node = PHONE_MOBILE_PROP;
3197 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3198 } else if (phone_type && !strcmp(phone_type, "home")) {
3199 phone_node = PHONE_HOME_PROP;
3200 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3201 } else if (phone_type && !strcmp(phone_type, "other")) {
3202 phone_node = PHONE_OTHER_PROP;
3203 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3204 } else if (phone_type && !strcmp(phone_type, "custom1")) {
3205 phone_node = PHONE_CUSTOM1_PROP;
3206 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3209 sipe_update_user_info(sip, uri, phone_node, phone);
3210 if (phone_display_string) {
3211 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3215 static void
3216 sipe_update_calendar(struct sipe_account_data *sip)
3218 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3220 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3222 if (!strcmp(calendar, "EXCH")) {
3223 sipe_ews_update_calendar(sip);
3226 /* schedule repeat */
3227 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3229 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3233 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3234 * by using standard Purple's means of signals and saved statuses.
3236 * Thus all UI elements get updated: Status Button with Note, docklet.
3237 * This is ablolutely important as both our status and note can come
3238 * inbound (roaming) or be updated programmatically (e.g. based on our
3239 * calendar data).
3241 static void
3242 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3243 const char *status_id,
3244 const char *message,
3245 time_t do_not_publish[])
3247 PurpleStatus *status = purple_account_get_active_status(account);
3248 gboolean changed = TRUE;
3250 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3251 purple_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3253 changed = FALSE;
3256 if (changed) {
3257 PurpleSavedStatus *saved_status;
3258 const PurpleStatusType *acct_status_type =
3259 purple_status_type_find_with_id(account->status_types, status_id);
3260 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3261 sipe_activity activity = sipe_get_activity_by_token(status_id);
3263 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3264 if (saved_status) {
3265 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3268 /* If this type+message is unique then create a new transient saved status
3269 * Ref: gtkstatusbox.c
3271 if (!saved_status) {
3272 GList *tmp;
3273 GList *active_accts = purple_accounts_get_all_active();
3275 saved_status = purple_savedstatus_new(NULL, primitive);
3276 purple_savedstatus_set_message(saved_status, message);
3278 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3279 purple_savedstatus_set_substatus(saved_status,
3280 (PurpleAccount *)tmp->data, acct_status_type, message);
3282 g_list_free(active_accts);
3285 do_not_publish[activity] = time(NULL);
3286 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3287 status_id, (int)do_not_publish[activity]);
3289 /* Set the status for each account */
3290 purple_savedstatus_activate(saved_status);
3294 struct hash_table_delete_payload {
3295 GHashTable *hash_table;
3296 guint container;
3299 static void
3300 sipe_remove_category_container_publications_cb(const char *name,
3301 struct sipe_publication *publication,
3302 struct hash_table_delete_payload *payload)
3304 if (publication->container == payload->container) {
3305 g_hash_table_remove(payload->hash_table, name);
3308 static void
3309 sipe_remove_category_container_publications(GHashTable *our_publications,
3310 const char *category,
3311 guint container)
3313 struct hash_table_delete_payload payload;
3314 payload.hash_table = g_hash_table_lookup(our_publications, category);
3316 if (!payload.hash_table) return;
3318 payload.container = container;
3319 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3322 static void
3323 send_publish_category_initial(struct sipe_account_data *sip);
3326 * When we receive some self (BE) NOTIFY with a new subscriber
3327 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3330 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3332 gchar *contact;
3333 gchar *to;
3334 xmlnode *xml;
3335 xmlnode *node;
3336 xmlnode *node2;
3337 char *display_name = NULL;
3338 char *uri;
3339 GSList *category_names = NULL;
3340 int aggreg_avail = 0;
3341 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3342 gboolean do_update_status = FALSE;
3343 gboolean has_note_cleaned = FALSE;
3345 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3347 xml = xmlnode_from_str(msg->body, msg->bodylen);
3348 if (!xml) return;
3350 contact = get_contact(sip);
3351 to = sip_uri_self(sip);
3354 /* categories */
3355 /* set list of categories participating in this XML */
3356 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3357 const gchar *name = xmlnode_get_attrib(node, "name");
3358 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3360 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3361 category_names ? (int) g_slist_length(category_names) : -1);
3362 /* drop category information */
3363 if (category_names) {
3364 GSList *entry = category_names;
3365 while (entry) {
3366 GHashTable *cat_publications;
3367 const gchar *category = entry->data;
3368 entry = entry->next;
3369 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3370 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3371 if (cat_publications) {
3372 g_hash_table_remove(sip->our_publications, category);
3373 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3377 g_slist_free(category_names);
3378 /* filling our categories reflected in roaming data */
3379 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3380 const char *tmp;
3381 const gchar *name = xmlnode_get_attrib(node, "name");
3382 guint container = xmlnode_get_int_attrib(node, "container", -1);
3383 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3384 guint version = xmlnode_get_int_attrib(node, "version", 0);
3385 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3386 sipe_utils_str_to_time(tmp) : 0;
3387 gchar *key;
3388 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3390 /* Ex. clear note: <category name="note"/> */
3391 if (container == (guint)-1) {
3392 g_free(sip->note);
3393 sip->note = NULL;
3394 do_update_status = TRUE;
3395 continue;
3398 /* Ex. clear note: <category name="note" container="200"/> */
3399 if (instance == (guint)-1) {
3400 if (container == 200) {
3401 g_free(sip->note);
3402 sip->note = NULL;
3403 do_update_status = TRUE;
3405 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3406 sipe_remove_category_container_publications(
3407 sip->our_publications, name, container);
3408 continue;
3411 /* key is <category><instance><container> */
3412 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3413 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3415 /* capture all userState publication for later clean up if required */
3416 if (!strcmp(name, "state") && (container == 2 || container == 3)) {
3417 xmlnode *xn_state = xmlnode_get_child(node, "state");
3419 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3420 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3421 publication->category = g_strdup(name);
3422 publication->instance = instance;
3423 publication->container = container;
3424 publication->version = version;
3426 if (!sip->user_state_publications) {
3427 sip->user_state_publications = g_hash_table_new_full(
3428 g_str_hash, g_str_equal,
3429 g_free, (GDestroyNotify)free_publication);
3431 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3432 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3433 key, version);
3437 if (sipe_is_our_publication(sip, key)) {
3438 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3440 publication->category = g_strdup(name);
3441 publication->instance = instance;
3442 publication->container = container;
3443 publication->version = version;
3445 /* filling publication->availability */
3446 if (!strcmp(name, "state")) {
3447 xmlnode *xn_state = xmlnode_get_child(node, "state");
3448 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3450 if (xn_avail) {
3451 gchar *avail_str = xmlnode_get_data(xn_avail);
3452 if (avail_str) {
3453 publication->availability = atoi(avail_str);
3455 g_free(avail_str);
3457 /* for calendarState */
3458 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3459 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3460 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3462 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3463 if (xn_activity) {
3464 if (!strcmp(xmlnode_get_attrib(xn_activity, "token"),
3465 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3467 event->is_meeting = TRUE;
3470 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3471 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3473 publication->cal_event_hash = sipe_cal_event_hash(event);
3474 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3475 publication->cal_event_hash);
3476 sipe_cal_event_free(event);
3479 /* filling publication->note */
3480 if (!strcmp(name, "note")) {
3481 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3483 if (!has_note_cleaned) {
3484 has_note_cleaned = TRUE;
3486 g_free(sip->note);
3487 sip->note = NULL;
3488 sip->note_since = publish_time;
3490 do_update_status = TRUE;
3493 g_free(publication->note);
3494 publication->note = NULL;
3495 if (xn_body) {
3496 char *tmp;
3498 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3499 g_free(tmp);
3500 if (publish_time >= sip->note_since) {
3501 g_free(sip->note);
3502 sip->note = g_strdup(publication->note);
3503 sip->note_since = publish_time;
3504 sip->is_oof_note = !strcmp(xmlnode_get_attrib(xn_body, "type"), "OOF");
3506 do_update_status = TRUE;
3511 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3512 if (!strcmp(name, "calendarData") && (publication->container == 300)) {
3513 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3514 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3515 if (xn_free_busy) {
3516 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3517 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3519 if (xn_working_hours) {
3520 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3524 if (!cat_publications) {
3525 cat_publications = g_hash_table_new_full(
3526 g_str_hash, g_str_equal,
3527 g_free, (GDestroyNotify)free_publication);
3528 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3529 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3531 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3532 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3534 g_free(key);
3536 /* aggregateState (not an our publication) from 2-nd container */
3537 if (!strcmp(name, "state") && container == 2) {
3538 xmlnode *xn_state = xmlnode_get_child(node, "state");
3540 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3541 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3542 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3544 if (xn_avail) {
3545 gchar *avail_str = xmlnode_get_data(xn_avail);
3546 if (avail_str) {
3547 aggreg_avail = atoi(avail_str);
3549 g_free(avail_str);
3552 if (xn_activity) {
3553 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3555 aggreg_activity = sipe_get_activity_by_token(activity_token);
3558 do_update_status = TRUE;
3562 /* userProperties published by server from AD */
3563 if (!sip->csta && !strcmp(name, "userProperties")) {
3564 xmlnode *line;
3565 /* line, for Remote Call Control (RCC) */
3566 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3567 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3568 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3569 gchar *line_uri;
3571 if (!line_server || (strcmp(line_type, "Rcc") && strcmp(line_type, "Dual"))) continue;
3573 line_uri = xmlnode_get_data(line);
3574 if (line_uri) {
3575 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3576 sip_csta_open(sip, line_uri, line_server);
3578 g_free(line_uri);
3580 break;
3584 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3585 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3587 /* containers */
3588 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3589 guint id = xmlnode_get_int_attrib(node, "id", 0);
3590 struct sipe_container *container = sipe_find_container(sip, id);
3592 if (container) {
3593 sip->containers = g_slist_remove(sip->containers, container);
3594 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3595 free_container(container);
3597 container = g_new0(struct sipe_container, 1);
3598 container->id = id;
3599 container->version = xmlnode_get_int_attrib(node, "version", 0);
3600 sip->containers = g_slist_append(sip->containers, container);
3601 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3603 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3604 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3605 member->type = xmlnode_get_attrib(node2, "type");
3606 member->value = xmlnode_get_attrib(node2, "value");
3607 container->members = g_slist_append(container->members, member);
3608 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3609 member->type, member->value ? member->value : "");
3613 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3614 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3615 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3616 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3617 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3618 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3619 /* initial set-up to let counterparties see your status */
3620 if (sameEnterpriseAL < 0) {
3621 struct sipe_container *container = sipe_find_container(sip, 200);
3622 guint version = container ? container->version : 0;
3623 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3625 if (federatedAL < 0) {
3626 struct sipe_container *container = sipe_find_container(sip, 100);
3627 guint version = container ? container->version : 0;
3628 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3630 sip->access_level_set = TRUE;
3633 /* subscribers */
3634 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3635 const char *user;
3636 const char *acknowledged;
3637 gchar *hdr;
3638 gchar *body;
3640 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3641 if (!user) continue;
3642 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3643 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3644 uri = sip_uri_from_name(user);
3646 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3648 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3649 if(!g_ascii_strcasecmp(acknowledged,"false")){
3650 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3651 if (!purple_find_buddy(sip->account, uri)) {
3652 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3655 hdr = g_strdup_printf(
3656 "Contact: %s\r\n"
3657 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3659 body = g_strdup_printf(
3660 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3661 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3662 "</setSubscribers>", user);
3664 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3665 g_free(body);
3666 g_free(hdr);
3668 g_free(display_name);
3669 g_free(uri);
3672 g_free(contact);
3673 xmlnode_free(xml);
3675 /* Publish initial state if not yet.
3676 * Assuming this happens on initial responce to subscription to roaming-self
3677 * so we've already updated our roaming data in full.
3678 * Only for 2007+
3680 if (!sip->initial_state_published) {
3681 send_publish_category_initial(sip);
3682 sip->initial_state_published = TRUE;
3683 /* dalayed run */
3684 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3685 do_update_status = FALSE;
3686 } else if (aggreg_avail) {
3688 g_free(sip->status);
3689 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3690 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3691 } else {
3692 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3696 if (do_update_status) {
3697 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3698 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3701 g_free(to);
3704 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3706 gchar *to = sip_uri_self(sip);
3707 gchar *tmp = get_contact(sip);
3708 gchar *hdr = g_strdup_printf(
3709 "Event: vnd-microsoft-roaming-ACL\r\n"
3710 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3711 "Supported: com.microsoft.autoextend\r\n"
3712 "Supported: ms-benotify\r\n"
3713 "Proxy-Require: ms-benotify\r\n"
3714 "Supported: ms-piggyback-first-notify\r\n"
3715 "Contact: %s\r\n", tmp);
3716 g_free(tmp);
3718 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3719 g_free(to);
3720 g_free(hdr);
3724 * To request for presence information about the user, access level settings that have already been configured by the user
3725 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3726 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3729 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3731 gchar *to = sip_uri_self(sip);
3732 gchar *tmp = get_contact(sip);
3733 gchar *hdr = g_strdup_printf(
3734 "Event: vnd-microsoft-roaming-self\r\n"
3735 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3736 "Supported: ms-benotify\r\n"
3737 "Proxy-Require: ms-benotify\r\n"
3738 "Supported: ms-piggyback-first-notify\r\n"
3739 "Contact: %s\r\n"
3740 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3742 gchar *body=g_strdup(
3743 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3744 "<roaming type=\"categories\"/>"
3745 "<roaming type=\"containers\"/>"
3746 "<roaming type=\"subscribers\"/></roamingList>");
3748 g_free(tmp);
3749 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3750 g_free(body);
3751 g_free(to);
3752 g_free(hdr);
3756 * For 2005 version
3758 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3760 gchar *to = sip_uri_self(sip);
3761 gchar *tmp = get_contact(sip);
3762 gchar *hdr = g_strdup_printf(
3763 "Event: vnd-microsoft-provisioning\r\n"
3764 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3765 "Supported: com.microsoft.autoextend\r\n"
3766 "Supported: ms-benotify\r\n"
3767 "Proxy-Require: ms-benotify\r\n"
3768 "Supported: ms-piggyback-first-notify\r\n"
3769 "Expires: 0\r\n"
3770 "Contact: %s\r\n", tmp);
3772 g_free(tmp);
3773 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3774 g_free(to);
3775 g_free(hdr);
3778 /** Subscription for provisioning information to help with initial
3779 * configuration. This subscription is a one-time query (denoted by the Expires header,
3780 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3781 * configuration, meeting policies, and policy settings that Communicator must enforce.
3782 * TODO: for what we need this information.
3785 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3787 gchar *to = sip_uri_self(sip);
3788 gchar *tmp = get_contact(sip);
3789 gchar *hdr = g_strdup_printf(
3790 "Event: vnd-microsoft-provisioning-v2\r\n"
3791 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3792 "Supported: com.microsoft.autoextend\r\n"
3793 "Supported: ms-benotify\r\n"
3794 "Proxy-Require: ms-benotify\r\n"
3795 "Supported: ms-piggyback-first-notify\r\n"
3796 "Expires: 0\r\n"
3797 "Contact: %s\r\n"
3798 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3799 gchar *body = g_strdup(
3800 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3801 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3802 "<provisioningGroup name=\"ucPolicy\"/>"
3803 "</provisioningGroupList>");
3805 g_free(tmp);
3806 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3807 g_free(body);
3808 g_free(to);
3809 g_free(hdr);
3812 static void
3813 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3814 gpointer value, gpointer user_data)
3816 struct sip_subscription *subscription = value;
3817 struct sip_dialog *dialog = &subscription->dialog;
3818 struct sipe_account_data *sip = user_data;
3819 gchar *tmp = get_contact(sip);
3820 gchar *hdr = g_strdup_printf(
3821 "Event: %s\r\n"
3822 "Expires: 0\r\n"
3823 "Contact: %s\r\n", subscription->event, tmp);
3824 g_free(tmp);
3826 /* Rate limit to max. 25 requests per seconds */
3827 g_usleep(1000000 / 25);
3829 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3830 g_free(hdr);
3833 /* IM Session (INVITE and MESSAGE methods) */
3835 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3836 static gchar *
3837 get_end_points (struct sipe_account_data *sip,
3838 struct sip_session *session)
3840 gchar *res;
3842 if (session == NULL) {
3843 return NULL;
3846 res = g_strdup_printf("<sip:%s>", sip->username);
3848 SIPE_DIALOG_FOREACH {
3849 gchar *tmp = res;
3850 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3851 g_free(tmp);
3853 if (dialog->theirepid) {
3854 tmp = res;
3855 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3856 g_free(tmp);
3858 } SIPE_DIALOG_FOREACH_END;
3860 return res;
3863 static gboolean
3864 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3865 struct sipmsg *msg,
3866 SIPE_UNUSED_PARAMETER struct transaction *trans)
3868 gboolean ret = TRUE;
3870 if (msg->response != 200) {
3871 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3872 return FALSE;
3875 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3877 return ret;
3881 * Asks UA/proxy about its capabilities.
3883 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3885 gchar *to = sip_uri(who);
3886 gchar *contact = get_contact(sip);
3887 gchar *request = g_strdup_printf(
3888 "Accept: application/sdp\r\n"
3889 "Contact: %s\r\n", contact);
3890 g_free(contact);
3892 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3894 g_free(to);
3895 g_free(request);
3898 static void
3899 sipe_notify_user(struct sipe_account_data *sip,
3900 struct sip_session *session,
3901 PurpleMessageFlags flags,
3902 const gchar *message)
3904 PurpleConversation *conv;
3906 if (!session->conv) {
3907 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3908 } else {
3909 conv = session->conv;
3911 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3914 void
3915 sipe_present_info(struct sipe_account_data *sip,
3916 struct sip_session *session,
3917 const gchar *message)
3919 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3922 static void
3923 sipe_present_err(struct sipe_account_data *sip,
3924 struct sip_session *session,
3925 const gchar *message)
3927 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3930 void
3931 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3932 struct sip_session *session,
3933 int sip_error,
3934 const gchar *who,
3935 const gchar *message)
3937 char *msg, *msg_tmp, *msg_tmp2;
3938 const char *label;
3940 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
3941 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3942 g_free(msg_tmp);
3943 /* Service unavailable; Server Internal Error; Server Time-out */
3944 if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3945 label = _("This message was not delivered to %s because the service is not available");
3946 } else if (sip_error == 486) { /* Busy Here */
3947 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3948 } else {
3949 label = _("This message was not delivered to %s because one or more recipients are offline");
3952 msg_tmp = g_strdup_printf( "%s:\n%s" ,
3953 msg_tmp2 = g_strdup_printf(label, who ? who : ""), msg ? msg : "");
3954 sipe_present_err(sip, session, msg_tmp);
3955 g_free(msg_tmp2);
3956 g_free(msg_tmp);
3957 g_free(msg);
3961 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
3963 static gboolean
3964 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
3965 SIPE_UNUSED_PARAMETER struct transaction *trans)
3967 gboolean ret = TRUE;
3968 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3969 struct sip_session *session = sipe_session_find_im(sip, with);
3970 struct sip_dialog *dialog;
3971 gchar *cseq;
3972 char *key;
3973 gchar *message;
3975 if (!session) {
3976 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3977 g_free(with);
3978 return FALSE;
3981 dialog = sipe_dialog_find(session, with);
3982 if (!dialog) {
3983 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3984 g_free(with);
3985 return FALSE;
3988 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3989 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3990 g_free(cseq);
3991 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3993 if (msg->response >= 400) {
3994 PurpleBuddy *pbuddy;
3995 const char *alias = with;
3997 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3999 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4000 alias = purple_buddy_get_alias(pbuddy);
4003 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
4004 ret = FALSE;
4005 } else {
4006 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4007 if (message_id) {
4008 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
4009 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4010 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4013 g_hash_table_remove(session->unconfirmed_messages, key);
4014 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4015 key, g_hash_table_size(session->unconfirmed_messages));
4018 g_free(key);
4019 g_free(with);
4021 if (ret) sipe_im_process_queue(sip, session);
4022 return ret;
4025 static gboolean
4026 sipe_is_election_finished(struct sip_session *session);
4028 static void
4029 sipe_election_result(struct sipe_account_data *sip,
4030 void *sess);
4032 static gboolean
4033 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4034 SIPE_UNUSED_PARAMETER struct transaction *trans)
4036 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4037 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4038 struct sip_dialog *dialog;
4039 struct sip_session *session;
4041 session = sipe_session_find_chat_by_callid(sip, callid);
4042 if (!session) {
4043 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4044 return FALSE;
4047 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
4048 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4049 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4050 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4052 if (xn_request_rm_response) {
4053 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4054 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4056 dialog = sipe_dialog_find(session, with);
4057 if (!dialog) {
4058 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4059 return FALSE;
4062 if (allow && !g_strcasecmp(allow, "true")) {
4063 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4064 dialog->election_vote = 1;
4065 } else if (allow && !g_strcasecmp(allow, "false")) {
4066 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4067 dialog->election_vote = -1;
4070 if (sipe_is_election_finished(session)) {
4071 sipe_election_result(sip, session);
4074 } else if (xn_set_rm_response) {
4077 xmlnode_free(xn_action);
4081 return TRUE;
4084 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
4086 gchar *hdr;
4087 gchar *tmp;
4088 char *msgformat;
4089 char *msgtext;
4090 gchar *msgr_value;
4091 gchar *msgr;
4093 sipe_parse_html(msg, &msgformat, &msgtext);
4094 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4096 msgr_value = sipmsg_get_msgr_string(msgformat);
4097 g_free(msgformat);
4098 if (msgr_value) {
4099 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4100 g_free(msgr_value);
4101 } else {
4102 msgr = g_strdup("");
4105 tmp = get_contact(sip);
4106 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4107 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4108 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4109 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
4110 g_free(tmp);
4111 g_free(msgr);
4113 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4114 g_free(msgtext);
4115 g_free(hdr);
4119 static void
4120 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4122 GSList *entry2 = session->outgoing_message_queue;
4123 while (entry2) {
4124 char *queued_msg = entry2->data;
4126 /* for multiparty chat or conference */
4127 if (session->is_multiparty || session->focus_uri) {
4128 gchar *who = sip_uri_self(sip);
4129 serv_got_chat_in(sip->gc, session->chat_id, who,
4130 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
4131 g_free(who);
4134 SIPE_DIALOG_FOREACH {
4135 char *key;
4137 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4139 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4140 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
4141 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4142 key, g_hash_table_size(session->unconfirmed_messages));
4143 g_free(key);
4145 sipe_send_message(sip, dialog, queued_msg);
4146 } SIPE_DIALOG_FOREACH_END;
4148 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
4149 g_free(queued_msg);
4153 static void
4154 sipe_refer_notify(struct sipe_account_data *sip,
4155 struct sip_session *session,
4156 const gchar *who,
4157 int status,
4158 const gchar *desc)
4160 gchar *hdr;
4161 gchar *body;
4162 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4164 hdr = g_strdup_printf(
4165 "Event: refer\r\n"
4166 "Subscription-State: %s\r\n"
4167 "Content-Type: message/sipfrag\r\n",
4168 status >= 200 ? "terminated" : "active");
4170 body = g_strdup_printf(
4171 "SIP/2.0 %d %s\r\n",
4172 status, desc);
4174 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4176 g_free(hdr);
4177 g_free(body);
4180 static gboolean
4181 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4183 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4184 struct sip_session *session;
4185 struct sip_dialog *dialog;
4186 char *cseq;
4187 char *key;
4188 gchar *message;
4189 struct sipmsg *request_msg = trans->msg;
4191 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4192 gchar *referred_by;
4194 session = sipe_session_find_chat_by_callid(sip, callid);
4195 if (!session) {
4196 session = sipe_session_find_im(sip, with);
4198 if (!session) {
4199 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4200 g_free(with);
4201 return FALSE;
4204 dialog = sipe_dialog_find(session, with);
4205 if (!dialog) {
4206 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4207 g_free(with);
4208 return FALSE;
4211 sipe_dialog_parse(dialog, msg, TRUE);
4213 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4214 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4215 g_free(cseq);
4216 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4218 if (msg->response != 200) {
4219 PurpleBuddy *pbuddy;
4220 const char *alias = with;
4222 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4224 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4225 alias = purple_buddy_get_alias(pbuddy);
4228 if (message) {
4229 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
4230 } else {
4231 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4232 sipe_present_err(sip, session, tmp_msg);
4233 g_free(tmp_msg);
4236 sipe_dialog_remove(session, with);
4238 g_free(key);
4239 g_free(with);
4240 return FALSE;
4243 dialog->cseq = 0;
4244 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4245 dialog->outgoing_invite = NULL;
4246 dialog->is_established = TRUE;
4248 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4249 if (referred_by) {
4250 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4251 g_free(referred_by);
4254 /* add user to chat if it is a multiparty session */
4255 if (session->is_multiparty) {
4256 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4257 with, NULL,
4258 PURPLE_CBFLAGS_NONE, TRUE);
4261 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4262 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4263 if (session->outgoing_message_queue) {
4264 char *queued_msg = session->outgoing_message_queue->data;
4265 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
4266 g_free(queued_msg);
4270 sipe_im_process_queue(sip, session);
4272 g_hash_table_remove(session->unconfirmed_messages, key);
4273 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4274 key, g_hash_table_size(session->unconfirmed_messages));
4276 g_free(key);
4277 g_free(with);
4278 return TRUE;
4282 void
4283 sipe_invite(struct sipe_account_data *sip,
4284 struct sip_session *session,
4285 const gchar *who,
4286 const gchar *msg_body,
4287 const gchar *referred_by,
4288 const gboolean is_triggered)
4290 gchar *hdr;
4291 gchar *to;
4292 gchar *contact;
4293 gchar *body;
4294 gchar *self;
4295 char *ms_text_format = NULL;
4296 gchar *roster_manager;
4297 gchar *end_points;
4298 gchar *referred_by_str;
4299 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4301 if (dialog && dialog->is_established) {
4302 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4303 return;
4306 if (!dialog) {
4307 dialog = sipe_dialog_add(session);
4308 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4309 dialog->with = g_strdup(who);
4312 if (!(dialog->ourtag)) {
4313 dialog->ourtag = gentag();
4316 to = sip_uri(who);
4318 if (msg_body) {
4319 char *msgformat;
4320 char *msgtext;
4321 char *base64_msg;
4322 gchar *msgr_value;
4323 gchar *msgr;
4324 char *key;
4326 sipe_parse_html(msg_body, &msgformat, &msgtext);
4327 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4329 msgr_value = sipmsg_get_msgr_string(msgformat);
4330 g_free(msgformat);
4331 msgr = "";
4332 if (msgr_value) {
4333 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4334 g_free(msgr_value);
4337 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4338 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
4339 g_free(msgtext);
4340 g_free(msgr);
4341 g_free(base64_msg);
4343 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4344 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
4345 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4346 key, g_hash_table_size(session->unconfirmed_messages));
4347 g_free(key);
4350 contact = get_contact(sip);
4351 end_points = get_end_points(sip, session);
4352 self = sip_uri_self(sip);
4353 roster_manager = g_strdup_printf(
4354 "Roster-Manager: %s\r\n"
4355 "EndPoints: %s\r\n",
4356 self,
4357 end_points);
4358 referred_by_str = referred_by ?
4359 g_strdup_printf(
4360 "Referred-By: %s\r\n",
4361 referred_by)
4362 : g_strdup("");
4363 hdr = g_strdup_printf(
4364 "Supported: ms-sender\r\n"
4365 "%s"
4366 "%s"
4367 "%s"
4368 "%s"
4369 "Contact: %s\r\n%s"
4370 "Content-Type: application/sdp\r\n",
4371 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
4372 referred_by_str,
4373 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4374 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4375 contact,
4376 ms_text_format ? ms_text_format : "");
4377 g_free(ms_text_format);
4378 g_free(self);
4380 body = g_strdup_printf(
4381 "v=0\r\n"
4382 "o=- 0 0 IN IP4 %s\r\n"
4383 "s=session\r\n"
4384 "c=IN IP4 %s\r\n"
4385 "t=0 0\r\n"
4386 "m=%s %d sip null\r\n"
4387 "a=accept-types:text/plain text/html image/gif "
4388 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4389 purple_network_get_my_ip(-1),
4390 purple_network_get_my_ip(-1),
4391 sip->ocs2007 ? "message" : "x-ms-message",
4392 sip->realport);
4394 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4395 to, to, hdr, body, dialog, process_invite_response);
4397 g_free(to);
4398 g_free(roster_manager);
4399 g_free(end_points);
4400 g_free(referred_by_str);
4401 g_free(body);
4402 g_free(hdr);
4403 g_free(contact);
4406 static void
4407 sipe_refer(struct sipe_account_data *sip,
4408 struct sip_session *session,
4409 const gchar *who)
4411 gchar *hdr;
4412 gchar *contact;
4413 gchar *epid = get_epid(sip);
4414 struct sip_dialog *dialog = sipe_dialog_find(session,
4415 session->roster_manager);
4416 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4418 contact = get_contact(sip);
4419 hdr = g_strdup_printf(
4420 "Contact: %s\r\n"
4421 "Refer-to: <%s>\r\n"
4422 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4423 "Require: com.microsoft.rtc-multiparty\r\n",
4424 contact,
4425 who,
4426 sip->username,
4427 ourtag ? ";tag=" : "",
4428 ourtag ? ourtag : "",
4429 epid);
4430 g_free(epid);
4432 send_sip_request(sip->gc, "REFER",
4433 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4435 g_free(hdr);
4436 g_free(contact);
4439 static void
4440 sipe_send_election_request_rm(struct sipe_account_data *sip,
4441 struct sip_dialog *dialog,
4442 int bid)
4444 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4446 gchar *body = g_strdup_printf(
4447 "<?xml version=\"1.0\"?>\r\n"
4448 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4449 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4450 sip->username, bid);
4452 send_sip_request(sip->gc, "INFO",
4453 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4455 g_free(body);
4458 static void
4459 sipe_send_election_set_rm(struct sipe_account_data *sip,
4460 struct sip_dialog *dialog)
4462 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4464 gchar *body = g_strdup_printf(
4465 "<?xml version=\"1.0\"?>\r\n"
4466 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4467 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4468 sip->username);
4470 send_sip_request(sip->gc, "INFO",
4471 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4473 g_free(body);
4476 static void
4477 sipe_session_close(struct sipe_account_data *sip,
4478 struct sip_session * session)
4480 if (session && session->focus_uri) {
4481 sipe_conf_immcu_closed(sip, session);
4482 conf_session_close(sip, session);
4485 if (session) {
4486 SIPE_DIALOG_FOREACH {
4487 /* @TODO slow down BYE message sending rate */
4488 /* @see single subscription code */
4489 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4490 } SIPE_DIALOG_FOREACH_END;
4492 sipe_session_remove(sip, session);
4496 static void
4497 sipe_session_close_all(struct sipe_account_data *sip)
4499 GSList *entry;
4500 while ((entry = sip->sessions) != NULL) {
4501 sipe_session_close(sip, entry->data);
4505 static void
4506 sipe_convo_closed(PurpleConnection * gc, const char *who)
4508 struct sipe_account_data *sip = gc->proto_data;
4510 purple_debug_info("sipe", "conversation with %s closed\n", who);
4511 sipe_session_close(sip, sipe_session_find_im(sip, who));
4514 static void
4515 sipe_chat_leave (PurpleConnection *gc, int id)
4517 struct sipe_account_data *sip = gc->proto_data;
4518 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4520 sipe_session_close(sip, session);
4523 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4524 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4526 struct sipe_account_data *sip = gc->proto_data;
4527 struct sip_session *session;
4528 struct sip_dialog *dialog;
4529 gchar *uri = sip_uri(who);
4531 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4533 session = sipe_session_find_or_add_im(sip, uri);
4534 dialog = sipe_dialog_find(session, uri);
4536 // Queue the message
4537 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
4539 if (dialog && !dialog->outgoing_invite) {
4540 sipe_im_process_queue(sip, session);
4541 } else if (!dialog || !dialog->outgoing_invite) {
4542 // Need to send the INVITE to get the outgoing dialog setup
4543 sipe_invite(sip, session, uri, what, NULL, FALSE);
4546 g_free(uri);
4547 return 1;
4550 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4551 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4553 struct sipe_account_data *sip = gc->proto_data;
4554 struct sip_session *session;
4556 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4558 session = sipe_session_find_chat_by_id(sip, id);
4560 // Queue the message
4561 if (session && session->dialogs) {
4562 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4563 g_strdup(what));
4564 sipe_im_process_queue(sip, session);
4565 } else if (sip) {
4566 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4567 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4569 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4570 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4572 if (sip->ocs2007) {
4573 struct sip_session *session = sipe_session_add_chat(sip);
4575 session->is_multiparty = FALSE;
4576 session->focus_uri = g_strdup(proto_chat_id);
4577 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4578 g_strdup(what));
4579 sipe_invite_conf_focus(sip, session);
4583 return 1;
4586 /* End IM Session (INVITE and MESSAGE methods) */
4588 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4590 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4591 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4592 gchar *from;
4593 struct sip_session *session;
4595 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4597 /* Call Control protocol */
4598 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4600 process_incoming_info_csta(sip, msg);
4601 return;
4604 from = parse_from(sipmsg_find_header(msg, "From"));
4605 session = sipe_session_find_chat_by_callid(sip, callid);
4606 if (!session) {
4607 session = sipe_session_find_im(sip, from);
4609 if (!session) {
4610 g_free(from);
4611 return;
4614 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4616 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4617 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4618 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4620 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4622 if (xn_request_rm) {
4623 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4624 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4625 gchar *body = g_strdup_printf(
4626 "<?xml version=\"1.0\"?>\r\n"
4627 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4628 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4629 sip->username,
4630 session->bid < bid ? "true" : "false");
4631 send_sip_response(sip->gc, msg, 200, "OK", body);
4632 g_free(body);
4633 } else if (xn_set_rm) {
4634 gchar *body;
4635 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4636 g_free(session->roster_manager);
4637 session->roster_manager = g_strdup(rm);
4639 body = g_strdup_printf(
4640 "<?xml version=\"1.0\"?>\r\n"
4641 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4642 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4643 sip->username);
4644 send_sip_response(sip->gc, msg, 200, "OK", body);
4645 g_free(body);
4647 xmlnode_free(xn_action);
4650 else
4652 /* looks like purple lacks typing notification for chat */
4653 if (!session->is_multiparty && !session->focus_uri) {
4654 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4655 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4656 "status");
4657 if (status && !strcmp(status, "type")) {
4658 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4659 } else if (status && !strcmp(status, "idle")) {
4660 serv_got_typing_stopped(sip->gc, from);
4662 xmlnode_free(xn_keyboard_activity);
4665 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4667 g_free(from);
4670 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4672 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4673 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4674 struct sip_session *session;
4675 struct sip_dialog *dialog;
4677 /* collect dialog identification
4678 * we need callid, ourtag and theirtag to unambiguously identify dialog
4680 /* take data before 'msg' will be modified by send_sip_response */
4681 dialog = g_new0(struct sip_dialog, 1);
4682 dialog->callid = g_strdup(callid);
4683 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4684 dialog->with = g_strdup(from);
4685 sipe_dialog_parse(dialog, msg, FALSE);
4687 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4689 session = sipe_session_find_chat_by_callid(sip, callid);
4690 if (!session) {
4691 session = sipe_session_find_im(sip, from);
4693 if (!session) {
4694 sipe_dialog_free(dialog);
4695 g_free(from);
4696 return;
4699 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4700 g_free(session->roster_manager);
4701 session->roster_manager = NULL;
4704 /* This what BYE is essentially for - terminating dialog */
4705 sipe_dialog_remove_3(session, dialog);
4706 sipe_dialog_free(dialog);
4707 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4708 sipe_conf_immcu_closed(sip, session);
4709 } else if (session->is_multiparty) {
4710 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4713 g_free(from);
4716 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4718 gchar *self = sip_uri_self(sip);
4719 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4720 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4721 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4722 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4723 struct sip_session *session;
4724 struct sip_dialog *dialog;
4726 session = sipe_session_find_chat_by_callid(sip, callid);
4727 dialog = sipe_dialog_find(session, from);
4729 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
4730 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4731 } else {
4732 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4734 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
4737 g_free(self);
4738 g_free(from);
4739 g_free(refer_to);
4740 g_free(referred_by);
4743 static unsigned int
4744 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4746 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4747 struct sip_session *session;
4748 struct sip_dialog *dialog;
4750 if (state == PURPLE_NOT_TYPING)
4751 return 0;
4753 session = sipe_session_find_im(sip, who);
4754 dialog = sipe_dialog_find(session, who);
4756 if (session && dialog && dialog->is_established) {
4757 send_sip_request(gc, "INFO", who, who,
4758 "Content-Type: application/xml\r\n",
4759 SIPE_SEND_TYPING, dialog, NULL);
4761 return SIPE_TYPING_SEND_TIMEOUT;
4764 static gboolean resend_timeout(struct sipe_account_data *sip)
4766 GSList *tmp = sip->transactions;
4767 time_t currtime = time(NULL);
4768 while (tmp) {
4769 struct transaction *trans = tmp->data;
4770 tmp = tmp->next;
4771 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4772 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4773 /* TODO 408 */
4774 } else {
4775 if ((currtime - trans->time > 2) && trans->retries == 0) {
4776 trans->retries++;
4777 sendout_sipmsg(sip, trans->msg);
4781 return TRUE;
4784 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4785 SIPE_UNUSED_PARAMETER void *unused)
4787 /* register again when security token expires */
4788 /* we have to start a new authentication as the security token
4789 * is almost expired by sending a not signed REGISTER message */
4790 purple_debug_info("sipe", "do a full reauthentication\n");
4791 sipe_auth_free(&sip->registrar);
4792 sipe_auth_free(&sip->proxy);
4793 sip->registerstatus = 0;
4794 do_register(sip);
4795 sip->reauthenticate_set = FALSE;
4798 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4800 gchar *from;
4801 gchar *contenttype;
4802 gboolean found = FALSE;
4804 from = parse_from(sipmsg_find_header(msg, "From"));
4806 if (!from) return;
4808 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4810 contenttype = sipmsg_find_header(msg, "Content-Type");
4811 if (!strncmp(contenttype, "text/plain", 10)
4812 || !strncmp(contenttype, "text/html", 9)
4813 || !strncmp(contenttype, "multipart/related", 17)
4814 || !strncmp(contenttype, "multipart/alternative", 21))
4816 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4817 gchar *html = get_html_message(contenttype, msg->body);
4819 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4820 if (!session) {
4821 session = sipe_session_find_im(sip, from);
4824 if (session && session->focus_uri) { /* a conference */
4825 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4826 gchar *sender = parse_from(tmp);
4827 g_free(tmp);
4828 serv_got_chat_in(sip->gc, session->chat_id, sender,
4829 PURPLE_MESSAGE_RECV, html, time(NULL));
4830 g_free(sender);
4831 } else if (session && session->is_multiparty) { /* a multiparty chat */
4832 serv_got_chat_in(sip->gc, session->chat_id, from,
4833 PURPLE_MESSAGE_RECV, html, time(NULL));
4834 } else {
4835 serv_got_im(sip->gc, from, html, 0, time(NULL));
4837 g_free(html);
4838 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4839 found = TRUE;
4841 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
4842 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4843 xmlnode *state;
4844 gchar *statedata;
4846 if (!isc) {
4847 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4848 return;
4851 state = xmlnode_get_child(isc, "state");
4853 if (!state) {
4854 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4855 xmlnode_free(isc);
4856 return;
4859 statedata = xmlnode_get_data(state);
4860 if (statedata) {
4861 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4862 else serv_got_typing_stopped(sip->gc, from);
4864 g_free(statedata);
4866 xmlnode_free(isc);
4867 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4868 found = TRUE;
4870 if (!found) {
4871 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4872 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4873 if (!session) {
4874 session = sipe_session_find_im(sip, from);
4876 if (session) {
4877 gchar *msg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4878 from);
4879 sipe_present_err(sip, session, msg);
4880 g_free(msg);
4883 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
4884 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
4886 g_free(from);
4889 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
4891 gchar *body;
4892 gchar *newTag;
4893 gchar *oldHeader;
4894 gchar *newHeader;
4895 gboolean is_multiparty = FALSE;
4896 gboolean is_triggered = FALSE;
4897 gboolean was_multiparty = TRUE;
4898 gboolean just_joined = FALSE;
4899 gchar *from;
4900 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4901 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4902 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4903 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4904 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4905 GSList *end_points = NULL;
4906 char *tmp = NULL;
4907 struct sip_session *session;
4909 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
4910 g_free(tmp);
4912 /* Invitation to join conference */
4913 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
4914 process_incoming_invite_conf(sip, msg);
4915 return;
4918 /* Only accept text invitations */
4919 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4920 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4921 return;
4924 // TODO There *must* be a better way to clean up the To header to add a tag...
4925 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4926 oldHeader = sipmsg_find_header(msg, "To");
4927 newTag = gentag();
4928 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4929 sipmsg_remove_header_now(msg, "To");
4930 sipmsg_add_header_now(msg, "To", newHeader);
4931 g_free(newHeader);
4933 if (end_points_hdr) {
4934 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4936 if (g_slist_length(end_points) > 2) {
4937 is_multiparty = TRUE;
4940 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4941 is_triggered = TRUE;
4942 is_multiparty = TRUE;
4945 session = sipe_session_find_chat_by_callid(sip, callid);
4946 /* Convert to multiparty */
4947 if (session && is_multiparty && !session->is_multiparty) {
4948 g_free(session->with);
4949 session->with = NULL;
4950 was_multiparty = FALSE;
4951 session->is_multiparty = TRUE;
4952 session->chat_id = rand();
4955 if (!session && is_multiparty) {
4956 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
4958 /* IM session */
4959 from = parse_from(sipmsg_find_header(msg, "From"));
4960 if (!session) {
4961 session = sipe_session_find_or_add_im(sip, from);
4964 if (session) {
4965 g_free(session->callid);
4966 session->callid = g_strdup(callid);
4968 session->is_multiparty = is_multiparty;
4969 if (roster_manager) {
4970 session->roster_manager = g_strdup(roster_manager);
4974 if (is_multiparty && end_points) {
4975 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
4976 GSList *entry = end_points;
4977 while (entry) {
4978 struct sip_dialog *dialog;
4979 struct sipendpoint *end_point = entry->data;
4980 entry = entry->next;
4982 if (!g_strcasecmp(from, end_point->contact) ||
4983 !g_strcasecmp(to, end_point->contact))
4984 continue;
4986 dialog = sipe_dialog_find(session, end_point->contact);
4987 if (dialog) {
4988 g_free(dialog->theirepid);
4989 dialog->theirepid = end_point->epid;
4990 end_point->epid = NULL;
4991 } else {
4992 dialog = sipe_dialog_add(session);
4994 dialog->callid = g_strdup(session->callid);
4995 dialog->with = end_point->contact;
4996 end_point->contact = NULL;
4997 dialog->theirepid = end_point->epid;
4998 end_point->epid = NULL;
5000 just_joined = TRUE;
5002 /* send triggered INVITE */
5003 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
5006 g_free(to);
5009 if (end_points) {
5010 GSList *entry = end_points;
5011 while (entry) {
5012 struct sipendpoint *end_point = entry->data;
5013 entry = entry->next;
5014 g_free(end_point->contact);
5015 g_free(end_point->epid);
5016 g_free(end_point);
5018 g_slist_free(end_points);
5021 if (session) {
5022 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5023 if (dialog) {
5024 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5025 } else {
5026 dialog = sipe_dialog_add(session);
5028 dialog->callid = g_strdup(session->callid);
5029 dialog->with = g_strdup(from);
5030 sipe_dialog_parse(dialog, msg, FALSE);
5032 if (!dialog->ourtag) {
5033 dialog->ourtag = newTag;
5034 newTag = NULL;
5037 just_joined = TRUE;
5039 } else {
5040 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5042 g_free(newTag);
5044 if (is_multiparty && !session->conv) {
5045 gchar *chat_title = sipe_chat_get_name(callid);
5046 gchar *self = sip_uri_self(sip);
5047 /* create prpl chat */
5048 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5049 session->chat_title = g_strdup(chat_title);
5050 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5051 /* add self */
5052 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5053 self, NULL,
5054 PURPLE_CBFLAGS_NONE, FALSE);
5055 g_free(chat_title);
5056 g_free(self);
5059 if (is_multiparty && !was_multiparty) {
5060 /* add current IM counterparty to chat */
5061 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5062 sipe_dialog_first(session)->with, NULL,
5063 PURPLE_CBFLAGS_NONE, FALSE);
5066 /* add inviting party to chat */
5067 if (just_joined && session->conv) {
5068 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5069 from, NULL,
5070 PURPLE_CBFLAGS_NONE, TRUE);
5073 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5075 /* This used only in 2005 official client, not 2007 or Reuters.
5076 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5077 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5079 if (is_multiparty) {
5080 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5081 gchar *ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5082 if (ms_text_format) {
5083 if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html")) {
5085 gchar *html = get_html_message(ms_text_format, NULL);
5086 if (html) {
5087 if (is_multiparty) {
5088 serv_got_chat_in(sip->gc, session->chat_id, from,
5089 PURPLE_MESSAGE_RECV, html, time(NULL));
5090 } else {
5091 serv_got_im(sip->gc, from, html, 0, time(NULL));
5093 g_free(html);
5094 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5101 g_free(from);
5103 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5104 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5105 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5107 body = g_strdup_printf(
5108 "v=0\r\n"
5109 "o=- 0 0 IN IP4 %s\r\n"
5110 "s=session\r\n"
5111 "c=IN IP4 %s\r\n"
5112 "t=0 0\r\n"
5113 "m=%s %d sip sip:%s\r\n"
5114 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5115 purple_network_get_my_ip(-1),
5116 purple_network_get_my_ip(-1),
5117 sip->ocs2007 ? "message" : "x-ms-message",
5118 sip->realport,
5119 sip->username);
5120 send_sip_response(sip->gc, msg, 200, "OK", body);
5121 g_free(body);
5124 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5126 gchar *body;
5128 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5129 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5130 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5132 body = g_strdup_printf(
5133 "v=0\r\n"
5134 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5135 "s=session\r\n"
5136 "c=IN IP4 0.0.0.0\r\n"
5137 "t=0 0\r\n"
5138 "m=%s %d sip sip:%s\r\n"
5139 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5140 sip->ocs2007 ? "message" : "x-ms-message",
5141 sip->realport,
5142 sip->username);
5143 send_sip_response(sip->gc, msg, 200, "OK", body);
5144 g_free(body);
5147 static const char*
5148 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5150 const char *res = "NTLM";
5151 #ifdef USE_KERBEROS
5152 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5153 res = "Kerberos";
5155 #else
5156 (void) sip; /* make compiler happy */
5157 #endif
5158 return res;
5161 static void sipe_connection_cleanup(struct sipe_account_data *);
5162 static void create_connection(struct sipe_account_data *, gchar *, int);
5164 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5165 SIPE_UNUSED_PARAMETER struct transaction *trans)
5167 gchar *tmp;
5168 const gchar *expires_header;
5169 int expires, i;
5170 GSList *hdr = msg->headers;
5171 struct siphdrelement *elem;
5173 expires_header = sipmsg_find_header(msg, "Expires");
5174 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5175 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5177 switch (msg->response) {
5178 case 200:
5179 if (expires == 0) {
5180 sip->registerstatus = 0;
5181 } else {
5182 gchar *contact_hdr = NULL;
5183 gchar *gruu = NULL;
5184 gchar *epid;
5185 gchar *uuid;
5186 gchar *timeout;
5187 gchar *server_hdr = sipmsg_find_header(msg, "Server");
5188 const char *auth_scheme;
5190 if (!sip->reregister_set) {
5191 gchar *action_name = g_strdup_printf("<%s>", "registration");
5192 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5193 g_free(action_name);
5194 sip->reregister_set = TRUE;
5197 sip->registerstatus = 3;
5199 if (server_hdr && !sip->server_version) {
5200 sip->server_version = g_strdup(server_hdr);
5201 g_free(default_ua);
5202 default_ua = NULL;
5205 auth_scheme = sipe_get_auth_scheme_name(sip);
5206 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5208 if (tmp) {
5209 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5210 fill_auth(tmp, &sip->registrar);
5213 if (!sip->reauthenticate_set) {
5214 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5215 guint reauth_timeout;
5216 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5217 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5218 reauth_timeout = sip->registrar.expires - 300;
5219 } else {
5220 /* NTLM: we have to reauthenticate as our security token expires
5221 after eight hours (be five minutes early) */
5222 reauth_timeout = (8 * 3600) - 300;
5224 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5225 g_free(action_name);
5226 sip->reauthenticate_set = TRUE;
5229 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5231 epid = get_epid(sip);
5232 uuid = generateUUIDfromEPID(epid);
5233 g_free(epid);
5235 // There can be multiple Contact headers (one per location where the user is logged in) so
5236 // make sure to only get the one for this uuid
5237 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5238 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5239 if (valid_contact) {
5240 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5241 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5242 g_free(valid_contact);
5243 break;
5244 } else {
5245 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5248 g_free(uuid);
5250 g_free(sip->contact);
5251 if(gruu) {
5252 sip->contact = g_strdup_printf("<%s>", gruu);
5253 g_free(gruu);
5254 } else {
5255 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5256 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);
5258 sip->ocs2007 = FALSE;
5259 sip->batched_support = FALSE;
5261 while(hdr)
5263 elem = hdr->data;
5264 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5265 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5266 /* We interpret this as OCS2007+ indicator */
5267 sip->ocs2007 = TRUE;
5268 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5270 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5271 sip->batched_support = TRUE;
5272 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5275 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5276 gchar **caps = g_strsplit(elem->value,",",0);
5277 i = 0;
5278 while (caps[i]) {
5279 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5280 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5281 i++;
5283 g_strfreev(caps);
5285 hdr = g_slist_next(hdr);
5288 /* rejoin open chats to be able to use them by continue to send messages */
5289 purple_conversation_foreach(sipe_rejoin_chat);
5291 /* subscriptions */
5292 if (!sip->subscribed) { //do it just once, not every re-register
5294 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5295 (GCompareFunc)g_ascii_strcasecmp)) {
5296 sipe_subscribe_roaming_contacts(sip);
5299 /* For 2007+ it does not make sence to subscribe to:
5300 * vnd-microsoft-roaming-ACL
5301 * vnd-microsoft-provisioning (not v2)
5302 * presence.wpending
5303 * These are for backward compatibility.
5305 if (sip->ocs2007)
5307 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5308 (GCompareFunc)g_ascii_strcasecmp)) {
5309 sipe_subscribe_roaming_self(sip);
5311 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5312 (GCompareFunc)g_ascii_strcasecmp)) {
5313 sipe_subscribe_roaming_provisioning_v2(sip);
5316 /* For 2005- servers */
5317 else
5319 //sipe_options_request(sip, sip->sipdomain);
5321 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5322 (GCompareFunc)g_ascii_strcasecmp)) {
5323 sipe_subscribe_roaming_acl(sip);
5325 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5326 (GCompareFunc)g_ascii_strcasecmp)) {
5327 sipe_subscribe_roaming_provisioning(sip);
5329 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5330 (GCompareFunc)g_ascii_strcasecmp)) {
5331 sipe_subscribe_presence_wpending(sip, msg);
5334 /* For 2007+ we publish our initial statuses and calendar data only after
5335 * received our existing publications in sipe_process_roaming_self()
5336 * Only in this case we know versions of current publications made
5337 * on our behalf.
5339 /* For 2005- we publish our initial statuses only after
5340 * received our existing UserInfo data in response to
5341 * self subscription.
5342 * Only in this case we won't override existing UserInfo data
5343 * set earlier or by other client on our behalf.
5347 sip->subscribed = TRUE;
5350 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5351 "timeout=", ";", NULL);
5352 if (timeout != NULL) {
5353 sscanf(timeout, "%u", &sip->keepalive_timeout);
5354 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5355 sip->keepalive_timeout);
5356 g_free(timeout);
5359 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5361 break;
5362 case 301:
5364 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5366 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5367 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5368 gchar **tmp;
5369 gchar *hostname;
5370 int port = 0;
5371 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5372 int i = 1;
5374 tmp = g_strsplit(parts[0], ":", 0);
5375 hostname = g_strdup(tmp[0]);
5376 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5377 g_strfreev(tmp);
5379 while (parts[i]) {
5380 tmp = g_strsplit(parts[i], "=", 0);
5381 if (tmp[1]) {
5382 if (g_strcasecmp("transport", tmp[0]) == 0) {
5383 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5384 transport = SIPE_TRANSPORT_TCP;
5385 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5386 transport = SIPE_TRANSPORT_UDP;
5390 g_strfreev(tmp);
5391 i++;
5393 g_strfreev(parts);
5395 /* Close old connection */
5396 sipe_connection_cleanup(sip);
5398 /* Create new connection */
5399 sip->transport = transport;
5400 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5401 hostname, port, TRANSPORT_DESCRIPTOR);
5402 create_connection(sip, hostname, port);
5404 g_free(redirect);
5406 break;
5407 case 401:
5408 if (sip->registerstatus != 2) {
5409 const char *auth_scheme;
5410 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5411 if (sip->registrar.retries > 3) {
5412 sip->gc->wants_to_die = TRUE;
5413 purple_connection_error(sip->gc, _("Wrong password"));
5414 return TRUE;
5417 auth_scheme = sipe_get_auth_scheme_name(sip);
5418 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5420 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5421 if (!tmp) {
5422 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5423 sip->gc->wants_to_die = TRUE;
5424 purple_connection_error(sip->gc, tmp2);
5425 g_free(tmp2);
5426 return TRUE;
5428 fill_auth(tmp, &sip->registrar);
5429 sip->registerstatus = 2;
5430 if (sip->account->disconnecting) {
5431 do_register_exp(sip, 0);
5432 } else {
5433 do_register(sip);
5436 break;
5437 case 403:
5439 gchar *warning = sipmsg_find_header(msg, "Warning");
5440 gchar **reason = NULL;
5441 if (warning != NULL) {
5442 /* Example header:
5443 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5445 reason = g_strsplit(warning, "\"", 0);
5447 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5448 (reason && reason[1]) ? reason[1] : _("no reason given"));
5449 g_strfreev(reason);
5451 sip->gc->wants_to_die = TRUE;
5452 purple_connection_error(sip->gc, warning);
5453 g_free(warning);
5454 return TRUE;
5456 break;
5457 case 404:
5459 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5460 gchar *reason = NULL;
5461 if (warning != NULL) {
5462 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5464 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5465 warning ? (reason ? reason : _("no reason given")) :
5466 _("SIP is either not enabled for the destination URI or it does not exist"));
5467 g_free(reason);
5469 sip->gc->wants_to_die = TRUE;
5470 purple_connection_error(sip->gc, warning);
5471 g_free(warning);
5472 return TRUE;
5474 break;
5475 case 503:
5476 case 504: /* Server time-out */
5478 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5479 gchar *reason = NULL;
5480 if (warning != NULL) {
5481 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5483 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : "<a href=\"http://www.reuters.com\">http://www.reuters.com</a>"/*_("no reason given")*/);
5484 g_free(reason);
5486 sip->gc->wants_to_die = TRUE;
5487 purple_connection_error(sip->gc, warning);
5488 g_free(warning);
5489 return TRUE;
5491 break;
5493 return TRUE;
5497 * Returns 2005-style activity and Availability.
5499 * @param status Sipe statis id.
5501 static void
5502 sipe_get_act_avail_by_status_2005(const char *status,
5503 int *activity,
5504 int *availability)
5506 int avail = 300; /* online */
5507 int act = 400; /* Available */
5509 if (!strcmp(status, SIPE_STATUS_ID_AWAY)) {
5510 act = 100;
5511 //} else if (!strcmp(status, SIPE_STATUS_ID_LUNCH)) {
5512 // act = 150;
5513 } else if (!strcmp(status, SIPE_STATUS_ID_BRB)) {
5514 act = 300;
5515 } else if (!strcmp(status, SIPE_STATUS_ID_AVAILABLE)) {
5516 act = 400;
5517 //} else if (!strcmp(status, SIPE_STATUS_ID_ON_PHONE)) {
5518 // act = 500;
5519 } else if (!strcmp(status, SIPE_STATUS_ID_BUSY) ||
5520 !strcmp(status, SIPE_STATUS_ID_DND)) {
5521 act = 600;
5522 } else if (!strcmp(status, SIPE_STATUS_ID_INVISIBLE) ||
5523 !strcmp(status, SIPE_STATUS_ID_OFFLINE)) {
5524 avail = 0; /* offline */
5525 act = 100;
5526 } else {
5527 act = 400; /* Available */
5530 if (activity) *activity = act;
5531 if (availability) *availability = avail;
5535 * [MS-SIP] 2.2.1
5537 * @param activity 2005 aggregated activity. Ex.: 600
5538 * @param availablity 2005 aggregated availablity. Ex.: 300
5540 static const char *
5541 sipe_get_status_by_act_avail_2005(const int activity,
5542 const int availablity,
5543 char **activity_desc)
5545 const char *status_id = NULL;
5546 const char *act = NULL;
5548 if (activity < 150) {
5549 status_id = SIPE_STATUS_ID_AWAY;
5550 } else if (activity < 200) {
5551 //status_id = SIPE_STATUS_ID_LUNCH;
5552 status_id = SIPE_STATUS_ID_AWAY;
5553 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5554 } else if (activity < 300) {
5555 //status_id = SIPE_STATUS_ID_IDLE;
5556 status_id = SIPE_STATUS_ID_AWAY;
5557 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5558 } else if (activity < 400) {
5559 status_id = SIPE_STATUS_ID_BRB;
5560 } else if (activity < 500) {
5561 status_id = SIPE_STATUS_ID_AVAILABLE;
5562 } else if (activity < 600) {
5563 //status_id = SIPE_STATUS_ID_ON_PHONE;
5564 status_id = SIPE_STATUS_ID_BUSY;
5565 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5566 } else if (activity < 700) {
5567 status_id = SIPE_STATUS_ID_BUSY;
5568 } else if (activity < 800) {
5569 status_id = SIPE_STATUS_ID_AWAY;
5570 } else {
5571 status_id = SIPE_STATUS_ID_AVAILABLE;
5574 if (availablity < 100)
5575 status_id = SIPE_STATUS_ID_OFFLINE;
5577 if (activity_desc && act) {
5578 g_free(*activity_desc);
5579 *activity_desc = g_strdup(act);
5582 return status_id;
5586 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5588 static const char*
5589 sipe_get_status_by_availability(int avail,
5590 char** activity_desc)
5592 const char *status;
5593 const char *act = NULL;
5595 if (avail < 3000) {
5596 status = SIPE_STATUS_ID_OFFLINE;
5597 } else if (avail < 4500) {
5598 status = SIPE_STATUS_ID_AVAILABLE;
5599 } else if (avail < 6000) {
5600 //status = SIPE_STATUS_ID_IDLE;
5601 status = SIPE_STATUS_ID_AVAILABLE;
5602 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5603 } else if (avail < 7500) {
5604 status = SIPE_STATUS_ID_BUSY;
5605 } else if (avail < 9000) {
5606 //status = SIPE_STATUS_ID_BUSYIDLE;
5607 status = SIPE_STATUS_ID_BUSY;
5608 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5609 } else if (avail < 12000) {
5610 status = SIPE_STATUS_ID_DND;
5611 } else if (avail < 15000) {
5612 status = SIPE_STATUS_ID_BRB;
5613 } else if (avail < 18000) {
5614 status = SIPE_STATUS_ID_AWAY;
5615 } else {
5616 status = SIPE_STATUS_ID_OFFLINE;
5619 if (activity_desc && act) {
5620 g_free(*activity_desc);
5621 *activity_desc = g_strdup(act);
5624 return status;
5628 * Returns 2007-style availability value
5630 * @param sipe_status_id (in)
5631 * @param activity_token (out) Must be g_free()'d after use if consumed.
5633 static int
5634 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5636 int availability;
5637 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5639 if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5640 availability = 15500;
5641 if (!activity_token || !(*activity_token)) {
5642 activity = SIPE_ACTIVITY_AWAY;
5644 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5645 availability = 12500;
5646 activity = SIPE_ACTIVITY_BRB;
5647 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_DND)) {
5648 availability = 9500;
5649 activity = SIPE_ACTIVITY_DND;
5650 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5651 availability = 6500;
5652 if (!activity_token || !(*activity_token)) {
5653 activity = SIPE_ACTIVITY_BUSY;
5655 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5656 availability = 3500;
5657 activity = SIPE_ACTIVITY_ONLINE;
5658 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5659 availability = 0;
5660 } else {
5661 // Offline or invisible
5662 availability = 18500;
5663 activity = SIPE_ACTIVITY_OFFLINE;
5666 if (activity_token) {
5667 *activity_token = g_strdup(sipe_activity_map[activity].token);
5669 return availability;
5672 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5674 const char *uri;
5675 xmlnode *xn_categories;
5676 xmlnode *xn_category;
5677 xmlnode *xn_node;
5678 const char *status = NULL;
5679 gboolean do_update_status = FALSE;
5680 gboolean has_note_cleaned = FALSE;
5681 gboolean has_free_busy_cleaned = FALSE;
5683 xn_categories = xmlnode_from_str(data, len);
5684 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5686 for (xn_category = xmlnode_get_child(xn_categories, "category");
5687 xn_category ;
5688 xn_category = xmlnode_get_next_twin(xn_category) )
5690 const char *tmp;
5691 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5692 time_t publish_time = (tmp = xmlnode_get_attrib(xn_category, "publishTime")) ?
5693 sipe_utils_str_to_time(tmp) : 0;
5695 /* contactCard */
5696 if (!strcmp(attrVar, "contactCard"))
5698 xmlnode *node;
5699 /* identity - Display Name and email */
5700 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5701 if (node) {
5702 char* display_name = xmlnode_get_data(
5703 xmlnode_get_descendant(node, "name", "displayName", NULL));
5704 char* email = xmlnode_get_data(
5705 xmlnode_get_child(node, "email"));
5707 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5708 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5710 g_free(display_name);
5711 g_free(email);
5713 /* company */
5714 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5715 if (node) {
5716 char* company = xmlnode_get_data(node);
5717 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5718 g_free(company);
5720 /* department */
5721 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5722 if (node) {
5723 char* department = xmlnode_get_data(node);
5724 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5725 g_free(department);
5727 /* title */
5728 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5729 if (node) {
5730 char* title = xmlnode_get_data(node);
5731 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5732 g_free(title);
5734 /* office */
5735 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5736 if (node) {
5737 char* office = xmlnode_get_data(node);
5738 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5739 g_free(office);
5741 /* site (url) */
5742 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5743 if (node) {
5744 char* site = xmlnode_get_data(node);
5745 sipe_update_user_info(sip, uri, SITE_PROP, site);
5746 g_free(site);
5748 /* phone */
5749 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5750 node;
5751 node = xmlnode_get_next_twin(node))
5753 const char *phone_type = xmlnode_get_attrib(node, "type");
5754 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5755 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5757 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5759 g_free(phone);
5760 g_free(phone_display_string);
5762 /* address */
5763 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5764 node;
5765 node = xmlnode_get_next_twin(node))
5767 if (!strcmp(xmlnode_get_attrib(node, "type"), "work")) {
5768 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5769 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5770 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5771 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5772 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5774 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5775 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5776 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5777 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5778 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5780 g_free(street);
5781 g_free(city);
5782 g_free(state);
5783 g_free(zipcode);
5784 g_free(country_code);
5786 break;
5790 /* note */
5791 else if (!strcmp(attrVar, "note"))
5793 if (uri) {
5794 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5796 if (!has_note_cleaned) {
5797 has_note_cleaned = TRUE;
5799 g_free(sbuddy->note);
5800 sbuddy->note = NULL;
5801 sbuddy->is_oof_note = FALSE;
5802 sbuddy->note_since = publish_time;
5804 do_update_status = TRUE;
5806 if (sbuddy && (publish_time >= sbuddy->note_since)) {
5807 /* clean up in case no 'note' element is supplied
5808 * which indicate note removal in client
5810 g_free(sbuddy->note);
5811 sbuddy->note = NULL;
5812 sbuddy->is_oof_note = FALSE;
5813 sbuddy->note_since = publish_time;
5815 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
5816 if (xn_node) {
5817 char *tmp;
5818 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
5819 g_free(tmp);
5820 sbuddy->is_oof_note = !strcmp(xmlnode_get_attrib(xn_node, "type"), "OOF");
5821 sbuddy->note_since = publish_time;
5823 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
5824 uri, sbuddy->note ? sbuddy->note : "");
5826 /* to trigger UI refresh in case no status info is supplied in this update */
5827 do_update_status = TRUE;
5831 /* state */
5832 else if(!strcmp(attrVar, "state"))
5834 char *data;
5835 int availability;
5836 xmlnode *xn_availability;
5837 xmlnode *xn_activity;
5838 xmlnode *xn_meeting_subject;
5839 xmlnode *xn_meeting_location;
5840 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5842 xn_node = xmlnode_get_child(xn_category, "state");
5843 if (!xn_node) continue;
5844 xn_availability = xmlnode_get_child(xn_node, "availability");
5845 if (!xn_availability) continue;
5846 xn_activity = xmlnode_get_child(xn_node, "activity");
5847 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
5848 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
5850 data = xmlnode_get_data(xn_availability);
5851 availability = atoi(data);
5852 g_free(data);
5854 /* activity, meeting_subject, meeting_location */
5855 if (sbuddy) {
5856 char *tmp = NULL;
5858 /* activity */
5859 g_free(sbuddy->activity);
5860 sbuddy->activity = NULL;
5861 if (xn_activity) {
5862 const char *token = xmlnode_get_attrib(xn_activity, "token");
5863 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
5865 /* from token */
5866 if (!is_empty(token)) {
5867 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
5869 /* from custom element */
5870 if (xn_custom) {
5871 char *custom = xmlnode_get_data(xn_custom);
5873 if (!is_empty(custom)) {
5874 sbuddy->activity = custom;
5875 custom = NULL;
5877 g_free(custom);
5880 /* meeting_subject */
5881 g_free(sbuddy->meeting_subject);
5882 sbuddy->meeting_subject = NULL;
5883 if (xn_meeting_subject) {
5884 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
5886 if (!is_empty(meeting_subject)) {
5887 sbuddy->meeting_subject = meeting_subject;
5888 meeting_subject = NULL;
5890 g_free(meeting_subject);
5892 /* meeting_location */
5893 g_free(sbuddy->meeting_location);
5894 sbuddy->meeting_location = NULL;
5895 if (xn_meeting_location) {
5896 char *meeting_location = xmlnode_get_data(xn_meeting_location);
5898 if (!is_empty(meeting_location)) {
5899 sbuddy->meeting_location = meeting_location;
5900 meeting_location = NULL;
5902 g_free(meeting_location);
5905 status = sipe_get_status_by_availability(availability, &tmp);
5906 if (sbuddy->activity && tmp) {
5907 char *tmp2 = sbuddy->activity;
5909 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
5910 g_free(tmp);
5911 g_free(tmp2);
5912 } else if (tmp) {
5913 sbuddy->activity = tmp;
5917 do_update_status = TRUE;
5919 /* calendarData */
5920 else if(!strcmp(attrVar, "calendarData"))
5922 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5923 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
5924 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
5926 if (sbuddy && xn_free_busy) {
5927 if (!has_free_busy_cleaned) {
5928 has_free_busy_cleaned = TRUE;
5930 g_free(sbuddy->cal_start_time);
5931 sbuddy->cal_start_time = NULL;
5933 g_free(sbuddy->cal_free_busy_base64);
5934 sbuddy->cal_free_busy_base64 = NULL;
5936 g_free(sbuddy->cal_free_busy);
5937 sbuddy->cal_free_busy = NULL;
5939 sbuddy->cal_free_busy_published = publish_time;
5942 if (publish_time >= sbuddy->cal_free_busy_published) {
5943 g_free(sbuddy->cal_start_time);
5944 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
5946 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
5947 15 : 0;
5949 g_free(sbuddy->cal_free_busy_base64);
5950 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
5952 g_free(sbuddy->cal_free_busy);
5953 sbuddy->cal_free_busy = NULL;
5955 sbuddy->cal_free_busy_published = publish_time;
5957 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);
5961 if (sbuddy && xn_working_hours) {
5962 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
5967 if (do_update_status) {
5968 if (!status) { /* no status category in this update, using contact's current status */
5969 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
5970 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
5971 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
5972 status = purple_status_get_id(pstatus);
5975 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
5976 sipe_got_user_status(sip, uri, status);
5979 xmlnode_free(xn_categories);
5982 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
5984 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5985 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
5986 payload->host = g_strdup(host);
5987 payload->buddies = server;
5988 sipe_subscribe_presence_batched_routed(sip, payload);
5989 sipe_subscribe_presence_batched_routed_free(payload);
5992 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
5994 xmlnode *xn_list;
5995 xmlnode *xn_resource;
5996 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
5997 g_free, NULL);
5998 GSList *server;
5999 gchar *host;
6001 xn_list = xmlnode_from_str(data, len);
6003 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6004 xn_resource;
6005 xn_resource = xmlnode_get_next_twin(xn_resource) )
6007 const char *uri, *state;
6008 xmlnode *xn_instance;
6010 xn_instance = xmlnode_get_child(xn_resource, "instance");
6011 if (!xn_instance) continue;
6013 uri = xmlnode_get_attrib(xn_resource, "uri");
6014 state = xmlnode_get_attrib(xn_instance, "state");
6015 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6017 if (strstr(state, "resubscribe")) {
6018 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6020 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6021 gchar *user = g_strdup(uri);
6022 host = g_strdup(poolFqdn);
6023 server = g_hash_table_lookup(servers, host);
6024 server = g_slist_append(server, user);
6025 g_hash_table_insert(servers, host, server);
6026 } else {
6027 sipe_subscribe_presence_single(sip, (void *) uri);
6032 /* Send out any deferred poolFqdn subscriptions */
6033 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6034 g_hash_table_destroy(servers);
6036 xmlnode_free(xn_list);
6039 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6041 gchar *uri;
6042 gchar *getbasic;
6043 gchar *activity = NULL;
6044 xmlnode *pidf;
6045 xmlnode *basicstatus = NULL, *tuple, *status;
6046 gboolean isonline = FALSE;
6047 xmlnode *display_name_node;
6049 pidf = xmlnode_from_str(data, len);
6050 if (!pidf) {
6051 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6052 return;
6055 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6057 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6059 if ((status = xmlnode_get_child(tuple, "status"))) {
6060 basicstatus = xmlnode_get_child(status, "basic");
6064 if (!basicstatus) {
6065 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6066 xmlnode_free(pidf);
6067 return;
6070 getbasic = xmlnode_get_data(basicstatus);
6071 if (!getbasic) {
6072 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6073 xmlnode_free(pidf);
6074 return;
6077 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6078 if (strstr(getbasic, "open")) {
6079 isonline = TRUE;
6081 g_free(getbasic);
6083 display_name_node = xmlnode_get_child(pidf, "display-name");
6084 if (display_name_node) {
6085 char * display_name = xmlnode_get_data(display_name_node);
6087 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6088 g_free(display_name);
6091 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6092 if ((status = xmlnode_get_child(tuple, "status"))) {
6093 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6094 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6095 activity = xmlnode_get_data(basicstatus);
6096 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6102 if (isonline) {
6103 const gchar * status_id = NULL;
6104 if (activity) {
6105 if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6106 status_id = SIPE_STATUS_ID_BUSY;
6107 } else if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6108 status_id = SIPE_STATUS_ID_AWAY;
6112 if (!status_id) {
6113 status_id = SIPE_STATUS_ID_AVAILABLE;
6116 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6117 sipe_got_user_status(sip, uri, status_id);
6118 } else {
6119 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6122 g_free(activity);
6123 g_free(uri);
6124 xmlnode_free(pidf);
6127 /** 2005 */
6128 static void
6129 sipe_user_info_has_updated(struct sipe_account_data *sip,
6130 xmlnode *xn_userinfo)
6132 if (sip->user_info) {
6133 xmlnode_free(sip->user_info);
6135 sip->user_info = xmlnode_copy(xn_userinfo);
6137 /* Publish initial state if not yet.
6138 * Assuming this happens on initial responce to self subscription
6139 * so we've already updated our UserInfo.
6141 if (!sip->initial_state_published) {
6142 send_presence_soap(sip, FALSE);
6143 /* dalayed run */
6144 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6148 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6150 char *activity = NULL;
6151 const char *epid;
6152 const char *status_id = NULL;
6153 const char *name;
6154 char *uri;
6155 char *self_uri = sip_uri_self(sip);
6156 int avl;
6157 int act;
6158 const char *device_name = NULL;
6159 const char *cal_start_time = NULL;
6160 const char *cal_granularity = NULL;
6161 char *cal_free_busy_base64 = NULL;
6162 struct sipe_buddy *sbuddy;
6163 xmlnode *node;
6164 xmlnode *xn_presentity;
6165 xmlnode *xn_availability;
6166 xmlnode *xn_activity;
6167 xmlnode *xn_display_name;
6168 xmlnode *xn_email;
6169 xmlnode *xn_phone_number;
6170 xmlnode *xn_userinfo;
6171 xmlnode *xn_note;
6172 xmlnode *xn_oof;
6173 xmlnode *xn_state;
6174 xmlnode *xn_contact;
6175 char *note;
6176 char *free_activity;
6177 int user_avail;
6178 const char *user_avail_nil;
6179 int res_avail;
6180 time_t user_avail_since = 0;
6181 time_t activity_since = 0;
6183 /* fix for Reuters environment on Linux */
6184 if (data && strstr(data, "encoding=\"utf-16\"")) {
6185 char *tmp_data;
6186 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6187 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6188 g_free(tmp_data);
6189 } else {
6190 xn_presentity = xmlnode_from_str(data, len);
6193 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6194 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6195 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6196 xn_email = xmlnode_get_child(xn_presentity, "email");
6197 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6198 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6199 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6200 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6201 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6202 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6203 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6204 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6205 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6206 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6208 if (user_avail_nil && !strcmp(user_avail_nil, "true")) { /* null-ed */
6209 user_avail = 0;
6210 user_avail_since = 0;
6213 free_activity = NULL;
6215 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6216 uri = sip_uri_from_name(name);
6217 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6218 epid = xmlnode_get_attrib(xn_availability, "epid");
6219 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6221 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6222 res_avail = sipe_get_availability_by_status(status_id, NULL);
6223 if (user_avail > res_avail) {
6224 res_avail = user_avail;
6225 status_id = sipe_get_status_by_availability(user_avail, NULL);
6228 if (xn_display_name) {
6229 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6230 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6231 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6232 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6233 char *tel_uri = sip_to_tel_uri(phone_number);
6235 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6236 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6237 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6238 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6240 g_free(tel_uri);
6241 g_free(phone_label);
6242 g_free(phone_number);
6243 g_free(email);
6244 g_free(display_name);
6247 if (xn_contact) {
6248 /* tel */
6249 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6251 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6252 const char *phone_type = xmlnode_get_attrib(node, "type");
6253 char* phone = xmlnode_get_data(node);
6255 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6257 g_free(phone);
6261 /* devicePresence */
6262 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6263 xmlnode *xn_device_name;
6264 xmlnode *xn_calendar_info;
6265 xmlnode *xn_state;
6266 char *state;
6268 /* deviceName */
6269 if (!strcmp(xmlnode_get_attrib(node, "epid"), epid)) {
6270 xn_device_name = xmlnode_get_child(node, "deviceName");
6271 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6274 /* calendarInfo */
6275 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6276 if (xn_calendar_info) {
6277 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6279 if (cal_start_time) {
6280 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6281 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6283 if (cal_start_time_t_tmp > cal_start_time_t) {
6284 cal_start_time = cal_start_time_tmp;
6285 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6286 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6288 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);
6290 } else {
6291 cal_start_time = cal_start_time_tmp;
6292 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6293 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6295 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);
6299 /* state */
6300 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6301 if (xn_state) {
6302 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6303 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6305 state = xmlnode_get_data(xn_state);
6306 if (dev_avail_since > user_avail_since &&
6307 dev_avail >= res_avail)
6309 res_avail = dev_avail;
6310 if (!is_empty(state))
6312 if (!strcmp(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6313 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6314 } else if (!strcmp(state, "presenting")) {
6315 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6316 } else {
6317 activity = state;
6319 activity_since = dev_avail_since;
6321 status_id = sipe_get_status_by_availability(res_avail, &activity);
6323 g_free(state);
6327 /* oof */
6328 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6329 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6330 activity_since = 0;
6333 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6334 if (sbuddy)
6336 g_free(sbuddy->activity);
6337 sbuddy->activity = activity;
6339 sbuddy->activity_since = activity_since;
6341 sbuddy->user_avail = user_avail;
6342 sbuddy->user_avail_since = user_avail_since;
6344 g_free(sbuddy->note);
6345 sbuddy->note = NULL;
6346 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6348 sbuddy->is_oof_note = (xn_oof != NULL);
6350 g_free(sbuddy->device_name);
6351 sbuddy->device_name = NULL;
6352 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6354 if (!is_empty(cal_free_busy_base64)) {
6355 g_free(sbuddy->cal_start_time);
6356 sbuddy->cal_start_time = g_strdup(cal_start_time);
6358 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6360 g_free(sbuddy->cal_free_busy_base64);
6361 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6363 g_free(sbuddy->cal_free_busy);
6364 sbuddy->cal_free_busy = NULL;
6367 sbuddy->last_non_cal_status_id = status_id;
6368 g_free(sbuddy->last_non_cal_activity);
6369 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6371 if (!strcmp(sbuddy->name, self_uri)) {
6372 if (!(sbuddy->note && sip->note && !strcmp(sbuddy->note, sip->note))) /* not same */
6374 sip->is_oof_note = sbuddy->is_oof_note;
6376 g_free(sip->note);
6377 sip->note = g_strdup(sbuddy->note);
6379 sip->note_since = time(NULL);
6382 g_free(sip->status);
6383 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6387 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6388 sipe_got_user_status(sip, uri, status_id);
6390 if (!sip->ocs2007 && !strcmp(self_uri, uri)) {
6391 sipe_user_info_has_updated(sip, xn_userinfo);
6394 g_free(note);
6395 xmlnode_free(xn_presentity);
6396 g_free(uri);
6397 g_free(self_uri);
6400 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6402 char *ctype = sipmsg_find_header(msg, "Content-Type");
6404 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6406 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6407 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6409 const char *content = msg->body;
6410 unsigned length = msg->bodylen;
6411 PurpleMimeDocument *mime = NULL;
6413 if (strstr(ctype, "multipart"))
6415 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6416 const char *content_type;
6417 GList* parts;
6418 mime = purple_mime_document_parse(doc);
6419 parts = purple_mime_document_get_parts(mime);
6420 while(parts) {
6421 content = purple_mime_part_get_data(parts->data);
6422 length = purple_mime_part_get_length(parts->data);
6423 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6424 if(content_type && strstr(content_type,"application/rlmi+xml"))
6426 process_incoming_notify_rlmi_resub(sip, content, length);
6428 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6430 process_incoming_notify_msrtc(sip, content, length);
6432 else
6434 process_incoming_notify_rlmi(sip, content, length);
6436 parts = parts->next;
6438 g_free(doc);
6440 if (mime)
6442 purple_mime_document_free(mime);
6445 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6447 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6449 else if(strstr(ctype, "application/rlmi+xml"))
6451 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6454 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6456 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6458 else
6460 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6464 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6466 char *ctype = sipmsg_find_header(msg, "Content-Type");
6467 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6469 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6471 if (ctype &&
6472 strstr(ctype, "multipart") &&
6473 (strstr(ctype, "application/rlmi+xml") ||
6474 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6475 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6476 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6477 GList *parts = purple_mime_document_get_parts(mime);
6478 GSList *buddies = NULL;
6479 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6481 while (parts) {
6482 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6483 purple_mime_part_get_length(parts->data));
6485 if (strcmp(xml->name, "list")) {
6486 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6488 buddies = g_slist_append(buddies, uri);
6490 xmlnode_free(xml);
6492 parts = parts->next;
6494 g_free(doc);
6495 if (mime) purple_mime_document_free(mime);
6497 payload->host = g_strdup(who);
6498 payload->buddies = buddies;
6499 sipe_schedule_action(action_name, timeout,
6500 sipe_subscribe_presence_batched_routed,
6501 sipe_subscribe_presence_batched_routed_free,
6502 sip, payload);
6503 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6505 } else {
6506 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6507 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6509 g_free(action_name);
6513 * Dispatcher for all incoming subscription information
6514 * whether it comes from NOTIFY, BENOTIFY requests or
6515 * piggy-backed to subscription's OK responce.
6517 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6518 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6520 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6522 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6523 gchar *event = sipmsg_find_header(msg, "Event");
6524 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6525 char *tmp;
6526 int timeout = 0;
6528 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6529 event ? event : "",
6530 tmp = fix_newlines(msg->body));
6531 g_free(tmp);
6532 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6534 /* implicit subscriptions */
6535 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6536 sipe_process_imdn(sip, msg);
6539 if (!request)
6541 const gchar *expires_header;
6542 expires_header = sipmsg_find_header(msg, "Expires");
6543 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6544 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6545 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout; // 2 min ahead of expiration
6548 /* for one off subscriptions (send with Expire: 0) */
6549 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6551 sipe_process_provisioning_v2(sip, msg);
6553 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6555 sipe_process_provisioning(sip, msg);
6558 if (!subscription_state || strstr(subscription_state, "active"))
6560 if (event && !g_ascii_strcasecmp(event, "presence"))
6562 sipe_process_presence(sip, msg);
6564 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6566 sipe_process_roaming_contacts(sip, msg);
6568 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6570 sipe_process_roaming_self(sip, msg);
6572 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6574 sipe_process_roaming_acl(sip, msg);
6576 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
6578 sipe_process_presence_wpending(sip, msg);
6580 else if (event && !g_ascii_strcasecmp(event, "conference"))
6582 sipe_process_conference(sip, msg);
6586 /* The server sends status 'terminated' */
6587 if (subscription_state && strstr(subscription_state, "terminated") ) {
6588 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6589 gchar *key = sipe_get_subscription_key(event, who);
6591 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6592 g_free(who);
6594 if (g_hash_table_lookup(sip->subscriptions, key)) {
6595 g_hash_table_remove(sip->subscriptions, key);
6596 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6599 g_free(key);
6602 if (timeout && event) {// For LSC 2005 and OCS 2007
6603 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
6604 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
6606 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
6607 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
6608 g_free(action_name);
6610 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
6611 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
6613 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
6614 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
6615 g_free(action_name);
6617 else*/
6618 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6619 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6621 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6622 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6623 g_free(action_name);
6625 else if (!g_ascii_strcasecmp(event, "presence") &&
6626 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6628 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6629 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6630 if (sip->batched_support) {
6631 sipe_process_presence_timeout(sip, msg, who, timeout);
6633 else {
6634 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6635 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6637 g_free(action_name);
6638 g_free(who);
6642 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
6644 sipe_process_registration_notify(sip, msg);
6647 /* The client responses on received a NOTIFY message */
6648 if (request && !benotify)
6650 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6655 * Whether user manually changed status or
6656 * it was changed automatically due to user
6657 * became inactive/active again
6659 static gboolean
6660 sipe_is_user_state(struct sipe_account_data *sip)
6662 gboolean res;
6663 time_t now = time(NULL);
6665 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6666 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6668 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6670 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6671 return res;
6674 static void
6675 send_presence_soap0(struct sipe_account_data *sip,
6676 gboolean do_publish_calendar,
6677 gboolean do_reset_status)
6679 struct sipe_ews* ews = sip->ews;
6680 int availability = 0;
6681 int activity = 0;
6682 gchar *body;
6683 gchar *tmp;
6684 gchar *tmp2 = NULL;
6685 gchar *res_note = NULL;
6686 gchar *res_oof = NULL;
6687 const gchar *note_pub = NULL;
6688 gchar *states = NULL;
6689 gchar *calendar_data = NULL;
6690 gchar *epid = get_epid(sip);
6691 time_t now = time(NULL);
6692 gchar *since_time_str = sipe_utils_time_to_str(now);
6693 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6694 const char *user_input;
6695 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6697 if (oof_note && sip->note) {
6698 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6699 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6702 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6704 if (!sip->initial_state_published ||
6705 do_reset_status)
6707 g_free(sip->status);
6708 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6711 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6713 /* Note */
6714 if (pub_oof) {
6715 note_pub = oof_note;
6716 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6717 ews->published = TRUE;
6718 } else if (sip->note) {
6719 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6720 g_free(sip->note);
6721 sip->note = NULL;
6722 sip->is_oof_note = FALSE;
6723 sip->note_since = 0;
6724 } else {
6725 note_pub = sip->note;
6726 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6730 if (note_pub)
6732 /* to protocol internal plain text format */
6733 tmp = purple_markup_strip_html(note_pub);
6734 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6735 g_free(tmp);
6738 /* User State */
6739 if (!do_reset_status) {
6740 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6742 gchar *activity_token = NULL;
6743 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6745 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6746 avail_2007,
6747 since_time_str,
6748 epid,
6749 activity_token);
6750 g_free(activity_token);
6752 else /* preserve existing publication */
6754 xmlnode *xn_states;
6755 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6756 states = xmlnode_to_str(xn_states, NULL);
6757 /* this is a hack-around to remove added newline after inner element,
6758 * state in this case, where it shouldn't be.
6759 * After several use of xmlnode_to_str, amount of added newlines
6760 * grows significantly.
6762 purple_str_strip_char(states, '\n');
6763 //purple_str_strip_char(states, '\r');
6766 } else {
6767 /* do nothing - then User state will be erased */
6769 sip->initial_state_published = TRUE;
6771 /* CalendarInfo */
6772 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6774 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6775 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6776 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6777 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6778 fb_start_str,
6779 free_busy_base64);
6780 g_free(fb_start_str);
6781 g_free(free_busy_base64);
6784 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6786 /* forming resulting XML */
6787 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6788 sip->username,
6789 availability,
6790 activity,
6791 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6792 res_note ? res_note : "",
6793 res_oof ? res_oof : "",
6794 states ? states : "",
6795 calendar_data ? calendar_data : "",
6796 epid,
6797 since_time_str,
6798 since_time_str,
6799 user_input);
6800 g_free(tmp);
6801 g_free(tmp2);
6802 g_free(res_note);
6803 g_free(states);
6804 g_free(calendar_data);
6806 send_soap_request(sip, body);
6808 g_free(body);
6809 g_free(since_time_str);
6810 g_free(epid);
6813 void
6814 send_presence_soap(struct sipe_account_data *sip,
6815 gboolean do_publish_calendar)
6817 return send_presence_soap0(sip, do_publish_calendar, FALSE);
6821 static gboolean
6822 process_send_presence_category_publish_response(struct sipe_account_data *sip,
6823 struct sipmsg *msg,
6824 struct transaction *trans)
6826 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
6828 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
6829 xmlnode *xml;
6830 xmlnode *node;
6831 gchar *fault_code;
6832 GHashTable *faults;
6833 int index_our;
6834 gboolean has_device_publication = FALSE;
6836 xml = xmlnode_from_str(msg->body, msg->bodylen);
6838 /* test if version mismatch fault */
6839 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
6840 if (strcmp(fault_code, "Client.BadCall.WrongDelta")) {
6841 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
6842 g_free(fault_code);
6843 xmlnode_free(xml);
6844 return TRUE;
6846 g_free(fault_code);
6848 /* accumulating information about faulty versions */
6849 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
6850 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
6851 node;
6852 node = xmlnode_get_next_twin(node))
6854 const gchar *index = xmlnode_get_attrib(node, "index");
6855 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
6857 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
6858 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
6860 xmlnode_free(xml);
6862 /* here we are parsing own request to figure out what publication
6863 * referensed here only by index went wrong
6865 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
6867 /* publication */
6868 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
6869 index_our = 1; /* starts with 1 - our first publication */
6870 node;
6871 node = xmlnode_get_next_twin(node), index_our++)
6873 gchar *idx = g_strdup_printf("%d", index_our);
6874 const gchar *curVersion = g_hash_table_lookup(faults, idx);
6875 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
6876 g_free(idx);
6878 if (!strcmp("device", categoryName)) {
6879 has_device_publication = TRUE;
6882 if (curVersion) { /* fault exist on this index */
6883 const gchar *container = xmlnode_get_attrib(node, "container");
6884 const gchar *instance = xmlnode_get_attrib(node, "instance");
6885 /* key is <category><instance><container> */
6886 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
6887 struct sipe_publication *publication =
6888 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
6890 purple_debug_info("sipe", "key is %s\n", key);
6892 if (publication) {
6893 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6894 key, curVersion, publication->version);
6895 /* updating publication's version to the correct one */
6896 publication->version = atoi(curVersion);
6898 g_free(key);
6901 xmlnode_free(xml);
6902 g_hash_table_destroy(faults);
6904 /* rebublishing with right versions */
6905 if (has_device_publication) {
6906 send_publish_category_initial(sip);
6907 } else {
6908 send_presence_status(sip);
6911 return TRUE;
6915 * Returns 'device' XML part for publication.
6916 * Must be g_free'd after use.
6918 static gchar *
6919 sipe_publish_get_category_device(struct sipe_account_data *sip)
6921 gchar *uri;
6922 gchar *doc;
6923 gchar *epid = get_epid(sip);
6924 gchar *uuid = generateUUIDfromEPID(epid);
6925 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
6926 /* key is <category><instance><container> */
6927 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
6928 struct sipe_publication *publication =
6929 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
6931 g_free(key);
6932 g_free(epid);
6934 uri = sip_uri_self(sip);
6935 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
6936 device_instance,
6937 publication ? publication->version : 0,
6938 uuid,
6939 uri,
6940 "00:00:00+01:00", /* @TODO make timezone real*/
6941 sipe_get_host_name()
6944 g_free(uri);
6945 g_free(uuid);
6947 return doc;
6951 * A service method - use
6952 * - send_publish_get_category_state_machine and
6953 * - send_publish_get_category_state_user instead.
6954 * Must be g_free'd after use.
6956 static gchar *
6957 sipe_publish_get_category_state(struct sipe_account_data *sip,
6958 gboolean is_user_state)
6960 int availability = sipe_get_availability_by_status(sip->status, NULL);
6961 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
6962 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
6963 /* key is <category><instance><container> */
6964 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6965 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6966 struct sipe_publication *publication_2 =
6967 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6968 struct sipe_publication *publication_3 =
6969 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6971 g_free(key_2);
6972 g_free(key_3);
6974 if (publication_2 && (publication_2->availability == availability))
6976 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6977 return NULL; /* nothing to update */
6980 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
6981 instance,
6982 publication_2 ? publication_2->version : 0,
6983 availability,
6984 instance,
6985 publication_3 ? publication_3->version : 0,
6986 availability);
6990 * Only Busy and OOF calendar event are published.
6991 * Different instances are used for that.
6993 * Must be g_free'd after use.
6995 static gchar *
6996 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
6997 struct sipe_cal_event *event,
6998 const char *uri,
6999 int cal_satus)
7001 gchar *start_time_str;
7002 int availability = 0;
7003 gchar *res;
7004 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7005 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7006 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7008 /* key is <category><instance><container> */
7009 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7010 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7011 struct sipe_publication *publication_2 =
7012 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7013 struct sipe_publication *publication_3 =
7014 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7016 g_free(key_2);
7017 g_free(key_3);
7019 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7020 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7021 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7022 return NULL;
7025 if (event &&
7026 publication_3 &&
7027 (publication_3->availability == availability) &&
7028 !strcmp(publication_3->cal_event_hash, sipe_cal_event_hash(event)))
7030 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7031 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7032 return NULL; /* nothing to update */
7035 if (event &&
7036 (event->cal_status == SIPE_CAL_BUSY ||
7037 event->cal_status == SIPE_CAL_OOF))
7039 gchar *availability_xml_str = NULL;
7040 gchar *activity_xml_str = NULL;
7042 if (event->cal_status == SIPE_CAL_BUSY) {
7043 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7046 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7047 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7048 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7049 "minAvailability=\"6500\"",
7050 "maxAvailability=\"8999\"");
7051 } else if (event->cal_status == SIPE_CAL_OOF) {
7052 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7053 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7054 "minAvailability=\"12000\"",
7055 "");
7057 start_time_str = sipe_utils_time_to_str(event->start_time);
7059 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7060 instance,
7061 publication_2 ? publication_2->version : 0,
7062 uri,
7063 start_time_str,
7064 availability_xml_str ? availability_xml_str : "",
7065 activity_xml_str ? activity_xml_str : "",
7066 event->subject ? event->subject : "",
7067 event->location ? event->location : "",
7069 instance,
7070 publication_3 ? publication_3->version : 0,
7071 uri,
7072 start_time_str,
7073 availability_xml_str ? availability_xml_str : "",
7074 activity_xml_str ? activity_xml_str : "",
7075 event->subject ? event->subject : "",
7076 event->location ? event->location : ""
7078 g_free(start_time_str);
7079 g_free(availability_xml_str);
7080 g_free(activity_xml_str);
7083 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7085 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7086 instance,
7087 publication_2 ? publication_2->version : 0,
7089 instance,
7090 publication_3 ? publication_3->version : 0
7094 return res;
7098 * Returns 'machineState' XML part for publication.
7099 * Must be g_free'd after use.
7101 static gchar *
7102 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7104 return sipe_publish_get_category_state(sip, FALSE);
7108 * Returns 'userState' XML part for publication.
7109 * Must be g_free'd after use.
7111 static gchar *
7112 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7114 return sipe_publish_get_category_state(sip, TRUE);
7118 * Compares two strings even in case both are NULL/empty
7120 static gboolean
7121 sipe_is_equal(const char* n1, const char* n2) {
7122 return ((!n1 || !strlen(n1)) && (!n2 || !strlen(n2))) /* both empty */
7123 || (n1 && n2 && !strcmp(n1, n2)); /* or not empty and equal */
7127 * Returns 'note' XML part for publication.
7128 * Must be g_free'd after use.
7130 * Protocol format for Note is plain text.
7132 * @param note a note in Sipe internal HTML format
7133 * @param note_type either personal or OOF
7135 static gchar *
7136 sipe_publish_get_category_note(struct sipe_account_data *sip,
7137 const char *note, /* html */
7138 const char *note_type,
7139 time_t note_start,
7140 time_t note_end)
7142 guint instance = !strcmp("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7143 /* key is <category><instance><container> */
7144 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7145 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7146 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7148 struct sipe_publication *publication_note_200 =
7149 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7150 struct sipe_publication *publication_note_300 =
7151 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7152 struct sipe_publication *publication_note_400 =
7153 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7155 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7156 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7157 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7158 char *res, *tmp1, *tmp2, *tmp3;
7159 char *start_time_attr;
7160 char *end_time_attr;
7162 g_free(tmp);
7163 tmp = NULL;
7164 g_free(key_note_200);
7165 g_free(key_note_300);
7166 g_free(key_note_400);
7168 /* we even need to republish empty note */
7169 if (n1 && n2 && !strcmp(n1, n2))
7171 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7172 g_free(n1);
7173 return NULL; /* nothing to update */
7176 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7177 g_free(tmp);
7178 tmp = NULL;
7179 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7180 g_free(tmp);
7182 if (n1) {
7183 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7184 instance,
7185 200,
7186 publication_note_200 ? publication_note_200->version : 0,
7187 note_type,
7188 start_time_attr ? start_time_attr : "",
7189 end_time_attr ? end_time_attr : "",
7190 n1);
7192 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7193 instance,
7194 300,
7195 publication_note_300 ? publication_note_300->version : 0,
7196 note_type,
7197 start_time_attr ? start_time_attr : "",
7198 end_time_attr ? end_time_attr : "",
7199 n1);
7201 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7202 instance,
7203 400,
7204 publication_note_400 ? publication_note_400->version : 0,
7205 note_type,
7206 start_time_attr ? start_time_attr : "",
7207 end_time_attr ? end_time_attr : "",
7208 n1);
7209 } else {
7210 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7211 "note",
7212 instance,
7213 200,
7214 publication_note_200 ? publication_note_200->version : 0,
7215 "static");
7216 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7217 "note",
7218 instance,
7219 300,
7220 publication_note_200 ? publication_note_200->version : 0,
7221 "static");
7222 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7223 "note",
7224 instance,
7225 400,
7226 publication_note_200 ? publication_note_200->version : 0,
7227 "static");
7229 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7231 g_free(start_time_attr);
7232 g_free(end_time_attr);
7233 g_free(tmp1);
7234 g_free(tmp2);
7235 g_free(tmp3);
7236 g_free(n1);
7238 return res;
7242 * Returns 'calendarData' XML part with WorkingHours for publication.
7243 * Must be g_free'd after use.
7245 static gchar *
7246 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7248 struct sipe_ews* ews = sip->ews;
7250 /* key is <category><instance><container> */
7251 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7252 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7253 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7254 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7255 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7256 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7258 struct sipe_publication *publication_cal_1 =
7259 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7260 struct sipe_publication *publication_cal_100 =
7261 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7262 struct sipe_publication *publication_cal_200 =
7263 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7264 struct sipe_publication *publication_cal_300 =
7265 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7266 struct sipe_publication *publication_cal_400 =
7267 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7268 struct sipe_publication *publication_cal_32000 =
7269 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7271 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7272 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7274 g_free(key_cal_1);
7275 g_free(key_cal_100);
7276 g_free(key_cal_200);
7277 g_free(key_cal_300);
7278 g_free(key_cal_400);
7279 g_free(key_cal_32000);
7281 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7282 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7283 return NULL;
7286 if (sipe_is_equal(n1, n2))
7288 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7289 return NULL; /* nothing to update */
7292 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7293 /* 1 */
7294 publication_cal_1 ? publication_cal_1->version : 0,
7295 ews->email,
7296 ews->working_hours_xml_str,
7297 /* 100 - Public */
7298 publication_cal_100 ? publication_cal_100->version : 0,
7299 /* 200 - Company */
7300 publication_cal_200 ? publication_cal_200->version : 0,
7301 ews->email,
7302 ews->working_hours_xml_str,
7303 /* 300 - Team */
7304 publication_cal_300 ? publication_cal_300->version : 0,
7305 ews->email,
7306 ews->working_hours_xml_str,
7307 /* 400 - Personal */
7308 publication_cal_400 ? publication_cal_400->version : 0,
7309 ews->email,
7310 ews->working_hours_xml_str,
7311 /* 32000 - Blocked */
7312 publication_cal_32000 ? publication_cal_32000->version : 0
7317 * Returns 'calendarData' XML part with FreeBusy for publication.
7318 * Must be g_free'd after use.
7320 static gchar *
7321 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7323 struct sipe_ews* ews = sip->ews;
7324 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7325 char *fb_start_str;
7326 char *free_busy_base64;
7327 const char *st;
7328 const char *fb;
7329 char *res;
7331 /* key is <category><instance><container> */
7332 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7333 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7334 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7335 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7336 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7337 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7339 struct sipe_publication *publication_cal_1 =
7340 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7341 struct sipe_publication *publication_cal_100 =
7342 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7343 struct sipe_publication *publication_cal_200 =
7344 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7345 struct sipe_publication *publication_cal_300 =
7346 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7347 struct sipe_publication *publication_cal_400 =
7348 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7349 struct sipe_publication *publication_cal_32000 =
7350 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7352 g_free(key_cal_1);
7353 g_free(key_cal_100);
7354 g_free(key_cal_200);
7355 g_free(key_cal_300);
7356 g_free(key_cal_400);
7357 g_free(key_cal_32000);
7359 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7360 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7361 return NULL;
7364 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7365 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7367 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7368 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7370 /* we will rebuplish the same data to refresh publication time,
7371 * so if data from multiple sources, most recent will be choosen
7373 //if (sipe_is_equal(st, fb_start_str) && sipe_is_equal(fb, free_busy_base64))
7375 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7376 // g_free(fb_start_str);
7377 // g_free(free_busy_base64);
7378 // return NULL; /* nothing to update */
7381 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7382 /* 1 */
7383 cal_data_instance,
7384 publication_cal_1 ? publication_cal_1->version : 0,
7385 /* 100 - Public */
7386 cal_data_instance,
7387 publication_cal_100 ? publication_cal_100->version : 0,
7388 /* 200 - Company */
7389 cal_data_instance,
7390 publication_cal_200 ? publication_cal_200->version : 0,
7391 ews->email,
7392 fb_start_str,
7393 free_busy_base64,
7394 /* 300 - Team */
7395 cal_data_instance,
7396 publication_cal_300 ? publication_cal_300->version : 0,
7397 ews->email,
7398 fb_start_str,
7399 free_busy_base64,
7400 /* 400 - Personal */
7401 cal_data_instance,
7402 publication_cal_400 ? publication_cal_400->version : 0,
7403 ews->email,
7404 fb_start_str,
7405 free_busy_base64,
7406 /* 32000 - Blocked */
7407 cal_data_instance,
7408 publication_cal_32000 ? publication_cal_32000->version : 0
7411 g_free(fb_start_str);
7412 g_free(free_busy_base64);
7413 return res;
7416 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7418 gchar *uri;
7419 gchar *doc;
7420 gchar *tmp;
7421 gchar *hdr;
7423 uri = sip_uri_self(sip);
7424 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7425 uri,
7426 publications);
7428 tmp = get_contact(sip);
7429 hdr = g_strdup_printf("Contact: %s\r\n"
7430 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7432 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7434 g_free(tmp);
7435 g_free(hdr);
7436 g_free(uri);
7437 g_free(doc);
7440 static void
7441 send_publish_category_initial(struct sipe_account_data *sip)
7443 gchar *pub_device = sipe_publish_get_category_device(sip);
7444 gchar *pub_machine;
7445 gchar *publications;
7447 g_free(sip->status);
7448 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7450 pub_machine = sipe_publish_get_category_state_machine(sip);
7451 publications = g_strdup_printf("%s%s",
7452 pub_device,
7453 pub_machine ? pub_machine : "");
7454 g_free(pub_device);
7455 g_free(pub_machine);
7457 send_presence_publish(sip, publications);
7458 g_free(publications);
7461 static void
7462 send_presence_category_publish(struct sipe_account_data *sip)
7464 gchar *pub_state = sipe_is_user_state(sip) ?
7465 sipe_publish_get_category_state_user(sip) :
7466 sipe_publish_get_category_state_machine(sip);
7467 gchar *pub_note = sipe_publish_get_category_note(sip,
7468 sip->note,
7469 sip->is_oof_note ? "OOF" : "personal",
7472 gchar *publications;
7474 if (!pub_state && !pub_note) {
7475 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7476 return;
7479 publications = g_strdup_printf("%s%s",
7480 pub_state ? pub_state : "",
7481 pub_note ? pub_note : "");
7483 g_free(pub_state);
7484 g_free(pub_note);
7486 send_presence_publish(sip, publications);
7487 g_free(publications);
7491 * Publishes self status
7492 * based on own calendar information.
7494 * For 2007+
7496 void
7497 publish_calendar_status_self(struct sipe_account_data *sip)
7499 struct sipe_cal_event* event = NULL;
7500 gchar *pub_cal_working_hours = NULL;
7501 gchar *pub_cal_free_busy = NULL;
7502 gchar *pub_calendar = NULL;
7503 gchar *pub_calendar2 = NULL;
7504 gchar *pub_oof_note = NULL;
7505 const gchar *oof_note;
7506 time_t oof_start = 0;
7507 time_t oof_end = 0;
7509 if (!sip->ews) {
7510 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7511 return;
7514 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7515 if (sip->ews->cal_events) {
7516 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7519 if (!event) {
7520 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7521 } else {
7522 char *desc = sipe_cal_event_describe(event);
7523 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7524 g_free(desc);
7527 /* Logic
7528 if OOF
7529 OOF publish, Busy clean
7530 ilse if Busy
7531 OOF clean, Busy publish
7532 else
7533 OOF clean, Busy clean
7535 if (event && event->cal_status == SIPE_CAL_OOF) {
7536 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7537 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7538 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7539 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7540 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7541 } else {
7542 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7543 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7546 oof_note = sipe_ews_get_oof_note(sip->ews);
7547 if (!strcmp("Scheduled", sip->ews->oof_state)) {
7548 oof_start = sip->ews->oof_start;
7549 oof_end = sip->ews->oof_end;
7551 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7553 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7554 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7556 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7557 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7558 } else {
7559 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7560 pub_cal_working_hours ? pub_cal_working_hours : "",
7561 pub_cal_free_busy ? pub_cal_free_busy : "",
7562 pub_calendar ? pub_calendar : "",
7563 pub_calendar2 ? pub_calendar2 : "",
7564 pub_oof_note ? pub_oof_note : "");
7566 send_presence_publish(sip, publications);
7567 g_free(publications);
7570 g_free(pub_cal_working_hours);
7571 g_free(pub_cal_free_busy);
7572 g_free(pub_calendar);
7573 g_free(pub_calendar2);
7574 g_free(pub_oof_note);
7576 /* repeat scheduling */
7577 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7580 static void send_presence_status(struct sipe_account_data *sip)
7582 PurpleStatus * status = purple_account_get_active_status(sip->account);
7584 if (!status) return;
7586 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7587 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7588 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7590 if (sip->ocs2007) {
7591 send_presence_category_publish(sip);
7592 } else {
7593 send_presence_soap(sip, FALSE);
7597 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7599 gboolean found = FALSE;
7600 const char *method = msg->method ? msg->method : "NOT FOUND";
7601 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7602 if (msg->response == 0) { /* request */
7603 if (!strcmp(method, "MESSAGE")) {
7604 process_incoming_message(sip, msg);
7605 found = TRUE;
7606 } else if (!strcmp(method, "NOTIFY")) {
7607 purple_debug_info("sipe","send->process_incoming_notify\n");
7608 process_incoming_notify(sip, msg, TRUE, FALSE);
7609 found = TRUE;
7610 } else if (!strcmp(method, "BENOTIFY")) {
7611 purple_debug_info("sipe","send->process_incoming_benotify\n");
7612 process_incoming_notify(sip, msg, TRUE, TRUE);
7613 found = TRUE;
7614 } else if (!strcmp(method, "INVITE")) {
7615 process_incoming_invite(sip, msg);
7616 found = TRUE;
7617 } else if (!strcmp(method, "REFER")) {
7618 process_incoming_refer(sip, msg);
7619 found = TRUE;
7620 } else if (!strcmp(method, "OPTIONS")) {
7621 process_incoming_options(sip, msg);
7622 found = TRUE;
7623 } else if (!strcmp(method, "INFO")) {
7624 process_incoming_info(sip, msg);
7625 found = TRUE;
7626 } else if (!strcmp(method, "ACK")) {
7627 // ACK's don't need any response
7628 found = TRUE;
7629 } else if (!strcmp(method, "SUBSCRIBE")) {
7630 // LCS 2005 sends us these - just respond 200 OK
7631 found = TRUE;
7632 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7633 } else if (!strcmp(method, "BYE")) {
7634 process_incoming_bye(sip, msg);
7635 found = TRUE;
7636 } else {
7637 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7639 } else { /* response */
7640 struct transaction *trans = transactions_find(sip, msg);
7641 if (trans) {
7642 if (msg->response == 407) {
7643 gchar *resend, *auth, *ptmp;
7645 if (sip->proxy.retries > 30) return;
7646 sip->proxy.retries++;
7647 /* do proxy authentication */
7649 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7651 fill_auth(ptmp, &sip->proxy);
7652 auth = auth_header(sip, &sip->proxy, trans->msg);
7653 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7654 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7655 g_free(auth);
7656 resend = sipmsg_to_string(trans->msg);
7657 /* resend request */
7658 sendout_pkt(sip->gc, resend);
7659 g_free(resend);
7660 } else {
7661 if (msg->response < 200) {
7662 /* ignore provisional response */
7663 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7664 } else {
7665 sip->proxy.retries = 0;
7666 if (!strcmp(trans->msg->method, "REGISTER")) {
7667 if (msg->response == 401)
7669 sip->registrar.retries++;
7671 else
7673 sip->registrar.retries = 0;
7675 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7676 } else {
7677 if (msg->response == 401) {
7678 gchar *resend, *auth, *ptmp;
7679 const char* auth_scheme;
7681 if (sip->registrar.retries > 4) return;
7682 sip->registrar.retries++;
7684 auth_scheme = sipe_get_auth_scheme_name(sip);
7685 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7687 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7688 if (!ptmp) {
7689 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7690 sip->gc->wants_to_die = TRUE;
7691 purple_connection_error(sip->gc, tmp2);
7692 g_free(tmp2);
7693 return;
7696 fill_auth(ptmp, &sip->registrar);
7697 auth = auth_header(sip, &sip->registrar, trans->msg);
7698 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7699 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7701 //sipmsg_remove_header_now(trans->msg, "Authorization");
7702 //sipmsg_add_header(trans->msg, "Authorization", auth);
7703 g_free(auth);
7704 resend = sipmsg_to_string(trans->msg);
7705 /* resend request */
7706 sendout_pkt(sip->gc, resend);
7707 g_free(resend);
7711 if (trans->callback) {
7712 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7713 /* call the callback to process response*/
7714 (trans->callback)(sip, msg, trans);
7717 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7718 transactions_remove(sip, trans);
7722 found = TRUE;
7723 } else {
7724 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7727 if (!found) {
7728 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7732 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7734 char *cur;
7735 char *dummy;
7736 char *tmp;
7737 struct sipmsg *msg;
7738 int restlen;
7739 cur = conn->inbuf;
7741 /* according to the RFC remove CRLF at the beginning */
7742 while (*cur == '\r' || *cur == '\n') {
7743 cur++;
7745 if (cur != conn->inbuf) {
7746 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7747 conn->inbufused = strlen(conn->inbuf);
7750 /* Received a full Header? */
7751 sip->processing_input = TRUE;
7752 while (sip->processing_input &&
7753 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7754 time_t currtime = time(NULL);
7755 cur += 2;
7756 cur[0] = '\0';
7757 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7758 g_free(tmp);
7759 msg = sipmsg_parse_header(conn->inbuf);
7760 cur[0] = '\r';
7761 cur += 2;
7762 restlen = conn->inbufused - (cur - conn->inbuf);
7763 if (msg && restlen >= msg->bodylen) {
7764 dummy = g_malloc(msg->bodylen + 1);
7765 memcpy(dummy, cur, msg->bodylen);
7766 dummy[msg->bodylen] = '\0';
7767 msg->body = dummy;
7768 cur += msg->bodylen;
7769 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7770 conn->inbufused = strlen(conn->inbuf);
7771 } else {
7772 if (msg){
7773 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7774 sipmsg_free(msg);
7776 return;
7779 /*if (msg->body) {
7780 purple_debug_info("sipe", "body:\n%s", msg->body);
7783 // Verify the signature before processing it
7784 if (sip->registrar.gssapi_context) {
7785 struct sipmsg_breakdown msgbd;
7786 gchar *signature_input_str;
7787 gchar *rspauth;
7788 msgbd.msg = msg;
7789 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7790 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
7792 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7794 if (rspauth != NULL) {
7795 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7796 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7797 process_input_message(sip, msg);
7798 } else {
7799 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7800 purple_connection_error(sip->gc, _("Invalid message signature received"));
7801 sip->gc->wants_to_die = TRUE;
7803 } else if (msg->response == 401) {
7804 purple_connection_error(sip->gc, _("Wrong password"));
7805 sip->gc->wants_to_die = TRUE;
7807 g_free(signature_input_str);
7809 g_free(rspauth);
7810 sipmsg_breakdown_free(&msgbd);
7811 } else {
7812 process_input_message(sip, msg);
7815 sipmsg_free(msg);
7819 static void sipe_udp_process(gpointer data, gint source,
7820 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
7822 PurpleConnection *gc = data;
7823 struct sipe_account_data *sip = gc->proto_data;
7824 int len;
7826 static char buffer[65536];
7827 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
7828 time_t currtime = time(NULL);
7829 struct sipmsg *msg;
7830 buffer[len] = '\0';
7831 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
7832 msg = sipmsg_parse_msg(buffer);
7833 if (msg) process_input_message(sip, msg);
7837 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
7839 struct sipe_account_data *sip = gc->proto_data;
7840 PurpleSslConnection *gsc = sip->gsc;
7842 purple_debug_error("sipe", "%s",debug);
7843 purple_connection_error(gc, msg);
7845 /* Invalidate this connection. Next send will open a new one */
7846 if (gsc) {
7847 connection_remove(sip, gsc->fd);
7848 purple_ssl_close(gsc);
7850 sip->gsc = NULL;
7851 sip->fd = -1;
7854 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7855 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7857 PurpleConnection *gc = data;
7858 struct sipe_account_data *sip;
7859 struct sip_connection *conn;
7860 int readlen, len;
7861 gboolean firstread = TRUE;
7863 /* NOTE: This check *IS* necessary */
7864 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
7865 purple_ssl_close(gsc);
7866 return;
7869 sip = gc->proto_data;
7870 conn = connection_find(sip, gsc->fd);
7871 if (conn == NULL) {
7872 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7873 gc->wants_to_die = TRUE;
7874 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
7875 return;
7878 /* Read all available data from the SSL connection */
7879 do {
7880 /* Increase input buffer size as needed */
7881 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7882 conn->inbuflen += SIMPLE_BUF_INC;
7883 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7884 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
7887 /* Try to read as much as there is space left in the buffer */
7888 readlen = conn->inbuflen - conn->inbufused - 1;
7889 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
7891 if (len < 0 && errno == EAGAIN) {
7892 /* Try again later */
7893 return;
7894 } else if (len < 0) {
7895 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
7896 return;
7897 } else if (firstread && (len == 0)) {
7898 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
7899 return;
7902 conn->inbufused += len;
7903 firstread = FALSE;
7905 /* Equivalence indicates that there is possibly more data to read */
7906 } while (len == readlen);
7908 conn->inbuf[conn->inbufused] = '\0';
7909 process_input(sip, conn);
7913 static void sipe_input_cb(gpointer data, gint source,
7914 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7916 PurpleConnection *gc = data;
7917 struct sipe_account_data *sip = gc->proto_data;
7918 int len;
7919 struct sip_connection *conn = connection_find(sip, source);
7920 if (!conn) {
7921 purple_debug_error("sipe", "Connection not found!\n");
7922 return;
7925 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7926 conn->inbuflen += SIMPLE_BUF_INC;
7927 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7930 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
7932 if (len < 0 && errno == EAGAIN)
7933 return;
7934 else if (len <= 0) {
7935 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7936 connection_remove(sip, source);
7937 if (sip->fd == source) sip->fd = -1;
7938 return;
7941 conn->inbufused += len;
7942 conn->inbuf[conn->inbufused] = '\0';
7944 process_input(sip, conn);
7947 /* Callback for new connections on incoming TCP port */
7948 static void sipe_newconn_cb(gpointer data, gint source,
7949 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7951 PurpleConnection *gc = data;
7952 struct sipe_account_data *sip = gc->proto_data;
7953 struct sip_connection *conn;
7955 int newfd = accept(source, NULL, NULL);
7957 conn = connection_create(sip, newfd);
7959 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7962 static void login_cb(gpointer data, gint source,
7963 SIPE_UNUSED_PARAMETER const gchar *error_message)
7965 PurpleConnection *gc = data;
7966 struct sipe_account_data *sip;
7967 struct sip_connection *conn;
7969 if (!PURPLE_CONNECTION_IS_VALID(gc))
7971 if (source >= 0)
7972 close(source);
7973 return;
7976 if (source < 0) {
7977 purple_connection_error(gc, _("Could not connect"));
7978 return;
7981 sip = gc->proto_data;
7982 sip->fd = source;
7983 sip->last_keepalive = time(NULL);
7985 conn = connection_create(sip, source);
7987 do_register(sip);
7989 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7992 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7993 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7995 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
7996 if (sip == NULL) return;
7998 do_register(sip);
8001 static guint sipe_ht_hash_nick(const char *nick)
8003 char *lc = g_utf8_strdown(nick, -1);
8004 guint bucket = g_str_hash(lc);
8005 g_free(lc);
8007 return bucket;
8010 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8012 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8015 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8017 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8019 sip->listen_data = NULL;
8021 if (listenfd == -1) {
8022 purple_connection_error(sip->gc, _("Could not create listen socket"));
8023 return;
8026 sip->fd = listenfd;
8028 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8029 sip->listenfd = sip->fd;
8031 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8033 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8034 do_register(sip);
8037 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8038 SIPE_UNUSED_PARAMETER const char *error_message)
8040 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8042 sip->query_data = NULL;
8044 if (!hosts || !hosts->data) {
8045 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8046 return;
8049 hosts = g_slist_remove(hosts, hosts->data);
8050 g_free(sip->serveraddr);
8051 sip->serveraddr = hosts->data;
8052 hosts = g_slist_remove(hosts, hosts->data);
8053 while (hosts) {
8054 hosts = g_slist_remove(hosts, hosts->data);
8055 g_free(hosts->data);
8056 hosts = g_slist_remove(hosts, hosts->data);
8059 /* create socket for incoming connections */
8060 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8061 sipe_udp_host_resolved_listen_cb, sip);
8062 if (sip->listen_data == NULL) {
8063 purple_connection_error(sip->gc, _("Could not create listen socket"));
8064 return;
8068 static const struct sipe_service_data *current_service = NULL;
8070 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8071 PurpleSslErrorType error,
8072 gpointer data)
8074 PurpleConnection *gc = data;
8075 struct sipe_account_data *sip;
8077 /* If the connection is already disconnected, we don't need to do anything else */
8078 if (!PURPLE_CONNECTION_IS_VALID(gc))
8079 return;
8081 sip = gc->proto_data;
8082 current_service = sip->service_data;
8083 if (current_service) {
8084 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8085 current_service->transport ? current_service->transport : "NULL",
8086 current_service->service ? current_service->service : "NULL");
8089 sip->fd = -1;
8090 sip->gsc = NULL;
8092 switch(error) {
8093 case PURPLE_SSL_CONNECT_FAILED:
8094 purple_connection_error(gc, _("Connection failed"));
8095 break;
8096 case PURPLE_SSL_HANDSHAKE_FAILED:
8097 purple_connection_error(gc, _("SSL handshake failed"));
8098 break;
8099 case PURPLE_SSL_CERTIFICATE_INVALID:
8100 purple_connection_error(gc, _("SSL certificate invalid"));
8101 break;
8105 static void
8106 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8108 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8109 PurpleProxyConnectData *connect_data;
8111 sip->listen_data = NULL;
8113 sip->listenfd = listenfd;
8114 if (sip->listenfd == -1) {
8115 purple_connection_error(sip->gc, _("Could not create listen socket"));
8116 return;
8119 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8120 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8121 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8122 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8123 sipe_newconn_cb, sip->gc);
8124 purple_debug_info("sipe", "connecting to %s port %d\n",
8125 sip->realhostname, sip->realport);
8126 /* open tcp connection to the server */
8127 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8128 sip->realport, login_cb, sip->gc);
8130 if (connect_data == NULL) {
8131 purple_connection_error(sip->gc, _("Could not create socket"));
8135 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8137 PurpleAccount *account = sip->account;
8138 PurpleConnection *gc = sip->gc;
8140 if (port == 0) {
8141 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8144 sip->realhostname = hostname;
8145 sip->realport = port;
8147 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8148 hostname, port);
8150 /* TODO: is there a good default grow size? */
8151 if (sip->transport != SIPE_TRANSPORT_UDP)
8152 sip->txbuf = purple_circ_buffer_new(0);
8154 if (sip->transport == SIPE_TRANSPORT_TLS) {
8155 /* SSL case */
8156 if (!purple_ssl_is_supported()) {
8157 gc->wants_to_die = TRUE;
8158 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8159 return;
8162 purple_debug_info("sipe", "using SSL\n");
8164 sip->gsc = purple_ssl_connect(account, hostname, port,
8165 login_cb_ssl, sipe_ssl_connect_failure, gc);
8166 if (sip->gsc == NULL) {
8167 purple_connection_error(gc, _("Could not create SSL context"));
8168 return;
8170 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8171 /* UDP case */
8172 purple_debug_info("sipe", "using UDP\n");
8174 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8175 if (sip->query_data == NULL) {
8176 purple_connection_error(gc, _("Could not resolve hostname"));
8178 } else {
8179 /* TCP case */
8180 purple_debug_info("sipe", "using TCP\n");
8181 /* create socket for incoming connections */
8182 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8183 sipe_tcp_connect_listen_cb, sip);
8184 if (sip->listen_data == NULL) {
8185 purple_connection_error(gc, _("Could not create listen socket"));
8186 return;
8191 /* Service list for autodection */
8192 static const struct sipe_service_data service_autodetect[] = {
8193 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8194 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8195 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8196 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8197 { NULL, NULL, 0 }
8200 /* Service list for SSL/TLS */
8201 static const struct sipe_service_data service_tls[] = {
8202 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8203 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8204 { NULL, NULL, 0 }
8207 /* Service list for TCP */
8208 static const struct sipe_service_data service_tcp[] = {
8209 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8210 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8211 { NULL, NULL, 0 }
8214 /* Service list for UDP */
8215 static const struct sipe_service_data service_udp[] = {
8216 { "sip", "udp", SIPE_TRANSPORT_UDP },
8217 { NULL, NULL, 0 }
8220 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8221 static void resolve_next_service(struct sipe_account_data *sip,
8222 const struct sipe_service_data *start)
8224 if (start) {
8225 sip->service_data = start;
8226 } else {
8227 sip->service_data++;
8228 if (sip->service_data->service == NULL) {
8229 gchar *hostname;
8230 /* Try connecting to the SIP hostname directly */
8231 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8232 if (sip->auto_transport) {
8233 // If SSL is supported, default to using it; OCS servers aren't configured
8234 // by default to accept TCP
8235 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8236 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8237 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8240 hostname = g_strdup(sip->sipdomain);
8241 create_connection(sip, hostname, 0);
8242 return;
8246 /* Try to resolve next service */
8247 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8248 sip->service_data->transport,
8249 sip->sipdomain,
8250 srvresolved, sip);
8253 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8255 struct sipe_account_data *sip = data;
8257 sip->srv_query_data = NULL;
8259 /* find the host to connect to */
8260 if (results) {
8261 gchar *hostname = g_strdup(resp->hostname);
8262 int port = resp->port;
8263 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8264 hostname, port);
8265 g_free(resp);
8267 sip->transport = sip->service_data->type;
8269 create_connection(sip, hostname, port);
8270 } else {
8271 resolve_next_service(sip, NULL);
8275 static void sipe_login(PurpleAccount *account)
8277 PurpleConnection *gc;
8278 struct sipe_account_data *sip;
8279 gchar **signinname_login, **userserver;
8280 const char *transport;
8281 const char *email;
8283 const char *username = purple_account_get_username(account);
8284 gc = purple_account_get_connection(account);
8286 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8288 if (strpbrk(username, "\t\v\r\n") != NULL) {
8289 gc->wants_to_die = TRUE;
8290 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8291 return;
8294 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8295 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8296 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8297 sip->gc = gc;
8298 sip->account = account;
8299 sip->reregister_set = FALSE;
8300 sip->reauthenticate_set = FALSE;
8301 sip->subscribed = FALSE;
8302 sip->subscribed_buddies = FALSE;
8303 sip->initial_state_published = FALSE;
8305 /* username format: <username>,[<optional login>] */
8306 signinname_login = g_strsplit(username, ",", 2);
8307 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8309 /* ensure that username format is name@domain */
8310 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8311 g_strfreev(signinname_login);
8312 gc->wants_to_die = TRUE;
8313 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8314 return;
8316 sip->username = g_strdup(signinname_login[0]);
8318 /* ensure that email format is name@domain if provided */
8319 email = purple_account_get_string(sip->account, "email", NULL);
8320 if (!is_empty(email) &&
8321 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8323 gc->wants_to_die = TRUE;
8324 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8325 return;
8327 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8329 /* login name specified? */
8330 if (signinname_login[1] && strlen(signinname_login[1])) {
8331 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8332 gboolean has_domain = domain_user[1] != NULL;
8333 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8334 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8335 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8336 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8337 sip->authdomain ? sip->authdomain : "", sip->authuser);
8338 g_strfreev(domain_user);
8341 userserver = g_strsplit(signinname_login[0], "@", 2);
8342 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8343 purple_connection_set_display_name(gc, userserver[0]);
8344 sip->sipdomain = g_strdup(userserver[1]);
8345 g_strfreev(userserver);
8346 g_strfreev(signinname_login);
8348 if (strchr(sip->username, ' ') != NULL) {
8349 gc->wants_to_die = TRUE;
8350 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8351 return;
8354 sip->password = g_strdup(purple_connection_get_password(gc));
8356 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8357 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8358 g_free, (GDestroyNotify)g_hash_table_destroy);
8359 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8360 g_free, (GDestroyNotify)sipe_subscription_free);
8362 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8364 g_free(sip->status);
8365 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8367 sip->auto_transport = FALSE;
8368 transport = purple_account_get_string(account, "transport", "auto");
8369 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8370 if (userserver[0]) {
8371 /* Use user specified server[:port] */
8372 int port = 0;
8374 if (userserver[1])
8375 port = atoi(userserver[1]);
8377 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8378 userserver[0], port);
8380 if (strcmp(transport, "auto") == 0) {
8381 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8382 } else if (strcmp(transport, "tls") == 0) {
8383 sip->transport = SIPE_TRANSPORT_TLS;
8384 } else if (strcmp(transport, "tcp") == 0) {
8385 sip->transport = SIPE_TRANSPORT_TCP;
8386 } else {
8387 sip->transport = SIPE_TRANSPORT_UDP;
8390 create_connection(sip, g_strdup(userserver[0]), port);
8391 } else {
8392 /* Server auto-discovery */
8393 if (strcmp(transport, "auto") == 0) {
8394 sip->auto_transport = TRUE;
8395 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8396 current_service++;
8397 resolve_next_service(sip, current_service);
8398 } else {
8399 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8401 } else if (strcmp(transport, "tls") == 0) {
8402 resolve_next_service(sip, service_tls);
8403 } else if (strcmp(transport, "tcp") == 0) {
8404 resolve_next_service(sip, service_tcp);
8405 } else {
8406 resolve_next_service(sip, service_udp);
8409 g_strfreev(userserver);
8412 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8414 connection_free_all(sip);
8416 g_free(sip->epid);
8417 sip->epid = NULL;
8419 if (sip->query_data != NULL)
8420 purple_dnsquery_destroy(sip->query_data);
8421 sip->query_data = NULL;
8423 if (sip->srv_query_data != NULL)
8424 purple_srv_cancel(sip->srv_query_data);
8425 sip->srv_query_data = NULL;
8427 if (sip->listen_data != NULL)
8428 purple_network_listen_cancel(sip->listen_data);
8429 sip->listen_data = NULL;
8431 if (sip->gsc != NULL)
8432 purple_ssl_close(sip->gsc);
8433 sip->gsc = NULL;
8435 sipe_auth_free(&sip->registrar);
8436 sipe_auth_free(&sip->proxy);
8438 if (sip->txbuf)
8439 purple_circ_buffer_destroy(sip->txbuf);
8440 sip->txbuf = NULL;
8442 g_free(sip->realhostname);
8443 sip->realhostname = NULL;
8445 g_free(sip->server_version);
8446 sip->server_version = NULL;
8448 if (sip->listenpa)
8449 purple_input_remove(sip->listenpa);
8450 sip->listenpa = 0;
8451 if (sip->tx_handler)
8452 purple_input_remove(sip->tx_handler);
8453 sip->tx_handler = 0;
8454 if (sip->resendtimeout)
8455 purple_timeout_remove(sip->resendtimeout);
8456 sip->resendtimeout = 0;
8457 if (sip->timeouts) {
8458 GSList *entry = sip->timeouts;
8459 while (entry) {
8460 struct scheduled_action *sched_action = entry->data;
8461 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8462 purple_timeout_remove(sched_action->timeout_handler);
8463 if (sched_action->destroy) {
8464 (*sched_action->destroy)(sched_action->payload);
8466 g_free(sched_action->name);
8467 g_free(sched_action);
8468 entry = entry->next;
8471 g_slist_free(sip->timeouts);
8473 if (sip->allow_events) {
8474 GSList *entry = sip->allow_events;
8475 while (entry) {
8476 g_free(entry->data);
8477 entry = entry->next;
8480 g_slist_free(sip->allow_events);
8482 if (sip->containers) {
8483 GSList *entry = sip->containers;
8484 while (entry) {
8485 free_container((struct sipe_container *)entry->data);
8486 entry = entry->next;
8489 g_slist_free(sip->containers);
8491 if (sip->contact)
8492 g_free(sip->contact);
8493 sip->contact = NULL;
8494 if (sip->regcallid)
8495 g_free(sip->regcallid);
8496 sip->regcallid = NULL;
8498 if (sip->serveraddr)
8499 g_free(sip->serveraddr);
8500 sip->serveraddr = NULL;
8502 if (sip->focus_factory_uri)
8503 g_free(sip->focus_factory_uri);
8504 sip->focus_factory_uri = NULL;
8506 sip->fd = -1;
8507 sip->processing_input = FALSE;
8509 if (sip->ews) {
8510 sipe_ews_free(sip->ews);
8512 sip->ews = NULL;
8516 * A callback for g_hash_table_foreach_remove
8518 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8519 SIPE_UNUSED_PARAMETER gpointer user_data)
8521 sipe_free_buddy((struct sipe_buddy *) buddy);
8523 /* We must return TRUE as the key/value have already been deleted */
8524 return(TRUE);
8527 static void sipe_close(PurpleConnection *gc)
8529 struct sipe_account_data *sip = gc->proto_data;
8531 if (sip) {
8532 /* leave all conversations */
8533 sipe_session_close_all(sip);
8534 sipe_session_remove_all(sip);
8536 if (sip->csta) {
8537 sip_csta_close(sip);
8540 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8541 /* unsubscribe all */
8542 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8544 /* unregister */
8545 do_register_exp(sip, 0);
8548 sipe_connection_cleanup(sip);
8549 g_free(sip->sipdomain);
8550 g_free(sip->username);
8551 g_free(sip->email);
8552 g_free(sip->password);
8553 g_free(sip->authdomain);
8554 g_free(sip->authuser);
8555 g_free(sip->status);
8556 g_free(sip->note);
8558 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8559 g_hash_table_destroy(sip->buddies);
8560 g_hash_table_destroy(sip->our_publications);
8561 g_hash_table_destroy(sip->user_state_publications);
8562 g_hash_table_destroy(sip->subscriptions);
8564 if (sip->groups) {
8565 GSList *entry = sip->groups;
8566 while (entry) {
8567 struct sipe_group *group = entry->data;
8568 g_free(group->name);
8569 g_free(group);
8570 entry = entry->next;
8573 g_slist_free(sip->groups);
8575 if (sip->our_publication_keys) {
8576 GSList *entry = sip->our_publication_keys;
8577 while (entry) {
8578 g_free(entry->data);
8579 entry = entry->next;
8582 g_slist_free(sip->our_publication_keys);
8584 while (sip->transactions)
8585 transactions_remove(sip, sip->transactions->data);
8587 g_free(gc->proto_data);
8588 gc->proto_data = NULL;
8591 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8592 SIPE_UNUSED_PARAMETER void *user_data)
8594 PurpleAccount *acct = purple_connection_get_account(gc);
8595 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8596 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8597 if (conv == NULL)
8598 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8599 purple_conversation_present(conv);
8600 g_free(id);
8603 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8604 SIPE_UNUSED_PARAMETER void *user_data)
8607 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8608 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8611 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8612 SIPE_UNUSED_PARAMETER struct transaction *trans)
8614 PurpleNotifySearchResults *results;
8615 PurpleNotifySearchColumn *column;
8616 xmlnode *searchResults;
8617 xmlnode *mrow;
8618 int match_count = 0;
8619 gboolean more = FALSE;
8620 gchar *secondary;
8622 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8624 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8625 if (!searchResults) {
8626 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8627 return FALSE;
8630 results = purple_notify_searchresults_new();
8632 if (results == NULL) {
8633 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8634 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8636 xmlnode_free(searchResults);
8637 return FALSE;
8640 column = purple_notify_searchresults_column_new(_("User name"));
8641 purple_notify_searchresults_column_add(results, column);
8643 column = purple_notify_searchresults_column_new(_("Name"));
8644 purple_notify_searchresults_column_add(results, column);
8646 column = purple_notify_searchresults_column_new(_("Company"));
8647 purple_notify_searchresults_column_add(results, column);
8649 column = purple_notify_searchresults_column_new(_("Country"));
8650 purple_notify_searchresults_column_add(results, column);
8652 column = purple_notify_searchresults_column_new(_("Email"));
8653 purple_notify_searchresults_column_add(results, column);
8655 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8656 GList *row = NULL;
8658 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8659 row = g_list_append(row, g_strdup(uri_parts[1]));
8660 g_strfreev(uri_parts);
8662 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8663 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8664 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8665 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8667 purple_notify_searchresults_row_add(results, row);
8668 match_count++;
8671 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8672 char *data = xmlnode_get_data_unescaped(mrow);
8673 more = (g_strcasecmp(data, "true") == 0);
8674 g_free(data);
8677 secondary = g_strdup_printf(
8678 dngettext(GETTEXT_PACKAGE,
8679 "Found %d contact%s:",
8680 "Found %d contacts%s:", match_count),
8681 match_count, more ? _(" (more matched your query)") : "");
8683 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8684 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8685 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8687 g_free(secondary);
8688 xmlnode_free(searchResults);
8689 return TRUE;
8692 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8694 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8695 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8696 unsigned i = 0;
8698 do {
8699 PurpleRequestField *field = entries->data;
8700 const char *id = purple_request_field_get_id(field);
8701 const char *value = purple_request_field_string_get_value(field);
8703 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8705 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8706 } while ((entries = g_list_next(entries)) != NULL);
8707 attrs[i] = NULL;
8709 if (i > 0) {
8710 struct sipe_account_data *sip = gc->proto_data;
8711 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8712 gchar *query = g_strjoinv(NULL, attrs);
8713 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8714 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8715 send_soap_request_with_cb(sip, domain_uri, body,
8716 (TransCallback) process_search_contact_response, NULL);
8717 g_free(domain_uri);
8718 g_free(body);
8719 g_free(query);
8722 g_strfreev(attrs);
8725 static void sipe_show_find_contact(PurplePluginAction *action)
8727 PurpleConnection *gc = (PurpleConnection *) action->context;
8728 PurpleRequestFields *fields;
8729 PurpleRequestFieldGroup *group;
8730 PurpleRequestField *field;
8732 fields = purple_request_fields_new();
8733 group = purple_request_field_group_new(NULL);
8734 purple_request_fields_add_group(fields, group);
8736 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8737 purple_request_field_group_add_field(group, field);
8738 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8739 purple_request_field_group_add_field(group, field);
8740 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8741 purple_request_field_group_add_field(group, field);
8742 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8743 purple_request_field_group_add_field(group, field);
8745 purple_request_fields(gc,
8746 _("Search"),
8747 _("Search for a contact"),
8748 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8749 fields,
8750 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8751 _("_Cancel"), NULL,
8752 purple_connection_get_account(gc), NULL, NULL, gc);
8755 static void sipe_show_about_plugin(PurplePluginAction *action)
8757 PurpleConnection *gc = (PurpleConnection *) action->context;
8758 char *tmp = g_strdup_printf(
8760 * Non-translatable parts, like markup, are hard-coded
8761 * into the format string. This requires more translatable
8762 * texts but it makes the translations less error prone.
8764 "<b><font size=\"+1\">SIPE " SIPE_VERSION " </font></b><br/>"
8765 "<br/>"
8766 /* 1 */ "%s:<br/>"
8767 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8768 "<li> - MS Office Communications Server 2007</li><br/>"
8769 "<li> - MS Live Communications Server 2005</li><br/>"
8770 "<li> - MS Live Communications Server 2003</li><br/>"
8771 "<li> - Reuters Messaging</li><br/>"
8772 "<br/>"
8773 /* 2 */ "%s: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8774 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8775 /* 5 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8776 /* 6 */ "%s: GPLv2+<br/>"
8777 "<br/>"
8778 /* 7 */ "%s:<br/>"
8779 " - CERN<br/>"
8780 " - Reuters Messaging network<br/>"
8781 " - Deutsche Bank<br/>"
8782 " - Merrill Lynch<br/>"
8783 " - Wachovia<br/>"
8784 " - Intel<br/>"
8785 " - Nokia<br/>"
8786 " - HP<br/>"
8787 " - Siemens<br/>"
8788 " - Alcatel-Lucent<br/>"
8789 " - BT<br/>"
8790 "<br/>"
8791 /* 8,9 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
8792 "<br/>"
8793 /* 10 */ "<b>%s:</b><br/>"
8794 " - Anibal Avelar<br/>"
8795 " - Gabriel Burt<br/>"
8796 " - Stefan Becker<br/>"
8797 " - pier11<br/>"
8798 "<br/>"
8799 /* 11 */ "%s<br/>"
8801 /* The next 11 texts make up the SIPE about note text */
8802 /* About note, part 1/11: introduction */
8803 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
8804 /* About note, part 2/11: home page URL (label) */
8805 _("Home"),
8806 /* About note, part 3/11: support forum URL (label) */
8807 _("Support"),
8808 /* About note, part 4/11: support forum name (hyperlink text) */
8809 _("Help Forum"),
8810 /* About note, part 5/11: translation service URL (label) */
8811 _("Translations"),
8812 /* About note, part 6/11: license type (label) */
8813 _("License"),
8814 /* About note, part 7/11: known users */
8815 _("We support users in such organizations as"),
8816 /* About note, part 8/11: translation request, text before Transifex.net URL */
8817 /* append a space if text is not empty */
8818 _("Please help us to translate SIPE to your native language here at "),
8819 /* About note, part 9/11: translation request, text after Transifex.net URL */
8820 /* start with a space if text is not empty */
8821 _(" using convenient web interface"),
8822 /* About note, part 10/11: author list (header) */
8823 _("Authors"),
8824 /* About note, part 11/11: Localization credit */
8825 /* PLEASE NOTE: do *NOT* simply translate the english original */
8826 /* but write something similar to the following sentence: */
8827 /* "Localization for <language name> (<language code>): <name>" */
8828 _("Original texts in English (en): SIPE developers")
8830 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
8831 g_free(tmp);
8834 static void sipe_republish_calendar(PurplePluginAction *action)
8836 PurpleConnection *gc = (PurpleConnection *) action->context;
8837 struct sipe_account_data *sip = gc->proto_data;
8839 sipe_update_calendar(sip);
8842 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
8843 gpointer value,
8844 GString* str)
8846 struct sipe_publication *publication = value;
8848 g_string_append_printf( str,
8849 SIPE_PUB_XML_PUBLICATION_CLEAR,
8850 publication->category,
8851 publication->instance,
8852 publication->container,
8853 publication->version,
8854 "static");
8857 static void sipe_reset_status(PurplePluginAction *action)
8859 PurpleConnection *gc = (PurpleConnection *) action->context;
8860 struct sipe_account_data *sip = gc->proto_data;
8862 if (sip->ocs2007) /* 2007+ */
8864 GString* str = g_string_new(NULL);
8865 gchar *publications;
8867 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
8868 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8869 return;
8872 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
8873 publications = g_string_free(str, FALSE);
8875 send_presence_publish(sip, publications);
8876 g_free(publications);
8878 else /* 2005 */
8880 send_presence_soap0(sip, FALSE, TRUE);
8884 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
8885 gpointer context)
8887 PurpleConnection *gc = (PurpleConnection *)context;
8888 struct sipe_account_data *sip = gc->proto_data;
8889 GList *menu = NULL;
8890 PurplePluginAction *act;
8891 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
8893 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
8894 menu = g_list_prepend(menu, act);
8896 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
8897 menu = g_list_prepend(menu, act);
8899 if (!strcmp(calendar, "EXCH")) {
8900 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
8901 menu = g_list_prepend(menu, act);
8904 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
8905 menu = g_list_prepend(menu, act);
8907 menu = g_list_reverse(menu);
8909 return menu;
8912 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
8916 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8918 return TRUE;
8922 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8924 return TRUE;
8928 static char *sipe_status_text(PurpleBuddy *buddy)
8930 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8931 const PurpleStatus *status = purple_presence_get_active_status(presence);
8932 const char *status_id = purple_status_get_id(status);
8933 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
8934 struct sipe_buddy *sbuddy;
8935 char *text = NULL;
8937 if (!sip) return NULL; /* happens on pidgin exit */
8939 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8940 if (sbuddy) {
8941 const char *activity_str = sbuddy->activity ?
8942 sbuddy->activity :
8943 !strcmp(status_id, SIPE_STATUS_ID_BUSY) || !strcmp(status_id, SIPE_STATUS_ID_BRB) ?
8944 purple_status_get_name(status) : NULL;
8946 if (activity_str && sbuddy->note)
8948 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
8950 else if (activity_str)
8952 text = g_strdup(activity_str);
8954 else if (sbuddy->note)
8956 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
8960 return text;
8963 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
8965 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8966 const PurpleStatus *status = purple_presence_get_active_status(presence);
8967 struct sipe_account_data *sip;
8968 struct sipe_buddy *sbuddy;
8969 char *note = NULL;
8970 gboolean is_oof_note = FALSE;
8971 char *activity = NULL;
8972 char *calendar = NULL;
8973 char *meeting_subject = NULL;
8974 char *meeting_location = NULL;
8976 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
8977 if (sip) //happens on pidgin exit
8979 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8980 if (sbuddy)
8982 note = sbuddy->note;
8983 is_oof_note = sbuddy->is_oof_note;
8984 activity = sbuddy->activity;
8985 calendar = sipe_cal_get_description(sbuddy);
8986 meeting_subject = sbuddy->meeting_subject;
8987 meeting_location = sbuddy->meeting_location;
8991 //Layout
8992 if (purple_presence_is_online(presence))
8994 const char *status_str = activity ? activity : purple_status_get_name(status);
8996 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
8998 if (purple_presence_is_online(presence) &&
8999 !is_empty(calendar))
9001 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9003 g_free(calendar);
9004 if (!is_empty(meeting_location))
9006 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9008 if (!is_empty(meeting_subject))
9010 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9013 if (note)
9015 char *tmp = g_strdup_printf("<i>%s</i>", note);
9016 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9018 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9019 g_free(tmp);
9024 #if PURPLE_VERSION_CHECK(2,5,0)
9025 static GHashTable *
9026 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9028 GHashTable *table;
9029 table = g_hash_table_new(g_str_hash, g_str_equal);
9030 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9031 return table;
9033 #endif
9035 static PurpleBuddy *
9036 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9038 PurpleBuddy *clone;
9039 const gchar *server_alias, *email;
9040 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9042 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9044 purple_blist_add_buddy(clone, NULL, group, NULL);
9046 server_alias = purple_buddy_get_server_alias(buddy);
9047 if (server_alias) {
9048 purple_blist_server_alias_buddy(clone, server_alias);
9051 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9052 if (email) {
9053 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9056 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9057 //for UI to update;
9058 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9059 return clone;
9062 static void
9063 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9065 PurpleBuddy *buddy, *b;
9066 PurpleConnection *gc;
9067 PurpleGroup * group = purple_find_group(group_name);
9069 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9071 buddy = (PurpleBuddy *)node;
9073 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9074 gc = purple_account_get_connection(buddy->account);
9076 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9077 if (!b){
9078 purple_blist_add_buddy_clone(group, buddy);
9081 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9084 static void
9085 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9087 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9089 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9091 /* 2007+ conference */
9092 if (sip->ocs2007)
9094 sipe_conf_add(sip, buddy->name);
9096 else /* 2005- multiparty chat */
9098 gchar *self = sip_uri_self(sip);
9099 struct sip_session *session;
9101 session = sipe_session_add_chat(sip);
9102 session->chat_title = sipe_chat_get_name(session->callid);
9103 session->roster_manager = g_strdup(self);
9105 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9106 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9107 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9108 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
9110 g_free(self);
9114 static gboolean
9115 sipe_is_election_finished(struct sip_session *session)
9117 gboolean res = TRUE;
9119 SIPE_DIALOG_FOREACH {
9120 if (dialog->election_vote == 0) {
9121 res = FALSE;
9122 break;
9124 } SIPE_DIALOG_FOREACH_END;
9126 if (res) {
9127 session->is_voting_in_progress = FALSE;
9129 return res;
9132 static void
9133 sipe_election_start(struct sipe_account_data *sip,
9134 struct sip_session *session)
9136 int election_timeout;
9138 if (session->is_voting_in_progress) {
9139 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9140 return;
9141 } else {
9142 session->is_voting_in_progress = TRUE;
9144 session->bid = rand();
9146 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9148 SIPE_DIALOG_FOREACH {
9149 /* reset election_vote for each chat participant */
9150 dialog->election_vote = 0;
9152 /* send RequestRM to each chat participant*/
9153 sipe_send_election_request_rm(sip, dialog, session->bid);
9154 } SIPE_DIALOG_FOREACH_END;
9156 election_timeout = 15; /* sec */
9157 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9161 * @param who a URI to whom to invite to chat
9163 void
9164 sipe_invite_to_chat(struct sipe_account_data *sip,
9165 struct sip_session *session,
9166 const gchar *who)
9168 /* a conference */
9169 if (session->focus_uri)
9171 sipe_invite_conf(sip, session, who);
9173 else /* a multi-party chat */
9175 gchar *self = sip_uri_self(sip);
9176 if (session->roster_manager) {
9177 if (!strcmp(session->roster_manager, self)) {
9178 sipe_invite(sip, session, who, NULL, NULL, FALSE);
9179 } else {
9180 sipe_refer(sip, session, who);
9182 } else {
9183 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9185 session->pending_invite_queue = slist_insert_unique_sorted(
9186 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9188 sipe_election_start(sip, session);
9190 g_free(self);
9194 void
9195 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9196 struct sip_session *session)
9198 gchar *invitee;
9199 GSList *entry = session->pending_invite_queue;
9201 while (entry) {
9202 invitee = entry->data;
9203 sipe_invite_to_chat(sip, session, invitee);
9204 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9205 g_free(invitee);
9209 static void
9210 sipe_election_result(struct sipe_account_data *sip,
9211 void *sess)
9213 struct sip_session *session = (struct sip_session *)sess;
9214 gchar *rival;
9215 gboolean has_won = TRUE;
9217 if (session->roster_manager) {
9218 purple_debug_info("sipe",
9219 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9220 return;
9223 session->is_voting_in_progress = FALSE;
9225 SIPE_DIALOG_FOREACH {
9226 if (dialog->election_vote < 0) {
9227 has_won = FALSE;
9228 rival = dialog->with;
9229 break;
9231 } SIPE_DIALOG_FOREACH_END;
9233 if (has_won) {
9234 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9236 session->roster_manager = sip_uri_self(sip);
9238 SIPE_DIALOG_FOREACH {
9239 /* send SetRM to each chat participant*/
9240 sipe_send_election_set_rm(sip, dialog);
9241 } SIPE_DIALOG_FOREACH_END;
9242 } else {
9243 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9245 session->bid = 0;
9247 sipe_process_pending_invite_queue(sip, session);
9251 * For 2007+ conference only.
9253 static void
9254 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9256 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9257 struct sip_session *session;
9259 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9260 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9262 session = sipe_session_find_chat_by_title(sip, chat_title);
9264 sipe_conf_modify_user_role(sip, session, buddy->name);
9268 * For 2007+ conference only.
9270 static void
9271 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9273 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9274 struct sip_session *session;
9276 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9277 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9279 session = sipe_session_find_chat_by_title(sip, chat_title);
9281 sipe_conf_delete_user(sip, session, buddy->name);
9284 static void
9285 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9287 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9288 struct sip_session *session;
9290 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9291 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9293 session = sipe_session_find_chat_by_title(sip, chat_title);
9295 sipe_invite_to_chat(sip, session, buddy->name);
9298 static void
9299 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9301 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9303 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9304 if (phone) {
9305 char *tel_uri = sip_to_tel_uri(phone);
9307 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9308 sip_csta_make_call(sip, tel_uri);
9310 g_free(tel_uri);
9314 static void
9315 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9317 const gchar *email;
9318 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9320 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9321 if (email)
9323 char *mailto = g_strdup_printf("mailto:%s", email);
9324 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9325 #ifndef _WIN32
9327 pid_t pid;
9328 char *const parmList[] = {"xdg-email", mailto, NULL};
9329 if ((pid = fork()) == -1)
9331 purple_debug_info("sipe", "fork() error\n");
9333 else if (pid == 0)
9335 execvp(parmList[0], parmList);
9336 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9339 #else
9341 BOOL ret;
9342 _flushall();
9343 errno = 0;
9344 //@TODO resolve env variable %WINDIR% first
9345 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9346 if (errno)
9348 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9351 #endif
9353 g_free(mailto);
9355 else
9357 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9362 * A menu which appear when right-clicking on buddy in contact list.
9364 static GList *
9365 sipe_buddy_menu(PurpleBuddy *buddy)
9367 PurpleBlistNode *g_node;
9368 PurpleGroup *group, *gr_parent;
9369 PurpleMenuAction *act;
9370 GList *menu = NULL;
9371 GList *menu_groups = NULL;
9372 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9373 const char *email;
9374 const char *phone;
9375 const char *phone_disp_str;
9376 gchar *self = sip_uri_self(sip);
9378 SIPE_SESSION_FOREACH {
9379 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9381 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9383 PurpleConvChatBuddyFlags flags;
9384 PurpleConvChatBuddyFlags flags_us;
9386 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9387 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9388 if (session->focus_uri
9389 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9390 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9392 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9393 act = purple_menu_action_new(label,
9394 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9395 session->chat_title, NULL);
9396 g_free(label);
9397 menu = g_list_prepend(menu, act);
9400 if (session->focus_uri
9401 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9403 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9404 act = purple_menu_action_new(label,
9405 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9406 session->chat_title, NULL);
9407 g_free(label);
9408 menu = g_list_prepend(menu, act);
9411 else
9413 if (!session->focus_uri
9414 || (session->focus_uri && !session->locked))
9416 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9417 act = purple_menu_action_new(label,
9418 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9419 session->chat_title, NULL);
9420 g_free(label);
9421 menu = g_list_prepend(menu, act);
9425 } SIPE_SESSION_FOREACH_END;
9427 act = purple_menu_action_new(_("New chat"),
9428 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9429 NULL, NULL);
9430 menu = g_list_prepend(menu, act);
9432 if (sip->csta && !sip->csta->line_status) {
9433 gchar *tmp = NULL;
9434 /* work phone */
9435 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9436 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9437 if (phone) {
9438 gchar *label = g_strdup_printf(_("Work %s"),
9439 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9440 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9441 g_free(tmp);
9442 tmp = NULL;
9443 g_free(label);
9444 menu = g_list_prepend(menu, act);
9447 /* mobile phone */
9448 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9449 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9450 if (phone) {
9451 gchar *label = g_strdup_printf(_("Mobile %s"),
9452 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9453 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9454 g_free(tmp);
9455 tmp = NULL;
9456 g_free(label);
9457 menu = g_list_prepend(menu, act);
9460 /* home phone */
9461 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9462 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9463 if (phone) {
9464 gchar *label = g_strdup_printf(_("Home %s"),
9465 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9466 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9467 g_free(tmp);
9468 tmp = NULL;
9469 g_free(label);
9470 menu = g_list_prepend(menu, act);
9473 /* other phone */
9474 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9475 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9476 if (phone) {
9477 gchar *label = g_strdup_printf(_("Other %s"),
9478 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9479 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9480 g_free(tmp);
9481 tmp = NULL;
9482 g_free(label);
9483 menu = g_list_prepend(menu, act);
9486 /* custom1 phone */
9487 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9488 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9489 if (phone) {
9490 gchar *label = g_strdup_printf(_("Custom1 %s"),
9491 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9492 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9493 g_free(tmp);
9494 tmp = NULL;
9495 g_free(label);
9496 menu = g_list_prepend(menu, act);
9500 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9501 if (email) {
9502 act = purple_menu_action_new(_("Send email..."),
9503 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9504 NULL, NULL);
9505 menu = g_list_prepend(menu, act);
9508 gr_parent = purple_buddy_get_group(buddy);
9509 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9510 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9511 continue;
9513 group = (PurpleGroup *)g_node;
9514 if (group == gr_parent)
9515 continue;
9517 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9518 continue;
9520 act = purple_menu_action_new(purple_group_get_name(group),
9521 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9522 group->name, NULL);
9523 menu_groups = g_list_prepend(menu_groups, act);
9525 menu_groups = g_list_reverse(menu_groups);
9527 act = purple_menu_action_new(_("Copy to"),
9528 NULL,
9529 NULL, menu_groups);
9530 menu = g_list_prepend(menu, act);
9531 menu = g_list_reverse(menu);
9533 g_free(self);
9534 return menu;
9537 static void
9538 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9540 struct sipe_account_data *sip = chat->account->gc->proto_data;
9541 struct sip_session *session;
9543 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9544 sipe_conf_modify_conference_lock(sip, session, locked);
9547 static void
9548 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9550 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9551 sipe_conf_modify_lock(chat, FALSE);
9554 static void
9555 sipe_chat_menu_lock_cb(PurpleChat *chat)
9557 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9558 sipe_conf_modify_lock(chat, TRUE);
9561 static GList *
9562 sipe_chat_menu(PurpleChat *chat)
9564 PurpleMenuAction *act;
9565 PurpleConvChatBuddyFlags flags_us;
9566 GList *menu = NULL;
9567 struct sipe_account_data *sip = chat->account->gc->proto_data;
9568 struct sip_session *session;
9569 gchar *self;
9571 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9572 if (!session) return NULL;
9574 self = sip_uri_self(sip);
9575 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9577 if (session->focus_uri
9578 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9580 if (session->locked) {
9581 act = purple_menu_action_new(_("Unlock"),
9582 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9583 NULL, NULL);
9584 menu = g_list_prepend(menu, act);
9585 } else {
9586 act = purple_menu_action_new(_("Lock"),
9587 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9588 NULL, NULL);
9589 menu = g_list_prepend(menu, act);
9593 menu = g_list_reverse(menu);
9595 g_free(self);
9596 return menu;
9599 static GList *
9600 sipe_blist_node_menu(PurpleBlistNode *node)
9602 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9603 return sipe_buddy_menu((PurpleBuddy *) node);
9604 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9605 return sipe_chat_menu((PurpleChat *)node);
9606 } else {
9607 return NULL;
9611 static gboolean
9612 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9614 char *uri = trans->payload->data;
9616 PurpleNotifyUserInfo *info;
9617 PurpleBuddy *pbuddy = NULL;
9618 struct sipe_buddy *sbuddy;
9619 const char *alias = NULL;
9620 char *device_name = NULL;
9621 char *server_alias = NULL;
9622 char *phone_number = NULL;
9623 char *email = NULL;
9624 const char *site;
9625 char *first_name = NULL;
9626 char *last_name = NULL;
9628 if (!sip) return FALSE;
9630 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9632 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9633 alias = purple_buddy_get_local_alias(pbuddy);
9635 //will query buddy UA's capabilities and send answer to log
9636 sipe_options_request(sip, uri);
9638 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9639 if (sbuddy) {
9640 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9643 info = purple_notify_user_info_new();
9645 if (msg->response != 200) {
9646 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9647 } else {
9648 xmlnode *searchResults;
9649 xmlnode *mrow;
9651 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9652 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9653 if (!searchResults) {
9654 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9655 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9656 const char *value;
9657 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9658 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9659 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9661 /* For 2007 system we will take this from ContactCard -
9662 * it has cleaner tel: URIs at least
9664 if (!sip->ocs2007) {
9665 char *tel_uri = sip_to_tel_uri(phone_number);
9666 /* trims its parameters, so call first */
9667 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9668 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9669 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9670 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9671 g_free(tel_uri);
9674 if (server_alias && strlen(server_alias) > 0) {
9675 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9677 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9678 purple_notify_user_info_add_pair(info, _("Job title"), value);
9680 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9681 purple_notify_user_info_add_pair(info, _("Office"), value);
9683 if (phone_number && strlen(phone_number) > 0) {
9684 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9686 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9687 purple_notify_user_info_add_pair(info, _("Company"), value);
9689 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9690 purple_notify_user_info_add_pair(info, _("City"), value);
9692 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9693 purple_notify_user_info_add_pair(info, _("State"), value);
9695 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9696 purple_notify_user_info_add_pair(info, _("Country"), value);
9698 if (email && strlen(email) > 0) {
9699 purple_notify_user_info_add_pair(info, _("Email address"), email);
9703 xmlnode_free(searchResults);
9706 purple_notify_user_info_add_section_break(info);
9708 if (!server_alias || !strcmp("", server_alias)) {
9709 g_free(server_alias);
9710 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9711 if (server_alias) {
9712 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9716 /* present alias if it differs from server alias */
9717 if (alias && (!server_alias || strcmp(alias, server_alias)))
9719 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9722 if (!email || !strcmp("", email)) {
9723 g_free(email);
9724 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9725 if (email) {
9726 purple_notify_user_info_add_pair(info, _("Email address"), email);
9730 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9731 if (site) {
9732 purple_notify_user_info_add_pair(info, _("Site"), site);
9735 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9736 if (first_name && last_name) {
9737 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9739 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9740 g_free(link);
9742 g_free(first_name);
9743 g_free(last_name);
9745 if (device_name) {
9746 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9749 /* show a buddy's user info in a nice dialog box */
9750 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9751 uri, /* buddy's URI */
9752 info, /* body */
9753 NULL, /* callback called when dialog closed */
9754 NULL); /* userdata for callback */
9756 g_free(phone_number);
9757 g_free(server_alias);
9758 g_free(email);
9759 g_free(device_name);
9761 return TRUE;
9765 * AD search first, LDAP based
9767 static void sipe_get_info(PurpleConnection *gc, const char *username)
9769 struct sipe_account_data *sip = gc->proto_data;
9770 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9771 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9772 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9773 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9775 payload->destroy = g_free;
9776 payload->data = g_strdup(username);
9778 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9779 send_soap_request_with_cb(sip, domain_uri, body,
9780 (TransCallback) process_get_info_response, payload);
9781 g_free(domain_uri);
9782 g_free(body);
9783 g_free(row);
9786 static PurplePlugin *my_protocol = NULL;
9788 static PurplePluginProtocolInfo prpl_info =
9790 OPT_PROTO_CHAT_TOPIC,
9791 NULL, /* user_splits */
9792 NULL, /* protocol_options */
9793 NO_BUDDY_ICONS, /* icon_spec */
9794 sipe_list_icon, /* list_icon */
9795 NULL, /* list_emblems */
9796 sipe_status_text, /* status_text */
9797 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9798 sipe_status_types, /* away_states */
9799 sipe_blist_node_menu, /* blist_node_menu */
9800 NULL, /* chat_info */
9801 NULL, /* chat_info_defaults */
9802 sipe_login, /* login */
9803 sipe_close, /* close */
9804 sipe_im_send, /* send_im */
9805 NULL, /* set_info */ // TODO maybe
9806 sipe_send_typing, /* send_typing */
9807 sipe_get_info, /* get_info */
9808 sipe_set_status, /* set_status */
9809 sipe_set_idle, /* set_idle */
9810 NULL, /* change_passwd */
9811 sipe_add_buddy, /* add_buddy */
9812 NULL, /* add_buddies */
9813 sipe_remove_buddy, /* remove_buddy */
9814 NULL, /* remove_buddies */
9815 sipe_add_permit, /* add_permit */
9816 sipe_add_deny, /* add_deny */
9817 sipe_add_deny, /* rem_permit */
9818 sipe_add_permit, /* rem_deny */
9819 dummy_permit_deny, /* set_permit_deny */
9820 NULL, /* join_chat */
9821 NULL, /* reject_chat */
9822 NULL, /* get_chat_name */
9823 sipe_chat_invite, /* chat_invite */
9824 sipe_chat_leave, /* chat_leave */
9825 NULL, /* chat_whisper */
9826 sipe_chat_send, /* chat_send */
9827 sipe_keep_alive, /* keepalive */
9828 NULL, /* register_user */
9829 NULL, /* get_cb_info */ // deprecated
9830 NULL, /* get_cb_away */ // deprecated
9831 sipe_alias_buddy, /* alias_buddy */
9832 sipe_group_buddy, /* group_buddy */
9833 sipe_rename_group, /* rename_group */
9834 NULL, /* buddy_free */
9835 sipe_convo_closed, /* convo_closed */
9836 purple_normalize_nocase, /* normalize */
9837 NULL, /* set_buddy_icon */
9838 sipe_remove_group, /* remove_group */
9839 NULL, /* get_cb_real_name */ // TODO?
9840 NULL, /* set_chat_topic */
9841 NULL, /* find_blist_chat */
9842 NULL, /* roomlist_get_list */
9843 NULL, /* roomlist_cancel */
9844 NULL, /* roomlist_expand_category */
9845 NULL, /* can_receive_file */
9846 NULL, /* send_file */
9847 NULL, /* new_xfer */
9848 NULL, /* offline_message */
9849 NULL, /* whiteboard_prpl_ops */
9850 sipe_send_raw, /* send_raw */
9851 NULL, /* roomlist_room_serialize */
9852 NULL, /* unregister_user */
9853 NULL, /* send_attention */
9854 NULL, /* get_attention_types */
9855 #if !PURPLE_VERSION_CHECK(2,5,0)
9856 /* Backward compatibility when compiling against 2.4.x API */
9857 (void (*)(void)) /* _purple_reserved4 */
9858 #endif
9859 sizeof(PurplePluginProtocolInfo), /* struct_size */
9860 #if PURPLE_VERSION_CHECK(2,5,0)
9861 sipe_get_account_text_table, /* get_account_text_table */
9862 #if PURPLE_VERSION_CHECK(2,6,0)
9863 NULL, /* initiate_media */
9864 NULL, /* get_media_caps */
9865 #endif
9866 #endif
9870 static PurplePluginInfo info = {
9871 PURPLE_PLUGIN_MAGIC,
9872 PURPLE_MAJOR_VERSION,
9873 PURPLE_MINOR_VERSION,
9874 PURPLE_PLUGIN_PROTOCOL, /**< type */
9875 NULL, /**< ui_requirement */
9876 0, /**< flags */
9877 NULL, /**< dependencies */
9878 PURPLE_PRIORITY_DEFAULT, /**< priority */
9879 "prpl-sipe", /**< id */
9880 "Office Communicator", /**< name */
9881 SIPE_VERSION, /**< version */
9882 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9883 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9884 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9885 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9886 "Gabriel Burt <gburt@novell.com>, " /**< author */
9887 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9888 "pier11 <pier11@operamail.com>", /**< author */
9889 "http://sipe.sourceforge.net/", /**< homepage */
9890 sipe_plugin_load, /**< load */
9891 sipe_plugin_unload, /**< unload */
9892 sipe_plugin_destroy, /**< destroy */
9893 NULL, /**< ui_info */
9894 &prpl_info, /**< extra_info */
9895 NULL,
9896 sipe_actions,
9897 NULL,
9898 NULL,
9899 NULL,
9900 NULL
9903 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9905 GList *entry;
9907 entry = prpl_info.protocol_options;
9908 while (entry) {
9909 purple_account_option_destroy(entry->data);
9910 entry = g_list_delete_link(entry, entry);
9912 prpl_info.protocol_options = NULL;
9914 entry = prpl_info.user_splits;
9915 while (entry) {
9916 purple_account_user_split_destroy(entry->data);
9917 entry = g_list_delete_link(entry, entry);
9919 prpl_info.user_splits = NULL;
9922 static void init_plugin(PurplePlugin *plugin)
9924 PurpleAccountUserSplit *split;
9925 PurpleAccountOption *option;
9927 srand(time(NULL));
9929 #ifdef ENABLE_NLS
9930 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
9931 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
9932 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
9933 textdomain(GETTEXT_PACKAGE);
9934 #endif
9936 purple_plugin_register(plugin);
9938 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
9939 purple_account_user_split_set_reverse(split, FALSE);
9940 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
9942 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
9943 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9945 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
9946 purple_account_option_add_list_item(option, _("Auto"), "auto");
9947 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
9948 purple_account_option_add_list_item(option, _("TCP"), "tcp");
9949 purple_account_option_add_list_item(option, _("UDP"), "udp");
9950 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9952 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
9953 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
9955 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
9956 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9958 #ifdef USE_KERBEROS
9959 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
9960 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9962 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
9963 * No login/password is taken into account if this option present,
9964 * instead used default credentials stored in OS.
9966 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
9967 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9968 #endif
9970 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
9971 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
9972 purple_account_option_add_list_item(option, _("None"), "NONE");
9973 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9975 /** Example: https://server.company.com/EWS/Exchange.asmx */
9976 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
9977 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9979 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
9980 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9982 /** Example: DOMAIN\user or user@company.com */
9983 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
9984 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9986 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
9987 purple_account_option_set_masked(option, TRUE);
9988 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9990 my_protocol = plugin;
9993 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
9996 Local Variables:
9997 mode: c
9998 c-file-style: "bsd"
9999 indent-tabs-mode: t
10000 tab-width: 8
10001 End: