presence: made pidgin show our info in statusbox
[siplcs.git] / src / core / sipe.c
blob27ba9e44fc8fd4819cae42b613295a77f2d20715
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 UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
96 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
98 /* Keep in sync with sipe_transport_type! */
99 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
100 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
102 /* Status identifiers (see also: sipe_status_types()) */
103 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
104 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
105 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
106 /* PURPLE_STATUS_UNAVAILABLE: */
107 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
108 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
109 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
110 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
111 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
112 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
113 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
114 /* PURPLE_STATUS_AWAY: */
115 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
116 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
117 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
118 /** Reuters status (user settable) */
119 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
120 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
121 /* ??? PURPLE_STATUS_MOBILE */
122 /* ??? PURPLE_STATUS_TUNE */
124 /* Status attributes (see also sipe_status_types() */
125 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
126 #define SIPE_STATUS_ATTR_ID_DONT_PUB "do-not-publish"
128 /** Activity (token and description) 2007 */
129 typedef enum
131 SIPE_ACTIVITY_UNSET = 0,
132 SIPE_ACTIVITY_ONLINE,
133 SIPE_ACTIVITY_INACTIVE,
134 SIPE_ACTIVITY_BUSY,
135 SIPE_ACTIVITY_BUSYIDLE,
136 SIPE_ACTIVITY_DND,
137 SIPE_ACTIVITY_BRB,
138 SIPE_ACTIVITY_AWAY,
139 SIPE_ACTIVITY_LUNCH,
140 SIPE_ACTIVITY_OFFLINE,
141 SIPE_ACTIVITY_ON_PHONE,
142 SIPE_ACTIVITY_IN_CONF,
143 SIPE_ACTIVITY_IN_MEETING,
144 SIPE_ACTIVITY_OOF,
145 SIPE_ACTIVITY_URGENT_ONLY,
146 SIPE_ACTIVITY_NUM_TYPES
147 } sipe_activity;
149 static struct sipe_activity_map_struct
151 sipe_activity type;
152 const char *token;
153 const char *desc;
154 const char *status_id;
156 } const sipe_activity_map[] =
158 /* This has nothing to do with Availability numbers, like 3500 (online).
159 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
161 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
162 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
163 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , SIPE_STATUS_ID_IDLE },
164 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
165 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("BusyIdle") , SIPE_STATUS_ID_BUSYIDLE },
166 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
167 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
168 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
169 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , SIPE_STATUS_ID_LUNCH },
170 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
171 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , SIPE_STATUS_ID_ON_PHONE },
172 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , SIPE_STATUS_ID_IN_CONF },
173 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , SIPE_STATUS_ID_IN_MEETING },
174 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
175 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
177 /** @param x is sipe_activity */
178 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
181 /* Action name templates */
182 #define ACTION_NAME_PRESENCE "<presence><%s>"
184 static sipe_activity
185 sipe_get_activity_by_token(const char *token)
187 int i;
189 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
191 if (!strcmp(token, sipe_activity_map[i].token))
192 return sipe_activity_map[i].type;
195 return sipe_activity_map[0].type;
198 static const char *
199 sipe_get_activity_desc_by_token(const char *token)
201 if (!token) return NULL;
203 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
206 /** Allows to send typed messages from chat window again after account reinstantiation. */
207 static void
208 sipe_rejoin_chat(PurpleConversation *conv)
210 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
211 PURPLE_CONV_CHAT(conv)->left)
213 PURPLE_CONV_CHAT(conv)->left = FALSE;
214 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
218 static char *genbranch()
220 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
221 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
222 rand() & 0xFFFF, rand() & 0xFFFF);
226 static char *default_ua = NULL;
227 static const char*
228 sipe_get_useragent(struct sipe_account_data *sip)
230 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
231 if (is_empty(useragent)) {
232 if (!default_ua) {
233 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
234 /* ref: lzodefs.h */
235 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
236 #define SIPE_TARGET_PLATFORM "linux"
237 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
238 #define SIPE_TARGET_PLATFORM "bsd"
239 # elif defined(__APPLE__) || defined(__MACOS__)
240 #define SIPE_TARGET_PLATFORM "macosx"
241 #elif defined(__solaris__) || defined(__sun)
242 #define SIPE_TARGET_PLATFORM "sun"
243 #elif defined(_WIN32)
244 #define SIPE_TARGET_PLATFORM "win"
245 #else
246 #define SIPE_TARGET_PLATFORM "generic"
247 #endif
249 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
250 #define SIPE_TARGET_ARCH "x86_64"
251 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
252 #define SIPE_TARGET_ARCH "i386"
253 #elif defined(__ppc64__)
254 #define SIPE_TARGET_ARCH "ppc64"
255 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
256 #define SIPE_TARGET_ARCH "ppc"
257 #else
258 #define SIPE_TARGET_ARCH "other"
259 #endif
261 default_ua = g_strdup_printf("Purple/%s Sipe/%s (%s-%s; %s)",
262 purple_core_get_version(),
263 SIPE_VERSION,
264 SIPE_TARGET_PLATFORM,
265 SIPE_TARGET_ARCH,
266 sip->server_version ? sip->server_version : "");
268 useragent = default_ua;
270 return useragent;
273 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
274 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
276 return "sipe";
279 static void sipe_plugin_destroy(PurplePlugin *plugin);
281 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
283 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
284 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
285 gpointer data);
287 static void sipe_close(PurpleConnection *gc);
289 static void send_presence_status(struct sipe_account_data *sip);
291 static void sendout_pkt(PurpleConnection *gc, const char *buf);
293 static void sipe_keep_alive(PurpleConnection *gc)
295 struct sipe_account_data *sip = gc->proto_data;
296 if (sip->transport == SIPE_TRANSPORT_UDP) {
297 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
298 gchar buf[2] = {0, 0};
299 purple_debug_info("sipe", "sending keep alive\n");
300 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
301 } else {
302 time_t now = time(NULL);
303 if ((sip->keepalive_timeout > 0) &&
304 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
305 #if PURPLE_VERSION_CHECK(2,4,0)
306 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
307 #endif
309 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
310 sendout_pkt(gc, "\r\n\r\n");
311 sip->last_keepalive = now;
316 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
318 struct sip_connection *ret = NULL;
319 GSList *entry = sip->openconns;
320 while (entry) {
321 ret = entry->data;
322 if (ret->fd == fd) return ret;
323 entry = entry->next;
325 return NULL;
328 static void sipe_auth_free(struct sip_auth *auth)
330 g_free(auth->opaque);
331 auth->opaque = NULL;
332 g_free(auth->realm);
333 auth->realm = NULL;
334 g_free(auth->target);
335 auth->target = NULL;
336 auth->type = AUTH_TYPE_UNSET;
337 auth->retries = 0;
338 auth->expires = 0;
339 g_free(auth->gssapi_data);
340 auth->gssapi_data = NULL;
341 sip_sec_destroy_context(auth->gssapi_context);
342 auth->gssapi_context = NULL;
345 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
347 struct sip_connection *ret = g_new0(struct sip_connection, 1);
348 ret->fd = fd;
349 sip->openconns = g_slist_append(sip->openconns, ret);
350 return ret;
353 static void connection_remove(struct sipe_account_data *sip, int fd)
355 struct sip_connection *conn = connection_find(sip, fd);
356 if (conn) {
357 sip->openconns = g_slist_remove(sip->openconns, conn);
358 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
359 g_free(conn->inbuf);
360 g_free(conn);
364 static void connection_free_all(struct sipe_account_data *sip)
366 struct sip_connection *ret = NULL;
367 GSList *entry = sip->openconns;
368 while (entry) {
369 ret = entry->data;
370 connection_remove(sip, ret->fd);
371 entry = sip->openconns;
375 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
377 gchar noncecount[9];
378 const char *authuser = sip->authuser;
379 gchar *response;
380 gchar *ret;
382 if (!authuser || strlen(authuser) < 1) {
383 authuser = sip->username;
386 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
387 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
389 // If we have a signature for the message, include that
390 if (msg->signature) {
391 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);
394 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
395 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
396 gchar *gssapi_data;
397 gchar *opaque;
399 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
400 &(auth->expires),
401 auth->type,
402 purple_account_get_bool(sip->account, "sso", TRUE),
403 sip->authdomain ? sip->authdomain : "",
404 authuser,
405 sip->password,
406 auth->target,
407 auth->gssapi_data);
408 if (!gssapi_data || !auth->gssapi_context) {
409 sip->gc->wants_to_die = TRUE;
410 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
411 return NULL;
414 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
415 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
416 g_free(opaque);
417 g_free(gssapi_data);
418 return ret;
421 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
423 } else { /* Digest */
425 /* Calculate new session key */
426 if (!auth->opaque) {
427 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
428 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
429 authuser, auth->realm, sip->password,
430 auth->gssapi_data, NULL);
433 sprintf(noncecount, "%08d", auth->nc++);
434 response = purple_cipher_http_digest_calculate_response("md5",
435 msg->method, msg->target, NULL, NULL,
436 auth->gssapi_data, noncecount, NULL,
437 auth->opaque);
438 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
440 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);
441 g_free(response);
442 return ret;
446 static char *parse_attribute(const char *attrname, const char *source)
448 const char *tmp, *tmp2;
449 char *retval = NULL;
450 int len = strlen(attrname);
452 if (!strncmp(source, attrname, len)) {
453 tmp = source + len;
454 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
455 if (tmp2)
456 retval = g_strndup(tmp, tmp2 - tmp);
457 else
458 retval = g_strdup(tmp);
461 return retval;
464 static void fill_auth(gchar *hdr, struct sip_auth *auth)
466 int i;
467 gchar **parts;
469 if (!hdr) {
470 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
471 return;
474 if (!g_strncasecmp(hdr, "NTLM", 4)) {
475 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
476 auth->type = AUTH_TYPE_NTLM;
477 hdr += 5;
478 auth->nc = 1;
479 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
480 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
481 auth->type = AUTH_TYPE_KERBEROS;
482 hdr += 9;
483 auth->nc = 3;
484 } else {
485 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
486 auth->type = AUTH_TYPE_DIGEST;
487 hdr += 7;
490 parts = g_strsplit(hdr, "\", ", 0);
491 for (i = 0; parts[i]; i++) {
492 char *tmp;
494 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
496 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
497 g_free(auth->gssapi_data);
498 auth->gssapi_data = tmp;
500 if (auth->type == AUTH_TYPE_NTLM) {
501 /* NTLM module extracts nonce from gssapi-data */
502 auth->nc = 3;
505 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
506 /* Only used with AUTH_TYPE_DIGEST */
507 g_free(auth->gssapi_data);
508 auth->gssapi_data = tmp;
509 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
510 g_free(auth->opaque);
511 auth->opaque = tmp;
512 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
513 g_free(auth->realm);
514 auth->realm = tmp;
516 if (auth->type == AUTH_TYPE_DIGEST) {
517 /* Throw away old session key */
518 g_free(auth->opaque);
519 auth->opaque = NULL;
520 auth->nc = 1;
523 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
524 g_free(auth->target);
525 auth->target = tmp;
528 g_strfreev(parts);
530 return;
533 static void sipe_canwrite_cb(gpointer data,
534 SIPE_UNUSED_PARAMETER gint source,
535 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
537 PurpleConnection *gc = data;
538 struct sipe_account_data *sip = gc->proto_data;
539 gsize max_write;
540 gssize written;
542 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
544 if (max_write == 0) {
545 if (sip->tx_handler != 0){
546 purple_input_remove(sip->tx_handler);
547 sip->tx_handler = 0;
549 return;
552 written = write(sip->fd, sip->txbuf->outptr, max_write);
554 if (written < 0 && errno == EAGAIN)
555 written = 0;
556 else if (written <= 0) {
557 /*TODO: do we really want to disconnect on a failure to write?*/
558 purple_connection_error(gc, _("Could not write"));
559 return;
562 purple_circ_buffer_mark_read(sip->txbuf, written);
565 static void sipe_canwrite_cb_ssl(gpointer data,
566 SIPE_UNUSED_PARAMETER gint src,
567 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
569 PurpleConnection *gc = data;
570 struct sipe_account_data *sip = gc->proto_data;
571 gsize max_write;
572 gssize written;
574 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
576 if (max_write == 0) {
577 if (sip->tx_handler != 0) {
578 purple_input_remove(sip->tx_handler);
579 sip->tx_handler = 0;
580 return;
584 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
586 if (written < 0 && errno == EAGAIN)
587 written = 0;
588 else if (written <= 0) {
589 /*TODO: do we really want to disconnect on a failure to write?*/
590 purple_connection_error(gc, _("Could not write"));
591 return;
594 purple_circ_buffer_mark_read(sip->txbuf, written);
597 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
599 static void send_later_cb(gpointer data, gint source,
600 SIPE_UNUSED_PARAMETER const gchar *error)
602 PurpleConnection *gc = data;
603 struct sipe_account_data *sip;
604 struct sip_connection *conn;
606 if (!PURPLE_CONNECTION_IS_VALID(gc))
608 if (source >= 0)
609 close(source);
610 return;
613 if (source < 0) {
614 purple_connection_error(gc, _("Could not connect"));
615 return;
618 sip = gc->proto_data;
619 sip->fd = source;
620 sip->connecting = FALSE;
621 sip->last_keepalive = time(NULL);
623 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
625 /* If there is more to write now, we need to register a handler */
626 if (sip->txbuf->bufused > 0)
627 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
629 conn = connection_create(sip, source);
630 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
633 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
635 struct sipe_account_data *sip;
636 struct sip_connection *conn;
638 if (!PURPLE_CONNECTION_IS_VALID(gc))
640 if (gsc) purple_ssl_close(gsc);
641 return NULL;
644 sip = gc->proto_data;
645 sip->fd = gsc->fd;
646 sip->gsc = gsc;
647 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
648 sip->connecting = FALSE;
649 sip->last_keepalive = time(NULL);
651 conn = connection_create(sip, gsc->fd);
653 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
655 return sip;
658 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
659 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
661 PurpleConnection *gc = data;
662 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
663 if (sip == NULL) return;
665 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
667 /* If there is more to write now */
668 if (sip->txbuf->bufused > 0) {
669 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
674 static void sendlater(PurpleConnection *gc, const char *buf)
676 struct sipe_account_data *sip = gc->proto_data;
678 if (!sip->connecting) {
679 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
680 if (sip->transport == SIPE_TRANSPORT_TLS){
681 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
682 } else {
683 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
684 purple_connection_error(gc, _("Could not create socket"));
687 sip->connecting = TRUE;
690 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
691 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
693 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
696 static void sendout_pkt(PurpleConnection *gc, const char *buf)
698 struct sipe_account_data *sip = gc->proto_data;
699 time_t currtime = time(NULL);
700 int writelen = strlen(buf);
701 char *tmp;
703 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
704 g_free(tmp);
705 if (sip->transport == SIPE_TRANSPORT_UDP) {
706 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
707 purple_debug_info("sipe", "could not send packet\n");
709 } else {
710 int ret;
711 if (sip->fd < 0) {
712 sendlater(gc, buf);
713 return;
716 if (sip->tx_handler) {
717 ret = -1;
718 errno = EAGAIN;
719 } else{
720 if (sip->gsc){
721 ret = purple_ssl_write(sip->gsc, buf, writelen);
722 }else{
723 ret = write(sip->fd, buf, writelen);
727 if (ret < 0 && errno == EAGAIN)
728 ret = 0;
729 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
730 sendlater(gc, buf);
731 return;
734 if (ret < writelen) {
735 if (!sip->tx_handler){
736 if (sip->gsc){
737 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
739 else{
740 sip->tx_handler = purple_input_add(sip->fd,
741 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
742 gc);
746 /* XXX: is it OK to do this? You might get part of a request sent
747 with part of another. */
748 if (sip->txbuf->bufused > 0)
749 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
751 purple_circ_buffer_append(sip->txbuf, buf + ret,
752 writelen - ret);
757 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
759 sendout_pkt(gc, buf);
760 return len;
763 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
765 GSList *tmp = msg->headers;
766 gchar *name;
767 gchar *value;
768 GString *outstr = g_string_new("");
769 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
770 while (tmp) {
771 name = ((struct siphdrelement*) (tmp->data))->name;
772 value = ((struct siphdrelement*) (tmp->data))->value;
773 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
774 tmp = g_slist_next(tmp);
776 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
777 sendout_pkt(sip->gc, outstr->str);
778 g_string_free(outstr, TRUE);
781 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
783 gchar * buf;
785 if (sip->registrar.type == AUTH_TYPE_UNSET) {
786 return;
789 if (sip->registrar.gssapi_context) {
790 struct sipmsg_breakdown msgbd;
791 gchar *signature_input_str;
792 msgbd.msg = msg;
793 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
794 msgbd.rand = g_strdup_printf("%08x", g_random_int());
795 sip->registrar.ntlm_num++;
796 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
797 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
798 if (signature_input_str != NULL) {
799 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
800 msg->signature = signature_hex;
801 msg->rand = g_strdup(msgbd.rand);
802 msg->num = g_strdup(msgbd.num);
803 g_free(signature_input_str);
805 sipmsg_breakdown_free(&msgbd);
808 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
809 buf = auth_header(sip, &sip->registrar, msg);
810 if (buf) {
811 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
813 g_free(buf);
814 } 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")) {
815 sip->registrar.nc = 3;
816 #ifdef USE_KERBEROS
817 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
818 #endif
819 sip->registrar.type = AUTH_TYPE_NTLM;
820 #ifdef USE_KERBEROS
821 } else {
822 sip->registrar.type = AUTH_TYPE_KERBEROS;
824 #endif
827 buf = auth_header(sip, &sip->registrar, msg);
828 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
829 g_free(buf);
830 } else {
831 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
835 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
836 const char *text, const char *body)
838 gchar *name;
839 gchar *value;
840 GString *outstr = g_string_new("");
841 struct sipe_account_data *sip = gc->proto_data;
842 gchar *contact;
843 GSList *tmp;
844 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
846 contact = get_contact(sip);
847 sipmsg_add_header(msg, "Contact", contact);
848 g_free(contact);
850 if (body) {
851 gchar len[12];
852 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
853 sipmsg_add_header(msg, "Content-Length", len);
854 } else {
855 sipmsg_add_header(msg, "Content-Length", "0");
858 msg->response = code;
860 sipmsg_strip_headers(msg, keepers);
861 sipmsg_merge_new_headers(msg);
862 sign_outgoing_message(msg, sip, msg->method);
864 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
865 tmp = msg->headers;
866 while (tmp) {
867 name = ((struct siphdrelement*) (tmp->data))->name;
868 value = ((struct siphdrelement*) (tmp->data))->value;
870 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
871 tmp = g_slist_next(tmp);
873 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
874 sendout_pkt(gc, outstr->str);
875 g_string_free(outstr, TRUE);
878 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
880 if (sip->transactions) {
881 sip->transactions = g_slist_remove(sip->transactions, trans);
882 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
884 if (trans->msg) sipmsg_free(trans->msg);
885 if (trans->payload) {
886 (*trans->payload->destroy)(trans->payload->data);
887 g_free(trans->payload);
889 g_free(trans->key);
890 g_free(trans);
894 static struct transaction *
895 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
897 gchar *call_id = NULL;
898 gchar *cseq = NULL;
899 struct transaction *trans = g_new0(struct transaction, 1);
901 trans->time = time(NULL);
902 trans->msg = (struct sipmsg *)msg;
903 call_id = sipmsg_find_header(trans->msg, "Call-ID");
904 cseq = sipmsg_find_header(trans->msg, "CSeq");
905 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
906 trans->callback = callback;
907 sip->transactions = g_slist_append(sip->transactions, trans);
908 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
909 return trans;
912 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
914 struct transaction *trans;
915 GSList *transactions = sip->transactions;
916 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
917 gchar *cseq = sipmsg_find_header(msg, "CSeq");
918 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
920 while (transactions) {
921 trans = transactions->data;
922 if (!g_strcasecmp(trans->key, key)) {
923 g_free(key);
924 return trans;
926 transactions = transactions->next;
929 g_free(key);
930 return NULL;
933 struct transaction *
934 send_sip_request(PurpleConnection *gc, const gchar *method,
935 const gchar *url, const gchar *to, const gchar *addheaders,
936 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
938 struct sipe_account_data *sip = gc->proto_data;
939 const char *addh = "";
940 char *buf;
941 struct sipmsg *msg;
942 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
943 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
944 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
945 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
946 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
947 gchar *route = g_strdup("");
948 gchar *epid = get_epid(sip);
949 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
950 struct transaction *trans = NULL;
952 if (dialog && dialog->routes)
954 GSList *iter = dialog->routes;
956 while(iter)
958 char *tmp = route;
959 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
960 g_free(tmp);
961 iter = g_slist_next(iter);
965 if (!ourtag && !dialog) {
966 ourtag = gentag();
969 if (!strcmp(method, "REGISTER")) {
970 if (sip->regcallid) {
971 g_free(callid);
972 callid = g_strdup(sip->regcallid);
973 } else {
974 sip->regcallid = g_strdup(callid);
976 cseq = ++sip->cseq;
979 if (addheaders) addh = addheaders;
981 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
982 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
983 "From: <sip:%s>%s%s;epid=%s\r\n"
984 "To: <%s>%s%s%s%s\r\n"
985 "Max-Forwards: 70\r\n"
986 "CSeq: %d %s\r\n"
987 "User-Agent: %s\r\n"
988 "Call-ID: %s\r\n"
989 "%s%s"
990 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
991 method,
992 dialog && dialog->request ? dialog->request : url,
993 TRANSPORT_DESCRIPTOR,
994 purple_network_get_my_ip(-1),
995 sip->listenport,
996 branch ? ";branch=" : "",
997 branch ? branch : "",
998 sip->username,
999 ourtag ? ";tag=" : "",
1000 ourtag ? ourtag : "",
1001 epid,
1003 theirtag ? ";tag=" : "",
1004 theirtag ? theirtag : "",
1005 theirepid ? ";epid=" : "",
1006 theirepid ? theirepid : "",
1007 cseq,
1008 method,
1009 sipe_get_useragent(sip),
1010 callid,
1011 route,
1012 addh,
1013 body ? (gsize) strlen(body) : 0,
1014 body ? body : "");
1017 //printf ("parsing msg buf:\n%s\n\n", buf);
1018 msg = sipmsg_parse_msg(buf);
1020 g_free(buf);
1021 g_free(ourtag);
1022 g_free(theirtag);
1023 g_free(theirepid);
1024 g_free(branch);
1025 g_free(callid);
1026 g_free(route);
1027 g_free(epid);
1029 sign_outgoing_message (msg, sip, method);
1031 buf = sipmsg_to_string (msg);
1033 /* add to ongoing transactions */
1034 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1035 if (strcmp(method, "ACK")) {
1036 trans = transactions_add_buf(sip, msg, tc);
1037 } else {
1038 sipmsg_free(msg);
1040 sendout_pkt(gc, buf);
1041 g_free(buf);
1043 return trans;
1047 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1049 static void
1050 send_soap_request_with_cb(struct sipe_account_data *sip,
1051 gchar *from0,
1052 gchar *body,
1053 TransCallback callback,
1054 struct transaction_payload *payload)
1056 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1057 gchar *contact = get_contact(sip);
1058 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1059 "Content-Type: application/SOAP+xml\r\n",contact);
1061 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1062 trans->payload = payload;
1064 g_free(from);
1065 g_free(contact);
1066 g_free(hdr);
1069 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1071 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1074 static char *get_contact_register(struct sipe_account_data *sip)
1076 char *epid = get_epid(sip);
1077 char *uuid = generateUUIDfromEPID(epid);
1078 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);
1079 g_free(uuid);
1080 g_free(epid);
1081 return(buf);
1084 static void do_register_exp(struct sipe_account_data *sip, int expire)
1086 char *uri;
1087 char *expires;
1088 char *to;
1089 char *contact;
1090 char *hdr;
1092 if (!sip->sipdomain) return;
1094 uri = sip_uri_from_name(sip->sipdomain);
1095 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1096 to = sip_uri_self(sip);
1097 contact = get_contact_register(sip);
1098 hdr = g_strdup_printf("Contact: %s\r\n"
1099 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1100 "Event: registration\r\n"
1101 "Allow-Events: presence\r\n"
1102 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1103 "%s", contact, expires);
1104 g_free(contact);
1105 g_free(expires);
1107 sip->registerstatus = 1;
1109 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1110 process_register_response);
1112 g_free(hdr);
1113 g_free(uri);
1114 g_free(to);
1117 static void do_register_cb(struct sipe_account_data *sip,
1118 SIPE_UNUSED_PARAMETER void *unused)
1120 do_register_exp(sip, -1);
1121 sip->reregister_set = FALSE;
1124 static void do_register(struct sipe_account_data *sip)
1126 do_register_exp(sip, -1);
1129 static void
1130 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1132 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1133 send_soap_request(sip, body);
1134 g_free(body);
1137 static void
1138 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1140 if (allow) {
1141 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1142 } else {
1143 purple_debug_info("sipe", "Blocking contact %s\n", who);
1146 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1149 static
1150 void sipe_auth_user_cb(void * data)
1152 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1153 if (!job) return;
1155 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1156 g_free(job);
1159 static
1160 void sipe_deny_user_cb(void * data)
1162 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1163 if (!job) return;
1165 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1166 g_free(job);
1169 static void
1170 sipe_add_permit(PurpleConnection *gc, const char *name)
1172 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1173 sipe_contact_allow_deny(sip, name, TRUE);
1176 static void
1177 sipe_add_deny(PurpleConnection *gc, const char *name)
1179 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1180 sipe_contact_allow_deny(sip, name, FALSE);
1183 /*static void
1184 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1186 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1187 sipe_contact_set_acl(sip, name, "");
1190 static void
1191 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1193 xmlnode *watchers;
1194 xmlnode *watcher;
1195 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1196 if (msg->response != 0 && msg->response != 200) return;
1198 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1200 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1201 if (!watchers) return;
1203 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1204 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1205 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1206 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1208 // TODO pull out optional displayName to pass as alias
1209 if (remote_user) {
1210 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1211 job->who = remote_user;
1212 job->sip = sip;
1213 purple_account_request_authorization(
1214 sip->account,
1215 remote_user,
1216 _("you"), /* id */
1217 alias,
1218 NULL, /* message */
1219 on_list,
1220 sipe_auth_user_cb,
1221 sipe_deny_user_cb,
1222 (void *) job);
1227 xmlnode_free(watchers);
1228 return;
1231 static void
1232 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1234 PurpleGroup * purple_group = purple_find_group(group->name);
1235 if (!purple_group) {
1236 purple_group = purple_group_new(group->name);
1237 purple_blist_add_group(purple_group, NULL);
1240 if (purple_group) {
1241 group->purple_group = purple_group;
1242 sip->groups = g_slist_append(sip->groups, group);
1243 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1244 } else {
1245 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1249 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1251 struct sipe_group *group;
1252 GSList *entry;
1253 if (sip == NULL) {
1254 return NULL;
1257 entry = sip->groups;
1258 while (entry) {
1259 group = entry->data;
1260 if (group->id == id) {
1261 return group;
1263 entry = entry->next;
1265 return NULL;
1268 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1270 struct sipe_group *group;
1271 GSList *entry;
1272 if (sip == NULL) {
1273 return NULL;
1276 entry = sip->groups;
1277 while (entry) {
1278 group = entry->data;
1279 if (!strcmp(group->name, name)) {
1280 return group;
1282 entry = entry->next;
1284 return NULL;
1287 static void
1288 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1290 gchar *body;
1291 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1292 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1293 send_soap_request(sip, body);
1294 g_free(body);
1295 g_free(group->name);
1296 group->name = g_strdup(name);
1300 * Only appends if no such value already stored.
1301 * Like Set in Java.
1303 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1304 GSList * res = list;
1305 if (!g_slist_find_custom(list, data, func)) {
1306 res = g_slist_insert_sorted(list, data, func);
1308 return res;
1311 static int
1312 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1313 return group1->id - group2->id;
1317 * Returns string like "2 4 7 8" - group ids buddy belong to.
1319 static gchar *
1320 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1321 int i = 0;
1322 gchar *res;
1323 //creating array from GList, converting int to gchar*
1324 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1325 GSList *entry = buddy->groups;
1326 while (entry) {
1327 struct sipe_group * group = entry->data;
1328 ids_arr[i] = g_strdup_printf("%d", group->id);
1329 entry = entry->next;
1330 i++;
1332 ids_arr[i] = NULL;
1333 res = g_strjoinv(" ", ids_arr);
1334 g_strfreev(ids_arr);
1335 return res;
1339 * Sends buddy update to server
1341 static void
1342 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1344 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1345 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1347 if (buddy && purple_buddy) {
1348 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1349 gchar *body;
1350 gchar *groups = sipe_get_buddy_groups_string(buddy);
1351 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1353 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1354 alias, groups, "true", buddy->name, sip->contacts_delta++
1356 send_soap_request(sip, body);
1357 g_free(groups);
1358 g_free(body);
1362 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1364 if (msg->response == 200) {
1365 struct sipe_group *group;
1366 struct group_user_context *ctx = trans->payload->data;
1367 xmlnode *xml;
1368 xmlnode *node;
1369 char *group_id;
1370 struct sipe_buddy *buddy;
1372 xml = xmlnode_from_str(msg->body, msg->bodylen);
1373 if (!xml) {
1374 return FALSE;
1377 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1378 if (!node) {
1379 xmlnode_free(xml);
1380 return FALSE;
1383 group_id = xmlnode_get_data(node);
1384 if (!group_id) {
1385 xmlnode_free(xml);
1386 return FALSE;
1389 group = g_new0(struct sipe_group, 1);
1390 group->id = (int)g_ascii_strtod(group_id, NULL);
1391 g_free(group_id);
1392 group->name = g_strdup(ctx->group_name);
1394 sipe_group_add(sip, group);
1396 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1397 if (buddy) {
1398 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1401 sipe_group_set_user(sip, ctx->user_name);
1403 xmlnode_free(xml);
1404 return TRUE;
1406 return FALSE;
1409 static void sipe_group_context_destroy(gpointer data)
1411 struct group_user_context *ctx = data;
1412 g_free(ctx->group_name);
1413 g_free(ctx->user_name);
1414 g_free(ctx);
1417 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1419 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1420 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1421 gchar *body;
1422 ctx->group_name = g_strdup(name);
1423 ctx->user_name = g_strdup(who);
1424 payload->destroy = sipe_group_context_destroy;
1425 payload->data = ctx;
1427 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1428 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1429 g_free(body);
1433 * Data structure for scheduled actions
1436 struct scheduled_action {
1438 * Name of action.
1439 * Format is <Event>[<Data>...]
1440 * Example: <presence><sip:user@domain.com> or <registration>
1442 gchar *name;
1443 guint timeout_handler;
1444 gboolean repetitive;
1445 Action action;
1446 GDestroyNotify destroy;
1447 struct sipe_account_data *sip;
1448 void *payload;
1452 * A timer callback
1453 * Should return FALSE if repetitive action is not needed
1455 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1457 gboolean ret;
1458 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1459 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1460 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1461 (sched_action->action)(sched_action->sip, sched_action->payload);
1462 ret = sched_action->repetitive;
1463 if (sched_action->destroy) {
1464 (*sched_action->destroy)(sched_action->payload);
1466 g_free(sched_action->name);
1467 g_free(sched_action);
1468 return ret;
1472 * Kills action timer effectively cancelling
1473 * scheduled action
1475 * @param name of action
1477 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1479 GSList *entry;
1481 if (!sip->timeouts || !name) return;
1483 entry = sip->timeouts;
1484 while (entry) {
1485 struct scheduled_action *sched_action = entry->data;
1486 if(!strcmp(sched_action->name, name)) {
1487 GSList *to_delete = entry;
1488 entry = entry->next;
1489 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1490 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1491 purple_timeout_remove(sched_action->timeout_handler);
1492 if (sched_action->destroy) {
1493 (*sched_action->destroy)(sched_action->payload);
1495 g_free(sched_action->name);
1496 g_free(sched_action);
1497 } else {
1498 entry = entry->next;
1503 static void
1504 sipe_schedule_action0(const gchar *name,
1505 int timeout,
1506 gboolean isSeconds,
1507 Action action,
1508 GDestroyNotify destroy,
1509 struct sipe_account_data *sip,
1510 void *payload)
1512 struct scheduled_action *sched_action;
1514 /* Make sure each action only exists once */
1515 sipe_cancel_scheduled_action(sip, name);
1517 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1518 sched_action = g_new0(struct scheduled_action, 1);
1519 sched_action->repetitive = FALSE;
1520 sched_action->name = g_strdup(name);
1521 sched_action->action = action;
1522 sched_action->destroy = destroy;
1523 sched_action->sip = sip;
1524 sched_action->payload = payload;
1525 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1526 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1527 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1528 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1531 void
1532 sipe_schedule_action(const gchar *name,
1533 int timeout,
1534 Action action,
1535 GDestroyNotify destroy,
1536 struct sipe_account_data *sip,
1537 void *payload)
1539 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1543 * Same as sipe_schedule_action() but timeout is in milliseconds.
1545 static void
1546 sipe_schedule_action_msec(const gchar *name,
1547 int timeout,
1548 Action action,
1549 GDestroyNotify destroy,
1550 struct sipe_account_data *sip,
1551 void *payload)
1553 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1556 static void
1557 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1558 time_t calculate_from);
1560 static int
1561 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1563 static const char*
1564 sipe_get_status_by_availability(int avail,
1565 const char* activity);
1567 static void
1568 sipe_apply_calendar_status(struct sipe_account_data *sip,
1569 struct sipe_buddy *sbuddy,
1570 const char *status_id)
1572 time_t cal_avail_since;
1573 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1574 int avail;
1575 gchar *self_uri = sip_uri_self(sip);
1577 if (!sbuddy) return;
1579 if (cal_status < SIPE_CAL_NO_DATA) {
1580 purple_debug_info("sipe", "update_calendar_status_cb: cal_status : %d for %s\n", cal_status, sbuddy->name);
1581 purple_debug_info("sipe", "update_calendar_status_cb: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1584 /* scheduled Cal update call */
1585 if (!status_id) {
1586 status_id = sbuddy->last_non_cal_status_id;
1587 g_free(sbuddy->activity);
1588 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1591 /* adjust to calendar status */
1592 if (cal_status != SIPE_CAL_NO_DATA) {
1593 purple_debug_info("sipe", "update_calendar_status_cb: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1595 if (cal_status == SIPE_CAL_BUSY
1596 && cal_avail_since > sbuddy->user_avail_since
1597 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1599 status_id = SIPE_STATUS_ID_IN_MEETING;
1600 g_free(sbuddy->activity);
1601 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1603 avail = sipe_get_availability_by_status(status_id, NULL);
1605 purple_debug_info("sipe", "update_calendar_status_cb: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1606 if (cal_avail_since > sbuddy->activity_since) {
1607 if (cal_status == SIPE_CAL_OOF
1608 && avail >= 15000) /* 12000 in 2007 */
1610 g_free(sbuddy->activity);
1611 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1616 /* then set status_id actually */
1617 purple_debug_info("sipe", "sipe_got_user_status: to %s for %s\n", status_id, sbuddy->name);
1618 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1620 /* set our account state to the one in roaming (including calendar info) */
1621 if (!strcmp(sbuddy->name, self_uri)) {
1622 PurpleStatus *status = purple_account_get_active_status(sip->account);
1623 const gchar *curr_note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
1625 g_free(sip->status);
1626 if (strcmp(status_id, SIPE_STATUS_ID_OFFLINE)) { /* not offline */
1627 sip->status = g_strdup(status_id);
1628 } else {
1629 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1632 purple_debug_info("sipe", "sipe_got_user_status: to %s for the account\n", sip->status);
1633 purple_prpl_got_account_status(sip->account, sip->status,
1634 SIPE_STATUS_ATTR_ID_MESSAGE, curr_note,
1635 SIPE_STATUS_ATTR_ID_DONT_PUB, TRUE,
1636 NULL);
1638 g_free(self_uri);
1641 static void
1642 sipe_got_user_status(struct sipe_account_data *sip,
1643 const char* uri,
1644 const char *status_id)
1646 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1648 if (!sbuddy) return;
1650 /* Check if on 2005 system contact's calendar,
1651 * then set/preserve it.
1653 if (!sip->ocs2007) {
1654 sipe_apply_calendar_status(sip, sbuddy, status_id);
1655 } else {
1656 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1660 static void
1661 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1662 struct sipe_buddy *sbuddy,
1663 struct sipe_account_data *sip)
1665 sipe_apply_calendar_status(sip, sbuddy, NULL);
1669 * Updates contact's status
1670 * based on their calendar information.
1672 * Applicability: 2005 systems
1674 static void
1675 update_calendar_status(struct sipe_account_data *sip)
1677 purple_debug_info("sipe", "update_calendar_status() started.\n");
1678 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1680 /* repeat scheduling */
1681 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1685 * Schedules process of contacts' status update
1686 * based on their calendar information.
1687 * Should be scheduled to the beginning of every
1688 * 15 min interval, like:
1689 * 13:00, 13:15, 13:30, 13:45, etc.
1691 * Applicability: 2005 systems
1693 static void
1694 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1695 time_t calculate_from)
1697 int interval = 15*60;
1698 /** start of the beginning of closest 15 min interval. */
1699 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1701 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1702 asctime(localtime(&calculate_from)));
1703 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1704 asctime(localtime(&next_start)));
1706 sipe_schedule_action("<+2005-cal-status>",
1707 (int)(next_start - time(NULL)),
1708 (Action)update_calendar_status,
1709 NULL,
1710 sip,
1711 NULL);
1715 * Schedules process of self status publish
1716 * based on own calendar information.
1717 * Should be scheduled to the beginning of every
1718 * 15 min interval, like:
1719 * 13:00, 13:15, 13:30, 13:45, etc.
1721 * Applicability: 2007+ systems
1723 static void
1724 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1725 time_t calculate_from)
1727 int interval = 5*60;
1728 /** start of the beginning of closest 5 min interval. */
1729 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1731 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1732 asctime(localtime(&calculate_from)));
1733 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1734 asctime(localtime(&next_start)));
1736 sipe_schedule_action("<+2007-cal-status>",
1737 (int)(next_start - time(NULL)),
1738 (Action)publish_calendar_status_self,
1739 NULL,
1740 sip,
1741 NULL);
1744 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1746 /** Should be g_free()'d
1748 static gchar *
1749 sipe_get_subscription_key(gchar *event,
1750 gchar *with)
1752 gchar *key = NULL;
1754 if (is_empty(event)) return NULL;
1756 if (event && !g_ascii_strcasecmp(event, "presence")) {
1757 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1758 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1760 /* @TODO drop participated buddies' just_added flag */
1761 } else if (event) {
1762 /* Subscription is identified by <event> key */
1763 key = g_strdup_printf("<%s>", event);
1766 return key;
1769 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1770 SIPE_UNUSED_PARAMETER struct transaction *trans)
1772 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1773 gchar *event = sipmsg_find_header(msg, "Event");
1774 gchar *key;
1776 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1777 if (!event) {
1778 struct sipmsg *request_msg = trans->msg;
1779 event = sipmsg_find_header(request_msg, "Event");
1782 key = sipe_get_subscription_key(event, with);
1784 /* 200 OK; 481 Call Leg Does Not Exist */
1785 if (key && (msg->response == 200 || msg->response == 481)) {
1786 if (g_hash_table_lookup(sip->subscriptions, key)) {
1787 g_hash_table_remove(sip->subscriptions, key);
1788 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1792 /* create/store subscription dialog if not yet */
1793 if (msg->response == 200) {
1794 gchar *callid = sipmsg_find_header(msg, "Call-ID");
1795 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1797 if (key) {
1798 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1799 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1801 subscription->dialog.callid = g_strdup(callid);
1802 subscription->dialog.cseq = atoi(cseq);
1803 subscription->dialog.with = g_strdup(with);
1804 subscription->event = g_strdup(event);
1805 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1807 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1810 g_free(cseq);
1813 g_free(key);
1814 g_free(with);
1816 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1818 process_incoming_notify(sip, msg, FALSE, FALSE);
1820 return TRUE;
1823 static void sipe_subscribe_resource_uri(const char *name,
1824 SIPE_UNUSED_PARAMETER gpointer value,
1825 gchar **resources_uri)
1827 gchar *tmp = *resources_uri;
1828 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1829 g_free(tmp);
1832 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1834 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1835 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1836 gchar *tmp = *resources_uri;
1838 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1840 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1841 g_free(tmp);
1845 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1846 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1847 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1848 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1849 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1852 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1854 gchar *key;
1855 gchar *contact = get_contact(sip);
1856 gchar *request;
1857 gchar *content;
1858 gchar *require = "";
1859 gchar *accept = "";
1860 gchar *autoextend = "";
1861 gchar *content_type;
1862 struct sip_dialog *dialog;
1864 if (sip->ocs2007) {
1865 require = ", categoryList";
1866 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1867 content_type = "application/msrtc-adrl-categorylist+xml";
1868 content = g_strdup_printf(
1869 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1870 "<action name=\"subscribe\" id=\"63792024\">\n"
1871 "<adhocList>\n%s</adhocList>\n"
1872 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1873 "<category name=\"calendarData\"/>\n"
1874 "<category name=\"contactCard\"/>\n"
1875 "<category name=\"note\"/>\n"
1876 "<category name=\"state\"/>\n"
1877 "</categoryList>\n"
1878 "</action>\n"
1879 "</batchSub>", sip->username, resources_uri);
1880 } else {
1881 autoextend = "Supported: com.microsoft.autoextend\r\n";
1882 content_type = "application/adrl+xml";
1883 content = g_strdup_printf(
1884 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1885 "<create xmlns=\"\">\n%s</create>\n"
1886 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1888 g_free(resources_uri);
1890 request = g_strdup_printf(
1891 "Require: adhoclist%s\r\n"
1892 "Supported: eventlist\r\n"
1893 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1894 "Supported: ms-piggyback-first-notify\r\n"
1895 "%sSupported: ms-benotify\r\n"
1896 "Proxy-Require: ms-benotify\r\n"
1897 "Event: presence\r\n"
1898 "Content-Type: %s\r\n"
1899 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1900 g_free(contact);
1902 /* subscribe to buddy presence */
1903 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1904 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1905 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1906 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1908 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1910 g_free(content);
1911 g_free(to);
1912 g_free(request);
1913 g_free(key);
1916 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1917 SIPE_UNUSED_PARAMETER void *unused)
1919 gchar *to = sip_uri_self(sip);
1920 gchar *resources_uri = g_strdup("");
1921 if (sip->ocs2007) {
1922 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1923 } else {
1924 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1927 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1930 struct presence_batched_routed {
1931 gchar *host;
1932 GSList *buddies;
1935 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1937 struct presence_batched_routed *data = payload;
1938 GSList *buddies = data->buddies;
1939 while (buddies) {
1940 g_free(buddies->data);
1941 buddies = buddies->next;
1943 g_slist_free(data->buddies);
1944 g_free(data->host);
1945 g_free(payload);
1948 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1950 struct presence_batched_routed *data = payload;
1951 GSList *buddies = data->buddies;
1952 gchar *resources_uri = g_strdup("");
1953 while (buddies) {
1954 gchar *tmp = resources_uri;
1955 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1956 g_free(tmp);
1957 buddies = buddies->next;
1959 sipe_subscribe_presence_batched_to(sip, resources_uri,
1960 g_strdup(data->host));
1964 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1965 * The user sends a single SUBSCRIBE request to the subscribed contact.
1966 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1970 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1973 gchar *key;
1974 gchar *to = sip_uri((char *)buddy_name);
1975 gchar *tmp = get_contact(sip);
1976 gchar *request;
1977 gchar *content = NULL;
1978 gchar *autoextend = "";
1979 gchar *content_type = "";
1980 struct sip_dialog *dialog;
1981 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
1982 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1984 if (sbuddy) sbuddy->just_added = FALSE;
1986 if (sip->ocs2007) {
1987 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1988 } else {
1989 autoextend = "Supported: com.microsoft.autoextend\r\n";
1992 request = g_strdup_printf(
1993 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1994 "Supported: ms-piggyback-first-notify\r\n"
1995 "%s%sSupported: ms-benotify\r\n"
1996 "Proxy-Require: ms-benotify\r\n"
1997 "Event: presence\r\n"
1998 "Contact: %s\r\n", autoextend, content_type, tmp);
2000 if (sip->ocs2007) {
2001 content = g_strdup_printf(
2002 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2003 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2004 "<resource uri=\"%s\"%s\n"
2005 "</adhocList>\n"
2006 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2007 "<category name=\"calendarData\"/>\n"
2008 "<category name=\"contactCard\"/>\n"
2009 "<category name=\"note\"/>\n"
2010 "<category name=\"state\"/>\n"
2011 "</categoryList>\n"
2012 "</action>\n"
2013 "</batchSub>", sip->username, to, context);
2016 g_free(tmp);
2018 /* subscribe to buddy presence */
2019 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2020 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2021 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2022 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2024 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2026 g_free(content);
2027 g_free(to);
2028 g_free(request);
2029 g_free(key);
2032 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2034 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2036 if (!purple_status_is_active(status))
2037 return;
2039 if (account->gc) {
2040 struct sipe_account_data *sip = account->gc->proto_data;
2042 if (sip) {
2043 gchar *action_name;
2044 const char* status_id = purple_status_get_id(status);
2045 gboolean dont_pub = purple_status_get_attr_boolean(status, SIPE_STATUS_ATTR_ID_DONT_PUB);
2047 if (dont_pub)
2049 purple_status_set_attr_boolean(status, SIPE_STATUS_ATTR_ID_DONT_PUB, FALSE);
2050 purple_debug_info("sipe", "sipe_set_status: status&note has NOT changed, exiting.\n");
2051 return;
2054 g_free(sip->status);
2055 sip->status = g_strdup(status_id);
2056 g_free(sip->note);
2057 sip->note = g_strdup(purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE));
2059 /* schedule 2 sec to capture idle flag */
2060 action_name = g_strdup_printf("<%s>", "+set-status");
2061 sipe_schedule_action(action_name, 2, (Action)send_presence_status, NULL, sip, NULL);
2062 g_free(action_name);
2066 static void
2067 sipe_set_idle(PurpleConnection * gc,
2068 int time)
2070 purple_debug_info("sipe", "sipe_set_idle: time=%d\n", time);
2072 if (gc) {
2073 struct sipe_account_data *sip = gc->proto_data;
2075 if (sip) {
2076 sip->was_idle = sip->is_idle;
2077 sip->is_idle = (time > 0);
2082 static void
2083 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2084 SIPE_UNUSED_PARAMETER const char *alias)
2086 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2087 sipe_group_set_user(sip, name);
2090 static void
2091 sipe_group_buddy(PurpleConnection *gc,
2092 const char *who,
2093 const char *old_group_name,
2094 const char *new_group_name)
2096 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2097 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2098 struct sipe_group * old_group = NULL;
2099 struct sipe_group * new_group;
2101 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2102 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2104 if(!buddy) { // buddy not in roaming list
2105 return;
2108 if (old_group_name) {
2109 old_group = sipe_group_find_by_name(sip, old_group_name);
2111 new_group = sipe_group_find_by_name(sip, new_group_name);
2113 if (old_group) {
2114 buddy->groups = g_slist_remove(buddy->groups, old_group);
2115 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2118 if (!new_group) {
2119 sipe_group_create(sip, new_group_name, who);
2120 } else {
2121 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2122 sipe_group_set_user(sip, who);
2126 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2128 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2130 /* libpurple can call us with undefined buddy or group */
2131 if (buddy && group) {
2132 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2134 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2135 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2136 purple_blist_rename_buddy(buddy, buddy_name);
2137 g_free(buddy_name);
2139 /* Prepend sip: if needed */
2140 if (strncmp("sip:", buddy->name, 4)) {
2141 gchar *buf = sip_uri_from_name(buddy->name);
2142 purple_blist_rename_buddy(buddy, buf);
2143 g_free(buf);
2146 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2147 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2148 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2149 b->name = g_strdup(buddy->name);
2150 b->just_added = TRUE;
2151 g_hash_table_insert(sip->buddies, b->name, b);
2152 sipe_group_buddy(gc, b->name, NULL, group->name);
2153 /* @TODO should go to callback */
2154 sipe_subscribe_presence_single(sip, b->name);
2155 } else {
2156 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2161 static void sipe_free_buddy(struct sipe_buddy *buddy)
2163 #ifndef _WIN32
2165 * We are calling g_hash_table_foreach_steal(). That means that no
2166 * key/value deallocation functions are called. Therefore the glib
2167 * hash code does not touch the key (buddy->name) or value (buddy)
2168 * of the to-be-deleted hash node at all. It follows that we
2170 * - MUST free the memory for the key ourselves and
2171 * - ARE allowed to do it in this function
2173 * Conclusion: glib must be broken on the Windows platform if sipe
2174 * crashes with SIGTRAP when closing. You'll have to live
2175 * with the memory leak until this is fixed.
2177 g_free(buddy->name);
2178 #endif
2179 g_free(buddy->activity);
2180 g_free(buddy->meeting_subject);
2181 g_free(buddy->meeting_location);
2182 g_free(buddy->annotation);
2184 g_free(buddy->cal_start_time);
2185 g_free(buddy->cal_free_busy_base64);
2186 g_free(buddy->cal_free_busy);
2187 g_free(buddy->last_non_cal_activity);
2189 sipe_cal_free_working_hours(buddy->cal_working_hours);
2191 g_free(buddy->device_name);
2192 g_slist_free(buddy->groups);
2193 g_free(buddy);
2197 * Unassociates buddy from group first.
2198 * Then see if no groups left, removes buddy completely.
2199 * Otherwise updates buddy groups on server.
2201 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2203 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2204 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
2205 struct sipe_group *g = NULL;
2207 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2209 if (!b) return;
2211 if (group) {
2212 g = sipe_group_find_by_name(sip, group->name);
2215 if (g) {
2216 b->groups = g_slist_remove(b->groups, g);
2217 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2220 if (g_slist_length(b->groups) < 1) {
2221 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2222 sipe_cancel_scheduled_action(sip, action_name);
2223 g_free(action_name);
2225 g_hash_table_remove(sip->buddies, buddy->name);
2227 if (b->name) {
2228 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2229 send_soap_request(sip, body);
2230 g_free(body);
2233 sipe_free_buddy(b);
2234 } else {
2235 //updates groups on server
2236 sipe_group_set_user(sip, b->name);
2241 static void
2242 sipe_rename_group(PurpleConnection *gc,
2243 const char *old_name,
2244 PurpleGroup *group,
2245 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2247 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2248 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2249 if (s_group) {
2250 sipe_group_rename(sip, s_group, group->name);
2251 } else {
2252 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2256 static void
2257 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
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, group->name);
2261 if (s_group) {
2262 gchar *body;
2263 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2264 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2265 send_soap_request(sip, body);
2266 g_free(body);
2268 sip->groups = g_slist_remove(sip->groups, s_group);
2269 g_free(s_group->name);
2270 g_free(s_group);
2271 } else {
2272 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2276 /** All statuses need message attribute to pass Note */
2277 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2279 PurpleStatusType *type;
2280 GList *types = NULL;
2282 /* Macros to reduce code repetition.
2283 Translators: noun */
2284 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2285 prim, id, name, \
2286 TRUE, user, FALSE, \
2287 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2288 SIPE_STATUS_ATTR_ID_DONT_PUB, _("Don't publish"), purple_value_new(PURPLE_TYPE_BOOLEAN), \
2289 NULL); \
2290 types = g_list_append(types, type);
2292 /* Online */
2293 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2294 NULL,
2295 NULL,
2296 TRUE);
2298 /* Busy */
2299 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2300 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2301 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2302 TRUE);
2304 /* BusyIdle (not user settable) */
2305 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2306 sipe_activity_map[SIPE_ACTIVITY_BUSYIDLE].status_id,
2307 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE),
2308 FALSE);
2310 /* Do Not Disturb */
2311 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2312 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2313 NULL,
2314 TRUE);
2316 /* In a meeting (not user settable) */
2317 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2318 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].status_id,
2319 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING),
2320 FALSE);
2322 /* In a conference (not user settable) */
2323 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2324 sipe_activity_map[SIPE_ACTIVITY_IN_CONF].status_id,
2325 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF),
2326 FALSE);
2328 /* Be Right Back */
2329 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2330 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2331 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2332 TRUE);
2334 /* Away */
2335 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2336 NULL,
2337 NULL,
2338 TRUE);
2340 /* On The Phone (not user settable) */
2341 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2342 sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].status_id,
2343 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE),
2344 FALSE);
2346 /* Out To Lunch (not user settable) */
2347 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2348 sipe_activity_map[SIPE_ACTIVITY_LUNCH].status_id,
2349 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH),
2350 FALSE);
2352 /* Idle/Inactive (not user settable) */
2353 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2354 sipe_activity_map[SIPE_ACTIVITY_INACTIVE].status_id,
2355 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE),
2356 FALSE);
2358 /* Appear Offline */
2359 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2360 NULL,
2361 NULL,
2362 TRUE);
2364 /* Offline (not user settable) */
2365 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE,
2366 NULL,
2367 NULL,
2368 FALSE);
2370 return types;
2374 * A callback for g_hash_table_foreach
2376 static void
2377 sipe_buddy_subscribe_cb(char *buddy_name,
2378 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2379 struct sipe_account_data *sip)
2381 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2382 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2383 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2384 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2386 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2387 g_free(action_name);
2391 * Removes entries from purple buddy list
2392 * that does not correspond ones in the roaming contact list.
2394 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2395 GSList *buddies = purple_find_buddies(sip->account, NULL);
2396 GSList *entry = buddies;
2397 struct sipe_buddy *buddy;
2398 PurpleBuddy *b;
2399 PurpleGroup *g;
2401 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2402 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2403 while (entry) {
2404 b = entry->data;
2405 g = purple_buddy_get_group(b);
2406 buddy = g_hash_table_lookup(sip->buddies, b->name);
2407 if(buddy) {
2408 gboolean in_sipe_groups = FALSE;
2409 GSList *entry2 = buddy->groups;
2410 while (entry2) {
2411 struct sipe_group *group = entry2->data;
2412 if (!strcmp(group->name, g->name)) {
2413 in_sipe_groups = TRUE;
2414 break;
2416 entry2 = entry2->next;
2418 if(!in_sipe_groups) {
2419 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2420 purple_blist_remove_buddy(b);
2422 } else {
2423 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2424 purple_blist_remove_buddy(b);
2426 entry = entry->next;
2428 g_slist_free(buddies);
2431 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2433 int len = msg->bodylen;
2435 gchar *tmp = sipmsg_find_header(msg, "Event");
2436 xmlnode *item;
2437 xmlnode *isc;
2438 const gchar *contacts_delta;
2439 xmlnode *group_node;
2440 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
2441 return FALSE;
2444 /* Convert the contact from XML to Purple Buddies */
2445 isc = xmlnode_from_str(msg->body, len);
2446 if (!isc) {
2447 return FALSE;
2450 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2451 if (contacts_delta) {
2452 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2455 if (!strcmp(isc->name, "contactList")) {
2457 /* Parse groups */
2458 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2459 struct sipe_group * group = g_new0(struct sipe_group, 1);
2460 const char *name = xmlnode_get_attrib(group_node, "name");
2462 if (!strncmp(name, "~", 1)) {
2463 name = _("Other Contacts");
2465 group->name = g_strdup(name);
2466 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2468 sipe_group_add(sip, group);
2471 // Make sure we have at least one group
2472 if (g_slist_length(sip->groups) == 0) {
2473 struct sipe_group * group = g_new0(struct sipe_group, 1);
2474 PurpleGroup *purple_group;
2475 group->name = g_strdup(_("Other Contacts"));
2476 group->id = 1;
2477 purple_group = purple_group_new(group->name);
2478 purple_blist_add_group(purple_group, NULL);
2479 sip->groups = g_slist_append(sip->groups, group);
2482 /* Parse contacts */
2483 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2484 const gchar *uri = xmlnode_get_attrib(item, "uri");
2485 const gchar *name = xmlnode_get_attrib(item, "name");
2486 gchar *buddy_name;
2487 struct sipe_buddy *buddy = NULL;
2488 gchar *tmp;
2489 gchar **item_groups;
2490 int i = 0;
2492 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2493 tmp = sip_uri_from_name(uri);
2494 buddy_name = g_ascii_strdown(tmp, -1);
2495 g_free(tmp);
2497 /* assign to group Other Contacts if nothing else received */
2498 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2499 if(!tmp || !strcmp("", tmp) ) {
2500 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2501 g_free(tmp);
2502 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2504 item_groups = g_strsplit(tmp, " ", 0);
2505 g_free(tmp);
2507 while (item_groups[i]) {
2508 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2510 // If couldn't find the right group for this contact, just put them in the first group we have
2511 if (group == NULL && g_slist_length(sip->groups) > 0) {
2512 group = sip->groups->data;
2515 if (group != NULL) {
2516 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2517 if (!b){
2518 b = purple_buddy_new(sip->account, buddy_name, uri);
2519 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2521 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2524 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2525 if (name != NULL && strlen(name) != 0) {
2526 purple_blist_alias_buddy(b, name);
2528 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2532 if (!buddy) {
2533 buddy = g_new0(struct sipe_buddy, 1);
2534 buddy->name = g_strdup(b->name);
2535 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2538 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2540 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2541 } else {
2542 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2543 name);
2546 i++;
2547 } // while, contact groups
2548 g_strfreev(item_groups);
2549 g_free(buddy_name);
2551 } // for, contacts
2553 sipe_cleanup_local_blist(sip);
2555 /* Add self-contact if not there yet. 2005 systems. */
2556 /* This will resemble subscription to roaming_self in 2007 systems */
2557 if (!sip->ocs2007) {
2558 gchar *self_uri = sip_uri_self(sip);
2559 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2561 if (!buddy) {
2562 buddy = g_new0(struct sipe_buddy, 1);
2563 buddy->name = g_strdup(self_uri);
2564 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2566 g_free(self_uri);
2569 xmlnode_free(isc);
2571 /* subscribe to buddies */
2572 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2573 if (sip->batched_support) {
2574 sipe_subscribe_presence_batched(sip, NULL);
2575 } else {
2576 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2578 sip->subscribed_buddies = TRUE;
2580 /* for 2005 systems schedule contacts' status update
2581 * based on their calendar information
2583 if (!sip->ocs2007) {
2584 sipe_sched_calendar_status_update(sip, time(NULL));
2587 return 0;
2591 * Subscribe roaming contacts
2593 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2595 gchar *to = sip_uri_self(sip);
2596 gchar *tmp = get_contact(sip);
2597 gchar *hdr = g_strdup_printf(
2598 "Event: vnd-microsoft-roaming-contacts\r\n"
2599 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2600 "Supported: com.microsoft.autoextend\r\n"
2601 "Supported: ms-benotify\r\n"
2602 "Proxy-Require: ms-benotify\r\n"
2603 "Supported: ms-piggyback-first-notify\r\n"
2604 "Contact: %s\r\n", tmp);
2605 g_free(tmp);
2607 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2608 g_free(to);
2609 g_free(hdr);
2612 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2613 SIPE_UNUSED_PARAMETER void *unused)
2615 gchar *key;
2616 struct sip_dialog *dialog;
2617 gchar *to = sip_uri_self(sip);
2618 gchar *tmp = get_contact(sip);
2619 gchar *hdr = g_strdup_printf(
2620 "Event: presence.wpending\r\n"
2621 "Accept: text/xml+msrtc.wpending\r\n"
2622 "Supported: com.microsoft.autoextend\r\n"
2623 "Supported: ms-benotify\r\n"
2624 "Proxy-Require: ms-benotify\r\n"
2625 "Supported: ms-piggyback-first-notify\r\n"
2626 "Contact: %s\r\n", tmp);
2627 g_free(tmp);
2629 /* Subscription is identified by <event> key */
2630 key = g_strdup_printf("<%s>", "presence.wpending");
2631 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2632 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2634 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2636 g_free(to);
2637 g_free(hdr);
2638 g_free(key);
2642 * Fires on deregistration event initiated by server.
2643 * [MS-SIPREGE] SIP extension.
2646 // 2007 Example
2648 // Content-Type: text/registration-event
2649 // subscription-state: terminated;expires=0
2650 // ms-diagnostics-public: 4141;reason="User disabled"
2652 // deregistered;event=rejected
2654 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2656 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2657 gchar *event = NULL;
2658 gchar *reason = NULL;
2659 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2661 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2662 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2664 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2665 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2666 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2667 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2668 } else {
2669 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2670 return;
2673 if (warning != NULL) {
2674 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2675 } else { // for LCS2005
2676 int error_id = 0;
2677 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2678 error_id = 4140; // [MS-SIPREGE]
2679 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2680 reason = g_strdup(_("you are already signed in at another location"));
2681 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2682 error_id = 4141;
2683 reason = g_strdup(_("user disabled")); // [MS-OCER]
2684 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2685 error_id = 4142;
2686 reason = g_strdup(_("user moved")); // [MS-OCER]
2689 g_free(event);
2690 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2691 g_free(reason);
2693 sip->gc->wants_to_die = TRUE;
2694 purple_connection_error(sip->gc, warning);
2695 g_free(warning);
2699 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2701 xmlnode *xn_provision_group_list;
2702 xmlnode *node;
2704 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2706 /* provisionGroup */
2707 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2708 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2709 g_free(sip->focus_factory_uri);
2710 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2711 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2712 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2713 break;
2716 xmlnode_free(xn_provision_group_list);
2719 /** for 2005 system */
2720 static void
2721 sipe_process_provisioning(struct sipe_account_data *sip,
2722 struct sipmsg *msg)
2724 xmlnode *xn_provision;
2725 xmlnode *node;
2727 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2728 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2729 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2730 if ((node = xmlnode_get_child(node, "line"))) {
2731 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2732 const gchar *server = xmlnode_get_attrib(node, "server");
2733 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2734 sip_csta_open(sip, line_uri, server);
2737 xmlnode_free(xn_provision);
2740 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2742 const gchar *contacts_delta;
2743 xmlnode *xml;
2745 xml = xmlnode_from_str(msg->body, msg->bodylen);
2746 if (!xml)
2748 return;
2751 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2752 if (contacts_delta)
2754 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2757 xmlnode_free(xml);
2760 static void
2761 free_container(struct sipe_container *container)
2763 GSList *entry;
2765 if (!container) return;
2767 entry = container->members;
2768 while (entry) {
2769 g_free(entry->data);
2770 entry = g_slist_remove(entry, entry->data);
2772 g_free(container);
2776 * Finds locally stored MS-PRES container member
2778 static struct sipe_container_member *
2779 sipe_find_container_member(struct sipe_container *container,
2780 const gchar *type,
2781 const gchar *value)
2783 struct sipe_container_member *member;
2784 GSList *entry;
2786 if (container == NULL || type == NULL) {
2787 return NULL;
2790 entry = container->members;
2791 while (entry) {
2792 member = entry->data;
2793 if (!g_strcasecmp(member->type, type)
2794 && ((!member->value && !value)
2795 || (value && member->value && !g_strcasecmp(member->value, value)))
2797 return member;
2799 entry = entry->next;
2801 return NULL;
2805 * Finds locally stored MS-PRES container by id
2807 static struct sipe_container *
2808 sipe_find_container(struct sipe_account_data *sip,
2809 guint id)
2811 struct sipe_container *container;
2812 GSList *entry;
2814 if (sip == NULL) {
2815 return NULL;
2818 entry = sip->containers;
2819 while (entry) {
2820 container = entry->data;
2821 if (id == container->id) {
2822 return container;
2824 entry = entry->next;
2826 return NULL;
2830 * Access Levels
2831 * 32000 - Blocked
2832 * 400 - Personal
2833 * 300 - Team
2834 * 200 - Company
2835 * 100 - Public
2837 static int
2838 sipe_find_access_level(struct sipe_account_data *sip,
2839 const gchar *type,
2840 const gchar *value)
2842 guint containers[] = {32000, 400, 300, 200, 100};
2843 int i = 0;
2845 for (i = 0; i < 5; i++) {
2846 struct sipe_container_member *member;
2847 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2848 if (!container) continue;
2850 member = sipe_find_container_member(container, type, value);
2851 if (member) {
2852 return containers[i];
2856 return -1;
2859 static void
2860 sipe_send_set_container_members(struct sipe_account_data *sip,
2861 guint container_id,
2862 guint container_version,
2863 const gchar* action,
2864 const gchar* type,
2865 const gchar* value)
2867 gchar *self = sip_uri_self(sip);
2868 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2869 gchar *contact;
2870 gchar *hdr;
2871 gchar *body = g_strdup_printf(
2872 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2873 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2874 "</setContainerMembers>",
2875 container_id,
2876 container_version,
2877 action,
2878 type,
2879 value_str);
2880 g_free(value_str);
2882 contact = get_contact(sip);
2883 hdr = g_strdup_printf("Contact: %s\r\n"
2884 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2885 g_free(contact);
2887 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2889 g_free(hdr);
2890 g_free(body);
2891 g_free(self);
2894 static void
2895 free_publication(struct sipe_publication *publication)
2897 g_free(publication->category);
2898 g_free(publication->cal_event_hash);
2899 g_free(publication->note);
2901 g_free(publication->working_hours_xml_str);
2902 g_free(publication->fb_start_str);
2903 g_free(publication->free_busy_base64);
2905 g_free(publication);
2908 /* key is <category><instance><container> */
2909 static gboolean
2910 sipe_is_our_publication(struct sipe_account_data *sip,
2911 const gchar *key)
2913 GSList *entry;
2915 /* filling keys for our publications if not yet cached */
2916 if (!sip->our_publication_keys) {
2917 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2918 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2919 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2920 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2921 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2922 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2923 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2925 /* device */
2926 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2927 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2929 /* state:machineState */
2930 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2931 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2932 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2933 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2935 /* state:userState */
2936 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2937 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2938 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2939 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2941 /* state:calendarState */
2942 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2943 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2944 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2945 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2947 /* state:calendarState OOF */
2948 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2949 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2950 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2951 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2953 /* note */
2954 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2955 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2956 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2957 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2958 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2959 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2961 /* note OOF */
2962 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2963 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2964 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2965 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2966 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2967 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2969 /* calendarData:WorkingHours */
2970 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2971 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2972 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2973 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2974 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2975 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2976 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2977 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2978 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2979 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2980 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2981 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2983 /* calendarData:FreeBusy */
2984 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2985 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2986 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2987 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2988 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2989 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2990 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2991 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2992 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2993 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2994 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2995 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2997 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2998 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3001 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3003 entry = sip->our_publication_keys;
3004 while (entry) {
3005 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3006 if (!strcmp(entry->data, key)) {
3007 return TRUE;
3009 entry = entry->next;
3011 return FALSE;
3014 /** Property names to store in blist.xml */
3015 #define ALIAS_PROP "alias"
3016 #define EMAIL_PROP "email"
3017 #define PHONE_PROP "phone"
3018 #define PHONE_DISPLAY_PROP "phone-display"
3019 #define PHONE_MOBILE_PROP "phone-mobile"
3020 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3021 #define PHONE_HOME_PROP "phone-home"
3022 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3023 #define PHONE_OTHER_PROP "phone-other"
3024 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3025 #define PHONE_CUSTOM1_PROP "phone-custom1"
3026 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3027 #define SITE_PROP "site"
3028 #define COMPANY_PROP "company"
3029 #define DEPARTMENT_PROP "department"
3030 #define TITLE_PROP "title"
3031 #define OFFICE_PROP "office"
3032 /** implies work address */
3033 #define ADDRESS_STREET_PROP "address-street"
3034 #define ADDRESS_CITY_PROP "address-city"
3035 #define ADDRESS_STATE_PROP "address-state"
3036 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3037 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3039 * Update user information
3041 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3042 * @param property_name
3043 * @param property_value may be modified to strip white space
3045 static void
3046 sipe_update_user_info(struct sipe_account_data *sip,
3047 const char *uri,
3048 const char *property_name,
3049 char *property_value)
3051 GSList *buddies, *entry;
3053 if (!property_name || strlen(property_name) == 0) return;
3055 if (property_value)
3056 property_value = g_strstrip(property_value);
3058 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3059 while (entry) {
3060 const char *prop_str;
3061 const char *server_alias;
3062 PurpleBuddy *p_buddy = entry->data;
3064 /* for Display Name */
3065 if (!strcmp(property_name, ALIAS_PROP)) {
3066 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3067 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3068 purple_blist_alias_buddy(p_buddy, property_value);
3071 server_alias = purple_buddy_get_server_alias(p_buddy);
3072 if (property_value && strlen(property_value) > 0 &&
3073 ( (server_alias && strcmp(property_value, server_alias))
3074 || !server_alias || strlen(server_alias) == 0 )
3076 purple_blist_server_alias_buddy(p_buddy, property_value);
3079 /* for other properties */
3080 else {
3081 if (property_value && strlen(property_value) > 0) {
3082 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3083 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3084 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3089 entry = entry->next;
3091 g_slist_free(buddies);
3095 * Update user phone
3096 * Suitable for both 2005 and 2007 systems.
3098 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3099 * @param phone_type
3100 * @param phone may be modified to strip white space
3101 * @param phone_display_string may be modified to strip white space
3103 static void
3104 sipe_update_user_phone(struct sipe_account_data *sip,
3105 const char *uri,
3106 const gchar *phone_type,
3107 gchar *phone,
3108 gchar *phone_display_string)
3110 const char *phone_node = PHONE_PROP; /* work phone by default */
3111 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3113 if(!phone || strlen(phone) == 0) return;
3115 if (phone_type && (!strcmp(phone_type, "mobile") || !strcmp(phone_type, "cell"))) {
3116 phone_node = PHONE_MOBILE_PROP;
3117 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3118 } else if (phone_type && !strcmp(phone_type, "home")) {
3119 phone_node = PHONE_HOME_PROP;
3120 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3121 } else if (phone_type && !strcmp(phone_type, "other")) {
3122 phone_node = PHONE_OTHER_PROP;
3123 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3124 } else if (phone_type && !strcmp(phone_type, "custom1")) {
3125 phone_node = PHONE_CUSTOM1_PROP;
3126 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3129 sipe_update_user_info(sip, uri, phone_node, phone);
3130 if (phone_display_string) {
3131 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3135 static void
3136 sipe_update_calendar(struct sipe_account_data *sip)
3138 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3140 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3142 if (!strcmp(calendar, "EXCH")) {
3143 sipe_ews_update_calendar(sip);
3146 /* schedule repeat */
3147 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3149 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3153 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3154 * by using standard Purple's means of signals and saved statuses.
3156 * Thus all UI elements get updated: Status Button with Note, docklet.
3157 * This is ablolutely important as both our status and note can come
3158 * inbound (roaming) or be updated programmatically (e.g. based on our
3159 * calendar data).
3161 static void
3162 sipe_set_purple_account_status_and_note(struct sipe_account_data *sip)
3164 PurpleSavedStatus *saved_status;
3165 const PurpleStatusType *acct_status_type =
3166 purple_status_type_find_with_id(sip->account->status_types, sip->status);
3167 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3169 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, sip->note);
3171 /* If this type+message is unique then create a new transient saved status
3172 * Ref: gtkstatusbox.c
3174 if (!saved_status) {
3175 GList *tmp;
3176 GList *active_accts = purple_accounts_get_all_active();
3178 saved_status = purple_savedstatus_new(NULL, primitive);
3179 purple_savedstatus_set_message(saved_status, sip->note);
3181 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3182 purple_savedstatus_set_substatus(saved_status,
3183 (PurpleAccount *)tmp->data, acct_status_type, sip->note);
3185 g_list_free(active_accts);
3188 /* Set the status for each account */
3189 purple_savedstatus_activate(saved_status);
3192 static void
3193 send_publish_category_initial(struct sipe_account_data *sip);
3196 * When we receive some self (BE) NOTIFY with a new subscriber
3197 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3200 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3202 gchar *contact;
3203 gchar *to;
3204 xmlnode *xml;
3205 xmlnode *node;
3206 xmlnode *node2;
3207 char *display_name = NULL;
3208 char *uri;
3209 GSList *category_names = NULL;
3210 int aggreg_avail = 0;
3211 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3212 gboolean do_update_status = FALSE;
3214 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3216 xml = xmlnode_from_str(msg->body, msg->bodylen);
3217 if (!xml) return;
3219 contact = get_contact(sip);
3220 to = sip_uri_self(sip);
3223 /* categories */
3224 /* set list of categories participating in this XML */
3225 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3226 const gchar *name = xmlnode_get_attrib(node, "name");
3227 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3229 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3230 category_names ? (int) g_slist_length(category_names) : -1);
3231 /* drop category information */
3232 if (category_names) {
3233 GSList *entry = category_names;
3234 while (entry) {
3235 GHashTable *cat_publications;
3236 const gchar *category = entry->data;
3237 entry = entry->next;
3238 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3239 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3240 if (cat_publications) {
3241 g_hash_table_remove(sip->our_publications, category);
3242 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3246 g_slist_free(category_names);
3247 /* filling our categories reflected in roaming data */
3248 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3249 const gchar *name = xmlnode_get_attrib(node, "name");
3250 const gchar *container = xmlnode_get_attrib(node, "container");
3251 const gchar *instance = xmlnode_get_attrib(node, "instance");
3252 const gchar *version = xmlnode_get_attrib(node, "version");
3253 guint version_int = version ? atoi(version) : 0;
3254 gchar *key;
3256 if (!container || !instance) continue;
3258 /* key is <category><instance><container> */
3259 key = g_strdup_printf("<%s><%s><%s>", name, instance, container);
3260 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version_int);
3262 /* capture all userState publication for later clean up if required */
3263 if (!strcmp(name, "state") && (atoi(container) == 2 || atoi(container) == 3)) {
3264 xmlnode *xn_state = xmlnode_get_child(node, "state");
3266 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3267 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3268 publication->category = g_strdup(name);
3269 publication->instance = atoi(instance);
3270 publication->container = atoi(container);
3271 publication->version = version_int;
3273 if (!sip->user_state_publications) {
3274 sip->user_state_publications = g_hash_table_new_full(
3275 g_str_hash, g_str_equal,
3276 g_free, (GDestroyNotify)free_publication);
3278 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3279 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3280 key, version_int);
3284 if (sipe_is_our_publication(sip, key)) {
3285 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3287 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3288 publication->category = g_strdup(name);
3289 publication->instance = atoi(instance);
3290 publication->container = atoi(container);
3291 publication->version = version_int;
3292 /* filling publication->availability */
3293 if (!strcmp(name, "state")) {
3294 xmlnode *xn_state = xmlnode_get_child(node, "state");
3295 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3297 if (xn_avail) {
3298 gchar *avail_str = xmlnode_get_data(xn_avail);
3299 if (avail_str) {
3300 publication->availability = atoi(avail_str);
3302 g_free(avail_str);
3304 /* for calendarState */
3305 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3306 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3307 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3309 event->start_time = purple_str_to_time(xmlnode_get_attrib(xn_state, "startTime"),
3310 FALSE, NULL, NULL, NULL);
3311 if (xn_activity) {
3312 if (!strcmp(xmlnode_get_attrib(xn_activity, "token"),
3313 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3315 event->is_meeting = TRUE;
3318 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3319 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3321 publication->cal_event_hash = sipe_cal_event_hash(event);
3322 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3323 publication->cal_event_hash);
3324 sipe_cal_event_free(event);
3327 /* filling publication->note */
3328 if (!strcmp(name, "note")) {
3329 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3331 g_free(sip->note);
3332 if (xn_body) {
3333 publication->note = xmlnode_get_data(xn_body);
3334 sip->note = g_strdup(publication->note);
3336 do_update_status = TRUE;
3339 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3340 if (!strcmp(name, "calendarData") && (publication->container == 300)) {
3341 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3342 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3343 if (xn_free_busy) {
3344 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3345 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3347 if (xn_working_hours) {
3348 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3352 if (!cat_publications) {
3353 cat_publications = g_hash_table_new_full(
3354 g_str_hash, g_str_equal,
3355 g_free, (GDestroyNotify)free_publication);
3356 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3357 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3359 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3360 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version_int);
3362 g_free(key);
3364 /* aggregateState (not an our publication) from 2-nd container */
3365 if (!strcmp(name, "state") && atoi(container) == 2) {
3366 xmlnode *xn_state = xmlnode_get_child(node, "state");
3368 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3369 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3370 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3372 if (xn_avail) {
3373 gchar *avail_str = xmlnode_get_data(xn_avail);
3374 if (avail_str) {
3375 aggreg_avail = atoi(avail_str);
3377 g_free(avail_str);
3380 if (xn_activity) {
3381 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3383 aggreg_activity = sipe_get_activity_by_token(activity_token);
3386 do_update_status = TRUE;
3390 /* userProperties published by server from AD */
3391 if (!sip->csta && !strcmp(name, "userProperties")) {
3392 xmlnode *line;
3393 /* line, for Remote Call Control (RCC) */
3394 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3395 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3396 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3397 gchar *line_uri;
3399 if (!line_server || (strcmp(line_type, "Rcc") && strcmp(line_type, "Dual"))) continue;
3401 line_uri = xmlnode_get_data(line);
3402 if (line_uri) {
3403 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3404 sip_csta_open(sip, line_uri, line_server);
3406 g_free(line_uri);
3408 break;
3412 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3413 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3415 /* containers */
3416 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3417 guint id = atoi(xmlnode_get_attrib(node, "id"));
3418 struct sipe_container *container = sipe_find_container(sip, id);
3420 if (container) {
3421 sip->containers = g_slist_remove(sip->containers, container);
3422 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3423 free_container(container);
3425 container = g_new0(struct sipe_container, 1);
3426 container->id = id;
3427 container->version = atoi(xmlnode_get_attrib(node, "version"));
3428 sip->containers = g_slist_append(sip->containers, container);
3429 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3431 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3432 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3433 member->type = xmlnode_get_attrib(node2, "type");
3434 member->value = xmlnode_get_attrib(node2, "value");
3435 container->members = g_slist_append(container->members, member);
3436 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3437 member->type, member->value ? member->value : "");
3441 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3442 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3443 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3444 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3445 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3446 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3447 /* initial set-up to let counterparties see your status */
3448 if (sameEnterpriseAL < 0) {
3449 struct sipe_container *container = sipe_find_container(sip, 200);
3450 guint version = container ? container->version : 0;
3451 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3453 if (federatedAL < 0) {
3454 struct sipe_container *container = sipe_find_container(sip, 100);
3455 guint version = container ? container->version : 0;
3456 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3458 sip->access_level_set = TRUE;
3461 /* subscribers */
3462 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3463 const char *user;
3464 const char *acknowledged;
3465 gchar *hdr;
3466 gchar *body;
3468 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3469 if (!user) continue;
3470 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3471 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3472 uri = sip_uri_from_name(user);
3474 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3476 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3477 if(!g_ascii_strcasecmp(acknowledged,"false")){
3478 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3479 if (!purple_find_buddy(sip->account, uri)) {
3480 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3483 hdr = g_strdup_printf(
3484 "Contact: %s\r\n"
3485 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3487 body = g_strdup_printf(
3488 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3489 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3490 "</setSubscribers>", user);
3492 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3493 g_free(body);
3494 g_free(hdr);
3496 g_free(display_name);
3497 g_free(uri);
3500 g_free(contact);
3501 xmlnode_free(xml);
3503 /* Publish initial state if not yet.
3504 * Assuming this happens on initial responce to subscription to roaming-self
3505 * so we've already updated our roaming data in full.
3506 * Only for 2007+
3508 if (!sip->initial_state_published) {
3509 send_publish_category_initial(sip);
3510 sip->initial_state_published = TRUE;
3511 /* dalayed run */
3512 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3513 do_update_status = FALSE;
3514 } else if (aggreg_avail) {
3516 g_free(sip->status);
3517 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3518 if (aggreg_activity == SIPE_ACTIVITY_IN_MEETING ||
3519 aggreg_activity == SIPE_ACTIVITY_IN_CONF ||
3520 aggreg_activity == SIPE_ACTIVITY_ON_PHONE)
3522 sip->status = g_strdup(sipe_activity_map[aggreg_activity].status_id);
3524 else
3526 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3528 } else {
3529 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3533 if (do_update_status) {
3534 purple_debug_info("sipe", "sipe_process_roaming_self: to %s for the account\n", sip->status);
3535 sipe_set_purple_account_status_and_note(sip);
3538 g_free(to);
3541 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3543 gchar *to = sip_uri_self(sip);
3544 gchar *tmp = get_contact(sip);
3545 gchar *hdr = g_strdup_printf(
3546 "Event: vnd-microsoft-roaming-ACL\r\n"
3547 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3548 "Supported: com.microsoft.autoextend\r\n"
3549 "Supported: ms-benotify\r\n"
3550 "Proxy-Require: ms-benotify\r\n"
3551 "Supported: ms-piggyback-first-notify\r\n"
3552 "Contact: %s\r\n", tmp);
3553 g_free(tmp);
3555 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3556 g_free(to);
3557 g_free(hdr);
3561 * To request for presence information about the user, access level settings that have already been configured by the user
3562 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3563 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3566 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3568 gchar *to = sip_uri_self(sip);
3569 gchar *tmp = get_contact(sip);
3570 gchar *hdr = g_strdup_printf(
3571 "Event: vnd-microsoft-roaming-self\r\n"
3572 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3573 "Supported: ms-benotify\r\n"
3574 "Proxy-Require: ms-benotify\r\n"
3575 "Supported: ms-piggyback-first-notify\r\n"
3576 "Contact: %s\r\n"
3577 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3579 gchar *body=g_strdup(
3580 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3581 "<roaming type=\"categories\"/>"
3582 "<roaming type=\"containers\"/>"
3583 "<roaming type=\"subscribers\"/></roamingList>");
3585 g_free(tmp);
3586 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3587 g_free(body);
3588 g_free(to);
3589 g_free(hdr);
3593 * For 2005 version
3595 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3597 gchar *to = sip_uri_self(sip);
3598 gchar *tmp = get_contact(sip);
3599 gchar *hdr = g_strdup_printf(
3600 "Event: vnd-microsoft-provisioning\r\n"
3601 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3602 "Supported: com.microsoft.autoextend\r\n"
3603 "Supported: ms-benotify\r\n"
3604 "Proxy-Require: ms-benotify\r\n"
3605 "Supported: ms-piggyback-first-notify\r\n"
3606 "Expires: 0\r\n"
3607 "Contact: %s\r\n", tmp);
3609 g_free(tmp);
3610 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3611 g_free(to);
3612 g_free(hdr);
3615 /** Subscription for provisioning information to help with initial
3616 * configuration. This subscription is a one-time query (denoted by the Expires header,
3617 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3618 * configuration, meeting policies, and policy settings that Communicator must enforce.
3619 * TODO: for what we need this information.
3622 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3624 gchar *to = sip_uri_self(sip);
3625 gchar *tmp = get_contact(sip);
3626 gchar *hdr = g_strdup_printf(
3627 "Event: vnd-microsoft-provisioning-v2\r\n"
3628 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3629 "Supported: com.microsoft.autoextend\r\n"
3630 "Supported: ms-benotify\r\n"
3631 "Proxy-Require: ms-benotify\r\n"
3632 "Supported: ms-piggyback-first-notify\r\n"
3633 "Expires: 0\r\n"
3634 "Contact: %s\r\n"
3635 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3636 gchar *body = g_strdup(
3637 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3638 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3639 "<provisioningGroup name=\"ucPolicy\"/>"
3640 "</provisioningGroupList>");
3642 g_free(tmp);
3643 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3644 g_free(body);
3645 g_free(to);
3646 g_free(hdr);
3649 static void
3650 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3651 gpointer value, gpointer user_data)
3653 struct sip_subscription *subscription = value;
3654 struct sip_dialog *dialog = &subscription->dialog;
3655 struct sipe_account_data *sip = user_data;
3656 gchar *tmp = get_contact(sip);
3657 gchar *hdr = g_strdup_printf(
3658 "Event: %s\r\n"
3659 "Expires: 0\r\n"
3660 "Contact: %s\r\n", subscription->event, tmp);
3661 g_free(tmp);
3663 /* Rate limit to max. 25 requests per seconds */
3664 g_usleep(1000000 / 25);
3666 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3667 g_free(hdr);
3670 /* IM Session (INVITE and MESSAGE methods) */
3672 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3673 static gchar *
3674 get_end_points (struct sipe_account_data *sip,
3675 struct sip_session *session)
3677 gchar *res;
3679 if (session == NULL) {
3680 return NULL;
3683 res = g_strdup_printf("<sip:%s>", sip->username);
3685 SIPE_DIALOG_FOREACH {
3686 gchar *tmp = res;
3687 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3688 g_free(tmp);
3690 if (dialog->theirepid) {
3691 tmp = res;
3692 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3693 g_free(tmp);
3695 } SIPE_DIALOG_FOREACH_END;
3697 return res;
3700 static gboolean
3701 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3702 struct sipmsg *msg,
3703 SIPE_UNUSED_PARAMETER struct transaction *trans)
3705 gboolean ret = TRUE;
3707 if (msg->response != 200) {
3708 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3709 return FALSE;
3712 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3714 return ret;
3718 * Asks UA/proxy about its capabilities.
3720 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3722 gchar *to = sip_uri(who);
3723 gchar *contact = get_contact(sip);
3724 gchar *request = g_strdup_printf(
3725 "Accept: application/sdp\r\n"
3726 "Contact: %s\r\n", contact);
3727 g_free(contact);
3729 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3731 g_free(to);
3732 g_free(request);
3735 static void
3736 sipe_notify_user(struct sipe_account_data *sip,
3737 struct sip_session *session,
3738 PurpleMessageFlags flags,
3739 const gchar *message)
3741 PurpleConversation *conv;
3743 if (!session->conv) {
3744 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3745 } else {
3746 conv = session->conv;
3748 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3751 void
3752 sipe_present_info(struct sipe_account_data *sip,
3753 struct sip_session *session,
3754 const gchar *message)
3756 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3759 static void
3760 sipe_present_err(struct sipe_account_data *sip,
3761 struct sip_session *session,
3762 const gchar *message)
3764 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3767 void
3768 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3769 struct sip_session *session,
3770 int sip_error,
3771 const gchar *who,
3772 const gchar *message)
3774 char *msg, *msg_tmp, *msg_tmp2;
3775 const char *label;
3777 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
3778 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3779 g_free(msg_tmp);
3780 /* Service unavailable; Server Internal Error; Server Time-out */
3781 if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3782 label = _("This message was not delivered to %s because the service is not available");
3783 } else if (sip_error == 486) { /* Busy Here */
3784 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3785 } else {
3786 label = _("This message was not delivered to %s because one or more recipients are offline");
3789 msg_tmp = g_strdup_printf( "%s:\n%s" ,
3790 msg_tmp2 = g_strdup_printf(label, who ? who : ""), msg ? msg : "");
3791 sipe_present_err(sip, session, msg_tmp);
3792 g_free(msg_tmp2);
3793 g_free(msg_tmp);
3794 g_free(msg);
3798 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
3800 static gboolean
3801 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
3802 SIPE_UNUSED_PARAMETER struct transaction *trans)
3804 gboolean ret = TRUE;
3805 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3806 struct sip_session *session = sipe_session_find_im(sip, with);
3807 struct sip_dialog *dialog;
3808 gchar *cseq;
3809 char *key;
3810 gchar *message;
3812 if (!session) {
3813 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3814 g_free(with);
3815 return FALSE;
3818 dialog = sipe_dialog_find(session, with);
3819 if (!dialog) {
3820 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3821 g_free(with);
3822 return FALSE;
3825 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3826 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3827 g_free(cseq);
3828 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3830 if (msg->response >= 400) {
3831 PurpleBuddy *pbuddy;
3832 gchar *alias = with;
3834 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3836 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3837 alias = (gchar *)purple_buddy_get_alias(pbuddy);
3840 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
3841 ret = FALSE;
3842 } else {
3843 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3844 if (message_id) {
3845 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
3846 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
3847 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3850 g_hash_table_remove(session->unconfirmed_messages, key);
3851 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
3852 key, g_hash_table_size(session->unconfirmed_messages));
3855 g_free(key);
3856 g_free(with);
3858 if (ret) sipe_im_process_queue(sip, session);
3859 return ret;
3862 static gboolean
3863 sipe_is_election_finished(struct sip_session *session);
3865 static void
3866 sipe_election_result(struct sipe_account_data *sip,
3867 void *sess);
3869 static gboolean
3870 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
3871 SIPE_UNUSED_PARAMETER struct transaction *trans)
3873 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3874 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3875 struct sip_dialog *dialog;
3876 struct sip_session *session;
3878 session = sipe_session_find_chat_by_callid(sip, callid);
3879 if (!session) {
3880 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
3881 return FALSE;
3884 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
3885 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3886 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
3887 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
3889 if (xn_request_rm_response) {
3890 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
3891 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
3893 dialog = sipe_dialog_find(session, with);
3894 if (!dialog) {
3895 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
3896 return FALSE;
3899 if (allow && !g_strcasecmp(allow, "true")) {
3900 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
3901 dialog->election_vote = 1;
3902 } else if (allow && !g_strcasecmp(allow, "false")) {
3903 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
3904 dialog->election_vote = -1;
3907 if (sipe_is_election_finished(session)) {
3908 sipe_election_result(sip, session);
3911 } else if (xn_set_rm_response) {
3914 xmlnode_free(xn_action);
3918 return TRUE;
3921 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
3923 gchar *hdr;
3924 gchar *tmp;
3925 char *msgformat;
3926 char *msgtext;
3927 gchar *msgr_value;
3928 gchar *msgr;
3930 sipe_parse_html(msg, &msgformat, &msgtext);
3931 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
3933 msgr_value = sipmsg_get_msgr_string(msgformat);
3934 g_free(msgformat);
3935 if (msgr_value) {
3936 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3937 g_free(msgr_value);
3938 } else {
3939 msgr = g_strdup("");
3942 tmp = get_contact(sip);
3943 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3944 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3945 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3946 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
3947 g_free(tmp);
3948 g_free(msgr);
3950 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
3951 g_free(msgtext);
3952 g_free(hdr);
3956 static void
3957 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
3959 GSList *entry2 = session->outgoing_message_queue;
3960 while (entry2) {
3961 char *queued_msg = entry2->data;
3963 /* for multiparty chat or conference */
3964 if (session->is_multiparty || session->focus_uri) {
3965 gchar *who = sip_uri_self(sip);
3966 serv_got_chat_in(sip->gc, session->chat_id, who,
3967 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
3968 g_free(who);
3971 SIPE_DIALOG_FOREACH {
3972 char *key;
3974 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3976 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3977 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
3978 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
3979 key, g_hash_table_size(session->unconfirmed_messages));
3980 g_free(key);
3982 sipe_send_message(sip, dialog, queued_msg);
3983 } SIPE_DIALOG_FOREACH_END;
3985 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3986 g_free(queued_msg);
3990 static void
3991 sipe_refer_notify(struct sipe_account_data *sip,
3992 struct sip_session *session,
3993 const gchar *who,
3994 int status,
3995 const gchar *desc)
3997 gchar *hdr;
3998 gchar *body;
3999 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4001 hdr = g_strdup_printf(
4002 "Event: refer\r\n"
4003 "Subscription-State: %s\r\n"
4004 "Content-Type: message/sipfrag\r\n",
4005 status >= 200 ? "terminated" : "active");
4007 body = g_strdup_printf(
4008 "SIP/2.0 %d %s\r\n",
4009 status, desc);
4011 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4013 g_free(hdr);
4014 g_free(body);
4017 static gboolean
4018 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4020 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4021 struct sip_session *session;
4022 struct sip_dialog *dialog;
4023 char *cseq;
4024 char *key;
4025 gchar *message;
4026 struct sipmsg *request_msg = trans->msg;
4028 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4029 gchar *referred_by;
4031 session = sipe_session_find_chat_by_callid(sip, callid);
4032 if (!session) {
4033 session = sipe_session_find_im(sip, with);
4035 if (!session) {
4036 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4037 g_free(with);
4038 return FALSE;
4041 dialog = sipe_dialog_find(session, with);
4042 if (!dialog) {
4043 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4044 g_free(with);
4045 return FALSE;
4048 sipe_dialog_parse(dialog, msg, TRUE);
4050 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4051 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4052 g_free(cseq);
4053 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4055 if (msg->response != 200) {
4056 PurpleBuddy *pbuddy;
4057 gchar *alias = with;
4059 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4061 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4062 alias = (gchar *)purple_buddy_get_alias(pbuddy);
4065 if (message) {
4066 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
4067 } else {
4068 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4069 sipe_present_err(sip, session, tmp_msg);
4070 g_free(tmp_msg);
4073 sipe_dialog_remove(session, with);
4075 g_free(key);
4076 g_free(with);
4077 return FALSE;
4080 dialog->cseq = 0;
4081 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4082 dialog->outgoing_invite = NULL;
4083 dialog->is_established = TRUE;
4085 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4086 if (referred_by) {
4087 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4088 g_free(referred_by);
4091 /* add user to chat if it is a multiparty session */
4092 if (session->is_multiparty) {
4093 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4094 with, NULL,
4095 PURPLE_CBFLAGS_NONE, TRUE);
4098 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4099 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4100 if (session->outgoing_message_queue) {
4101 char *queued_msg = session->outgoing_message_queue->data;
4102 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
4103 g_free(queued_msg);
4107 sipe_im_process_queue(sip, session);
4109 g_hash_table_remove(session->unconfirmed_messages, key);
4110 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4111 key, g_hash_table_size(session->unconfirmed_messages));
4113 g_free(key);
4114 g_free(with);
4115 return TRUE;
4119 void
4120 sipe_invite(struct sipe_account_data *sip,
4121 struct sip_session *session,
4122 const gchar *who,
4123 const gchar *msg_body,
4124 const gchar *referred_by,
4125 const gboolean is_triggered)
4127 gchar *hdr;
4128 gchar *to;
4129 gchar *contact;
4130 gchar *body;
4131 gchar *self;
4132 char *ms_text_format = NULL;
4133 gchar *roster_manager;
4134 gchar *end_points;
4135 gchar *referred_by_str;
4136 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4138 if (dialog && dialog->is_established) {
4139 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4140 return;
4143 if (!dialog) {
4144 dialog = sipe_dialog_add(session);
4145 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4146 dialog->with = g_strdup(who);
4149 if (!(dialog->ourtag)) {
4150 dialog->ourtag = gentag();
4153 to = sip_uri(who);
4155 if (msg_body) {
4156 char *msgformat;
4157 char *msgtext;
4158 char *base64_msg;
4159 gchar *msgr_value;
4160 gchar *msgr;
4161 char *key;
4163 sipe_parse_html(msg_body, &msgformat, &msgtext);
4164 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4166 msgr_value = sipmsg_get_msgr_string(msgformat);
4167 g_free(msgformat);
4168 msgr = "";
4169 if (msgr_value) {
4170 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4171 g_free(msgr_value);
4174 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4175 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
4176 g_free(msgtext);
4177 g_free(msgr);
4178 g_free(base64_msg);
4180 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4181 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
4182 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4183 key, g_hash_table_size(session->unconfirmed_messages));
4184 g_free(key);
4187 contact = get_contact(sip);
4188 end_points = get_end_points(sip, session);
4189 self = sip_uri_self(sip);
4190 roster_manager = g_strdup_printf(
4191 "Roster-Manager: %s\r\n"
4192 "EndPoints: %s\r\n",
4193 self,
4194 end_points);
4195 referred_by_str = referred_by ?
4196 g_strdup_printf(
4197 "Referred-By: %s\r\n",
4198 referred_by)
4199 : g_strdup("");
4200 hdr = g_strdup_printf(
4201 "Supported: ms-sender\r\n"
4202 "%s"
4203 "%s"
4204 "%s"
4205 "%s"
4206 "Contact: %s\r\n%s"
4207 "Content-Type: application/sdp\r\n",
4208 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
4209 referred_by_str,
4210 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4211 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4212 contact,
4213 ms_text_format ? ms_text_format : "");
4214 g_free(ms_text_format);
4215 g_free(self);
4217 body = g_strdup_printf(
4218 "v=0\r\n"
4219 "o=- 0 0 IN IP4 %s\r\n"
4220 "s=session\r\n"
4221 "c=IN IP4 %s\r\n"
4222 "t=0 0\r\n"
4223 "m=%s %d sip null\r\n"
4224 "a=accept-types:text/plain text/html image/gif "
4225 "multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4226 purple_network_get_my_ip(-1),
4227 purple_network_get_my_ip(-1),
4228 sip->ocs2007 ? "message" : "x-ms-message",
4229 sip->realport);
4231 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4232 to, to, hdr, body, dialog, process_invite_response);
4234 g_free(to);
4235 g_free(roster_manager);
4236 g_free(end_points);
4237 g_free(referred_by_str);
4238 g_free(body);
4239 g_free(hdr);
4240 g_free(contact);
4243 static void
4244 sipe_refer(struct sipe_account_data *sip,
4245 struct sip_session *session,
4246 const gchar *who)
4248 gchar *hdr;
4249 gchar *contact;
4250 gchar *epid = get_epid(sip);
4251 struct sip_dialog *dialog = sipe_dialog_find(session,
4252 session->roster_manager);
4254 contact = get_contact(sip);
4255 hdr = g_strdup_printf(
4256 "Contact: %s\r\n"
4257 "Refer-to: <%s>\r\n"
4258 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4259 "Require: com.microsoft.rtc-multiparty\r\n",
4260 contact,
4261 who,
4262 sip->username,
4263 dialog->ourtag ? ";tag=" : "",
4264 dialog->ourtag ? dialog->ourtag : "",
4265 epid);
4266 g_free(epid);
4268 send_sip_request(sip->gc, "REFER",
4269 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4271 g_free(hdr);
4272 g_free(contact);
4275 static void
4276 sipe_send_election_request_rm(struct sipe_account_data *sip,
4277 struct sip_dialog *dialog,
4278 int bid)
4280 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4282 gchar *body = g_strdup_printf(
4283 "<?xml version=\"1.0\"?>\r\n"
4284 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4285 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4286 sip->username, bid);
4288 send_sip_request(sip->gc, "INFO",
4289 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4291 g_free(body);
4294 static void
4295 sipe_send_election_set_rm(struct sipe_account_data *sip,
4296 struct sip_dialog *dialog)
4298 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4300 gchar *body = g_strdup_printf(
4301 "<?xml version=\"1.0\"?>\r\n"
4302 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4303 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4304 sip->username);
4306 send_sip_request(sip->gc, "INFO",
4307 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4309 g_free(body);
4312 static void
4313 sipe_session_close(struct sipe_account_data *sip,
4314 struct sip_session * session)
4316 if (session && session->focus_uri) {
4317 sipe_conf_immcu_closed(sip, session);
4318 conf_session_close(sip, session);
4321 if (session) {
4322 SIPE_DIALOG_FOREACH {
4323 /* @TODO slow down BYE message sending rate */
4324 /* @see single subscription code */
4325 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4326 } SIPE_DIALOG_FOREACH_END;
4328 sipe_session_remove(sip, session);
4332 static void
4333 sipe_session_close_all(struct sipe_account_data *sip)
4335 GSList *entry;
4336 while ((entry = sip->sessions) != NULL) {
4337 sipe_session_close(sip, entry->data);
4341 static void
4342 sipe_convo_closed(PurpleConnection * gc, const char *who)
4344 struct sipe_account_data *sip = gc->proto_data;
4346 purple_debug_info("sipe", "conversation with %s closed\n", who);
4347 sipe_session_close(sip, sipe_session_find_im(sip, who));
4350 static void
4351 sipe_chat_leave (PurpleConnection *gc, int id)
4353 struct sipe_account_data *sip = gc->proto_data;
4354 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4356 sipe_session_close(sip, session);
4359 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4360 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4362 struct sipe_account_data *sip = gc->proto_data;
4363 struct sip_session *session;
4364 struct sip_dialog *dialog;
4365 gchar *uri = sip_uri(who);
4367 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4369 session = sipe_session_find_or_add_im(sip, uri);
4370 dialog = sipe_dialog_find(session, uri);
4372 // Queue the message
4373 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
4375 if (dialog && !dialog->outgoing_invite) {
4376 sipe_im_process_queue(sip, session);
4377 } else if (!dialog || !dialog->outgoing_invite) {
4378 // Need to send the INVITE to get the outgoing dialog setup
4379 sipe_invite(sip, session, uri, what, NULL, FALSE);
4382 g_free(uri);
4383 return 1;
4386 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4387 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4389 struct sipe_account_data *sip = gc->proto_data;
4390 struct sip_session *session;
4392 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4394 session = sipe_session_find_chat_by_id(sip, id);
4396 // Queue the message
4397 if (session && session->dialogs) {
4398 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4399 g_strdup(what));
4400 sipe_im_process_queue(sip, session);
4401 } else if (sip) {
4402 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4403 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4405 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4406 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4408 if (sip->ocs2007) {
4409 struct sip_session *session = sipe_session_add_chat(sip);
4411 session->is_multiparty = FALSE;
4412 session->focus_uri = g_strdup(proto_chat_id);
4413 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4414 g_strdup(what));
4415 sipe_invite_conf_focus(sip, session);
4419 return 1;
4422 /* End IM Session (INVITE and MESSAGE methods) */
4424 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4426 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4427 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4428 gchar *from;
4429 struct sip_session *session;
4431 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4433 /* Call Control protocol */
4434 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4436 process_incoming_info_csta(sip, msg);
4437 return;
4440 from = parse_from(sipmsg_find_header(msg, "From"));
4441 session = sipe_session_find_chat_by_callid(sip, callid);
4442 if (!session) {
4443 session = sipe_session_find_im(sip, from);
4445 if (!session) {
4446 g_free(from);
4447 return;
4450 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4452 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4453 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4454 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4456 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4458 if (xn_request_rm) {
4459 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4460 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
4461 gchar *body = g_strdup_printf(
4462 "<?xml version=\"1.0\"?>\r\n"
4463 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4464 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4465 sip->username,
4466 session->bid < bid ? "true" : "false");
4467 send_sip_response(sip->gc, msg, 200, "OK", body);
4468 g_free(body);
4469 } else if (xn_set_rm) {
4470 gchar *body;
4471 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4472 g_free(session->roster_manager);
4473 session->roster_manager = g_strdup(rm);
4475 body = g_strdup_printf(
4476 "<?xml version=\"1.0\"?>\r\n"
4477 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4478 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4479 sip->username);
4480 send_sip_response(sip->gc, msg, 200, "OK", body);
4481 g_free(body);
4483 xmlnode_free(xn_action);
4486 else
4488 /* looks like purple lacks typing notification for chat */
4489 if (!session->is_multiparty && !session->focus_uri) {
4490 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4491 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4492 "status");
4493 if (status && !strcmp(status, "type")) {
4494 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4495 } else if (status && !strcmp(status, "idle")) {
4496 serv_got_typing_stopped(sip->gc, from);
4498 xmlnode_free(xn_keyboard_activity);
4501 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4503 g_free(from);
4506 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4508 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4509 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4510 struct sip_session *session;
4511 struct sip_dialog *dialog;
4513 /* collect dialog identification
4514 * we need callid, ourtag and theirtag to unambiguously identify dialog
4516 /* take data before 'msg' will be modified by send_sip_response */
4517 dialog = g_new0(struct sip_dialog, 1);
4518 dialog->callid = g_strdup(callid);
4519 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4520 dialog->with = g_strdup(from);
4521 sipe_dialog_parse(dialog, msg, FALSE);
4523 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4525 session = sipe_session_find_chat_by_callid(sip, callid);
4526 if (!session) {
4527 session = sipe_session_find_im(sip, from);
4529 if (!session) {
4530 g_free(from);
4531 return;
4534 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4535 g_free(session->roster_manager);
4536 session->roster_manager = NULL;
4539 /* This what BYE is essentially for - terminating dialog */
4540 sipe_dialog_remove_3(session, dialog);
4541 sipe_dialog_free(dialog);
4542 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4543 sipe_conf_immcu_closed(sip, session);
4544 } else if (session->is_multiparty) {
4545 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4548 g_free(from);
4551 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4553 gchar *self = sip_uri_self(sip);
4554 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4555 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4556 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4557 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4558 struct sip_session *session;
4559 struct sip_dialog *dialog;
4561 session = sipe_session_find_chat_by_callid(sip, callid);
4562 dialog = sipe_dialog_find(session, from);
4564 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
4565 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4566 } else {
4567 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4569 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
4572 g_free(self);
4573 g_free(from);
4574 g_free(refer_to);
4575 g_free(referred_by);
4578 static unsigned int
4579 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4581 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4582 struct sip_session *session;
4583 struct sip_dialog *dialog;
4585 if (state == PURPLE_NOT_TYPING)
4586 return 0;
4588 session = sipe_session_find_im(sip, who);
4589 dialog = sipe_dialog_find(session, who);
4591 if (session && dialog && dialog->is_established) {
4592 send_sip_request(gc, "INFO", who, who,
4593 "Content-Type: application/xml\r\n",
4594 SIPE_SEND_TYPING, dialog, NULL);
4596 return SIPE_TYPING_SEND_TIMEOUT;
4599 static gboolean resend_timeout(struct sipe_account_data *sip)
4601 GSList *tmp = sip->transactions;
4602 time_t currtime = time(NULL);
4603 while (tmp) {
4604 struct transaction *trans = tmp->data;
4605 tmp = tmp->next;
4606 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4607 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4608 /* TODO 408 */
4609 } else {
4610 if ((currtime - trans->time > 2) && trans->retries == 0) {
4611 trans->retries++;
4612 sendout_sipmsg(sip, trans->msg);
4616 return TRUE;
4619 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4620 SIPE_UNUSED_PARAMETER void *unused)
4622 /* register again when security token expires */
4623 /* we have to start a new authentication as the security token
4624 * is almost expired by sending a not signed REGISTER message */
4625 purple_debug_info("sipe", "do a full reauthentication\n");
4626 sipe_auth_free(&sip->registrar);
4627 sipe_auth_free(&sip->proxy);
4628 sip->registerstatus = 0;
4629 do_register(sip);
4630 sip->reauthenticate_set = FALSE;
4633 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4635 gchar *from;
4636 gchar *contenttype;
4637 gboolean found = FALSE;
4639 from = parse_from(sipmsg_find_header(msg, "From"));
4641 if (!from) return;
4643 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4645 contenttype = sipmsg_find_header(msg, "Content-Type");
4646 if (!strncmp(contenttype, "text/plain", 10)
4647 || !strncmp(contenttype, "text/html", 9)
4648 || !strncmp(contenttype, "multipart/related", 17)
4649 || !strncmp(contenttype, "multipart/alternative", 21))
4651 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4652 gchar *html = get_html_message(contenttype, msg->body);
4654 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4655 if (!session) {
4656 session = sipe_session_find_im(sip, from);
4659 if (session && session->focus_uri) { /* a conference */
4660 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4661 gchar *sender = parse_from(tmp);
4662 g_free(tmp);
4663 serv_got_chat_in(sip->gc, session->chat_id, sender,
4664 PURPLE_MESSAGE_RECV, html, time(NULL));
4665 g_free(sender);
4666 } else if (session && session->is_multiparty) { /* a multiparty chat */
4667 serv_got_chat_in(sip->gc, session->chat_id, from,
4668 PURPLE_MESSAGE_RECV, html, time(NULL));
4669 } else {
4670 serv_got_im(sip->gc, from, html, 0, time(NULL));
4672 g_free(html);
4673 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4674 found = TRUE;
4676 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
4677 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4678 xmlnode *state;
4679 gchar *statedata;
4681 if (!isc) {
4682 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4683 return;
4686 state = xmlnode_get_child(isc, "state");
4688 if (!state) {
4689 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4690 xmlnode_free(isc);
4691 return;
4694 statedata = xmlnode_get_data(state);
4695 if (statedata) {
4696 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4697 else serv_got_typing_stopped(sip->gc, from);
4699 g_free(statedata);
4701 xmlnode_free(isc);
4702 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4703 found = TRUE;
4705 if (!found) {
4706 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4707 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4708 if (!session) {
4709 session = sipe_session_find_im(sip, from);
4711 if (session) {
4712 gchar *msg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4713 from);
4714 sipe_present_err(sip, session, msg);
4715 g_free(msg);
4718 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
4719 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
4721 g_free(from);
4724 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
4726 gchar *body;
4727 gchar *newTag;
4728 gchar *oldHeader;
4729 gchar *newHeader;
4730 gboolean is_multiparty = FALSE;
4731 gboolean is_triggered = FALSE;
4732 gboolean was_multiparty = TRUE;
4733 gboolean just_joined = FALSE;
4734 gchar *from;
4735 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4736 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4737 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4738 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4739 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4740 GSList *end_points = NULL;
4741 char *tmp = NULL;
4742 struct sip_session *session;
4744 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
4745 g_free(tmp);
4747 /* Invitation to join conference */
4748 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
4749 process_incoming_invite_conf(sip, msg);
4750 return;
4753 /* Only accept text invitations */
4754 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4755 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4756 return;
4759 // TODO There *must* be a better way to clean up the To header to add a tag...
4760 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4761 oldHeader = sipmsg_find_header(msg, "To");
4762 newTag = gentag();
4763 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4764 sipmsg_remove_header_now(msg, "To");
4765 sipmsg_add_header_now(msg, "To", newHeader);
4766 g_free(newHeader);
4768 if (end_points_hdr) {
4769 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4771 if (g_slist_length(end_points) > 2) {
4772 is_multiparty = TRUE;
4775 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4776 is_triggered = TRUE;
4777 is_multiparty = TRUE;
4780 session = sipe_session_find_chat_by_callid(sip, callid);
4781 /* Convert to multiparty */
4782 if (session && is_multiparty && !session->is_multiparty) {
4783 g_free(session->with);
4784 session->with = NULL;
4785 was_multiparty = FALSE;
4786 session->is_multiparty = TRUE;
4787 session->chat_id = rand();
4790 if (!session && is_multiparty) {
4791 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
4793 /* IM session */
4794 from = parse_from(sipmsg_find_header(msg, "From"));
4795 if (!session) {
4796 session = sipe_session_find_or_add_im(sip, from);
4799 g_free(session->callid);
4800 session->callid = g_strdup(callid);
4802 session->is_multiparty = is_multiparty;
4803 if (roster_manager) {
4804 session->roster_manager = g_strdup(roster_manager);
4807 if (is_multiparty && end_points) {
4808 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
4809 GSList *entry = end_points;
4810 while (entry) {
4811 struct sip_dialog *dialog;
4812 struct sipendpoint *end_point = entry->data;
4813 entry = entry->next;
4815 if (!g_strcasecmp(from, end_point->contact) ||
4816 !g_strcasecmp(to, end_point->contact))
4817 continue;
4819 dialog = sipe_dialog_find(session, end_point->contact);
4820 if (dialog) {
4821 g_free(dialog->theirepid);
4822 dialog->theirepid = end_point->epid;
4823 end_point->epid = NULL;
4824 } else {
4825 dialog = sipe_dialog_add(session);
4827 dialog->callid = g_strdup(session->callid);
4828 dialog->with = end_point->contact;
4829 end_point->contact = NULL;
4830 dialog->theirepid = end_point->epid;
4831 end_point->epid = NULL;
4833 just_joined = TRUE;
4835 /* send triggered INVITE */
4836 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
4839 g_free(to);
4842 if (end_points) {
4843 GSList *entry = end_points;
4844 while (entry) {
4845 struct sipendpoint *end_point = entry->data;
4846 entry = entry->next;
4847 g_free(end_point->contact);
4848 g_free(end_point->epid);
4849 g_free(end_point);
4851 g_slist_free(end_points);
4854 if (session) {
4855 struct sip_dialog *dialog = sipe_dialog_find(session, from);
4856 if (dialog) {
4857 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
4858 } else {
4859 dialog = sipe_dialog_add(session);
4861 dialog->callid = g_strdup(session->callid);
4862 dialog->with = g_strdup(from);
4863 sipe_dialog_parse(dialog, msg, FALSE);
4865 if (!dialog->ourtag) {
4866 dialog->ourtag = newTag;
4867 newTag = NULL;
4870 just_joined = TRUE;
4872 } else {
4873 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
4875 g_free(newTag);
4877 if (is_multiparty && !session->conv) {
4878 gchar *chat_title = sipe_chat_get_name(callid);
4879 gchar *self = sip_uri_self(sip);
4880 /* create prpl chat */
4881 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
4882 session->chat_title = g_strdup(chat_title);
4883 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
4884 /* add self */
4885 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4886 self, NULL,
4887 PURPLE_CBFLAGS_NONE, FALSE);
4888 g_free(chat_title);
4889 g_free(self);
4892 if (is_multiparty && !was_multiparty) {
4893 /* add current IM counterparty to chat */
4894 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4895 sipe_dialog_first(session)->with, NULL,
4896 PURPLE_CBFLAGS_NONE, FALSE);
4899 /* add inviting party to chat */
4900 if (just_joined && session->conv) {
4901 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4902 from, NULL,
4903 PURPLE_CBFLAGS_NONE, TRUE);
4906 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4908 /* This used only in 2005 official client, not 2007 or Reuters.
4909 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4910 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4912 if (is_multiparty) {
4913 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4914 gchar *ms_text_format = sipmsg_find_header(msg, "ms-text-format");
4915 if (ms_text_format) {
4916 if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html")) {
4918 gchar *html = get_html_message(ms_text_format, NULL);
4919 if (html) {
4920 if (is_multiparty) {
4921 serv_got_chat_in(sip->gc, session->chat_id, from,
4922 PURPLE_MESSAGE_RECV, html, time(NULL));
4923 } else {
4924 serv_got_im(sip->gc, from, html, 0, time(NULL));
4926 g_free(html);
4927 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
4934 g_free(from);
4936 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
4937 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4938 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4940 body = g_strdup_printf(
4941 "v=0\r\n"
4942 "o=- 0 0 IN IP4 %s\r\n"
4943 "s=session\r\n"
4944 "c=IN IP4 %s\r\n"
4945 "t=0 0\r\n"
4946 "m=%s %d sip sip:%s\r\n"
4947 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4948 purple_network_get_my_ip(-1),
4949 purple_network_get_my_ip(-1),
4950 sip->ocs2007 ? "message" : "x-ms-message",
4951 sip->realport,
4952 sip->username);
4953 send_sip_response(sip->gc, msg, 200, "OK", body);
4954 g_free(body);
4957 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
4959 gchar *body;
4961 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
4962 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4963 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4965 body = g_strdup_printf(
4966 "v=0\r\n"
4967 "o=- 0 0 IN IP4 0.0.0.0\r\n"
4968 "s=session\r\n"
4969 "c=IN IP4 0.0.0.0\r\n"
4970 "t=0 0\r\n"
4971 "m=%s %d sip sip:%s\r\n"
4972 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4973 sip->ocs2007 ? "message" : "x-ms-message",
4974 sip->realport,
4975 sip->username);
4976 send_sip_response(sip->gc, msg, 200, "OK", body);
4977 g_free(body);
4980 static void sipe_connection_cleanup(struct sipe_account_data *);
4981 static void create_connection(struct sipe_account_data *, gchar *, int);
4983 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
4984 SIPE_UNUSED_PARAMETER struct transaction *trans)
4986 gchar *tmp;
4987 const gchar *expires_header;
4988 int expires, i;
4989 GSList *hdr = msg->headers;
4990 struct siphdrelement *elem;
4992 expires_header = sipmsg_find_header(msg, "Expires");
4993 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
4994 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
4996 switch (msg->response) {
4997 case 200:
4998 if (expires == 0) {
4999 sip->registerstatus = 0;
5000 } else {
5001 gchar *contact_hdr = NULL;
5002 gchar *gruu = NULL;
5003 gchar *epid;
5004 gchar *uuid;
5005 gchar *timeout;
5006 gchar *server_hdr = sipmsg_find_header(msg, "Server");
5008 if (!sip->reregister_set) {
5009 gchar *action_name = g_strdup_printf("<%s>", "registration");
5010 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5011 g_free(action_name);
5012 sip->reregister_set = TRUE;
5015 sip->registerstatus = 3;
5017 if (server_hdr && !sip->server_version) {
5018 sip->server_version = g_strdup(server_hdr);
5019 g_free(default_ua);
5020 default_ua = NULL;
5023 #ifdef USE_KERBEROS
5024 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5025 #endif
5026 tmp = sipmsg_find_auth_header(msg, "NTLM");
5027 #ifdef USE_KERBEROS
5028 } else {
5029 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5031 #endif
5032 if (tmp) {
5033 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5034 fill_auth(tmp, &sip->registrar);
5037 if (!sip->reauthenticate_set) {
5038 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5039 guint reauth_timeout;
5040 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5041 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5042 reauth_timeout = sip->registrar.expires - 300;
5043 } else {
5044 /* NTLM: we have to reauthenticate as our security token expires
5045 after eight hours (be five minutes early) */
5046 reauth_timeout = (8 * 3600) - 300;
5048 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5049 g_free(action_name);
5050 sip->reauthenticate_set = TRUE;
5053 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5055 epid = get_epid(sip);
5056 uuid = generateUUIDfromEPID(epid);
5057 g_free(epid);
5059 // There can be multiple Contact headers (one per location where the user is logged in) so
5060 // make sure to only get the one for this uuid
5061 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5062 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5063 if (valid_contact) {
5064 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5065 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5066 g_free(valid_contact);
5067 break;
5068 } else {
5069 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5072 g_free(uuid);
5074 g_free(sip->contact);
5075 if(gruu) {
5076 sip->contact = g_strdup_printf("<%s>", gruu);
5077 g_free(gruu);
5078 } else {
5079 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5080 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);
5082 sip->ocs2007 = FALSE;
5083 sip->batched_support = FALSE;
5085 while(hdr)
5087 elem = hdr->data;
5088 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5089 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5090 /* We interpret this as OCS2007+ indicator */
5091 sip->ocs2007 = TRUE;
5092 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5094 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5095 sip->batched_support = TRUE;
5096 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5099 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5100 gchar **caps = g_strsplit(elem->value,",",0);
5101 i = 0;
5102 while (caps[i]) {
5103 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5104 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5105 i++;
5107 g_strfreev(caps);
5109 hdr = g_slist_next(hdr);
5112 /* rejoin open chats to be able to use them by continue to send messages */
5113 purple_conversation_foreach(sipe_rejoin_chat);
5115 /* subscriptions */
5116 if (!sip->subscribed) { //do it just once, not every re-register
5118 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5119 (GCompareFunc)g_ascii_strcasecmp)) {
5120 sipe_subscribe_roaming_contacts(sip);
5123 /* For 2007+ it does not make sence to subscribe to:
5124 * vnd-microsoft-roaming-ACL
5125 * vnd-microsoft-provisioning (not v2)
5126 * presence.wpending
5127 * These are for backward compatibility.
5129 if (sip->ocs2007)
5131 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5132 (GCompareFunc)g_ascii_strcasecmp)) {
5133 sipe_subscribe_roaming_self(sip);
5135 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5136 (GCompareFunc)g_ascii_strcasecmp)) {
5137 sipe_subscribe_roaming_provisioning_v2(sip);
5140 /* For 2005- servers */
5141 else
5143 //sipe_options_request(sip, sip->sipdomain);
5145 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5146 (GCompareFunc)g_ascii_strcasecmp)) {
5147 sipe_subscribe_roaming_acl(sip);
5149 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5150 (GCompareFunc)g_ascii_strcasecmp)) {
5151 sipe_subscribe_roaming_provisioning(sip);
5153 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5154 (GCompareFunc)g_ascii_strcasecmp)) {
5155 sipe_subscribe_presence_wpending(sip, msg);
5158 /* For 2007+ we publish our initial statuses and calendar data only after
5159 * received our existing publications in sipe_process_roaming_self()
5160 * Only in this case we know versions of current publications made
5161 * on our behalf.
5163 /* For 2005- we publish our initial statuses only after
5164 * received our existing UserInfo data in response to
5165 * self subscription.
5166 * Only in this case we won't override existing UserInfo data
5167 * set earlier or by other client on our behalf.
5171 sip->subscribed = TRUE;
5174 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5175 "timeout=", ";", NULL);
5176 if (timeout != NULL) {
5177 sscanf(timeout, "%u", &sip->keepalive_timeout);
5178 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5179 sip->keepalive_timeout);
5180 g_free(timeout);
5183 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5185 break;
5186 case 301:
5188 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5190 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5191 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5192 gchar **tmp;
5193 gchar *hostname;
5194 int port = 0;
5195 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5196 int i = 1;
5198 tmp = g_strsplit(parts[0], ":", 0);
5199 hostname = g_strdup(tmp[0]);
5200 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5201 g_strfreev(tmp);
5203 while (parts[i]) {
5204 tmp = g_strsplit(parts[i], "=", 0);
5205 if (tmp[1]) {
5206 if (g_strcasecmp("transport", tmp[0]) == 0) {
5207 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5208 transport = SIPE_TRANSPORT_TCP;
5209 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5210 transport = SIPE_TRANSPORT_UDP;
5214 g_strfreev(tmp);
5215 i++;
5217 g_strfreev(parts);
5219 /* Close old connection */
5220 sipe_connection_cleanup(sip);
5222 /* Create new connection */
5223 sip->transport = transport;
5224 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5225 hostname, port, TRANSPORT_DESCRIPTOR);
5226 create_connection(sip, hostname, port);
5228 g_free(redirect);
5230 break;
5231 case 401:
5232 if (sip->registerstatus != 2) {
5233 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5234 if (sip->registrar.retries > 3) {
5235 sip->gc->wants_to_die = TRUE;
5236 purple_connection_error(sip->gc, _("Wrong password"));
5237 return TRUE;
5239 #ifdef USE_KERBEROS
5240 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5241 #endif
5242 tmp = sipmsg_find_auth_header(msg, "NTLM");
5243 #ifdef USE_KERBEROS
5244 } else {
5245 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5247 #endif
5248 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5249 fill_auth(tmp, &sip->registrar);
5250 sip->registerstatus = 2;
5251 if (sip->account->disconnecting) {
5252 do_register_exp(sip, 0);
5253 } else {
5254 do_register(sip);
5257 break;
5258 case 403:
5260 gchar *warning = sipmsg_find_header(msg, "Warning");
5261 gchar **reason = NULL;
5262 if (warning != NULL) {
5263 /* Example header:
5264 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5266 reason = g_strsplit(warning, "\"", 0);
5268 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5269 (reason && reason[1]) ? reason[1] : _("no reason given"));
5270 g_strfreev(reason);
5272 sip->gc->wants_to_die = TRUE;
5273 purple_connection_error(sip->gc, warning);
5274 g_free(warning);
5275 return TRUE;
5277 break;
5278 case 404:
5280 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5281 gchar *reason = NULL;
5282 if (warning != NULL) {
5283 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5285 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5286 warning ? (reason ? reason : _("no reason given")) :
5287 _("SIP is either not enabled for the destination URI or it does not exist"));
5288 g_free(reason);
5290 sip->gc->wants_to_die = TRUE;
5291 purple_connection_error(sip->gc, warning);
5292 g_free(warning);
5293 return TRUE;
5295 break;
5296 case 503:
5297 case 504: /* Server time-out */
5299 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5300 gchar *reason = NULL;
5301 if (warning != NULL) {
5302 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5304 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5305 g_free(reason);
5307 sip->gc->wants_to_die = TRUE;
5308 purple_connection_error(sip->gc, warning);
5309 g_free(warning);
5310 return TRUE;
5312 break;
5314 return TRUE;
5318 * Returns 2005-style activity and Availability.
5320 * @param status Sipe statis id.
5322 static void
5323 sipe_get_act_avail_by_status_2005(const char *status, int *activity, int *availability)
5325 int avail = 300; /* online */
5326 int act = 400; /* Available */
5328 if (!strcmp(status, SIPE_STATUS_ID_AWAY)) {
5329 act = 100;
5330 } else if (!strcmp(status, SIPE_STATUS_ID_LUNCH)) {
5331 act = 150;
5332 } else if (!strcmp(status, SIPE_STATUS_ID_BRB)) {
5333 act = 300;
5334 } else if (!strcmp(status, SIPE_STATUS_ID_AVAILABLE)) {
5335 act = 400;
5336 } else if (!strcmp(status, SIPE_STATUS_ID_ON_PHONE)) {
5337 act = 500;
5338 } else if (!strcmp(status, SIPE_STATUS_ID_BUSY) ||
5339 !strcmp(status, SIPE_STATUS_ID_IN_MEETING) ||
5340 !strcmp(status, SIPE_STATUS_ID_IN_CONF) ||
5341 !strcmp(status, SIPE_STATUS_ID_DND)) {
5342 act = 600;
5343 } else if (!strcmp(status, SIPE_STATUS_ID_INVISIBLE) ||
5344 !strcmp(status, SIPE_STATUS_ID_OFFLINE)) {
5345 avail = 0; /* offline */
5346 act = 100;
5347 } else {
5348 act = 400; /* Available */
5351 if (activity) *activity = act;
5352 if (availability) *availability = avail;
5356 * [MS-SIP] 2.2.1
5358 * @param activity 2005 aggregated activity. Ex.: 600
5359 * @param availablity 2005 aggregated availablity. Ex.: 300
5361 static const char *
5362 sipe_get_status_by_act_avail_2005(const int activity,
5363 const int availablity)
5365 const char *status_id = NULL;
5367 if (activity < 150)
5368 status_id = SIPE_STATUS_ID_AWAY;
5369 else if (activity < 200)
5370 status_id = SIPE_STATUS_ID_LUNCH;
5371 else if (activity < 300)
5372 status_id = SIPE_STATUS_ID_IDLE;
5373 else if (activity < 400)
5374 status_id = SIPE_STATUS_ID_BRB;
5375 else if (activity < 500)
5376 status_id = SIPE_STATUS_ID_AVAILABLE;
5377 else if (activity < 600)
5378 status_id = SIPE_STATUS_ID_ON_PHONE;
5379 else if (activity < 700)
5380 status_id = SIPE_STATUS_ID_BUSY;
5381 else if (activity < 800)
5382 status_id = SIPE_STATUS_ID_AWAY;
5383 else
5384 status_id = SIPE_STATUS_ID_AVAILABLE;
5386 if (availablity < 100)
5387 status_id = SIPE_STATUS_ID_OFFLINE;
5389 return status_id;
5393 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5395 static const char*
5396 sipe_get_status_by_availability(int avail,
5397 const char* activity)
5399 const char *status;
5401 if (avail < 3000)
5402 status = SIPE_STATUS_ID_OFFLINE;
5403 else if (avail < 4500)
5404 status = SIPE_STATUS_ID_AVAILABLE;
5405 else if (avail < 6000)
5406 status = SIPE_STATUS_ID_IDLE;
5407 else if (avail < 7500)
5408 if (activity && !strcmp(activity, SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING))) {
5409 status = SIPE_STATUS_ID_IN_MEETING;
5410 } else if (activity && !strcmp(activity, SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF))) {
5411 status = SIPE_STATUS_ID_IN_CONF;
5412 } else if (activity && !strcmp(activity, SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE))) {
5413 status = SIPE_STATUS_ID_ON_PHONE;
5414 } else {
5415 status = SIPE_STATUS_ID_BUSY;
5417 else if (avail < 9000)
5418 status = SIPE_STATUS_ID_BUSYIDLE;
5419 else if (avail < 12000)
5420 status = SIPE_STATUS_ID_DND;
5421 else if (avail < 15000)
5422 status = SIPE_STATUS_ID_BRB;
5423 else if (avail < 18000)
5424 status = SIPE_STATUS_ID_AWAY;
5425 else
5426 status = SIPE_STATUS_ID_OFFLINE;
5428 return status;
5432 * Returns 2007-style availability value
5434 * @param sipe_status_id (in)
5435 * @param activity_token (out) Must be g_free()'d after use if consumed.
5437 static int
5438 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5440 int availability;
5441 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5443 if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AWAY) ||
5444 !strcmp(sipe_status_id, SIPE_STATUS_ID_LUNCH)) {
5445 availability = 15500;
5446 activity = SIPE_ACTIVITY_AWAY;
5447 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5448 availability = 12500;
5449 activity = SIPE_ACTIVITY_BRB;
5450 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_DND)) {
5451 availability = 9500;
5452 activity = SIPE_ACTIVITY_DND;
5453 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BUSY) ||
5454 !strcmp(sipe_status_id, SIPE_STATUS_ID_IN_MEETING) ||
5455 !strcmp(sipe_status_id, SIPE_STATUS_ID_IN_CONF) ||
5456 !strcmp(sipe_status_id, SIPE_STATUS_ID_ON_PHONE)) {
5457 availability = 6500;
5458 activity = SIPE_ACTIVITY_BUSY;
5459 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5460 availability = 3500;
5461 activity = SIPE_ACTIVITY_ONLINE;
5462 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5463 availability = 0;
5464 } else {
5465 // Offline or invisible
5466 availability = 18500;
5467 activity = SIPE_ACTIVITY_OFFLINE;
5470 if (activity_token) {
5471 *activity_token = g_strdup(sipe_activity_map[activity].token);
5473 return availability;
5476 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5478 const char *uri;
5479 xmlnode *xn_categories;
5480 xmlnode *xn_category;
5481 xmlnode *xn_node;
5482 const char *status = NULL;
5483 gboolean do_update_status = FALSE;
5485 xn_categories = xmlnode_from_str(data, len);
5486 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5488 for (xn_category = xmlnode_get_child(xn_categories, "category");
5489 xn_category ;
5490 xn_category = xmlnode_get_next_twin(xn_category) )
5492 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5494 /* contactCard */
5495 if (!strcmp(attrVar, "contactCard"))
5497 xmlnode *node;
5498 /* identity - Display Name and email */
5499 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5500 if (node) {
5501 char* display_name = xmlnode_get_data(
5502 xmlnode_get_descendant(node, "name", "displayName", NULL));
5503 char* email = xmlnode_get_data(
5504 xmlnode_get_child(node, "email"));
5506 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5507 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5509 g_free(display_name);
5510 g_free(email);
5512 /* company */
5513 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5514 if (node) {
5515 char* company = xmlnode_get_data(node);
5516 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5517 g_free(company);
5519 /* department */
5520 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5521 if (node) {
5522 char* department = xmlnode_get_data(node);
5523 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5524 g_free(department);
5526 /* title */
5527 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5528 if (node) {
5529 char* title = xmlnode_get_data(node);
5530 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5531 g_free(title);
5533 /* office */
5534 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5535 if (node) {
5536 char* office = xmlnode_get_data(node);
5537 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5538 g_free(office);
5540 /* site (url) */
5541 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5542 if (node) {
5543 char* site = xmlnode_get_data(node);
5544 sipe_update_user_info(sip, uri, SITE_PROP, site);
5545 g_free(site);
5547 /* phone */
5548 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5549 node;
5550 node = xmlnode_get_next_twin(node))
5552 const char *phone_type = xmlnode_get_attrib(node, "type");
5553 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5554 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5556 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5558 g_free(phone);
5559 g_free(phone_display_string);
5561 /* address */
5562 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5563 node;
5564 node = xmlnode_get_next_twin(node))
5566 if (!strcmp(xmlnode_get_attrib(node, "type"), "work")) {
5567 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5568 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5569 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5570 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5571 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5573 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5574 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5575 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5576 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5577 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5579 g_free(street);
5580 g_free(city);
5581 g_free(state);
5582 g_free(zipcode);
5583 g_free(country_code);
5585 break;
5589 /* note */
5590 else if (!strcmp(attrVar, "note"))
5592 if (uri) {
5593 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5595 if (sbuddy) {
5596 /* clean up in case to 'note' element is supplied
5597 * which indicate note removal in client
5599 g_free(sbuddy->annotation);
5600 sbuddy->annotation = NULL;
5601 sbuddy->is_oof_note = FALSE;
5603 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
5604 if (xn_node) {
5605 sbuddy->annotation = xmlnode_get_data(xn_node);
5606 sbuddy->is_oof_note = !strcmp(xmlnode_get_attrib(xn_node, "type"), "OOF");
5608 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",
5609 uri, sbuddy->annotation ? sbuddy->annotation : "");
5612 /* to trigger UI refresh in case no status info is supplied in this update */
5613 do_update_status = TRUE;
5617 /* state */
5618 else if(!strcmp(attrVar, "state"))
5620 char *data;
5621 int availability;
5622 xmlnode *xn_availability;
5623 xmlnode *xn_activity;
5624 xmlnode *xn_meeting_subject;
5625 xmlnode *xn_meeting_location;
5626 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5628 xn_node = xmlnode_get_child(xn_category, "state");
5629 if (!xn_node) continue;
5630 xn_availability = xmlnode_get_child(xn_node, "availability");
5631 if (!xn_availability) continue;
5632 xn_activity = xmlnode_get_child(xn_node, "activity");
5633 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
5634 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
5636 data = xmlnode_get_data(xn_availability);
5637 availability = atoi(data);
5638 g_free(data);
5640 /* activity, meeting_subject, meeting_location */
5641 if (sbuddy) {
5642 /* activity */
5643 g_free(sbuddy->activity);
5644 sbuddy->activity = NULL;
5645 if (xn_activity) {
5646 const char *token = xmlnode_get_attrib(xn_activity, "token");
5647 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
5649 /* from token */
5650 if (!is_empty(token)) {
5651 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
5653 /* from custom element */
5654 if (xn_custom) {
5655 char *custom = xmlnode_get_data(xn_custom);
5657 if (!is_empty(custom)) {
5658 sbuddy->activity = custom;
5659 custom = NULL;
5661 g_free(custom);
5664 /* meeting_subject */
5665 g_free(sbuddy->meeting_subject);
5666 sbuddy->meeting_subject = NULL;
5667 if (xn_meeting_subject) {
5668 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
5670 if (!is_empty(meeting_subject)) {
5671 sbuddy->meeting_subject = meeting_subject;
5672 meeting_subject = NULL;
5674 g_free(meeting_subject);
5676 /* meeting_location */
5677 g_free(sbuddy->meeting_location);
5678 sbuddy->meeting_location = NULL;
5679 if (xn_meeting_location) {
5680 char *meeting_location = xmlnode_get_data(xn_meeting_location);
5682 if (!is_empty(meeting_location)) {
5683 sbuddy->meeting_location = meeting_location;
5684 meeting_location = NULL;
5686 g_free(meeting_location);
5690 status = sipe_get_status_by_availability(availability, sbuddy->activity);
5691 do_update_status = TRUE;
5693 /* calendarData */
5694 else if(!strcmp(attrVar, "calendarData"))
5696 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5697 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
5698 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
5700 if (sbuddy && xn_free_busy) {
5701 g_free(sbuddy->cal_start_time);
5702 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
5704 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
5705 15 : 0;
5707 g_free(sbuddy->cal_free_busy_base64);
5708 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
5710 g_free(sbuddy->cal_free_busy);
5711 sbuddy->cal_free_busy = NULL;
5713 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);
5716 if (sbuddy && xn_working_hours) {
5717 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
5722 if (do_update_status) {
5723 if (!status) { /* no status category in this update, using contact's current status */
5724 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
5725 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
5726 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
5727 status = purple_status_get_id(pstatus);
5730 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
5731 sipe_got_user_status(sip, uri, status);
5734 xmlnode_free(xn_categories);
5737 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
5739 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5740 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
5741 payload->host = g_strdup(host);
5742 payload->buddies = server;
5743 sipe_subscribe_presence_batched_routed(sip, payload);
5744 sipe_subscribe_presence_batched_routed_free(payload);
5747 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
5749 xmlnode *xn_list;
5750 xmlnode *xn_resource;
5751 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
5752 g_free, NULL);
5753 GSList *server;
5754 gchar *host;
5756 xn_list = xmlnode_from_str(data, len);
5758 for (xn_resource = xmlnode_get_child(xn_list, "resource");
5759 xn_resource;
5760 xn_resource = xmlnode_get_next_twin(xn_resource) )
5762 const char *uri, *state;
5763 xmlnode *xn_instance;
5765 xn_instance = xmlnode_get_child(xn_resource, "instance");
5766 if (!xn_instance) continue;
5768 uri = xmlnode_get_attrib(xn_resource, "uri");
5769 state = xmlnode_get_attrib(xn_instance, "state");
5770 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
5772 if (strstr(state, "resubscribe")) {
5773 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
5775 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
5776 gchar *user = g_strdup(uri);
5777 host = g_strdup(poolFqdn);
5778 server = g_hash_table_lookup(servers, host);
5779 server = g_slist_append(server, user);
5780 g_hash_table_insert(servers, host, server);
5781 } else {
5782 sipe_subscribe_presence_single(sip, (void *) uri);
5787 /* Send out any deferred poolFqdn subscriptions */
5788 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
5789 g_hash_table_destroy(servers);
5791 xmlnode_free(xn_list);
5794 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
5796 gchar *uri;
5797 gchar *getbasic;
5798 gchar *activity = NULL;
5799 xmlnode *pidf;
5800 xmlnode *basicstatus = NULL, *tuple, *status;
5801 gboolean isonline = FALSE;
5802 xmlnode *display_name_node;
5804 pidf = xmlnode_from_str(data, len);
5805 if (!pidf) {
5806 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
5807 return;
5810 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
5812 if ((tuple = xmlnode_get_child(pidf, "tuple")))
5814 if ((status = xmlnode_get_child(tuple, "status"))) {
5815 basicstatus = xmlnode_get_child(status, "basic");
5819 if (!basicstatus) {
5820 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
5821 xmlnode_free(pidf);
5822 return;
5825 getbasic = xmlnode_get_data(basicstatus);
5826 if (!getbasic) {
5827 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
5828 xmlnode_free(pidf);
5829 return;
5832 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
5833 if (strstr(getbasic, "open")) {
5834 isonline = TRUE;
5836 g_free(getbasic);
5838 display_name_node = xmlnode_get_child(pidf, "display-name");
5839 if (display_name_node) {
5840 char * display_name = xmlnode_get_data(display_name_node);
5842 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5843 g_free(display_name);
5846 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
5847 if ((status = xmlnode_get_child(tuple, "status"))) {
5848 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
5849 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
5850 activity = xmlnode_get_data(basicstatus);
5851 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
5857 if (isonline) {
5858 const gchar * status_id = NULL;
5859 if (activity) {
5860 if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
5861 status_id = SIPE_STATUS_ID_BUSY;
5862 } else if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
5863 status_id = SIPE_STATUS_ID_AWAY;
5867 if (!status_id) {
5868 status_id = SIPE_STATUS_ID_AVAILABLE;
5871 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
5872 sipe_got_user_status(sip, uri, status_id);
5873 } else {
5874 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
5877 g_free(activity);
5878 g_free(uri);
5879 xmlnode_free(pidf);
5882 /** 2005 */
5883 static void
5884 sipe_user_info_has_updated(struct sipe_account_data *sip,
5885 xmlnode *xn_userinfo)
5887 if (sip->user_info) {
5888 xmlnode_free(sip->user_info);
5890 sip->user_info = xmlnode_copy(xn_userinfo);
5892 /* Publish initial state if not yet.
5893 * Assuming this happens on initial responce to self subscription
5894 * so we've already updated our UserInfo.
5896 if (!sip->initial_state_published) {
5897 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
5898 /* dalayed run */
5899 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
5903 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
5905 const char *activity = NULL;
5906 const char *epid;
5907 const char *status_id = NULL;
5908 const char *name;
5909 char *uri;
5910 char *self_uri = sip_uri_self(sip);
5911 int avl;
5912 int act;
5913 const char *device_name = NULL;
5914 const char *cal_start_time = NULL;
5915 const char *cal_granularity = NULL;
5916 char *cal_free_busy_base64 = NULL;
5917 struct sipe_buddy *sbuddy;
5918 xmlnode *node;
5919 xmlnode *xn_presentity;
5920 xmlnode *xn_availability;
5921 xmlnode *xn_activity;
5922 xmlnode *xn_display_name;
5923 xmlnode *xn_email;
5924 xmlnode *xn_phone_number;
5925 xmlnode *xn_userinfo;
5926 xmlnode *xn_note;
5927 xmlnode *xn_oof;
5928 xmlnode *xn_state;
5929 xmlnode *xn_contact;
5930 char *note;
5931 char *free_activity;
5932 int user_avail;
5933 const char *user_avail_nil;
5934 int res_avail;
5935 time_t user_avail_since = 0;
5936 time_t activity_since = 0;
5938 /* fix for Reuters environment on Linux */
5939 if (data && strstr(data, "encoding=\"utf-16\"")) {
5940 char *tmp_data;
5941 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
5942 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
5943 g_free(tmp_data);
5944 } else {
5945 xn_presentity = xmlnode_from_str(data, len);
5948 xn_availability = xmlnode_get_child(xn_presentity, "availability");
5949 xn_activity = xmlnode_get_child(xn_presentity, "activity");
5950 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
5951 xn_email = xmlnode_get_child(xn_presentity, "email");
5952 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
5953 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
5954 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
5955 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
5956 user_avail = xn_state ? atoi(xmlnode_get_attrib(xn_state, "avail")) : 0;
5957 user_avail_since = xn_state ? purple_str_to_time(xmlnode_get_attrib(xn_state, "since"), FALSE, NULL, NULL, NULL) : 0;
5958 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
5959 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
5960 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
5961 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
5963 if (user_avail_nil && !strcmp(user_avail_nil, "true")) { /* null-ed */
5964 user_avail = 0;
5965 user_avail_since = 0;
5968 free_activity = NULL;
5970 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
5971 uri = sip_uri_from_name(name);
5972 avl = atoi(xmlnode_get_attrib(xn_availability, "aggregate"));
5973 epid = xmlnode_get_attrib(xn_availability, "epid");
5974 act = atoi(xmlnode_get_attrib(xn_activity, "aggregate"));
5976 status_id = sipe_get_status_by_act_avail_2005(act, avl);
5977 res_avail = sipe_get_availability_by_status(status_id, NULL);
5978 if (user_avail > res_avail) {
5979 res_avail = user_avail;
5980 status_id = sipe_get_status_by_availability(user_avail, NULL);
5983 if (xn_display_name) {
5984 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
5985 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
5986 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
5987 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
5988 char *tel_uri = sip_to_tel_uri(phone_number);
5990 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5991 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5992 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
5993 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
5995 g_free(tel_uri);
5996 g_free(phone_label);
5997 g_free(phone_number);
5998 g_free(email);
5999 g_free(display_name);
6002 if (xn_contact) {
6003 /* tel */
6004 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6006 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6007 const char *phone_type = xmlnode_get_attrib(node, "type");
6008 char* phone = xmlnode_get_data(node);
6010 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6012 g_free(phone);
6016 /* devicePresence */
6017 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6018 xmlnode *xn_device_name;
6019 xmlnode *xn_calendar_info;
6020 xmlnode *xn_state;
6021 char *state;
6023 /* deviceName */
6024 if (!strcmp(xmlnode_get_attrib(node, "epid"), epid)) {
6025 xn_device_name = xmlnode_get_child(node, "deviceName");
6026 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6029 /* calendarInfo */
6030 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6031 if (xn_calendar_info) {
6032 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6034 if (cal_start_time) {
6035 time_t cal_start_time_t = purple_str_to_time(cal_start_time, FALSE, NULL, NULL, NULL);
6036 time_t cal_start_time_t_tmp = purple_str_to_time(cal_start_time_tmp, FALSE, NULL, NULL, NULL);
6038 if (cal_start_time_t_tmp > cal_start_time_t) {
6039 cal_start_time = cal_start_time_tmp;
6040 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6041 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6043 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);
6045 } else {
6046 cal_start_time = cal_start_time_tmp;
6047 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6048 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6050 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);
6054 /* state */
6055 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6056 if (xn_state) {
6057 int dev_avail = atoi(xmlnode_get_attrib(xn_state, "avail"));
6058 time_t dev_avail_since = purple_str_to_time(xmlnode_get_attrib(xn_state, "since"), FALSE, NULL, NULL, NULL);
6060 state = xn_state ? xmlnode_get_data(xn_state) : NULL;
6061 if (dev_avail_since > user_avail_since &&
6062 dev_avail >= res_avail)
6064 res_avail = dev_avail;
6065 if (!is_empty(state))
6067 if (!strcmp(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6068 activity = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
6069 } else if (!strcmp(state, "presenting")) {
6070 activity = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF);
6071 } else {
6072 activity = free_activity = state;
6073 state = NULL;
6075 activity_since = dev_avail_since;
6077 status_id = sipe_get_status_by_availability(res_avail, activity);
6079 g_free(state);
6083 /* oof */
6084 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6085 activity = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF);
6086 activity_since = 0;
6089 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6090 if (sbuddy)
6092 g_free(sbuddy->activity);
6093 sbuddy->activity = NULL;
6094 if (!is_empty(activity)) { sbuddy->activity = g_strdup(activity); }
6096 sbuddy->activity_since = activity_since;
6098 sbuddy->user_avail = user_avail;
6099 sbuddy->user_avail_since = user_avail_since;
6101 g_free(sbuddy->annotation);
6102 sbuddy->annotation = NULL;
6103 if (!is_empty(note)) { sbuddy->annotation = g_strdup(note); }
6105 sbuddy->is_oof_note = (xn_oof != NULL);
6107 g_free(sbuddy->device_name);
6108 sbuddy->device_name = NULL;
6109 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6111 if (!is_empty(cal_free_busy_base64)) {
6112 g_free(sbuddy->cal_start_time);
6113 sbuddy->cal_start_time = g_strdup(cal_start_time);
6115 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6117 g_free(sbuddy->cal_free_busy_base64);
6118 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6120 g_free(sbuddy->cal_free_busy);
6121 sbuddy->cal_free_busy = NULL;
6124 sbuddy->last_non_cal_status_id = status_id;
6125 g_free(sbuddy->last_non_cal_activity);
6126 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6129 if (free_activity) g_free(free_activity);
6131 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6132 sipe_got_user_status(sip, uri, status_id);
6134 if (!sip->ocs2007 && !strcmp(self_uri, uri)) {
6135 sipe_user_info_has_updated(sip, xn_userinfo);
6138 g_free(note);
6139 xmlnode_free(xn_presentity);
6140 g_free(uri);
6141 g_free(self_uri);
6144 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6146 char *ctype = sipmsg_find_header(msg, "Content-Type");
6148 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6150 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6151 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6153 const char *content = msg->body;
6154 unsigned length = msg->bodylen;
6155 PurpleMimeDocument *mime = NULL;
6157 if (strstr(ctype, "multipart"))
6159 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6160 const char *content_type;
6161 GList* parts;
6162 mime = purple_mime_document_parse(doc);
6163 parts = purple_mime_document_get_parts(mime);
6164 while(parts) {
6165 content = purple_mime_part_get_data(parts->data);
6166 length = purple_mime_part_get_length(parts->data);
6167 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6168 if(content_type && strstr(content_type,"application/rlmi+xml"))
6170 process_incoming_notify_rlmi_resub(sip, content, length);
6172 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6174 process_incoming_notify_msrtc(sip, content, length);
6176 else
6178 process_incoming_notify_rlmi(sip, content, length);
6180 parts = parts->next;
6182 g_free(doc);
6184 if (mime)
6186 purple_mime_document_free(mime);
6189 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6191 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6193 else if(strstr(ctype, "application/rlmi+xml"))
6195 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6198 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6200 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6202 else
6204 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6208 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6210 char *ctype = sipmsg_find_header(msg, "Content-Type");
6211 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6213 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6215 if (ctype &&
6216 strstr(ctype, "multipart") &&
6217 (strstr(ctype, "application/rlmi+xml") ||
6218 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6219 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6220 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6221 GList *parts = purple_mime_document_get_parts(mime);
6222 GSList *buddies = NULL;
6223 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6225 while (parts) {
6226 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6227 purple_mime_part_get_length(parts->data));
6229 if (strcmp(xml->name, "list")) {
6230 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6232 buddies = g_slist_append(buddies, uri);
6234 xmlnode_free(xml);
6236 parts = parts->next;
6238 g_free(doc);
6239 if (mime) purple_mime_document_free(mime);
6241 payload->host = g_strdup(who);
6242 payload->buddies = buddies;
6243 sipe_schedule_action(action_name, timeout,
6244 sipe_subscribe_presence_batched_routed,
6245 sipe_subscribe_presence_batched_routed_free,
6246 sip, payload);
6247 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6249 } else {
6250 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6251 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6253 g_free(action_name);
6257 * Dispatcher for all incoming subscription information
6258 * whether it comes from NOTIFY, BENOTIFY requests or
6259 * piggy-backed to subscription's OK responce.
6261 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6262 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6264 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6266 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6267 gchar *event = sipmsg_find_header(msg, "Event");
6268 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6269 char *tmp;
6270 int timeout = 0;
6272 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6273 event ? event : "",
6274 tmp = fix_newlines(msg->body));
6275 g_free(tmp);
6276 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6278 /* implicit subscriptions */
6279 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6280 sipe_process_imdn(sip, msg);
6283 if (!request)
6285 const gchar *expires_header;
6286 expires_header = sipmsg_find_header(msg, "Expires");
6287 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6288 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6289 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout; // 2 min ahead of expiration
6292 /* for one off subscriptions (send with Expire: 0) */
6293 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6295 sipe_process_provisioning_v2(sip, msg);
6297 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6299 sipe_process_provisioning(sip, msg);
6302 if (!subscription_state || strstr(subscription_state, "active"))
6304 if (event && !g_ascii_strcasecmp(event, "presence"))
6306 sipe_process_presence(sip, msg);
6308 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6310 sipe_process_roaming_contacts(sip, msg);
6312 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6314 sipe_process_roaming_self(sip, msg);
6316 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6318 sipe_process_roaming_acl(sip, msg);
6320 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
6322 sipe_process_presence_wpending(sip, msg);
6324 else if (event && !g_ascii_strcasecmp(event, "conference"))
6326 sipe_process_conference(sip, msg);
6330 /* The server sends status 'terminated' */
6331 if (subscription_state && strstr(subscription_state, "terminated") ) {
6332 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6333 gchar *key = sipe_get_subscription_key(event, who);
6335 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6336 g_free(who);
6338 if (g_hash_table_lookup(sip->subscriptions, key)) {
6339 g_hash_table_remove(sip->subscriptions, key);
6340 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6343 g_free(key);
6346 if (timeout && event) {// For LSC 2005 and OCS 2007
6347 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
6348 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
6350 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
6351 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
6352 g_free(action_name);
6354 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
6355 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
6357 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
6358 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
6359 g_free(action_name);
6361 else*/
6362 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6363 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6365 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6366 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6367 g_free(action_name);
6369 else if (!g_ascii_strcasecmp(event, "presence") &&
6370 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6372 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6373 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6374 if (sip->batched_support) {
6375 sipe_process_presence_timeout(sip, msg, who, timeout);
6377 else {
6378 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6379 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6381 g_free(action_name);
6382 g_free(who);
6386 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
6388 sipe_process_registration_notify(sip, msg);
6391 /* The client responses on received a NOTIFY message */
6392 if (request && !benotify)
6394 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6399 * Whether user manually changed status or
6400 * it was changed automatically due to user
6401 * became inactive/active again
6403 static gboolean
6404 sipe_is_user_state(struct sipe_account_data *sip)
6406 gboolean res = (sip->was_idle == sip->is_idle);
6407 return res;
6410 static void
6411 send_presence_soap0(struct sipe_account_data *sip,
6412 gboolean do_publish_calendar,
6413 gboolean do_reset_status)
6415 struct sipe_ews* ews = sip->ews;
6416 int availability = 0;
6417 int activity = 0;
6418 gchar *body;
6419 gchar *tmp;
6420 gchar *res_note = NULL;
6421 gchar *res_oof = NULL;
6422 const gchar *note_pub = NULL;
6423 gchar *states = NULL;
6424 gchar *calendar_data = NULL;
6425 gchar *epid = get_epid(sip);
6426 time_t now = time(NULL);
6427 gchar *since_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&now)));
6428 const gchar *oof_note = sipe_ews_get_oof_note(ews);
6429 const char *user_input;
6431 if (!sip->initial_state_published ||
6432 do_reset_status)
6434 g_free(sip->status);
6435 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6438 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6440 /* Note */
6441 if (oof_note) {
6442 note_pub = oof_note;
6443 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6444 } else if (sip->note) {
6445 note_pub = sip->note;
6448 if (note_pub)
6450 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, note_pub);
6452 else if (!(ews && ews->is_updated)) /* preserve existing publication */
6454 xmlnode *xn_note;
6455 if (sip->user_info && (xn_note = xmlnode_get_child(sip->user_info, "note"))) {
6456 res_note = xmlnode_to_str(xn_note, NULL);
6459 if (sip->user_info && xmlnode_get_child(sip->user_info, "oof")) {
6460 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6464 /* User State */
6465 if (!do_reset_status) {
6466 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6468 gchar *activity_token = NULL;
6469 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6471 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6472 avail_2007,
6473 since_time_str,
6474 epid,
6475 activity_token);
6476 g_free(activity_token);
6478 else /* preserve existing publication */
6480 xmlnode *xn_states;
6481 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6482 states = xmlnode_to_str(xn_states, NULL);
6485 } else {
6486 /* do nothing - then User state will be erased */
6488 sip->initial_state_published = TRUE;
6490 /* CalendarInfo */
6491 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6493 char *fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
6494 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6495 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6496 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6497 fb_start_str,
6498 free_busy_base64);
6499 g_free(fb_start_str);
6500 g_free(free_busy_base64);
6503 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6505 /* forming resulting XML */
6506 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6507 sip->username,
6508 availability,
6509 activity,
6510 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6511 res_note ? res_note : "",
6512 res_oof ? res_oof : "",
6513 states ? states : "",
6514 calendar_data ? calendar_data : "",
6515 epid,
6516 since_time_str,
6517 since_time_str,
6518 user_input);
6519 g_free(tmp);
6520 g_free(res_note);
6521 g_free(states);
6522 g_free(calendar_data);
6524 send_soap_request(sip, body);
6526 g_free(body);
6527 g_free(since_time_str);
6528 g_free(epid);
6531 void
6532 send_presence_soap(struct sipe_account_data *sip,
6533 gboolean do_publish_calendar)
6535 return send_presence_soap0(sip, do_publish_calendar, FALSE);
6539 static gboolean
6540 process_send_presence_category_publish_response(struct sipe_account_data *sip,
6541 struct sipmsg *msg,
6542 struct transaction *trans)
6544 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
6546 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
6547 xmlnode *xml;
6548 xmlnode *node;
6549 gchar *fault_code;
6550 GHashTable *faults;
6551 int index_our;
6552 gboolean has_device_publication = FALSE;
6554 xml = xmlnode_from_str(msg->body, msg->bodylen);
6556 /* test if version mismatch fault */
6557 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
6558 if (strcmp(fault_code, "Client.BadCall.WrongDelta")) {
6559 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
6560 g_free(fault_code);
6561 xmlnode_free(xml);
6562 return TRUE;
6564 g_free(fault_code);
6566 /* accumulating information about faulty versions */
6567 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
6568 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
6569 node;
6570 node = xmlnode_get_next_twin(node))
6572 const gchar *index = xmlnode_get_attrib(node, "index");
6573 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
6575 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
6576 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
6578 xmlnode_free(xml);
6580 /* here we are parsing own request to figure out what publication
6581 * referensed here only by index went wrong
6583 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
6585 /* publication */
6586 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
6587 index_our = 1; /* starts with 1 - our first publication */
6588 node;
6589 node = xmlnode_get_next_twin(node), index_our++)
6591 gchar *idx = g_strdup_printf("%d", index_our);
6592 const gchar *curVersion = g_hash_table_lookup(faults, idx);
6593 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
6594 g_free(idx);
6596 if (!strcmp("device", categoryName)) {
6597 has_device_publication = TRUE;
6600 if (curVersion) { /* fault exist on this index */
6601 const gchar *container = xmlnode_get_attrib(node, "container");
6602 const gchar *instance = xmlnode_get_attrib(node, "instance");
6603 /* key is <category><instance><container> */
6604 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
6605 struct sipe_publication *publication =
6606 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
6608 purple_debug_info("sipe", "key is %s\n", key);
6610 if (publication) {
6611 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6612 key, curVersion, publication->version);
6613 /* updating publication's version to the correct one */
6614 publication->version = atoi(curVersion);
6616 g_free(key);
6619 xmlnode_free(xml);
6620 g_hash_table_destroy(faults);
6622 /* rebublishing with right versions */
6623 if (has_device_publication) {
6624 send_publish_category_initial(sip);
6625 } else {
6626 send_presence_status(sip);
6629 return TRUE;
6633 * Returns 'device' XML part for publication.
6634 * Must be g_free'd after use.
6636 static gchar *
6637 sipe_publish_get_category_device(struct sipe_account_data *sip)
6639 gchar *uri;
6640 gchar *doc;
6641 gchar *epid = get_epid(sip);
6642 gchar *uuid = generateUUIDfromEPID(epid);
6643 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
6644 /* key is <category><instance><container> */
6645 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
6646 struct sipe_publication *publication =
6647 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
6649 g_free(key);
6650 g_free(epid);
6652 uri = sip_uri_self(sip);
6653 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
6654 device_instance,
6655 publication ? publication->version : 0,
6656 uuid,
6657 uri,
6658 "00:00:00+01:00", /* @TODO make timezone real*/
6659 sipe_get_host_name()
6662 g_free(uri);
6663 g_free(uuid);
6665 return doc;
6669 * A service method - use
6670 * - send_publish_get_category_state_machine and
6671 * - send_publish_get_category_state_user instead.
6672 * Must be g_free'd after use.
6674 static gchar *
6675 sipe_publish_get_category_state(struct sipe_account_data *sip,
6676 gboolean is_user_state)
6678 int availability = sipe_get_availability_by_status(sip->status, NULL);
6679 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
6680 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
6681 /* key is <category><instance><container> */
6682 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6683 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6684 struct sipe_publication *publication_2 =
6685 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6686 struct sipe_publication *publication_3 =
6687 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6689 g_free(key_2);
6690 g_free(key_3);
6692 if (publication_2 && (publication_2->availability == availability))
6694 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6695 return NULL; /* nothing to update */
6698 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
6699 instance,
6700 publication_2 ? publication_2->version : 0,
6701 availability,
6702 instance,
6703 publication_3 ? publication_3->version : 0,
6704 availability);
6708 * Only Busy and OOF calendar event are published.
6709 * Different instances are used for that.
6711 * Must be g_free'd after use.
6713 static gchar *
6714 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
6715 struct sipe_cal_event *event,
6716 const char *uri,
6717 int cal_satus)
6719 gchar *start_time_str;
6720 int availability = 0;
6721 gchar *res;
6722 guint instance = (cal_satus == SIPE_CAL_OOF) ?
6723 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
6724 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
6726 /* key is <category><instance><container> */
6727 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6728 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6729 struct sipe_publication *publication_2 =
6730 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6731 struct sipe_publication *publication_3 =
6732 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6734 g_free(key_2);
6735 g_free(key_3);
6737 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
6738 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6739 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
6740 return NULL;
6743 if (event &&
6744 publication_3 &&
6745 (publication_3->availability == availability) &&
6746 !strcmp(publication_3->cal_event_hash, sipe_cal_event_hash(event)))
6748 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6749 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
6750 return NULL; /* nothing to update */
6753 if (event &&
6754 (event->cal_status == SIPE_CAL_BUSY ||
6755 event->cal_status == SIPE_CAL_OOF))
6757 gchar *availability_xml_str = NULL;
6758 gchar *activity_xml_str = NULL;
6760 if (event->cal_status == SIPE_CAL_BUSY) {
6761 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
6764 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
6765 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6766 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
6767 "minAvailability=\"6500\"",
6768 "maxAvailability=\"8999\"");
6769 } else if (event->cal_status == SIPE_CAL_OOF) {
6770 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6771 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
6772 "minAvailability=\"12000\"",
6773 "");
6775 start_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&event->start_time)));
6777 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
6778 instance,
6779 publication_2 ? publication_2->version : 0,
6780 uri,
6781 start_time_str,
6782 availability_xml_str ? availability_xml_str : "",
6783 activity_xml_str ? activity_xml_str : "",
6784 event->subject ? event->subject : "",
6785 event->location ? event->location : "",
6787 instance,
6788 publication_3 ? publication_3->version : 0,
6789 uri,
6790 start_time_str,
6791 availability_xml_str ? availability_xml_str : "",
6792 activity_xml_str ? activity_xml_str : "",
6793 event->subject ? event->subject : "",
6794 event->location ? event->location : ""
6796 g_free(start_time_str);
6797 g_free(availability_xml_str);
6798 g_free(activity_xml_str);
6801 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
6803 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
6804 instance,
6805 publication_2 ? publication_2->version : 0,
6807 instance,
6808 publication_3 ? publication_3->version : 0
6812 return res;
6816 * Returns 'machineState' XML part for publication.
6817 * Must be g_free'd after use.
6819 static gchar *
6820 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
6822 return sipe_publish_get_category_state(sip, FALSE);
6826 * Returns 'userState' XML part for publication.
6827 * Must be g_free'd after use.
6829 static gchar *
6830 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
6832 return sipe_publish_get_category_state(sip, TRUE);
6836 * Compares two strings even in case both are NULL/empty
6838 static gboolean
6839 sipe_is_equal(const char* n1, const char* n2) {
6840 return ((!n1 || !strlen(n1)) && (!n2 || !strlen(n2))) /* both empty */
6841 || (n1 && n2 && !strcmp(n1, n2)); /* or not empty and equal */
6845 * Returns 'note' XML part for publication.
6846 * Must be g_free'd after use.
6848 * @param note_type either personal or OOF
6850 static gchar *
6851 sipe_publish_get_category_note(struct sipe_account_data *sip,
6852 const char *note,
6853 const char *note_type)
6855 guint instance = !strcmp("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
6856 /* key is <category><instance><container> */
6857 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
6858 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
6859 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
6861 struct sipe_publication *publication_note_200 =
6862 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
6863 struct sipe_publication *publication_note_300 =
6864 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
6865 struct sipe_publication *publication_note_400 =
6866 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
6868 const char *n1 = note;
6869 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
6871 g_free(key_note_200);
6872 g_free(key_note_300);
6873 g_free(key_note_400);
6875 if (sipe_is_equal(n1, n2))
6877 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
6878 return NULL; /* nothing to update */
6881 return g_markup_printf_escaped(SIPE_PUB_XML_NOTE,
6882 instance,
6883 publication_note_200 ? publication_note_200->version : 0,
6884 note_type,
6885 note ? note : "",
6887 instance,
6888 publication_note_300 ? publication_note_300->version : 0,
6889 note_type,
6890 note ? note : "",
6892 instance,
6893 publication_note_400 ? publication_note_400->version : 0,
6894 note_type,
6895 note ? note : "");
6899 * Returns 'calendarData' XML part with WorkingHours for publication.
6900 * Must be g_free'd after use.
6902 static gchar *
6903 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
6905 struct sipe_ews* ews = sip->ews;
6907 /* key is <category><instance><container> */
6908 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
6909 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
6910 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
6911 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
6912 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
6913 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
6915 struct sipe_publication *publication_cal_1 =
6916 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
6917 struct sipe_publication *publication_cal_100 =
6918 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
6919 struct sipe_publication *publication_cal_200 =
6920 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
6921 struct sipe_publication *publication_cal_300 =
6922 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
6923 struct sipe_publication *publication_cal_400 =
6924 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
6925 struct sipe_publication *publication_cal_32000 =
6926 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
6928 const char *n1 = ews->working_hours_xml_str;
6929 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
6931 g_free(key_cal_1);
6932 g_free(key_cal_100);
6933 g_free(key_cal_200);
6934 g_free(key_cal_300);
6935 g_free(key_cal_400);
6936 g_free(key_cal_32000);
6938 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
6939 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
6940 return NULL;
6943 if (sipe_is_equal(n1, n2))
6945 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
6946 return NULL; /* nothing to update */
6949 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
6950 /* 1 */
6951 publication_cal_1 ? publication_cal_1->version : 0,
6952 ews->email,
6953 ews->working_hours_xml_str,
6954 /* 100 - Public */
6955 publication_cal_100 ? publication_cal_100->version : 0,
6956 /* 200 - Company */
6957 publication_cal_200 ? publication_cal_200->version : 0,
6958 ews->email,
6959 ews->working_hours_xml_str,
6960 /* 300 - Team */
6961 publication_cal_300 ? publication_cal_300->version : 0,
6962 ews->email,
6963 ews->working_hours_xml_str,
6964 /* 400 - Personal */
6965 publication_cal_400 ? publication_cal_400->version : 0,
6966 ews->email,
6967 ews->working_hours_xml_str,
6968 /* 32000 - Blocked */
6969 publication_cal_32000 ? publication_cal_32000->version : 0
6974 * Returns 'calendarData' XML part with FreeBusy for publication.
6975 * Must be g_free'd after use.
6977 static gchar *
6978 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
6980 struct sipe_ews* ews = sip->ews;
6981 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
6982 char *fb_start_str;
6983 char *free_busy_base64;
6984 const char *st;
6985 const char *fb;
6986 char *res;
6988 /* key is <category><instance><container> */
6989 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
6990 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
6991 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
6992 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
6993 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
6994 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
6996 struct sipe_publication *publication_cal_1 =
6997 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
6998 struct sipe_publication *publication_cal_100 =
6999 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7000 struct sipe_publication *publication_cal_200 =
7001 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7002 struct sipe_publication *publication_cal_300 =
7003 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7004 struct sipe_publication *publication_cal_400 =
7005 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7006 struct sipe_publication *publication_cal_32000 =
7007 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7009 g_free(key_cal_1);
7010 g_free(key_cal_100);
7011 g_free(key_cal_200);
7012 g_free(key_cal_300);
7013 g_free(key_cal_400);
7014 g_free(key_cal_32000);
7016 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7017 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7018 return NULL;
7021 fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
7022 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7024 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7025 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7027 if (sipe_is_equal(st, fb_start_str) && sipe_is_equal(fb, free_busy_base64))
7029 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7030 g_free(fb_start_str);
7031 g_free(free_busy_base64);
7032 return NULL; /* nothing to update */
7035 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7036 /* 1 */
7037 cal_data_instance,
7038 publication_cal_1 ? publication_cal_1->version : 0,
7039 /* 100 - Public */
7040 cal_data_instance,
7041 publication_cal_100 ? publication_cal_100->version : 0,
7042 /* 200 - Company */
7043 cal_data_instance,
7044 publication_cal_200 ? publication_cal_200->version : 0,
7045 ews->email,
7046 fb_start_str,
7047 free_busy_base64,
7048 /* 300 - Team */
7049 cal_data_instance,
7050 publication_cal_300 ? publication_cal_300->version : 0,
7051 ews->email,
7052 fb_start_str,
7053 free_busy_base64,
7054 /* 400 - Personal */
7055 cal_data_instance,
7056 publication_cal_400 ? publication_cal_400->version : 0,
7057 ews->email,
7058 fb_start_str,
7059 free_busy_base64,
7060 /* 32000 - Blocked */
7061 cal_data_instance,
7062 publication_cal_32000 ? publication_cal_32000->version : 0
7065 g_free(fb_start_str);
7066 g_free(free_busy_base64);
7067 return res;
7070 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7072 gchar *uri;
7073 gchar *doc;
7074 gchar *tmp;
7075 gchar *hdr;
7077 uri = sip_uri_self(sip);
7078 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7079 uri,
7080 publications);
7082 tmp = get_contact(sip);
7083 hdr = g_strdup_printf("Contact: %s\r\n"
7084 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7086 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7088 g_free(tmp);
7089 g_free(hdr);
7090 g_free(uri);
7091 g_free(doc);
7094 static void
7095 send_publish_category_initial(struct sipe_account_data *sip)
7097 gchar *pub_device = sipe_publish_get_category_device(sip);
7098 gchar *pub_machine;
7099 gchar *publications;
7101 g_free(sip->status);
7102 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7104 pub_machine = sipe_publish_get_category_state_machine(sip);
7105 publications = g_strdup_printf("%s%s",
7106 pub_device,
7107 pub_machine ? pub_machine : "");
7108 g_free(pub_device);
7109 g_free(pub_machine);
7111 send_presence_publish(sip, publications);
7112 g_free(publications);
7115 static void
7116 send_presence_category_publish(struct sipe_account_data *sip,
7117 const char *note)
7119 gchar *pub_state = sipe_is_user_state(sip) ?
7120 sipe_publish_get_category_state_user(sip) :
7121 sipe_publish_get_category_state_machine(sip);
7122 gchar *pub_note = sipe_publish_get_category_note(sip, note, "personal");
7123 gchar *publications;
7125 if (!pub_state && !pub_note) {
7126 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7127 return;
7130 publications = g_strdup_printf("%s%s",
7131 pub_state ? pub_state : "",
7132 pub_note ? pub_note : "");
7134 purple_debug_info("sipe", "send_presence_category_publish: sip->status: %s sip->is_idle:%s sip->was_idle:%s\n",
7135 sip->status, sip->is_idle ? "Y" : "N", sip->was_idle ? "Y" : "N");
7137 g_free(pub_state);
7138 g_free(pub_note);
7140 send_presence_publish(sip, publications);
7141 g_free(publications);
7145 * Publishes self status
7146 * based on own calendar information.
7148 * For 2007+
7150 void
7151 publish_calendar_status_self(struct sipe_account_data *sip)
7153 struct sipe_cal_event* event = NULL;
7154 gchar *pub_cal_working_hours = NULL;
7155 gchar *pub_cal_free_busy = NULL;
7156 gchar *pub_calendar = NULL;
7157 gchar *pub_calendar2 = NULL;
7158 gchar *pub_oof_note = NULL;
7159 const gchar *oof_note;
7160 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7162 if (sip->ews && sip->ews->cal_events) {
7163 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7166 if (!event) {
7167 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7168 } else {
7169 char *desc = sipe_cal_event_describe(event);
7170 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7171 g_free(desc);
7174 /* Logic
7175 if OOF
7176 OOF publish, Busy clean
7177 ilse if Busy
7178 OOF clean, Busy publish
7179 else
7180 OOF clean, Busy clean
7182 if (event && event->cal_status == SIPE_CAL_OOF) {
7183 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7184 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7185 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7186 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7187 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7188 } else {
7189 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7190 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7193 if ((oof_note = sipe_ews_get_oof_note(sip->ews))) {
7194 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF");
7197 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7198 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7200 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7201 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7202 } else {
7203 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7204 pub_cal_working_hours ? pub_cal_working_hours : "",
7205 pub_cal_free_busy ? pub_cal_free_busy : "",
7206 pub_calendar ? pub_calendar : "",
7207 pub_calendar2 ? pub_calendar2 : "",
7208 pub_oof_note ? pub_oof_note : "");
7210 send_presence_publish(sip, publications);
7211 g_free(publications);
7214 g_free(pub_cal_working_hours);
7215 g_free(pub_cal_free_busy);
7216 g_free(pub_calendar);
7217 g_free(pub_calendar2);
7218 g_free(pub_oof_note);
7220 /* repeat scheduling */
7221 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7224 static void send_presence_status(struct sipe_account_data *sip)
7226 PurpleStatus * status = purple_account_get_active_status(sip->account);
7227 const gchar *note;
7228 if (!status) return;
7230 note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
7231 purple_debug_info("sipe", "send_presence_status: status: '%s'\n", purple_status_get_id(status) ? purple_status_get_id(status) : "");
7232 purple_debug_info("sipe", "send_presence_status: note: '%s'\n", note ? note : "");
7234 if (sip->ocs2007) {
7235 send_presence_category_publish(sip, note);
7236 } else {
7237 send_presence_soap(sip, FALSE);
7241 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7243 gboolean found = FALSE;
7244 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
7245 if (msg->response == 0) { /* request */
7246 if (!strcmp(msg->method, "MESSAGE")) {
7247 process_incoming_message(sip, msg);
7248 found = TRUE;
7249 } else if (!strcmp(msg->method, "NOTIFY")) {
7250 purple_debug_info("sipe","send->process_incoming_notify\n");
7251 process_incoming_notify(sip, msg, TRUE, FALSE);
7252 found = TRUE;
7253 } else if (!strcmp(msg->method, "BENOTIFY")) {
7254 purple_debug_info("sipe","send->process_incoming_benotify\n");
7255 process_incoming_notify(sip, msg, TRUE, TRUE);
7256 found = TRUE;
7257 } else if (!strcmp(msg->method, "INVITE")) {
7258 process_incoming_invite(sip, msg);
7259 found = TRUE;
7260 } else if (!strcmp(msg->method, "REFER")) {
7261 process_incoming_refer(sip, msg);
7262 found = TRUE;
7263 } else if (!strcmp(msg->method, "OPTIONS")) {
7264 process_incoming_options(sip, msg);
7265 found = TRUE;
7266 } else if (!strcmp(msg->method, "INFO")) {
7267 process_incoming_info(sip, msg);
7268 found = TRUE;
7269 } else if (!strcmp(msg->method, "ACK")) {
7270 // ACK's don't need any response
7271 found = TRUE;
7272 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
7273 // LCS 2005 sends us these - just respond 200 OK
7274 found = TRUE;
7275 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7276 } else if (!strcmp(msg->method, "BYE")) {
7277 process_incoming_bye(sip, msg);
7278 found = TRUE;
7279 } else {
7280 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7282 } else { /* response */
7283 struct transaction *trans = transactions_find(sip, msg);
7284 if (trans) {
7285 if (msg->response == 407) {
7286 gchar *resend, *auth, *ptmp;
7288 if (sip->proxy.retries > 30) return;
7289 sip->proxy.retries++;
7290 /* do proxy authentication */
7292 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7294 fill_auth(ptmp, &sip->proxy);
7295 auth = auth_header(sip, &sip->proxy, trans->msg);
7296 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7297 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7298 g_free(auth);
7299 resend = sipmsg_to_string(trans->msg);
7300 /* resend request */
7301 sendout_pkt(sip->gc, resend);
7302 g_free(resend);
7303 } else {
7304 if (msg->response < 200) {
7305 /* ignore provisional response */
7306 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7307 } else {
7308 sip->proxy.retries = 0;
7309 if (!strcmp(trans->msg->method, "REGISTER")) {
7310 if (msg->response == 401)
7312 sip->registrar.retries++;
7314 else
7316 sip->registrar.retries = 0;
7318 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7319 } else {
7320 if (msg->response == 401) {
7321 gchar *resend, *auth, *ptmp;
7323 if (sip->registrar.retries > 4) return;
7324 sip->registrar.retries++;
7326 #ifdef USE_KERBEROS
7327 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
7328 #endif
7329 ptmp = sipmsg_find_auth_header(msg, "NTLM");
7330 #ifdef USE_KERBEROS
7331 } else {
7332 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
7334 #endif
7336 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp);
7338 fill_auth(ptmp, &sip->registrar);
7339 auth = auth_header(sip, &sip->registrar, trans->msg);
7340 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7341 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7343 //sipmsg_remove_header_now(trans->msg, "Authorization");
7344 //sipmsg_add_header(trans->msg, "Authorization", auth);
7345 g_free(auth);
7346 resend = sipmsg_to_string(trans->msg);
7347 /* resend request */
7348 sendout_pkt(sip->gc, resend);
7349 g_free(resend);
7353 if (trans->callback) {
7354 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7355 /* call the callback to process response*/
7356 (trans->callback)(sip, msg, trans);
7359 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7360 transactions_remove(sip, trans);
7364 found = TRUE;
7365 } else {
7366 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7369 if (!found) {
7370 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
7374 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7376 char *cur;
7377 char *dummy;
7378 char *tmp;
7379 struct sipmsg *msg;
7380 int restlen;
7381 cur = conn->inbuf;
7383 /* according to the RFC remove CRLF at the beginning */
7384 while (*cur == '\r' || *cur == '\n') {
7385 cur++;
7387 if (cur != conn->inbuf) {
7388 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7389 conn->inbufused = strlen(conn->inbuf);
7392 /* Received a full Header? */
7393 sip->processing_input = TRUE;
7394 while (sip->processing_input &&
7395 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7396 time_t currtime = time(NULL);
7397 cur += 2;
7398 cur[0] = '\0';
7399 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7400 g_free(tmp);
7401 msg = sipmsg_parse_header(conn->inbuf);
7402 cur[0] = '\r';
7403 cur += 2;
7404 restlen = conn->inbufused - (cur - conn->inbuf);
7405 if (msg && restlen >= msg->bodylen) {
7406 dummy = g_malloc(msg->bodylen + 1);
7407 memcpy(dummy, cur, msg->bodylen);
7408 dummy[msg->bodylen] = '\0';
7409 msg->body = dummy;
7410 cur += msg->bodylen;
7411 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7412 conn->inbufused = strlen(conn->inbuf);
7413 } else {
7414 if (msg){
7415 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7416 sipmsg_free(msg);
7418 return;
7421 /*if (msg->body) {
7422 purple_debug_info("sipe", "body:\n%s", msg->body);
7425 // Verify the signature before processing it
7426 if (sip->registrar.gssapi_context) {
7427 struct sipmsg_breakdown msgbd;
7428 gchar *signature_input_str;
7429 gchar *rspauth;
7430 msgbd.msg = msg;
7431 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7432 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
7434 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7436 if (rspauth != NULL) {
7437 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7438 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7439 process_input_message(sip, msg);
7440 } else {
7441 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7442 purple_connection_error(sip->gc, _("Invalid message signature received"));
7443 sip->gc->wants_to_die = TRUE;
7445 } else if (msg->response == 401) {
7446 purple_connection_error(sip->gc, _("Wrong password"));
7447 sip->gc->wants_to_die = TRUE;
7449 g_free(signature_input_str);
7451 g_free(rspauth);
7452 sipmsg_breakdown_free(&msgbd);
7453 } else {
7454 process_input_message(sip, msg);
7457 sipmsg_free(msg);
7461 static void sipe_udp_process(gpointer data, gint source,
7462 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
7464 PurpleConnection *gc = data;
7465 struct sipe_account_data *sip = gc->proto_data;
7466 struct sipmsg *msg;
7467 int len;
7468 time_t currtime;
7470 static char buffer[65536];
7471 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
7472 buffer[len] = '\0';
7473 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
7474 msg = sipmsg_parse_msg(buffer);
7475 if (msg) process_input_message(sip, msg);
7479 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
7481 struct sipe_account_data *sip = gc->proto_data;
7482 PurpleSslConnection *gsc = sip->gsc;
7484 purple_debug_error("sipe", "%s",debug);
7485 purple_connection_error(gc, msg);
7487 /* Invalidate this connection. Next send will open a new one */
7488 if (gsc) {
7489 connection_remove(sip, gsc->fd);
7490 purple_ssl_close(gsc);
7492 sip->gsc = NULL;
7493 sip->fd = -1;
7496 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7497 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7499 PurpleConnection *gc = data;
7500 struct sipe_account_data *sip;
7501 struct sip_connection *conn;
7502 int readlen, len;
7503 gboolean firstread = TRUE;
7505 /* NOTE: This check *IS* necessary */
7506 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
7507 purple_ssl_close(gsc);
7508 return;
7511 sip = gc->proto_data;
7512 conn = connection_find(sip, gsc->fd);
7513 if (conn == NULL) {
7514 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7515 gc->wants_to_die = TRUE;
7516 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
7517 return;
7520 /* Read all available data from the SSL connection */
7521 do {
7522 /* Increase input buffer size as needed */
7523 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7524 conn->inbuflen += SIMPLE_BUF_INC;
7525 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7526 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
7529 /* Try to read as much as there is space left in the buffer */
7530 readlen = conn->inbuflen - conn->inbufused - 1;
7531 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
7533 if (len < 0 && errno == EAGAIN) {
7534 /* Try again later */
7535 return;
7536 } else if (len < 0) {
7537 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
7538 return;
7539 } else if (firstread && (len == 0)) {
7540 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
7541 return;
7544 conn->inbufused += len;
7545 firstread = FALSE;
7547 /* Equivalence indicates that there is possibly more data to read */
7548 } while (len == readlen);
7550 conn->inbuf[conn->inbufused] = '\0';
7551 process_input(sip, conn);
7555 static void sipe_input_cb(gpointer data, gint source,
7556 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7558 PurpleConnection *gc = data;
7559 struct sipe_account_data *sip = gc->proto_data;
7560 int len;
7561 struct sip_connection *conn = connection_find(sip, source);
7562 if (!conn) {
7563 purple_debug_error("sipe", "Connection not found!\n");
7564 return;
7567 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7568 conn->inbuflen += SIMPLE_BUF_INC;
7569 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7572 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
7574 if (len < 0 && errno == EAGAIN)
7575 return;
7576 else if (len <= 0) {
7577 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7578 connection_remove(sip, source);
7579 if (sip->fd == source) sip->fd = -1;
7580 return;
7583 conn->inbufused += len;
7584 conn->inbuf[conn->inbufused] = '\0';
7586 process_input(sip, conn);
7589 /* Callback for new connections on incoming TCP port */
7590 static void sipe_newconn_cb(gpointer data, gint source,
7591 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7593 PurpleConnection *gc = data;
7594 struct sipe_account_data *sip = gc->proto_data;
7595 struct sip_connection *conn;
7597 int newfd = accept(source, NULL, NULL);
7599 conn = connection_create(sip, newfd);
7601 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7604 static void login_cb(gpointer data, gint source,
7605 SIPE_UNUSED_PARAMETER const gchar *error_message)
7607 PurpleConnection *gc = data;
7608 struct sipe_account_data *sip;
7609 struct sip_connection *conn;
7611 if (!PURPLE_CONNECTION_IS_VALID(gc))
7613 if (source >= 0)
7614 close(source);
7615 return;
7618 if (source < 0) {
7619 purple_connection_error(gc, _("Could not connect"));
7620 return;
7623 sip = gc->proto_data;
7624 sip->fd = source;
7625 sip->last_keepalive = time(NULL);
7627 conn = connection_create(sip, source);
7629 do_register(sip);
7631 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7634 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7635 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7637 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
7638 if (sip == NULL) return;
7640 do_register(sip);
7643 static guint sipe_ht_hash_nick(const char *nick)
7645 char *lc = g_utf8_strdown(nick, -1);
7646 guint bucket = g_str_hash(lc);
7647 g_free(lc);
7649 return bucket;
7652 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
7654 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
7657 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
7659 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7661 sip->listen_data = NULL;
7663 if (listenfd == -1) {
7664 purple_connection_error(sip->gc, _("Could not create listen socket"));
7665 return;
7668 sip->fd = listenfd;
7670 sip->listenport = purple_network_get_port_from_fd(sip->fd);
7671 sip->listenfd = sip->fd;
7673 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
7675 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
7676 do_register(sip);
7679 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
7680 SIPE_UNUSED_PARAMETER const char *error_message)
7682 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7684 sip->query_data = NULL;
7686 if (!hosts || !hosts->data) {
7687 purple_connection_error(sip->gc, _("Could not resolve hostname"));
7688 return;
7691 hosts = g_slist_remove(hosts, hosts->data);
7692 g_free(sip->serveraddr);
7693 sip->serveraddr = hosts->data;
7694 hosts = g_slist_remove(hosts, hosts->data);
7695 while (hosts) {
7696 hosts = g_slist_remove(hosts, hosts->data);
7697 g_free(hosts->data);
7698 hosts = g_slist_remove(hosts, hosts->data);
7701 /* create socket for incoming connections */
7702 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
7703 sipe_udp_host_resolved_listen_cb, sip);
7704 if (sip->listen_data == NULL) {
7705 purple_connection_error(sip->gc, _("Could not create listen socket"));
7706 return;
7710 static const struct sipe_service_data *current_service = NULL;
7712 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
7713 PurpleSslErrorType error,
7714 gpointer data)
7716 PurpleConnection *gc = data;
7717 struct sipe_account_data *sip;
7719 /* If the connection is already disconnected, we don't need to do anything else */
7720 if (!PURPLE_CONNECTION_IS_VALID(gc))
7721 return;
7723 sip = gc->proto_data;
7724 current_service = sip->service_data;
7725 if (current_service) {
7726 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
7727 current_service->transport ? current_service->transport : "NULL",
7728 current_service->service ? current_service->service : "NULL");
7731 sip->fd = -1;
7732 sip->gsc = NULL;
7734 switch(error) {
7735 case PURPLE_SSL_CONNECT_FAILED:
7736 purple_connection_error(gc, _("Connection failed"));
7737 break;
7738 case PURPLE_SSL_HANDSHAKE_FAILED:
7739 purple_connection_error(gc, _("SSL handshake failed"));
7740 break;
7741 case PURPLE_SSL_CERTIFICATE_INVALID:
7742 purple_connection_error(gc, _("SSL certificate invalid"));
7743 break;
7747 static void
7748 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
7750 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7751 PurpleProxyConnectData *connect_data;
7753 sip->listen_data = NULL;
7755 sip->listenfd = listenfd;
7756 if (sip->listenfd == -1) {
7757 purple_connection_error(sip->gc, _("Could not create listen socket"));
7758 return;
7761 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
7762 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7763 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7764 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
7765 sipe_newconn_cb, sip->gc);
7766 purple_debug_info("sipe", "connecting to %s port %d\n",
7767 sip->realhostname, sip->realport);
7768 /* open tcp connection to the server */
7769 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
7770 sip->realport, login_cb, sip->gc);
7772 if (connect_data == NULL) {
7773 purple_connection_error(sip->gc, _("Could not create socket"));
7777 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
7779 PurpleAccount *account = sip->account;
7780 PurpleConnection *gc = sip->gc;
7782 if (port == 0) {
7783 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
7786 sip->realhostname = hostname;
7787 sip->realport = port;
7789 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
7790 hostname, port);
7792 /* TODO: is there a good default grow size? */
7793 if (sip->transport != SIPE_TRANSPORT_UDP)
7794 sip->txbuf = purple_circ_buffer_new(0);
7796 if (sip->transport == SIPE_TRANSPORT_TLS) {
7797 /* SSL case */
7798 if (!purple_ssl_is_supported()) {
7799 gc->wants_to_die = TRUE;
7800 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
7801 return;
7804 purple_debug_info("sipe", "using SSL\n");
7806 sip->gsc = purple_ssl_connect(account, hostname, port,
7807 login_cb_ssl, sipe_ssl_connect_failure, gc);
7808 if (sip->gsc == NULL) {
7809 purple_connection_error(gc, _("Could not create SSL context"));
7810 return;
7812 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
7813 /* UDP case */
7814 purple_debug_info("sipe", "using UDP\n");
7816 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
7817 if (sip->query_data == NULL) {
7818 purple_connection_error(gc, _("Could not resolve hostname"));
7820 } else {
7821 /* TCP case */
7822 purple_debug_info("sipe", "using TCP\n");
7823 /* create socket for incoming connections */
7824 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
7825 sipe_tcp_connect_listen_cb, sip);
7826 if (sip->listen_data == NULL) {
7827 purple_connection_error(gc, _("Could not create listen socket"));
7828 return;
7833 /* Service list for autodection */
7834 static const struct sipe_service_data service_autodetect[] = {
7835 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
7836 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
7837 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
7838 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
7839 { NULL, NULL, 0 }
7842 /* Service list for SSL/TLS */
7843 static const struct sipe_service_data service_tls[] = {
7844 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
7845 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
7846 { NULL, NULL, 0 }
7849 /* Service list for TCP */
7850 static const struct sipe_service_data service_tcp[] = {
7851 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
7852 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
7853 { NULL, NULL, 0 }
7856 /* Service list for UDP */
7857 static const struct sipe_service_data service_udp[] = {
7858 { "sip", "udp", SIPE_TRANSPORT_UDP },
7859 { NULL, NULL, 0 }
7862 static void srvresolved(PurpleSrvResponse *, int, gpointer);
7863 static void resolve_next_service(struct sipe_account_data *sip,
7864 const struct sipe_service_data *start)
7866 if (start) {
7867 sip->service_data = start;
7868 } else {
7869 sip->service_data++;
7870 if (sip->service_data->service == NULL) {
7871 gchar *hostname;
7872 /* Try connecting to the SIP hostname directly */
7873 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
7874 if (sip->auto_transport) {
7875 // If SSL is supported, default to using it; OCS servers aren't configured
7876 // by default to accept TCP
7877 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
7878 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
7879 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
7882 hostname = g_strdup(sip->sipdomain);
7883 create_connection(sip, hostname, 0);
7884 return;
7888 /* Try to resolve next service */
7889 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
7890 sip->service_data->transport,
7891 sip->sipdomain,
7892 srvresolved, sip);
7895 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
7897 struct sipe_account_data *sip = data;
7899 sip->srv_query_data = NULL;
7901 /* find the host to connect to */
7902 if (results) {
7903 gchar *hostname = g_strdup(resp->hostname);
7904 int port = resp->port;
7905 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
7906 hostname, port);
7907 g_free(resp);
7909 sip->transport = sip->service_data->type;
7911 create_connection(sip, hostname, port);
7912 } else {
7913 resolve_next_service(sip, NULL);
7917 static void sipe_login(PurpleAccount *account)
7919 PurpleConnection *gc;
7920 struct sipe_account_data *sip;
7921 gchar **signinname_login, **userserver;
7922 const char *transport;
7923 const char *email;
7925 const char *username = purple_account_get_username(account);
7926 gc = purple_account_get_connection(account);
7928 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
7930 if (strpbrk(username, "\t\v\r\n") != NULL) {
7931 gc->wants_to_die = TRUE;
7932 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
7933 return;
7936 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
7937 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
7938 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
7939 sip->gc = gc;
7940 sip->account = account;
7941 sip->reregister_set = FALSE;
7942 sip->reauthenticate_set = FALSE;
7943 sip->subscribed = FALSE;
7944 sip->subscribed_buddies = FALSE;
7945 sip->initial_state_published = FALSE;
7947 /* username format: <username>,[<optional login>] */
7948 signinname_login = g_strsplit(username, ",", 2);
7949 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
7951 /* ensure that username format is name@domain */
7952 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
7953 g_strfreev(signinname_login);
7954 gc->wants_to_die = TRUE;
7955 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
7956 return;
7958 sip->username = g_strdup(signinname_login[0]);
7960 /* ensure that email format is name@domain if provided */
7961 email = purple_account_get_string(sip->account, "email", NULL);
7962 if (!is_empty(email) &&
7963 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
7965 gc->wants_to_die = TRUE;
7966 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
7967 return;
7969 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
7971 /* login name specified? */
7972 if (signinname_login[1] && strlen(signinname_login[1])) {
7973 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
7974 gboolean has_domain = domain_user[1] != NULL;
7975 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
7976 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
7977 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
7978 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
7979 sip->authdomain ? sip->authdomain : "", sip->authuser);
7980 g_strfreev(domain_user);
7983 userserver = g_strsplit(signinname_login[0], "@", 2);
7984 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
7985 purple_connection_set_display_name(gc, userserver[0]);
7986 sip->sipdomain = g_strdup(userserver[1]);
7987 g_strfreev(userserver);
7988 g_strfreev(signinname_login);
7990 if (strchr(sip->username, ' ') != NULL) {
7991 gc->wants_to_die = TRUE;
7992 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
7993 return;
7996 sip->password = g_strdup(purple_connection_get_password(gc));
7998 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
7999 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8000 g_free, (GDestroyNotify)g_hash_table_destroy);
8001 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8002 g_free, (GDestroyNotify)sipe_subscription_free);
8004 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8006 g_free(sip->status);
8007 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8009 sip->auto_transport = FALSE;
8010 transport = purple_account_get_string(account, "transport", "auto");
8011 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8012 if (userserver[0]) {
8013 /* Use user specified server[:port] */
8014 int port = 0;
8016 if (userserver[1])
8017 port = atoi(userserver[1]);
8019 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8020 userserver[0], port);
8022 if (strcmp(transport, "auto") == 0) {
8023 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8024 } else if (strcmp(transport, "tls") == 0) {
8025 sip->transport = SIPE_TRANSPORT_TLS;
8026 } else if (strcmp(transport, "tcp") == 0) {
8027 sip->transport = SIPE_TRANSPORT_TCP;
8028 } else {
8029 sip->transport = SIPE_TRANSPORT_UDP;
8032 create_connection(sip, g_strdup(userserver[0]), port);
8033 } else {
8034 /* Server auto-discovery */
8035 if (strcmp(transport, "auto") == 0) {
8036 sip->auto_transport = TRUE;
8037 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8038 current_service++;
8039 resolve_next_service(sip, current_service);
8040 } else {
8041 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8043 } else if (strcmp(transport, "tls") == 0) {
8044 resolve_next_service(sip, service_tls);
8045 } else if (strcmp(transport, "tcp") == 0) {
8046 resolve_next_service(sip, service_tcp);
8047 } else {
8048 resolve_next_service(sip, service_udp);
8051 g_strfreev(userserver);
8054 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8056 connection_free_all(sip);
8058 g_free(sip->epid);
8059 sip->epid = NULL;
8061 if (sip->query_data != NULL)
8062 purple_dnsquery_destroy(sip->query_data);
8063 sip->query_data = NULL;
8065 if (sip->srv_query_data != NULL)
8066 purple_srv_cancel(sip->srv_query_data);
8067 sip->srv_query_data = NULL;
8069 if (sip->listen_data != NULL)
8070 purple_network_listen_cancel(sip->listen_data);
8071 sip->listen_data = NULL;
8073 if (sip->gsc != NULL)
8074 purple_ssl_close(sip->gsc);
8075 sip->gsc = NULL;
8077 sipe_auth_free(&sip->registrar);
8078 sipe_auth_free(&sip->proxy);
8080 if (sip->txbuf)
8081 purple_circ_buffer_destroy(sip->txbuf);
8082 sip->txbuf = NULL;
8084 g_free(sip->realhostname);
8085 sip->realhostname = NULL;
8087 g_free(sip->server_version);
8088 sip->server_version = NULL;
8090 if (sip->listenpa)
8091 purple_input_remove(sip->listenpa);
8092 sip->listenpa = 0;
8093 if (sip->tx_handler)
8094 purple_input_remove(sip->tx_handler);
8095 sip->tx_handler = 0;
8096 if (sip->resendtimeout)
8097 purple_timeout_remove(sip->resendtimeout);
8098 sip->resendtimeout = 0;
8099 if (sip->timeouts) {
8100 GSList *entry = sip->timeouts;
8101 while (entry) {
8102 struct scheduled_action *sched_action = entry->data;
8103 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8104 purple_timeout_remove(sched_action->timeout_handler);
8105 if (sched_action->destroy) {
8106 (*sched_action->destroy)(sched_action->payload);
8108 g_free(sched_action->name);
8109 g_free(sched_action);
8110 entry = entry->next;
8113 g_slist_free(sip->timeouts);
8115 if (sip->allow_events) {
8116 GSList *entry = sip->allow_events;
8117 while (entry) {
8118 g_free(entry->data);
8119 entry = entry->next;
8122 g_slist_free(sip->allow_events);
8124 if (sip->containers) {
8125 GSList *entry = sip->containers;
8126 while (entry) {
8127 free_container((struct sipe_container *)entry->data);
8128 entry = entry->next;
8131 g_slist_free(sip->containers);
8133 if (sip->contact)
8134 g_free(sip->contact);
8135 sip->contact = NULL;
8136 if (sip->regcallid)
8137 g_free(sip->regcallid);
8138 sip->regcallid = NULL;
8140 if (sip->serveraddr)
8141 g_free(sip->serveraddr);
8142 sip->serveraddr = NULL;
8144 if (sip->focus_factory_uri)
8145 g_free(sip->focus_factory_uri);
8146 sip->focus_factory_uri = NULL;
8148 sip->fd = -1;
8149 sip->processing_input = FALSE;
8151 if (sip->ews) {
8152 sipe_ews_free(sip->ews);
8154 sip->ews = NULL;
8158 * A callback for g_hash_table_foreach_remove
8160 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8161 SIPE_UNUSED_PARAMETER gpointer user_data)
8163 sipe_free_buddy((struct sipe_buddy *) buddy);
8165 /* We must return TRUE as the key/value have already been deleted */
8166 return(TRUE);
8169 static void sipe_close(PurpleConnection *gc)
8171 struct sipe_account_data *sip = gc->proto_data;
8173 if (sip) {
8174 /* leave all conversations */
8175 sipe_session_close_all(sip);
8176 sipe_session_remove_all(sip);
8178 if (sip->csta) {
8179 sip_csta_close(sip);
8182 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8183 /* unsubscribe all */
8184 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8186 /* unregister */
8187 do_register_exp(sip, 0);
8190 sipe_connection_cleanup(sip);
8191 g_free(sip->sipdomain);
8192 g_free(sip->username);
8193 g_free(sip->email);
8194 g_free(sip->password);
8195 g_free(sip->authdomain);
8196 g_free(sip->authuser);
8197 g_free(sip->status);
8198 g_free(sip->note);
8200 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8201 g_hash_table_destroy(sip->buddies);
8202 g_hash_table_destroy(sip->our_publications);
8203 g_hash_table_destroy(sip->user_state_publications);
8204 g_hash_table_destroy(sip->subscriptions);
8206 if (sip->groups) {
8207 GSList *entry = sip->groups;
8208 while (entry) {
8209 struct sipe_group *group = entry->data;
8210 g_free(group->name);
8211 g_free(group);
8212 entry = entry->next;
8215 g_slist_free(sip->groups);
8217 if (sip->our_publication_keys) {
8218 GSList *entry = sip->our_publication_keys;
8219 while (entry) {
8220 g_free(entry->data);
8221 entry = entry->next;
8224 g_slist_free(sip->our_publication_keys);
8226 while (sip->transactions)
8227 transactions_remove(sip, sip->transactions->data);
8229 g_free(gc->proto_data);
8230 gc->proto_data = NULL;
8233 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8234 SIPE_UNUSED_PARAMETER void *user_data)
8236 PurpleAccount *acct = purple_connection_get_account(gc);
8237 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8238 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8239 if (conv == NULL)
8240 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8241 purple_conversation_present(conv);
8242 g_free(id);
8245 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8246 SIPE_UNUSED_PARAMETER void *user_data)
8249 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8250 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8253 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8254 SIPE_UNUSED_PARAMETER struct transaction *trans)
8256 PurpleNotifySearchResults *results;
8257 PurpleNotifySearchColumn *column;
8258 xmlnode *searchResults;
8259 xmlnode *mrow;
8260 int match_count = 0;
8261 gboolean more = FALSE;
8262 gchar *secondary;
8264 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8266 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8267 if (!searchResults) {
8268 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8269 return FALSE;
8272 results = purple_notify_searchresults_new();
8274 if (results == NULL) {
8275 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8276 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8278 xmlnode_free(searchResults);
8279 return FALSE;
8282 column = purple_notify_searchresults_column_new(_("User name"));
8283 purple_notify_searchresults_column_add(results, column);
8285 column = purple_notify_searchresults_column_new(_("Name"));
8286 purple_notify_searchresults_column_add(results, column);
8288 column = purple_notify_searchresults_column_new(_("Company"));
8289 purple_notify_searchresults_column_add(results, column);
8291 column = purple_notify_searchresults_column_new(_("Country"));
8292 purple_notify_searchresults_column_add(results, column);
8294 column = purple_notify_searchresults_column_new(_("Email"));
8295 purple_notify_searchresults_column_add(results, column);
8297 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8298 GList *row = NULL;
8300 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8301 row = g_list_append(row, g_strdup(uri_parts[1]));
8302 g_strfreev(uri_parts);
8304 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8305 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8306 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8307 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8309 purple_notify_searchresults_row_add(results, row);
8310 match_count++;
8313 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8314 char *data = xmlnode_get_data_unescaped(mrow);
8315 more = (g_strcasecmp(data, "true") == 0);
8316 g_free(data);
8319 secondary = g_strdup_printf(
8320 dngettext(GETTEXT_PACKAGE,
8321 "Found %d contact%s:",
8322 "Found %d contacts%s:", match_count),
8323 match_count, more ? _(" (more matched your query)") : "");
8325 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8326 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8327 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8329 g_free(secondary);
8330 xmlnode_free(searchResults);
8331 return TRUE;
8334 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8336 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8337 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8338 unsigned i = 0;
8340 do {
8341 PurpleRequestField *field = entries->data;
8342 const char *id = purple_request_field_get_id(field);
8343 const char *value = purple_request_field_string_get_value(field);
8345 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8347 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8348 } while ((entries = g_list_next(entries)) != NULL);
8349 attrs[i] = NULL;
8351 if (i > 0) {
8352 struct sipe_account_data *sip = gc->proto_data;
8353 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8354 gchar *query = g_strjoinv(NULL, attrs);
8355 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8356 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8357 send_soap_request_with_cb(sip, domain_uri, body,
8358 (TransCallback) process_search_contact_response, NULL);
8359 g_free(domain_uri);
8360 g_free(body);
8361 g_free(query);
8364 g_strfreev(attrs);
8367 static void sipe_show_find_contact(PurplePluginAction *action)
8369 PurpleConnection *gc = (PurpleConnection *) action->context;
8370 PurpleRequestFields *fields;
8371 PurpleRequestFieldGroup *group;
8372 PurpleRequestField *field;
8374 fields = purple_request_fields_new();
8375 group = purple_request_field_group_new(NULL);
8376 purple_request_fields_add_group(fields, group);
8378 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8379 purple_request_field_group_add_field(group, field);
8380 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8381 purple_request_field_group_add_field(group, field);
8382 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8383 purple_request_field_group_add_field(group, field);
8384 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8385 purple_request_field_group_add_field(group, field);
8387 purple_request_fields(gc,
8388 _("Search"),
8389 _("Search for a contact"),
8390 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8391 fields,
8392 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8393 _("_Cancel"), NULL,
8394 purple_connection_get_account(gc), NULL, NULL, gc);
8397 static void sipe_show_about_plugin(PurplePluginAction *action)
8399 PurpleConnection *gc = (PurpleConnection *) action->context;
8400 const char *txt =
8401 "<b><font size=\"+1\">Sipe " SIPE_VERSION "</font></b><br/>"
8402 "<br/>"
8403 "A third-party plugin implementing extended version of SIP/SIMPLE used by various products:<br/>"
8404 "<li> - MS Office Communications Server 2007 (R2)</li><br/>"
8405 "<li> - MS Live Communications Server 2005/2003</li><br/>"
8406 "<li> - Reuters Messaging</li><br/>"
8407 "<br/>"
8408 "Home: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8409 "Support: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">Help Forum</a><br/>"
8410 "License: GPLv2<br/>"
8411 "<br/>"
8412 "We support users in the following organizations to mention a few:<br/>"
8413 " - CERN<br/>"
8414 " - Reuters Messaging network<br/>"
8415 " - Deutsche Bank<br/>"
8416 " - Merrill Lynch<br/>"
8417 " - Wachovia<br/>"
8418 " - Siemens<br/>"
8419 " - Alcatel-Lucent<br/>"
8420 " - BT<br/>"
8421 " - Nokia<br/>"
8422 " - HP<br/>"
8423 "<br/>"
8424 "<b>Authors:</b><br/>"
8425 " - Anibal Avelar<br/>"
8426 " - Gabriel Burt<br/>"
8427 " - Stefan Becker<br/>"
8428 " - pier11<br/>";
8430 purple_notify_formatted(gc, NULL, " ", NULL, txt, NULL, NULL);
8433 static void sipe_republish_calendar(PurplePluginAction *action)
8435 PurpleConnection *gc = (PurpleConnection *) action->context;
8436 struct sipe_account_data *sip = gc->proto_data;
8438 sipe_update_calendar(sip);
8441 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
8442 gpointer value,
8443 GString* str)
8445 struct sipe_publication *publication = value;
8447 g_string_append_printf( str,
8448 SIPE_PUB_XML_PUBLICATION_CLEAR,
8449 publication->category,
8450 publication->instance,
8451 publication->container,
8452 publication->version,
8453 "static");
8456 static void sipe_reset_status(PurplePluginAction *action)
8458 PurpleConnection *gc = (PurpleConnection *) action->context;
8459 struct sipe_account_data *sip = gc->proto_data;
8461 if (sip->ocs2007) /* 2007+ */
8463 GString* str = g_string_new(NULL);
8464 gchar *publications;
8466 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
8467 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8468 return;
8471 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
8472 publications = g_string_free(str, FALSE);
8474 send_presence_publish(sip, publications);
8475 g_free(publications);
8477 else /* 2005 */
8479 send_presence_soap0(sip, FALSE, TRUE);
8483 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
8484 gpointer context)
8486 PurpleConnection *gc = (PurpleConnection *)context;
8487 struct sipe_account_data *sip = gc->proto_data;
8488 GList *menu = NULL;
8489 PurplePluginAction *act;
8490 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
8492 act = purple_plugin_action_new(_("About SIPE plugin"), sipe_show_about_plugin);
8493 menu = g_list_prepend(menu, act);
8495 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
8496 menu = g_list_prepend(menu, act);
8498 if (!strcmp(calendar, "EXCH")) {
8499 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
8500 menu = g_list_prepend(menu, act);
8503 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
8504 menu = g_list_prepend(menu, act);
8506 menu = g_list_reverse(menu);
8508 return menu;
8511 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
8515 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8517 return TRUE;
8521 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8523 return TRUE;
8527 static char *sipe_status_text(PurpleBuddy *buddy)
8529 struct sipe_account_data *sip;
8530 struct sipe_buddy *sbuddy;
8531 char *text = NULL;
8533 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
8534 if (sip) //happens on pidgin exit
8536 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8537 if (sbuddy) {
8538 if (!is_empty(sbuddy->activity) && !is_empty(sbuddy->annotation))
8540 text = g_strdup_printf("%s - %s", sbuddy->activity, sbuddy->annotation);
8542 else if (!is_empty(sbuddy->activity))
8544 text = g_strdup(sbuddy->activity);
8546 else
8548 text = g_strdup(sbuddy->annotation);
8553 return text;
8556 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
8558 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8559 const PurpleStatus *status = purple_presence_get_active_status(presence);
8560 struct sipe_account_data *sip;
8561 struct sipe_buddy *sbuddy;
8562 char *annotation = NULL;
8563 gboolean is_oof_note = FALSE;
8564 char *activity = NULL;
8565 char *calendar = NULL;
8566 char *meeting_subject = NULL;
8567 char *meeting_location = NULL;
8569 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
8570 if (sip) //happens on pidgin exit
8572 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8573 if (sbuddy)
8575 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
8576 is_oof_note = sbuddy->is_oof_note;
8577 activity = sbuddy->activity;
8578 calendar = sipe_cal_get_description(sbuddy);
8579 meeting_subject = sbuddy->meeting_subject;
8580 meeting_location = sbuddy->meeting_location;
8584 //Layout
8585 if (purple_presence_is_online(presence))
8587 const char *status_str = activity && status && strcmp(purple_status_get_id(status), SIPE_STATUS_ID_ON_PHONE) ?
8588 activity :
8589 purple_status_get_name(status);
8591 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
8593 if (purple_presence_is_online(presence) &&
8594 !is_empty(calendar))
8596 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
8598 g_free(calendar);
8599 if (!is_empty(meeting_location))
8601 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
8603 if (!is_empty(meeting_subject))
8605 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
8608 if (annotation)
8610 /* Tooltip does not know how to handle markup like <br> */
8611 gchar *s = annotation;
8612 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, annotation);
8613 while ((s = strchr(s, '<')) != NULL) {
8614 if (!g_ascii_strncasecmp(s, "<br>", 4)) {
8615 *s = '\n';
8616 strcpy(s + 1, s + 4);
8618 s++;
8620 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, annotation);
8622 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), annotation);
8623 g_free(annotation);
8628 #if PURPLE_VERSION_CHECK(2,5,0)
8629 static GHashTable *
8630 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
8632 GHashTable *table;
8633 table = g_hash_table_new(g_str_hash, g_str_equal);
8634 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
8635 return table;
8637 #endif
8639 static PurpleBuddy *
8640 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
8642 PurpleBuddy *clone;
8643 const gchar *server_alias, *email;
8644 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
8646 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
8648 purple_blist_add_buddy(clone, NULL, group, NULL);
8650 server_alias = purple_buddy_get_server_alias(buddy);
8651 if (server_alias) {
8652 purple_blist_server_alias_buddy(clone, server_alias);
8655 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8656 if (email) {
8657 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
8660 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
8661 //for UI to update;
8662 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
8663 return clone;
8666 static void
8667 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
8669 PurpleBuddy *buddy, *b;
8670 PurpleConnection *gc;
8671 PurpleGroup * group = purple_find_group(group_name);
8673 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
8675 buddy = (PurpleBuddy *)node;
8677 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
8678 gc = purple_account_get_connection(buddy->account);
8680 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
8681 if (!b){
8682 b = purple_blist_add_buddy_clone(group, buddy);
8685 sipe_group_buddy(gc, buddy->name, NULL, group_name);
8688 static void
8689 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
8691 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8693 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
8695 /* 2007+ conference */
8696 if (sip->ocs2007)
8698 sipe_conf_add(sip, buddy->name);
8700 else /* 2005- multiparty chat */
8702 gchar *self = sip_uri_self(sip);
8703 struct sip_session *session;
8705 session = sipe_session_add_chat(sip);
8706 session->chat_title = sipe_chat_get_name(session->callid);
8707 session->roster_manager = g_strdup(self);
8709 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
8710 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
8711 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
8712 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
8714 g_free(self);
8718 static gboolean
8719 sipe_is_election_finished(struct sip_session *session)
8721 gboolean res = TRUE;
8723 SIPE_DIALOG_FOREACH {
8724 if (dialog->election_vote == 0) {
8725 res = FALSE;
8726 break;
8728 } SIPE_DIALOG_FOREACH_END;
8730 if (res) {
8731 session->is_voting_in_progress = FALSE;
8733 return res;
8736 static void
8737 sipe_election_start(struct sipe_account_data *sip,
8738 struct sip_session *session)
8740 int election_timeout;
8742 if (session->is_voting_in_progress) {
8743 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
8744 return;
8745 } else {
8746 session->is_voting_in_progress = TRUE;
8748 session->bid = rand();
8750 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
8752 SIPE_DIALOG_FOREACH {
8753 /* reset election_vote for each chat participant */
8754 dialog->election_vote = 0;
8756 /* send RequestRM to each chat participant*/
8757 sipe_send_election_request_rm(sip, dialog, session->bid);
8758 } SIPE_DIALOG_FOREACH_END;
8760 election_timeout = 15; /* sec */
8761 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
8765 * @param who a URI to whom to invite to chat
8767 void
8768 sipe_invite_to_chat(struct sipe_account_data *sip,
8769 struct sip_session *session,
8770 const gchar *who)
8772 /* a conference */
8773 if (session->focus_uri)
8775 sipe_invite_conf(sip, session, who);
8777 else /* a multi-party chat */
8779 gchar *self = sip_uri_self(sip);
8780 if (session->roster_manager) {
8781 if (!strcmp(session->roster_manager, self)) {
8782 sipe_invite(sip, session, who, NULL, NULL, FALSE);
8783 } else {
8784 sipe_refer(sip, session, who);
8786 } else {
8787 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
8789 session->pending_invite_queue = slist_insert_unique_sorted(
8790 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
8792 sipe_election_start(sip, session);
8794 g_free(self);
8798 void
8799 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
8800 struct sip_session *session)
8802 gchar *invitee;
8803 GSList *entry = session->pending_invite_queue;
8805 while (entry) {
8806 invitee = entry->data;
8807 sipe_invite_to_chat(sip, session, invitee);
8808 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
8809 g_free(invitee);
8813 static void
8814 sipe_election_result(struct sipe_account_data *sip,
8815 void *sess)
8817 struct sip_session *session = (struct sip_session *)sess;
8818 gchar *rival;
8819 gboolean has_won = TRUE;
8821 if (session->roster_manager) {
8822 purple_debug_info("sipe",
8823 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
8824 return;
8827 session->is_voting_in_progress = FALSE;
8829 SIPE_DIALOG_FOREACH {
8830 if (dialog->election_vote < 0) {
8831 has_won = FALSE;
8832 rival = dialog->with;
8833 break;
8835 } SIPE_DIALOG_FOREACH_END;
8837 if (has_won) {
8838 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
8840 session->roster_manager = sip_uri_self(sip);
8842 SIPE_DIALOG_FOREACH {
8843 /* send SetRM to each chat participant*/
8844 sipe_send_election_set_rm(sip, dialog);
8845 } SIPE_DIALOG_FOREACH_END;
8846 } else {
8847 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
8849 session->bid = 0;
8851 sipe_process_pending_invite_queue(sip, session);
8855 * For 2007+ conference only.
8857 static void
8858 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
8860 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8861 struct sip_session *session;
8863 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
8864 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
8866 session = sipe_session_find_chat_by_title(sip, chat_title);
8868 sipe_conf_modify_user_role(sip, session, buddy->name);
8872 * For 2007+ conference only.
8874 static void
8875 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
8877 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8878 struct sip_session *session;
8880 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
8881 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
8883 session = sipe_session_find_chat_by_title(sip, chat_title);
8885 sipe_conf_delete_user(sip, session, buddy->name);
8888 static void
8889 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
8891 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8892 struct sip_session *session;
8894 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
8895 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
8897 session = sipe_session_find_chat_by_title(sip, chat_title);
8899 sipe_invite_to_chat(sip, session, buddy->name);
8902 static void
8903 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
8905 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8907 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
8908 if (phone) {
8909 char *tel_uri = sip_to_tel_uri(phone);
8911 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
8912 sip_csta_make_call(sip, tel_uri);
8914 g_free(tel_uri);
8918 static void
8919 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
8921 const gchar *email;
8922 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
8924 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8925 if (email)
8927 char *mailto = g_strdup_printf("mailto:%s", email);
8928 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
8929 #ifndef _WIN32
8931 pid_t pid;
8932 char *const parmList[] = {"xdg-email", mailto, NULL};
8933 if ((pid = fork()) == -1)
8935 purple_debug_info("sipe", "fork() error\n");
8937 else if (pid == 0)
8939 execvp(parmList[0], parmList);
8940 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
8943 #else
8945 BOOL ret;
8946 _flushall();
8947 errno = 0;
8948 //@TODO resolve env variable %WINDIR% first
8949 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
8950 if (errno)
8952 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
8955 #endif
8957 g_free(mailto);
8959 else
8961 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
8966 * A menu which appear when right-clicking on buddy in contact list.
8968 static GList *
8969 sipe_buddy_menu(PurpleBuddy *buddy)
8971 PurpleBlistNode *g_node;
8972 PurpleGroup *group, *gr_parent;
8973 PurpleMenuAction *act;
8974 GList *menu = NULL;
8975 GList *menu_groups = NULL;
8976 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8977 const char *email;
8978 const char *phone;
8979 const char *phone_disp_str;
8980 gchar *self = sip_uri_self(sip);
8982 SIPE_SESSION_FOREACH {
8983 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
8985 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
8987 PurpleConvChatBuddyFlags flags;
8988 PurpleConvChatBuddyFlags flags_us;
8990 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
8991 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
8992 if (session->focus_uri
8993 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
8994 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
8996 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
8997 act = purple_menu_action_new(label,
8998 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
8999 session->chat_title, NULL);
9000 g_free(label);
9001 menu = g_list_prepend(menu, act);
9004 if (session->focus_uri
9005 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9007 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9008 act = purple_menu_action_new(label,
9009 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9010 session->chat_title, NULL);
9011 g_free(label);
9012 menu = g_list_prepend(menu, act);
9015 else
9017 if (!session->focus_uri
9018 || (session->focus_uri && !session->locked))
9020 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9021 act = purple_menu_action_new(label,
9022 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9023 session->chat_title, NULL);
9024 g_free(label);
9025 menu = g_list_prepend(menu, act);
9029 } SIPE_SESSION_FOREACH_END;
9031 act = purple_menu_action_new(_("New chat"),
9032 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9033 NULL, NULL);
9034 menu = g_list_prepend(menu, act);
9036 if (sip->csta && !sip->csta->line_status) {
9037 gchar *tmp = NULL;
9038 /* work phone */
9039 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9040 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9041 if (phone) {
9042 gchar *label = g_strdup_printf(_("Work %s"),
9043 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9044 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9045 g_free(tmp);
9046 tmp = NULL;
9047 g_free(label);
9048 menu = g_list_prepend(menu, act);
9051 /* mobile phone */
9052 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9053 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9054 if (phone) {
9055 gchar *label = g_strdup_printf(_("Mobile %s"),
9056 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9057 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9058 g_free(tmp);
9059 tmp = NULL;
9060 g_free(label);
9061 menu = g_list_prepend(menu, act);
9064 /* home phone */
9065 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9066 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9067 if (phone) {
9068 gchar *label = g_strdup_printf(_("Home %s"),
9069 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9070 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9071 g_free(tmp);
9072 tmp = NULL;
9073 g_free(label);
9074 menu = g_list_prepend(menu, act);
9077 /* other phone */
9078 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9079 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9080 if (phone) {
9081 gchar *label = g_strdup_printf(_("Other %s"),
9082 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9083 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9084 g_free(tmp);
9085 tmp = NULL;
9086 g_free(label);
9087 menu = g_list_prepend(menu, act);
9090 /* custom1 phone */
9091 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9092 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9093 if (phone) {
9094 gchar *label = g_strdup_printf(_("Custom1 %s"),
9095 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9096 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9097 g_free(tmp);
9098 tmp = NULL;
9099 g_free(label);
9100 menu = g_list_prepend(menu, act);
9104 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9105 if (email) {
9106 act = purple_menu_action_new(_("Send email..."),
9107 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9108 NULL, NULL);
9109 menu = g_list_prepend(menu, act);
9112 gr_parent = purple_buddy_get_group(buddy);
9113 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9114 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9115 continue;
9117 group = (PurpleGroup *)g_node;
9118 if (group == gr_parent)
9119 continue;
9121 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9122 continue;
9124 act = purple_menu_action_new(purple_group_get_name(group),
9125 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9126 group->name, NULL);
9127 menu_groups = g_list_prepend(menu_groups, act);
9129 menu_groups = g_list_reverse(menu_groups);
9131 act = purple_menu_action_new(_("Copy to"),
9132 NULL,
9133 NULL, menu_groups);
9134 menu = g_list_prepend(menu, act);
9135 menu = g_list_reverse(menu);
9137 g_free(self);
9138 return menu;
9141 static void
9142 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9144 struct sipe_account_data *sip = chat->account->gc->proto_data;
9145 struct sip_session *session;
9147 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9148 sipe_conf_modify_conference_lock(sip, session, locked);
9151 static void
9152 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9154 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9155 sipe_conf_modify_lock(chat, FALSE);
9158 static void
9159 sipe_chat_menu_lock_cb(PurpleChat *chat)
9161 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9162 sipe_conf_modify_lock(chat, TRUE);
9165 static GList *
9166 sipe_chat_menu(PurpleChat *chat)
9168 PurpleMenuAction *act;
9169 PurpleConvChatBuddyFlags flags_us;
9170 GList *menu = NULL;
9171 struct sipe_account_data *sip = chat->account->gc->proto_data;
9172 struct sip_session *session;
9173 gchar *self;
9175 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9176 if (!session) return NULL;
9178 self = sip_uri_self(sip);
9179 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9181 if (session->focus_uri
9182 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9184 if (session->locked) {
9185 act = purple_menu_action_new(_("Unlock"),
9186 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9187 NULL, NULL);
9188 menu = g_list_prepend(menu, act);
9189 } else {
9190 act = purple_menu_action_new(_("Lock"),
9191 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9192 NULL, NULL);
9193 menu = g_list_prepend(menu, act);
9197 menu = g_list_reverse(menu);
9199 g_free(self);
9200 return menu;
9203 static GList *
9204 sipe_blist_node_menu(PurpleBlistNode *node)
9206 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9207 return sipe_buddy_menu((PurpleBuddy *) node);
9208 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9209 return sipe_chat_menu((PurpleChat *)node);
9210 } else {
9211 return NULL;
9215 static gboolean
9216 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9218 gboolean ret = TRUE;
9219 char *uri = trans->payload->data;
9221 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
9222 PurpleBuddy *pbuddy;
9223 struct sipe_buddy *sbuddy;
9224 const char *alias;
9225 char *device_name = NULL;
9226 char *server_alias = NULL;
9227 char *phone_number = NULL;
9228 char *email = NULL;
9229 const char *site;
9231 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9233 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9234 alias = purple_buddy_get_local_alias(pbuddy);
9236 if (sip)
9238 //will query buddy UA's capabilities and send answer to log
9239 sipe_options_request(sip, uri);
9241 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9242 if (sbuddy)
9244 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9248 if (msg->response != 200) {
9249 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9250 } else {
9251 xmlnode *searchResults;
9252 xmlnode *mrow;
9254 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9255 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9256 if (!searchResults) {
9257 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9258 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9259 const char *value;
9260 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9261 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9262 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9264 /* For 2007 system we will take this from ContactCard -
9265 * it has cleaner tel: URIs at least
9267 if (!sip->ocs2007) {
9268 char *tel_uri = sip_to_tel_uri(phone_number);
9269 /* trims its parameters, so call first */
9270 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9271 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9272 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9273 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9274 g_free(tel_uri);
9277 if (server_alias && strlen(server_alias) > 0) {
9278 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9280 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9281 purple_notify_user_info_add_pair(info, _("Job title"), value);
9283 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9284 purple_notify_user_info_add_pair(info, _("Office"), value);
9286 if (phone_number && strlen(phone_number) > 0) {
9287 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9289 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9290 purple_notify_user_info_add_pair(info, _("Company"), value);
9292 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9293 purple_notify_user_info_add_pair(info, _("City"), value);
9295 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9296 purple_notify_user_info_add_pair(info, _("State"), value);
9298 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9299 purple_notify_user_info_add_pair(info, _("Country"), value);
9301 if (email && strlen(email) > 0) {
9302 purple_notify_user_info_add_pair(info, _("Email address"), email);
9306 xmlnode_free(searchResults);
9309 purple_notify_user_info_add_section_break(info);
9311 if (!server_alias || !strcmp("", server_alias)) {
9312 g_free(server_alias);
9313 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9314 if (server_alias) {
9315 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9319 /* present alias if it differs from server alias */
9320 if (alias && (!server_alias || strcmp(alias, server_alias)))
9322 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9325 if (!email || !strcmp("", email)) {
9326 g_free(email);
9327 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9328 if (email) {
9329 purple_notify_user_info_add_pair(info, _("Email address"), email);
9333 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9334 if (site) {
9335 purple_notify_user_info_add_pair(info, _("Site"), site);
9338 if (device_name) {
9339 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9342 /* show a buddy's user info in a nice dialog box */
9343 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9344 uri, /* buddy's URI */
9345 info, /* body */
9346 NULL, /* callback called when dialog closed */
9347 NULL); /* userdata for callback */
9349 g_free(phone_number);
9350 g_free(server_alias);
9351 g_free(email);
9352 g_free(device_name);
9354 return ret;
9358 * AD search first, LDAP based
9360 static void sipe_get_info(PurpleConnection *gc, const char *username)
9362 struct sipe_account_data *sip = gc->proto_data;
9363 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9364 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9365 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9366 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9368 payload->destroy = g_free;
9369 payload->data = g_strdup(username);
9371 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9372 send_soap_request_with_cb(sip, domain_uri, body,
9373 (TransCallback) process_get_info_response, payload);
9374 g_free(domain_uri);
9375 g_free(body);
9376 g_free(row);
9379 static PurplePlugin *my_protocol = NULL;
9381 static PurplePluginProtocolInfo prpl_info =
9383 OPT_PROTO_CHAT_TOPIC,
9384 NULL, /* user_splits */
9385 NULL, /* protocol_options */
9386 NO_BUDDY_ICONS, /* icon_spec */
9387 sipe_list_icon, /* list_icon */
9388 NULL, /* list_emblems */
9389 sipe_status_text, /* status_text */
9390 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9391 sipe_status_types, /* away_states */
9392 sipe_blist_node_menu, /* blist_node_menu */
9393 NULL, /* chat_info */
9394 NULL, /* chat_info_defaults */
9395 sipe_login, /* login */
9396 sipe_close, /* close */
9397 sipe_im_send, /* send_im */
9398 NULL, /* set_info */ // TODO maybe
9399 sipe_send_typing, /* send_typing */
9400 sipe_get_info, /* get_info */
9401 sipe_set_status, /* set_status */
9402 sipe_set_idle, /* set_idle */
9403 NULL, /* change_passwd */
9404 sipe_add_buddy, /* add_buddy */
9405 NULL, /* add_buddies */
9406 sipe_remove_buddy, /* remove_buddy */
9407 NULL, /* remove_buddies */
9408 sipe_add_permit, /* add_permit */
9409 sipe_add_deny, /* add_deny */
9410 sipe_add_deny, /* rem_permit */
9411 sipe_add_permit, /* rem_deny */
9412 dummy_permit_deny, /* set_permit_deny */
9413 NULL, /* join_chat */
9414 NULL, /* reject_chat */
9415 NULL, /* get_chat_name */
9416 sipe_chat_invite, /* chat_invite */
9417 sipe_chat_leave, /* chat_leave */
9418 NULL, /* chat_whisper */
9419 sipe_chat_send, /* chat_send */
9420 sipe_keep_alive, /* keepalive */
9421 NULL, /* register_user */
9422 NULL, /* get_cb_info */ // deprecated
9423 NULL, /* get_cb_away */ // deprecated
9424 sipe_alias_buddy, /* alias_buddy */
9425 sipe_group_buddy, /* group_buddy */
9426 sipe_rename_group, /* rename_group */
9427 NULL, /* buddy_free */
9428 sipe_convo_closed, /* convo_closed */
9429 purple_normalize_nocase, /* normalize */
9430 NULL, /* set_buddy_icon */
9431 sipe_remove_group, /* remove_group */
9432 NULL, /* get_cb_real_name */ // TODO?
9433 NULL, /* set_chat_topic */
9434 NULL, /* find_blist_chat */
9435 NULL, /* roomlist_get_list */
9436 NULL, /* roomlist_cancel */
9437 NULL, /* roomlist_expand_category */
9438 NULL, /* can_receive_file */
9439 NULL, /* send_file */
9440 NULL, /* new_xfer */
9441 NULL, /* offline_message */
9442 NULL, /* whiteboard_prpl_ops */
9443 sipe_send_raw, /* send_raw */
9444 NULL, /* roomlist_room_serialize */
9445 NULL, /* unregister_user */
9446 NULL, /* send_attention */
9447 NULL, /* get_attention_types */
9448 #if !PURPLE_VERSION_CHECK(2,5,0)
9449 /* Backward compatibility when compiling against 2.4.x API */
9450 (void (*)(void)) /* _purple_reserved4 */
9451 #endif
9452 sizeof(PurplePluginProtocolInfo), /* struct_size */
9453 #if PURPLE_VERSION_CHECK(2,5,0)
9454 sipe_get_account_text_table, /* get_account_text_table */
9455 #if PURPLE_VERSION_CHECK(2,6,0)
9456 NULL, /* initiate_media */
9457 NULL, /* get_media_caps */
9458 #endif
9459 #endif
9463 static PurplePluginInfo info = {
9464 PURPLE_PLUGIN_MAGIC,
9465 PURPLE_MAJOR_VERSION,
9466 PURPLE_MINOR_VERSION,
9467 PURPLE_PLUGIN_PROTOCOL, /**< type */
9468 NULL, /**< ui_requirement */
9469 0, /**< flags */
9470 NULL, /**< dependencies */
9471 PURPLE_PRIORITY_DEFAULT, /**< priority */
9472 "prpl-sipe", /**< id */
9473 "Office Communicator", /**< name */
9474 SIPE_VERSION, /**< version */
9475 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9476 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9477 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9478 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9479 "Gabriel Burt <gburt@novell.com>, " /**< author */
9480 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9481 "pier11 <pier11@operamail.com>", /**< author */
9482 "http://sipe.sourceforge.net/", /**< homepage */
9483 sipe_plugin_load, /**< load */
9484 sipe_plugin_unload, /**< unload */
9485 sipe_plugin_destroy, /**< destroy */
9486 NULL, /**< ui_info */
9487 &prpl_info, /**< extra_info */
9488 NULL,
9489 sipe_actions,
9490 NULL,
9491 NULL,
9492 NULL,
9493 NULL
9496 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9498 GList *entry;
9500 entry = prpl_info.protocol_options;
9501 while (entry) {
9502 purple_account_option_destroy(entry->data);
9503 entry = g_list_delete_link(entry, entry);
9505 prpl_info.protocol_options = NULL;
9507 entry = prpl_info.user_splits;
9508 while (entry) {
9509 purple_account_user_split_destroy(entry->data);
9510 entry = g_list_delete_link(entry, entry);
9512 prpl_info.user_splits = NULL;
9515 static void init_plugin(PurplePlugin *plugin)
9517 PurpleAccountUserSplit *split;
9518 PurpleAccountOption *option;
9520 srand(time(NULL));
9522 #ifdef ENABLE_NLS
9523 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
9524 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
9525 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
9526 textdomain(GETTEXT_PACKAGE);
9527 #endif
9529 purple_plugin_register(plugin);
9531 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
9532 purple_account_user_split_set_reverse(split, FALSE);
9533 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
9535 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
9536 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9538 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
9539 purple_account_option_add_list_item(option, _("Auto"), "auto");
9540 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
9541 purple_account_option_add_list_item(option, _("TCP"), "tcp");
9542 purple_account_option_add_list_item(option, _("UDP"), "udp");
9543 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9545 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
9546 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
9548 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
9549 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9551 #ifdef USE_KERBEROS
9552 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
9553 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9555 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
9556 * No login/password is taken into account if this option present,
9557 * instead used default credentials stored in OS.
9559 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
9560 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9561 #endif
9563 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
9564 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
9565 purple_account_option_add_list_item(option, _("None"), "NONE");
9566 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9568 /** Example: https://server.company.com/EWS/Exchange.asmx */
9569 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
9570 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9572 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
9573 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9575 /** Example: DOMAIN\user or user@company.com */
9576 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
9577 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9579 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
9580 purple_account_option_set_masked(option, TRUE);
9581 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9583 my_protocol = plugin;
9586 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
9589 Local Variables:
9590 mode: c
9591 c-file-style: "bsd"
9592 indent-tabs-mode: t
9593 tab-width: 8
9594 End: