refactoring: time_to_str
[siplcs.git] / src / core / sipe.c
blob7a220e7cb651e924765ffc009c4cf2dae5692afe
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 pier11 <pier11@operamail.com>
7 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
10 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
13 * ***
14 * Thanks to Google's Summer of Code Program and the helpful mentors
15 * ***
17 * Session-based SIP MESSAGE documentation:
18 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 #ifndef _WIN32
36 #include <sys/socket.h>
37 #include <sys/ioctl.h>
38 #include <sys/types.h>
39 #include <netinet/in.h>
40 #include <net/if.h>
41 #else
42 #ifdef _DLL
43 #define _WS2TCPIP_H_
44 #define _WINSOCK2API_
45 #define _LIBC_INTERNAL_
46 #endif /* _DLL */
47 #include "internal.h"
48 #endif /* _WIN32 */
50 #include <time.h>
51 #include <stdio.h>
52 #include <errno.h>
53 #include <string.h>
54 #include <unistd.h>
55 #include <glib.h>
58 #include "accountopt.h"
59 #include "blist.h"
60 #include "conversation.h"
61 #include "dnsquery.h"
62 #include "debug.h"
63 #include "notify.h"
64 #include "savedstatuses.h"
65 #include "privacy.h"
66 #include "prpl.h"
67 #include "plugin.h"
68 #include "util.h"
69 #include "version.h"
70 #include "network.h"
71 #include "xmlnode.h"
72 #include "mime.h"
73 #include "core.h"
75 #include "sipe.h"
76 #include "sipe-cal.h"
77 #include "sipe-ews.h"
78 #include "sipe-chat.h"
79 #include "sipe-conf.h"
80 #include "sip-csta.h"
81 #include "sipe-dialog.h"
82 #include "sipe-nls.h"
83 #include "sipe-session.h"
84 #include "sipe-utils.h"
85 #include "sipmsg.h"
86 #include "sipe-sign.h"
87 #include "dnssrv.h"
88 #include "request.h"
90 /* Backward compatibility when compiling against 2.4.x API */
91 #if !PURPLE_VERSION_CHECK(2,5,0)
92 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
93 #endif
95 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
97 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
98 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
100 /* Keep in sync with sipe_transport_type! */
101 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
102 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
104 /* Status identifiers (see also: sipe_status_types()) */
105 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
106 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
107 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
108 /* PURPLE_STATUS_UNAVAILABLE: */
109 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
110 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
111 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
112 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
113 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
114 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
115 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
116 /* PURPLE_STATUS_AWAY: */
117 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
118 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
119 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
120 /** Reuters status (user settable) */
121 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
122 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
123 /* ??? PURPLE_STATUS_MOBILE */
124 /* ??? PURPLE_STATUS_TUNE */
126 /* Status attributes (see also sipe_status_types() */
127 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
129 static struct sipe_activity_map_struct
131 sipe_activity type;
132 const char *token;
133 const char *desc;
134 const char *status_id;
136 } const sipe_activity_map[] =
138 /* This has nothing to do with Availability numbers, like 3500 (online).
139 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
141 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
142 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
143 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
144 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
145 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
146 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
147 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
148 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
149 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
150 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
151 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
152 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
153 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
154 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
155 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
157 /** @param x is sipe_activity */
158 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
161 /* Action name templates */
162 #define ACTION_NAME_PRESENCE "<presence><%s>"
164 static sipe_activity
165 sipe_get_activity_by_token(const char *token)
167 int i;
169 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
171 if (!strcmp(token, sipe_activity_map[i].token))
172 return sipe_activity_map[i].type;
175 return sipe_activity_map[0].type;
178 static const char *
179 sipe_get_activity_desc_by_token(const char *token)
181 if (!token) return NULL;
183 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
186 /** Allows to send typed messages from chat window again after account reinstantiation. */
187 static void
188 sipe_rejoin_chat(PurpleConversation *conv)
190 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
191 PURPLE_CONV_CHAT(conv)->left)
193 PURPLE_CONV_CHAT(conv)->left = FALSE;
194 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
198 static char *genbranch()
200 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
201 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
202 rand() & 0xFFFF, rand() & 0xFFFF);
206 static char *default_ua = NULL;
207 static const char*
208 sipe_get_useragent(struct sipe_account_data *sip)
210 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
211 if (is_empty(useragent)) {
212 if (!default_ua) {
213 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
214 /* ref: lzodefs.h */
215 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
216 #define SIPE_TARGET_PLATFORM "linux"
217 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
218 #define SIPE_TARGET_PLATFORM "bsd"
219 # elif defined(__APPLE__) || defined(__MACOS__)
220 #define SIPE_TARGET_PLATFORM "macosx"
221 #elif defined(__solaris__) || defined(__sun)
222 #define SIPE_TARGET_PLATFORM "sun"
223 #elif defined(_WIN32)
224 #define SIPE_TARGET_PLATFORM "win"
225 #else
226 #define SIPE_TARGET_PLATFORM "generic"
227 #endif
229 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
230 #define SIPE_TARGET_ARCH "x86_64"
231 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
232 #define SIPE_TARGET_ARCH "i386"
233 #elif defined(__ppc64__)
234 #define SIPE_TARGET_ARCH "ppc64"
235 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
236 #define SIPE_TARGET_ARCH "ppc"
237 #else
238 #define SIPE_TARGET_ARCH "other"
239 #endif
241 default_ua = g_strdup_printf("Purple/%s Sipe/%s (%s-%s; %s)",
242 purple_core_get_version(),
243 SIPE_VERSION,
244 SIPE_TARGET_PLATFORM,
245 SIPE_TARGET_ARCH,
246 sip->server_version ? sip->server_version : "");
248 useragent = default_ua;
250 return useragent;
253 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
254 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
256 return "sipe";
259 static void sipe_plugin_destroy(PurplePlugin *plugin);
261 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
263 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
264 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
265 gpointer data);
267 static void sipe_close(PurpleConnection *gc);
269 static void send_presence_status(struct sipe_account_data *sip);
271 static void sendout_pkt(PurpleConnection *gc, const char *buf);
273 static void sipe_keep_alive(PurpleConnection *gc)
275 struct sipe_account_data *sip = gc->proto_data;
276 if (sip->transport == SIPE_TRANSPORT_UDP) {
277 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
278 gchar buf[2] = {0, 0};
279 purple_debug_info("sipe", "sending keep alive\n");
280 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
281 } else {
282 time_t now = time(NULL);
283 if ((sip->keepalive_timeout > 0) &&
284 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
285 #if PURPLE_VERSION_CHECK(2,4,0)
286 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
287 #endif
289 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
290 sendout_pkt(gc, "\r\n\r\n");
291 sip->last_keepalive = now;
296 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
298 struct sip_connection *ret = NULL;
299 GSList *entry = sip->openconns;
300 while (entry) {
301 ret = entry->data;
302 if (ret->fd == fd) return ret;
303 entry = entry->next;
305 return NULL;
308 static void sipe_auth_free(struct sip_auth *auth)
310 g_free(auth->opaque);
311 auth->opaque = NULL;
312 g_free(auth->realm);
313 auth->realm = NULL;
314 g_free(auth->target);
315 auth->target = NULL;
316 auth->type = AUTH_TYPE_UNSET;
317 auth->retries = 0;
318 auth->expires = 0;
319 g_free(auth->gssapi_data);
320 auth->gssapi_data = NULL;
321 sip_sec_destroy_context(auth->gssapi_context);
322 auth->gssapi_context = NULL;
325 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
327 struct sip_connection *ret = g_new0(struct sip_connection, 1);
328 ret->fd = fd;
329 sip->openconns = g_slist_append(sip->openconns, ret);
330 return ret;
333 static void connection_remove(struct sipe_account_data *sip, int fd)
335 struct sip_connection *conn = connection_find(sip, fd);
336 if (conn) {
337 sip->openconns = g_slist_remove(sip->openconns, conn);
338 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
339 g_free(conn->inbuf);
340 g_free(conn);
344 static void connection_free_all(struct sipe_account_data *sip)
346 struct sip_connection *ret = NULL;
347 GSList *entry = sip->openconns;
348 while (entry) {
349 ret = entry->data;
350 connection_remove(sip, ret->fd);
351 entry = sip->openconns;
355 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
357 gchar noncecount[9];
358 const char *authuser = sip->authuser;
359 gchar *response;
360 gchar *ret;
362 if (!authuser || strlen(authuser) < 1) {
363 authuser = sip->username;
366 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
367 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
369 // If we have a signature for the message, include that
370 if (msg->signature) {
371 return g_strdup_printf("%s qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth_protocol, auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
374 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
375 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
376 gchar *gssapi_data;
377 gchar *opaque;
379 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
380 &(auth->expires),
381 auth->type,
382 purple_account_get_bool(sip->account, "sso", TRUE),
383 sip->authdomain ? sip->authdomain : "",
384 authuser,
385 sip->password,
386 auth->target,
387 auth->gssapi_data);
388 if (!gssapi_data || !auth->gssapi_context) {
389 sip->gc->wants_to_die = TRUE;
390 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
391 return NULL;
394 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
395 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
396 g_free(opaque);
397 g_free(gssapi_data);
398 return ret;
401 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
403 } else { /* Digest */
405 /* Calculate new session key */
406 if (!auth->opaque) {
407 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
408 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
409 authuser, auth->realm, sip->password,
410 auth->gssapi_data, NULL);
413 sprintf(noncecount, "%08d", auth->nc++);
414 response = purple_cipher_http_digest_calculate_response("md5",
415 msg->method, msg->target, NULL, NULL,
416 auth->gssapi_data, noncecount, NULL,
417 auth->opaque);
418 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
420 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->gssapi_data, msg->target, noncecount, response);
421 g_free(response);
422 return ret;
426 static char *parse_attribute(const char *attrname, const char *source)
428 const char *tmp, *tmp2;
429 char *retval = NULL;
430 int len = strlen(attrname);
432 if (!strncmp(source, attrname, len)) {
433 tmp = source + len;
434 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
435 if (tmp2)
436 retval = g_strndup(tmp, tmp2 - tmp);
437 else
438 retval = g_strdup(tmp);
441 return retval;
444 static void fill_auth(gchar *hdr, struct sip_auth *auth)
446 int i;
447 gchar **parts;
449 if (!hdr) {
450 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
451 return;
454 if (!g_strncasecmp(hdr, "NTLM", 4)) {
455 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
456 auth->type = AUTH_TYPE_NTLM;
457 hdr += 5;
458 auth->nc = 1;
459 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
460 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
461 auth->type = AUTH_TYPE_KERBEROS;
462 hdr += 9;
463 auth->nc = 3;
464 } else {
465 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
466 auth->type = AUTH_TYPE_DIGEST;
467 hdr += 7;
470 parts = g_strsplit(hdr, "\", ", 0);
471 for (i = 0; parts[i]; i++) {
472 char *tmp;
474 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
476 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
477 g_free(auth->gssapi_data);
478 auth->gssapi_data = tmp;
480 if (auth->type == AUTH_TYPE_NTLM) {
481 /* NTLM module extracts nonce from gssapi-data */
482 auth->nc = 3;
485 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
486 /* Only used with AUTH_TYPE_DIGEST */
487 g_free(auth->gssapi_data);
488 auth->gssapi_data = tmp;
489 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
490 g_free(auth->opaque);
491 auth->opaque = tmp;
492 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
493 g_free(auth->realm);
494 auth->realm = tmp;
496 if (auth->type == AUTH_TYPE_DIGEST) {
497 /* Throw away old session key */
498 g_free(auth->opaque);
499 auth->opaque = NULL;
500 auth->nc = 1;
503 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
504 g_free(auth->target);
505 auth->target = tmp;
508 g_strfreev(parts);
510 return;
513 static void sipe_canwrite_cb(gpointer data,
514 SIPE_UNUSED_PARAMETER gint source,
515 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
517 PurpleConnection *gc = data;
518 struct sipe_account_data *sip = gc->proto_data;
519 gsize max_write;
520 gssize written;
522 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
524 if (max_write == 0) {
525 if (sip->tx_handler != 0){
526 purple_input_remove(sip->tx_handler);
527 sip->tx_handler = 0;
529 return;
532 written = write(sip->fd, sip->txbuf->outptr, max_write);
534 if (written < 0 && errno == EAGAIN)
535 written = 0;
536 else if (written <= 0) {
537 /*TODO: do we really want to disconnect on a failure to write?*/
538 purple_connection_error(gc, _("Could not write"));
539 return;
542 purple_circ_buffer_mark_read(sip->txbuf, written);
545 static void sipe_canwrite_cb_ssl(gpointer data,
546 SIPE_UNUSED_PARAMETER gint src,
547 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
549 PurpleConnection *gc = data;
550 struct sipe_account_data *sip = gc->proto_data;
551 gsize max_write;
552 gssize written;
554 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
556 if (max_write == 0) {
557 if (sip->tx_handler != 0) {
558 purple_input_remove(sip->tx_handler);
559 sip->tx_handler = 0;
560 return;
564 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
566 if (written < 0 && errno == EAGAIN)
567 written = 0;
568 else if (written <= 0) {
569 /*TODO: do we really want to disconnect on a failure to write?*/
570 purple_connection_error(gc, _("Could not write"));
571 return;
574 purple_circ_buffer_mark_read(sip->txbuf, written);
577 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
579 static void send_later_cb(gpointer data, gint source,
580 SIPE_UNUSED_PARAMETER const gchar *error)
582 PurpleConnection *gc = data;
583 struct sipe_account_data *sip;
584 struct sip_connection *conn;
586 if (!PURPLE_CONNECTION_IS_VALID(gc))
588 if (source >= 0)
589 close(source);
590 return;
593 if (source < 0) {
594 purple_connection_error(gc, _("Could not connect"));
595 return;
598 sip = gc->proto_data;
599 sip->fd = source;
600 sip->connecting = FALSE;
601 sip->last_keepalive = time(NULL);
603 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
605 /* If there is more to write now, we need to register a handler */
606 if (sip->txbuf->bufused > 0)
607 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
609 conn = connection_create(sip, source);
610 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
613 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
615 struct sipe_account_data *sip;
617 if (!PURPLE_CONNECTION_IS_VALID(gc))
619 if (gsc) purple_ssl_close(gsc);
620 return NULL;
623 sip = gc->proto_data;
624 sip->fd = gsc->fd;
625 sip->gsc = gsc;
626 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
627 sip->connecting = FALSE;
628 sip->last_keepalive = time(NULL);
630 connection_create(sip, gsc->fd);
632 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
634 return sip;
637 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
638 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
640 PurpleConnection *gc = data;
641 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
642 if (sip == NULL) return;
644 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
646 /* If there is more to write now */
647 if (sip->txbuf->bufused > 0) {
648 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
653 static void sendlater(PurpleConnection *gc, const char *buf)
655 struct sipe_account_data *sip = gc->proto_data;
657 if (!sip->connecting) {
658 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
659 if (sip->transport == SIPE_TRANSPORT_TLS){
660 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
661 } else {
662 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
663 purple_connection_error(gc, _("Could not create socket"));
666 sip->connecting = TRUE;
669 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
670 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
672 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
675 static void sendout_pkt(PurpleConnection *gc, const char *buf)
677 struct sipe_account_data *sip = gc->proto_data;
678 time_t currtime = time(NULL);
679 int writelen = strlen(buf);
680 char *tmp;
682 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
683 g_free(tmp);
684 if (sip->transport == SIPE_TRANSPORT_UDP) {
685 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
686 purple_debug_info("sipe", "could not send packet\n");
688 } else {
689 int ret;
690 if (sip->fd < 0) {
691 sendlater(gc, buf);
692 return;
695 if (sip->tx_handler) {
696 ret = -1;
697 errno = EAGAIN;
698 } else{
699 if (sip->gsc){
700 ret = purple_ssl_write(sip->gsc, buf, writelen);
701 }else{
702 ret = write(sip->fd, buf, writelen);
706 if (ret < 0 && errno == EAGAIN)
707 ret = 0;
708 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
709 sendlater(gc, buf);
710 return;
713 if (ret < writelen) {
714 if (!sip->tx_handler){
715 if (sip->gsc){
716 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
718 else{
719 sip->tx_handler = purple_input_add(sip->fd,
720 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
721 gc);
725 /* XXX: is it OK to do this? You might get part of a request sent
726 with part of another. */
727 if (sip->txbuf->bufused > 0)
728 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
730 purple_circ_buffer_append(sip->txbuf, buf + ret,
731 writelen - ret);
736 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
738 sendout_pkt(gc, buf);
739 return len;
742 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
744 GSList *tmp = msg->headers;
745 gchar *name;
746 gchar *value;
747 GString *outstr = g_string_new("");
748 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
749 while (tmp) {
750 name = ((struct siphdrelement*) (tmp->data))->name;
751 value = ((struct siphdrelement*) (tmp->data))->value;
752 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
753 tmp = g_slist_next(tmp);
755 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
756 sendout_pkt(sip->gc, outstr->str);
757 g_string_free(outstr, TRUE);
760 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
762 gchar * buf;
764 if (sip->registrar.type == AUTH_TYPE_UNSET) {
765 return;
768 if (sip->registrar.gssapi_context) {
769 struct sipmsg_breakdown msgbd;
770 gchar *signature_input_str;
771 msgbd.msg = msg;
772 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
773 msgbd.rand = g_strdup_printf("%08x", g_random_int());
774 sip->registrar.ntlm_num++;
775 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
776 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
777 if (signature_input_str != NULL) {
778 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
779 msg->signature = signature_hex;
780 msg->rand = g_strdup(msgbd.rand);
781 msg->num = g_strdup(msgbd.num);
782 g_free(signature_input_str);
784 sipmsg_breakdown_free(&msgbd);
787 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
788 buf = auth_header(sip, &sip->registrar, msg);
789 if (buf) {
790 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
792 g_free(buf);
793 } else if (!strcmp(method,"SUBSCRIBE") || !strcmp(method,"SERVICE") || !strcmp(method,"MESSAGE") || !strcmp(method,"INVITE") || !strcmp(method, "ACK") || !strcmp(method, "NOTIFY") || !strcmp(method, "BYE") || !strcmp(method, "INFO") || !strcmp(method, "OPTIONS") || !strcmp(method, "REFER")) {
794 sip->registrar.nc = 3;
795 #ifdef USE_KERBEROS
796 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
797 #endif
798 sip->registrar.type = AUTH_TYPE_NTLM;
799 #ifdef USE_KERBEROS
800 } else {
801 sip->registrar.type = AUTH_TYPE_KERBEROS;
803 #endif
806 buf = auth_header(sip, &sip->registrar, msg);
807 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
808 g_free(buf);
809 } else {
810 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
814 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
815 const char *text, const char *body)
817 gchar *name;
818 gchar *value;
819 GString *outstr = g_string_new("");
820 struct sipe_account_data *sip = gc->proto_data;
821 gchar *contact;
822 GSList *tmp;
823 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
825 contact = get_contact(sip);
826 sipmsg_add_header(msg, "Contact", contact);
827 g_free(contact);
829 if (body) {
830 gchar len[12];
831 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
832 sipmsg_add_header(msg, "Content-Length", len);
833 } else {
834 sipmsg_add_header(msg, "Content-Length", "0");
837 msg->response = code;
839 sipmsg_strip_headers(msg, keepers);
840 sipmsg_merge_new_headers(msg);
841 sign_outgoing_message(msg, sip, msg->method);
843 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
844 tmp = msg->headers;
845 while (tmp) {
846 name = ((struct siphdrelement*) (tmp->data))->name;
847 value = ((struct siphdrelement*) (tmp->data))->value;
849 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
850 tmp = g_slist_next(tmp);
852 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
853 sendout_pkt(gc, outstr->str);
854 g_string_free(outstr, TRUE);
857 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
859 if (sip->transactions) {
860 sip->transactions = g_slist_remove(sip->transactions, trans);
861 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
863 if (trans->msg) sipmsg_free(trans->msg);
864 if (trans->payload) {
865 (*trans->payload->destroy)(trans->payload->data);
866 g_free(trans->payload);
868 g_free(trans->key);
869 g_free(trans);
873 static struct transaction *
874 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
876 gchar *call_id = NULL;
877 gchar *cseq = NULL;
878 struct transaction *trans = g_new0(struct transaction, 1);
880 trans->time = time(NULL);
881 trans->msg = (struct sipmsg *)msg;
882 call_id = sipmsg_find_header(trans->msg, "Call-ID");
883 cseq = sipmsg_find_header(trans->msg, "CSeq");
884 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
885 trans->callback = callback;
886 sip->transactions = g_slist_append(sip->transactions, trans);
887 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
888 return trans;
891 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
893 struct transaction *trans;
894 GSList *transactions = sip->transactions;
895 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
896 gchar *cseq = sipmsg_find_header(msg, "CSeq");
897 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
899 while (transactions) {
900 trans = transactions->data;
901 if (!g_strcasecmp(trans->key, key)) {
902 g_free(key);
903 return trans;
905 transactions = transactions->next;
908 g_free(key);
909 return NULL;
912 struct transaction *
913 send_sip_request(PurpleConnection *gc, const gchar *method,
914 const gchar *url, const gchar *to, const gchar *addheaders,
915 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
917 struct sipe_account_data *sip = gc->proto_data;
918 const char *addh = "";
919 char *buf;
920 struct sipmsg *msg;
921 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
922 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
923 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
924 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
925 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
926 gchar *route = g_strdup("");
927 gchar *epid = get_epid(sip);
928 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
929 struct transaction *trans = NULL;
931 if (dialog && dialog->routes)
933 GSList *iter = dialog->routes;
935 while(iter)
937 char *tmp = route;
938 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
939 g_free(tmp);
940 iter = g_slist_next(iter);
944 if (!ourtag && !dialog) {
945 ourtag = gentag();
948 if (!strcmp(method, "REGISTER")) {
949 if (sip->regcallid) {
950 g_free(callid);
951 callid = g_strdup(sip->regcallid);
952 } else {
953 sip->regcallid = g_strdup(callid);
955 cseq = ++sip->cseq;
958 if (addheaders) addh = addheaders;
960 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
961 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
962 "From: <sip:%s>%s%s;epid=%s\r\n"
963 "To: <%s>%s%s%s%s\r\n"
964 "Max-Forwards: 70\r\n"
965 "CSeq: %d %s\r\n"
966 "User-Agent: %s\r\n"
967 "Call-ID: %s\r\n"
968 "%s%s"
969 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
970 method,
971 dialog && dialog->request ? dialog->request : url,
972 TRANSPORT_DESCRIPTOR,
973 purple_network_get_my_ip(-1),
974 sip->listenport,
975 branch ? ";branch=" : "",
976 branch ? branch : "",
977 sip->username,
978 ourtag ? ";tag=" : "",
979 ourtag ? ourtag : "",
980 epid,
982 theirtag ? ";tag=" : "",
983 theirtag ? theirtag : "",
984 theirepid ? ";epid=" : "",
985 theirepid ? theirepid : "",
986 cseq,
987 method,
988 sipe_get_useragent(sip),
989 callid,
990 route,
991 addh,
992 body ? (gsize) strlen(body) : 0,
993 body ? body : "");
996 //printf ("parsing msg buf:\n%s\n\n", buf);
997 msg = sipmsg_parse_msg(buf);
999 g_free(buf);
1000 g_free(ourtag);
1001 g_free(theirtag);
1002 g_free(theirepid);
1003 g_free(branch);
1004 g_free(callid);
1005 g_free(route);
1006 g_free(epid);
1008 sign_outgoing_message (msg, sip, method);
1010 buf = sipmsg_to_string (msg);
1012 /* add to ongoing transactions */
1013 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1014 if (strcmp(method, "ACK")) {
1015 trans = transactions_add_buf(sip, msg, tc);
1016 } else {
1017 sipmsg_free(msg);
1019 sendout_pkt(gc, buf);
1020 g_free(buf);
1022 return trans;
1026 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1028 static void
1029 send_soap_request_with_cb(struct sipe_account_data *sip,
1030 gchar *from0,
1031 gchar *body,
1032 TransCallback callback,
1033 struct transaction_payload *payload)
1035 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1036 gchar *contact = get_contact(sip);
1037 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1038 "Content-Type: application/SOAP+xml\r\n",contact);
1040 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1041 trans->payload = payload;
1043 g_free(from);
1044 g_free(contact);
1045 g_free(hdr);
1048 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1050 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1053 static char *get_contact_register(struct sipe_account_data *sip)
1055 char *epid = get_epid(sip);
1056 char *uuid = generateUUIDfromEPID(epid);
1057 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1058 g_free(uuid);
1059 g_free(epid);
1060 return(buf);
1063 static void do_register_exp(struct sipe_account_data *sip, int expire)
1065 char *uri;
1066 char *expires;
1067 char *to;
1068 char *contact;
1069 char *hdr;
1071 if (!sip->sipdomain) return;
1073 uri = sip_uri_from_name(sip->sipdomain);
1074 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1075 to = sip_uri_self(sip);
1076 contact = get_contact_register(sip);
1077 hdr = g_strdup_printf("Contact: %s\r\n"
1078 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1079 "Event: registration\r\n"
1080 "Allow-Events: presence\r\n"
1081 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1082 "%s", contact, expires);
1083 g_free(contact);
1084 g_free(expires);
1086 sip->registerstatus = 1;
1088 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1089 process_register_response);
1091 g_free(hdr);
1092 g_free(uri);
1093 g_free(to);
1096 static void do_register_cb(struct sipe_account_data *sip,
1097 SIPE_UNUSED_PARAMETER void *unused)
1099 do_register_exp(sip, -1);
1100 sip->reregister_set = FALSE;
1103 static void do_register(struct sipe_account_data *sip)
1105 do_register_exp(sip, -1);
1108 static void
1109 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1111 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1112 send_soap_request(sip, body);
1113 g_free(body);
1116 static void
1117 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1119 if (allow) {
1120 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1121 } else {
1122 purple_debug_info("sipe", "Blocking contact %s\n", who);
1125 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1128 static
1129 void sipe_auth_user_cb(void * data)
1131 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1132 if (!job) return;
1134 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1135 g_free(job);
1138 static
1139 void sipe_deny_user_cb(void * data)
1141 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1142 if (!job) return;
1144 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1145 g_free(job);
1148 static void
1149 sipe_add_permit(PurpleConnection *gc, const char *name)
1151 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1152 sipe_contact_allow_deny(sip, name, TRUE);
1155 static void
1156 sipe_add_deny(PurpleConnection *gc, const char *name)
1158 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1159 sipe_contact_allow_deny(sip, name, FALSE);
1162 /*static void
1163 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1165 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1166 sipe_contact_set_acl(sip, name, "");
1169 static void
1170 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1172 xmlnode *watchers;
1173 xmlnode *watcher;
1174 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1175 if (msg->response != 0 && msg->response != 200) return;
1177 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1179 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1180 if (!watchers) return;
1182 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1183 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1184 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1185 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1187 // TODO pull out optional displayName to pass as alias
1188 if (remote_user) {
1189 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1190 job->who = remote_user;
1191 job->sip = sip;
1192 purple_account_request_authorization(
1193 sip->account,
1194 remote_user,
1195 _("you"), /* id */
1196 alias,
1197 NULL, /* message */
1198 on_list,
1199 sipe_auth_user_cb,
1200 sipe_deny_user_cb,
1201 (void *) job);
1206 xmlnode_free(watchers);
1207 return;
1210 static void
1211 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1213 PurpleGroup * purple_group = purple_find_group(group->name);
1214 if (!purple_group) {
1215 purple_group = purple_group_new(group->name);
1216 purple_blist_add_group(purple_group, NULL);
1219 if (purple_group) {
1220 group->purple_group = purple_group;
1221 sip->groups = g_slist_append(sip->groups, group);
1222 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1223 } else {
1224 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1228 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1230 struct sipe_group *group;
1231 GSList *entry;
1232 if (sip == NULL) {
1233 return NULL;
1236 entry = sip->groups;
1237 while (entry) {
1238 group = entry->data;
1239 if (group->id == id) {
1240 return group;
1242 entry = entry->next;
1244 return NULL;
1247 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1249 struct sipe_group *group;
1250 GSList *entry;
1251 if (!sip || !name) {
1252 return NULL;
1255 entry = sip->groups;
1256 while (entry) {
1257 group = entry->data;
1258 if (!strcmp(group->name, name)) {
1259 return group;
1261 entry = entry->next;
1263 return NULL;
1266 static void
1267 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1269 gchar *body;
1270 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1271 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1272 send_soap_request(sip, body);
1273 g_free(body);
1274 g_free(group->name);
1275 group->name = g_strdup(name);
1279 * Only appends if no such value already stored.
1280 * Like Set in Java.
1282 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1283 GSList * res = list;
1284 if (!g_slist_find_custom(list, data, func)) {
1285 res = g_slist_insert_sorted(list, data, func);
1287 return res;
1290 static int
1291 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1292 return group1->id - group2->id;
1296 * Returns string like "2 4 7 8" - group ids buddy belong to.
1298 static gchar *
1299 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1300 int i = 0;
1301 gchar *res;
1302 //creating array from GList, converting int to gchar*
1303 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1304 GSList *entry = buddy->groups;
1305 while (entry) {
1306 struct sipe_group * group = entry->data;
1307 ids_arr[i] = g_strdup_printf("%d", group->id);
1308 entry = entry->next;
1309 i++;
1311 ids_arr[i] = NULL;
1312 res = g_strjoinv(" ", ids_arr);
1313 g_strfreev(ids_arr);
1314 return res;
1318 * Sends buddy update to server
1320 static void
1321 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1323 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1324 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1326 if (buddy && purple_buddy) {
1327 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1328 gchar *body;
1329 gchar *groups = sipe_get_buddy_groups_string(buddy);
1330 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1332 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1333 alias, groups, "true", buddy->name, sip->contacts_delta++
1335 send_soap_request(sip, body);
1336 g_free(groups);
1337 g_free(body);
1341 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1343 if (msg->response == 200) {
1344 struct sipe_group *group;
1345 struct group_user_context *ctx = trans->payload->data;
1346 xmlnode *xml;
1347 xmlnode *node;
1348 char *group_id;
1349 struct sipe_buddy *buddy;
1351 xml = xmlnode_from_str(msg->body, msg->bodylen);
1352 if (!xml) {
1353 return FALSE;
1356 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1357 if (!node) {
1358 xmlnode_free(xml);
1359 return FALSE;
1362 group_id = xmlnode_get_data(node);
1363 if (!group_id) {
1364 xmlnode_free(xml);
1365 return FALSE;
1368 group = g_new0(struct sipe_group, 1);
1369 group->id = (int)g_ascii_strtod(group_id, NULL);
1370 g_free(group_id);
1371 group->name = g_strdup(ctx->group_name);
1373 sipe_group_add(sip, group);
1375 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1376 if (buddy) {
1377 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1380 sipe_group_set_user(sip, ctx->user_name);
1382 xmlnode_free(xml);
1383 return TRUE;
1385 return FALSE;
1388 static void sipe_group_context_destroy(gpointer data)
1390 struct group_user_context *ctx = data;
1391 g_free(ctx->group_name);
1392 g_free(ctx->user_name);
1393 g_free(ctx);
1396 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1398 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1399 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1400 gchar *body;
1401 ctx->group_name = g_strdup(name);
1402 ctx->user_name = g_strdup(who);
1403 payload->destroy = sipe_group_context_destroy;
1404 payload->data = ctx;
1406 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1407 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1408 g_free(body);
1412 * Data structure for scheduled actions
1415 struct scheduled_action {
1417 * Name of action.
1418 * Format is <Event>[<Data>...]
1419 * Example: <presence><sip:user@domain.com> or <registration>
1421 gchar *name;
1422 guint timeout_handler;
1423 gboolean repetitive;
1424 Action action;
1425 GDestroyNotify destroy;
1426 struct sipe_account_data *sip;
1427 void *payload;
1431 * A timer callback
1432 * Should return FALSE if repetitive action is not needed
1434 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1436 gboolean ret;
1437 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1438 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1439 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1440 (sched_action->action)(sched_action->sip, sched_action->payload);
1441 ret = sched_action->repetitive;
1442 if (sched_action->destroy) {
1443 (*sched_action->destroy)(sched_action->payload);
1445 g_free(sched_action->name);
1446 g_free(sched_action);
1447 return ret;
1451 * Kills action timer effectively cancelling
1452 * scheduled action
1454 * @param name of action
1456 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1458 GSList *entry;
1460 if (!sip->timeouts || !name) return;
1462 entry = sip->timeouts;
1463 while (entry) {
1464 struct scheduled_action *sched_action = entry->data;
1465 if(!strcmp(sched_action->name, name)) {
1466 GSList *to_delete = entry;
1467 entry = entry->next;
1468 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1469 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1470 purple_timeout_remove(sched_action->timeout_handler);
1471 if (sched_action->destroy) {
1472 (*sched_action->destroy)(sched_action->payload);
1474 g_free(sched_action->name);
1475 g_free(sched_action);
1476 } else {
1477 entry = entry->next;
1482 static void
1483 sipe_schedule_action0(const gchar *name,
1484 int timeout,
1485 gboolean isSeconds,
1486 Action action,
1487 GDestroyNotify destroy,
1488 struct sipe_account_data *sip,
1489 void *payload)
1491 struct scheduled_action *sched_action;
1493 /* Make sure each action only exists once */
1494 sipe_cancel_scheduled_action(sip, name);
1496 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1497 sched_action = g_new0(struct scheduled_action, 1);
1498 sched_action->repetitive = FALSE;
1499 sched_action->name = g_strdup(name);
1500 sched_action->action = action;
1501 sched_action->destroy = destroy;
1502 sched_action->sip = sip;
1503 sched_action->payload = payload;
1504 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1505 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1506 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1507 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1510 void
1511 sipe_schedule_action(const gchar *name,
1512 int timeout,
1513 Action action,
1514 GDestroyNotify destroy,
1515 struct sipe_account_data *sip,
1516 void *payload)
1518 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1522 * Same as sipe_schedule_action() but timeout is in milliseconds.
1524 static void
1525 sipe_schedule_action_msec(const gchar *name,
1526 int timeout,
1527 Action action,
1528 GDestroyNotify destroy,
1529 struct sipe_account_data *sip,
1530 void *payload)
1532 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1535 static void
1536 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1537 time_t calculate_from);
1539 static int
1540 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1542 static const char*
1543 sipe_get_status_by_availability(int avail,
1544 char** activity);
1546 static void
1547 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1548 const char *status_id,
1549 const char *message,
1550 time_t do_not_publish[]);
1552 static void
1553 sipe_apply_calendar_status(struct sipe_account_data *sip,
1554 struct sipe_buddy *sbuddy,
1555 const char *status_id)
1557 time_t cal_avail_since;
1558 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1559 int avail;
1560 gchar *self_uri = sip_uri_self(sip);
1562 if (!sbuddy) return;
1564 if (cal_status < SIPE_CAL_NO_DATA) {
1565 purple_debug_info("sipe", "update_calendar_status_cb: cal_status : %d for %s\n", cal_status, sbuddy->name);
1566 purple_debug_info("sipe", "update_calendar_status_cb: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1569 /* scheduled Cal update call */
1570 if (!status_id) {
1571 status_id = sbuddy->last_non_cal_status_id;
1572 g_free(sbuddy->activity);
1573 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1576 /* adjust to calendar status */
1577 if (cal_status != SIPE_CAL_NO_DATA) {
1578 purple_debug_info("sipe", "update_calendar_status_cb: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1580 if (cal_status == SIPE_CAL_BUSY
1581 && cal_avail_since > sbuddy->user_avail_since
1582 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1584 status_id = SIPE_STATUS_ID_BUSY;
1585 g_free(sbuddy->activity);
1586 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1588 avail = sipe_get_availability_by_status(status_id, NULL);
1590 purple_debug_info("sipe", "update_calendar_status_cb: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1591 if (cal_avail_since > sbuddy->activity_since) {
1592 if (cal_status == SIPE_CAL_OOF
1593 && avail >= 15000) /* 12000 in 2007 */
1595 g_free(sbuddy->activity);
1596 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1601 /* then set status_id actually */
1602 purple_debug_info("sipe", "sipe_got_user_status: to %s for %s\n", status_id, sbuddy->name);
1603 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1605 /* set our account state to the one in roaming (including calendar info) */
1606 if (sip->initial_state_published && !strcmp(sbuddy->name, self_uri)) {
1607 if (!strcmp(status_id, SIPE_STATUS_ID_OFFLINE)) {
1608 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1611 purple_debug_info("sipe", "sipe_got_user_status: switch to '%s' for the account\n", sip->status);
1612 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1614 g_free(self_uri);
1617 static void
1618 sipe_got_user_status(struct sipe_account_data *sip,
1619 const char* uri,
1620 const char *status_id)
1622 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1624 if (!sbuddy) return;
1626 /* Check if on 2005 system contact's calendar,
1627 * then set/preserve it.
1629 if (!sip->ocs2007) {
1630 sipe_apply_calendar_status(sip, sbuddy, status_id);
1631 } else {
1632 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1636 static void
1637 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1638 struct sipe_buddy *sbuddy,
1639 struct sipe_account_data *sip)
1641 sipe_apply_calendar_status(sip, sbuddy, NULL);
1645 * Updates contact's status
1646 * based on their calendar information.
1648 * Applicability: 2005 systems
1650 static void
1651 update_calendar_status(struct sipe_account_data *sip)
1653 purple_debug_info("sipe", "update_calendar_status() started.\n");
1654 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1656 /* repeat scheduling */
1657 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1661 * Schedules process of contacts' status update
1662 * based on their calendar information.
1663 * Should be scheduled to the beginning of every
1664 * 15 min interval, like:
1665 * 13:00, 13:15, 13:30, 13:45, etc.
1667 * Applicability: 2005 systems
1669 static void
1670 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1671 time_t calculate_from)
1673 int interval = 15*60;
1674 /** start of the beginning of closest 15 min interval. */
1675 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1677 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1678 asctime(localtime(&calculate_from)));
1679 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1680 asctime(localtime(&next_start)));
1682 sipe_schedule_action("<+2005-cal-status>",
1683 (int)(next_start - time(NULL)),
1684 (Action)update_calendar_status,
1685 NULL,
1686 sip,
1687 NULL);
1691 * Schedules process of self status publish
1692 * based on own calendar information.
1693 * Should be scheduled to the beginning of every
1694 * 15 min interval, like:
1695 * 13:00, 13:15, 13:30, 13:45, etc.
1697 * Applicability: 2007+ systems
1699 static void
1700 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1701 time_t calculate_from)
1703 int interval = 5*60;
1704 /** start of the beginning of closest 5 min interval. */
1705 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1707 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1708 asctime(localtime(&calculate_from)));
1709 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1710 asctime(localtime(&next_start)));
1712 sipe_schedule_action("<+2007-cal-status>",
1713 (int)(next_start - time(NULL)),
1714 (Action)publish_calendar_status_self,
1715 NULL,
1716 sip,
1717 NULL);
1720 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1722 /** Should be g_free()'d
1724 static gchar *
1725 sipe_get_subscription_key(gchar *event,
1726 gchar *with)
1728 gchar *key = NULL;
1730 if (is_empty(event)) return NULL;
1732 if (event && !g_ascii_strcasecmp(event, "presence")) {
1733 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1734 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1736 /* @TODO drop participated buddies' just_added flag */
1737 } else if (event) {
1738 /* Subscription is identified by <event> key */
1739 key = g_strdup_printf("<%s>", event);
1742 return key;
1745 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1746 SIPE_UNUSED_PARAMETER struct transaction *trans)
1748 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1749 gchar *event = sipmsg_find_header(msg, "Event");
1750 gchar *key;
1752 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1753 if (!event) {
1754 struct sipmsg *request_msg = trans->msg;
1755 event = sipmsg_find_header(request_msg, "Event");
1758 key = sipe_get_subscription_key(event, with);
1760 /* 200 OK; 481 Call Leg Does Not Exist */
1761 if (key && (msg->response == 200 || msg->response == 481)) {
1762 if (g_hash_table_lookup(sip->subscriptions, key)) {
1763 g_hash_table_remove(sip->subscriptions, key);
1764 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1768 /* create/store subscription dialog if not yet */
1769 if (msg->response == 200) {
1770 gchar *callid = sipmsg_find_header(msg, "Call-ID");
1771 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1773 if (key) {
1774 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1775 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1777 subscription->dialog.callid = g_strdup(callid);
1778 subscription->dialog.cseq = atoi(cseq);
1779 subscription->dialog.with = g_strdup(with);
1780 subscription->event = g_strdup(event);
1781 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1783 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1786 g_free(cseq);
1789 g_free(key);
1790 g_free(with);
1792 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1794 process_incoming_notify(sip, msg, FALSE, FALSE);
1796 return TRUE;
1799 static void sipe_subscribe_resource_uri(const char *name,
1800 SIPE_UNUSED_PARAMETER gpointer value,
1801 gchar **resources_uri)
1803 gchar *tmp = *resources_uri;
1804 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1805 g_free(tmp);
1808 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1810 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1811 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1812 gchar *tmp = *resources_uri;
1814 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1816 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1817 g_free(tmp);
1821 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1822 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1823 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1824 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1825 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1828 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1830 gchar *key;
1831 gchar *contact = get_contact(sip);
1832 gchar *request;
1833 gchar *content;
1834 gchar *require = "";
1835 gchar *accept = "";
1836 gchar *autoextend = "";
1837 gchar *content_type;
1838 struct sip_dialog *dialog;
1840 if (sip->ocs2007) {
1841 require = ", categoryList";
1842 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1843 content_type = "application/msrtc-adrl-categorylist+xml";
1844 content = g_strdup_printf(
1845 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1846 "<action name=\"subscribe\" id=\"63792024\">\n"
1847 "<adhocList>\n%s</adhocList>\n"
1848 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1849 "<category name=\"calendarData\"/>\n"
1850 "<category name=\"contactCard\"/>\n"
1851 "<category name=\"note\"/>\n"
1852 "<category name=\"state\"/>\n"
1853 "</categoryList>\n"
1854 "</action>\n"
1855 "</batchSub>", sip->username, resources_uri);
1856 } else {
1857 autoextend = "Supported: com.microsoft.autoextend\r\n";
1858 content_type = "application/adrl+xml";
1859 content = g_strdup_printf(
1860 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1861 "<create xmlns=\"\">\n%s</create>\n"
1862 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1864 g_free(resources_uri);
1866 request = g_strdup_printf(
1867 "Require: adhoclist%s\r\n"
1868 "Supported: eventlist\r\n"
1869 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1870 "Supported: ms-piggyback-first-notify\r\n"
1871 "%sSupported: ms-benotify\r\n"
1872 "Proxy-Require: ms-benotify\r\n"
1873 "Event: presence\r\n"
1874 "Content-Type: %s\r\n"
1875 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1876 g_free(contact);
1878 /* subscribe to buddy presence */
1879 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1880 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1881 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1882 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1884 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1886 g_free(content);
1887 g_free(to);
1888 g_free(request);
1889 g_free(key);
1892 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1893 SIPE_UNUSED_PARAMETER void *unused)
1895 gchar *to = sip_uri_self(sip);
1896 gchar *resources_uri = g_strdup("");
1897 if (sip->ocs2007) {
1898 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1899 } else {
1900 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1903 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1906 struct presence_batched_routed {
1907 gchar *host;
1908 GSList *buddies;
1911 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1913 struct presence_batched_routed *data = payload;
1914 GSList *buddies = data->buddies;
1915 while (buddies) {
1916 g_free(buddies->data);
1917 buddies = buddies->next;
1919 g_slist_free(data->buddies);
1920 g_free(data->host);
1921 g_free(payload);
1924 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1926 struct presence_batched_routed *data = payload;
1927 GSList *buddies = data->buddies;
1928 gchar *resources_uri = g_strdup("");
1929 while (buddies) {
1930 gchar *tmp = resources_uri;
1931 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1932 g_free(tmp);
1933 buddies = buddies->next;
1935 sipe_subscribe_presence_batched_to(sip, resources_uri,
1936 g_strdup(data->host));
1940 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1941 * The user sends a single SUBSCRIBE request to the subscribed contact.
1942 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1946 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1949 gchar *key;
1950 gchar *to = sip_uri((char *)buddy_name);
1951 gchar *tmp = get_contact(sip);
1952 gchar *request;
1953 gchar *content = NULL;
1954 gchar *autoextend = "";
1955 gchar *content_type = "";
1956 struct sip_dialog *dialog;
1957 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
1958 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1960 if (sbuddy) sbuddy->just_added = FALSE;
1962 if (sip->ocs2007) {
1963 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1964 } else {
1965 autoextend = "Supported: com.microsoft.autoextend\r\n";
1968 request = g_strdup_printf(
1969 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1970 "Supported: ms-piggyback-first-notify\r\n"
1971 "%s%sSupported: ms-benotify\r\n"
1972 "Proxy-Require: ms-benotify\r\n"
1973 "Event: presence\r\n"
1974 "Contact: %s\r\n", autoextend, content_type, tmp);
1976 if (sip->ocs2007) {
1977 content = g_strdup_printf(
1978 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1979 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1980 "<resource uri=\"%s\"%s\n"
1981 "</adhocList>\n"
1982 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1983 "<category name=\"calendarData\"/>\n"
1984 "<category name=\"contactCard\"/>\n"
1985 "<category name=\"note\"/>\n"
1986 "<category name=\"state\"/>\n"
1987 "</categoryList>\n"
1988 "</action>\n"
1989 "</batchSub>", sip->username, to, context);
1992 g_free(tmp);
1994 /* subscribe to buddy presence */
1995 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1996 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1997 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1998 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2000 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2002 g_free(content);
2003 g_free(to);
2004 g_free(request);
2005 g_free(key);
2008 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2010 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2012 if (!purple_status_is_active(status))
2013 return;
2015 if (account->gc) {
2016 struct sipe_account_data *sip = account->gc->proto_data;
2018 if (sip) {
2019 gchar *action_name;
2020 gchar *tmp;
2021 time_t now = time(NULL);
2022 const char *status_id = purple_status_get_id(status);
2023 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2024 sipe_activity activity = sipe_get_activity_by_token(status_id);
2025 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2027 /* when other point of presence clears note, but we are keeping
2028 * state if OOF note.
2030 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2031 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2032 do_not_publish = FALSE;
2035 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2036 status_id, (int)sip->do_not_publish[activity], (int)now);
2038 sip->do_not_publish[activity] = 0;
2039 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2040 status_id, (int)sip->do_not_publish[activity]);
2042 if (do_not_publish)
2044 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2045 return;
2048 g_free(sip->status);
2049 sip->status = g_strdup(status_id);
2051 /* hack to escape apostrof before comparison */
2052 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2054 /* this will preserve OOF flag as well */
2055 if (!(tmp && sip->note && !strcmp(tmp, sip->note))) {
2056 sip->is_oof_note = FALSE;
2057 g_free(sip->note);
2058 sip->note = g_strdup(note);
2059 sip->note_since = time(NULL);
2061 g_free(tmp);
2063 /* schedule 2 sec to capture idle flag */
2064 action_name = g_strdup_printf("<%s>", "+set-status");
2065 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2066 g_free(action_name);
2070 static void
2071 sipe_set_idle(PurpleConnection * gc,
2072 int interval)
2074 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2076 if (gc) {
2077 struct sipe_account_data *sip = gc->proto_data;
2079 if (sip) {
2080 sip->idle_switch = time(NULL);
2081 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2086 static void
2087 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2088 SIPE_UNUSED_PARAMETER const char *alias)
2090 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2091 sipe_group_set_user(sip, name);
2094 static void
2095 sipe_group_buddy(PurpleConnection *gc,
2096 const char *who,
2097 const char *old_group_name,
2098 const char *new_group_name)
2100 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2101 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2102 struct sipe_group * old_group = NULL;
2103 struct sipe_group * new_group;
2105 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2106 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2108 if(!buddy) { // buddy not in roaming list
2109 return;
2112 if (old_group_name) {
2113 old_group = sipe_group_find_by_name(sip, old_group_name);
2115 new_group = sipe_group_find_by_name(sip, new_group_name);
2117 if (old_group) {
2118 buddy->groups = g_slist_remove(buddy->groups, old_group);
2119 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2122 if (!new_group) {
2123 sipe_group_create(sip, new_group_name, who);
2124 } else {
2125 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2126 sipe_group_set_user(sip, who);
2130 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2132 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2134 /* libpurple can call us with undefined buddy or group */
2135 if (buddy && group) {
2136 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2138 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2139 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2140 purple_blist_rename_buddy(buddy, buddy_name);
2141 g_free(buddy_name);
2143 /* Prepend sip: if needed */
2144 if (strncmp("sip:", buddy->name, 4)) {
2145 gchar *buf = sip_uri_from_name(buddy->name);
2146 purple_blist_rename_buddy(buddy, buf);
2147 g_free(buf);
2150 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2151 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2152 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2153 b->name = g_strdup(buddy->name);
2154 b->just_added = TRUE;
2155 g_hash_table_insert(sip->buddies, b->name, b);
2156 sipe_group_buddy(gc, b->name, NULL, group->name);
2157 /* @TODO should go to callback */
2158 sipe_subscribe_presence_single(sip, b->name);
2159 } else {
2160 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2165 static void sipe_free_buddy(struct sipe_buddy *buddy)
2167 #ifndef _WIN32
2169 * We are calling g_hash_table_foreach_steal(). That means that no
2170 * key/value deallocation functions are called. Therefore the glib
2171 * hash code does not touch the key (buddy->name) or value (buddy)
2172 * of the to-be-deleted hash node at all. It follows that we
2174 * - MUST free the memory for the key ourselves and
2175 * - ARE allowed to do it in this function
2177 * Conclusion: glib must be broken on the Windows platform if sipe
2178 * crashes with SIGTRAP when closing. You'll have to live
2179 * with the memory leak until this is fixed.
2181 g_free(buddy->name);
2182 #endif
2183 g_free(buddy->activity);
2184 g_free(buddy->meeting_subject);
2185 g_free(buddy->meeting_location);
2186 g_free(buddy->note);
2188 g_free(buddy->cal_start_time);
2189 g_free(buddy->cal_free_busy_base64);
2190 g_free(buddy->cal_free_busy);
2191 g_free(buddy->last_non_cal_activity);
2193 sipe_cal_free_working_hours(buddy->cal_working_hours);
2195 g_free(buddy->device_name);
2196 g_slist_free(buddy->groups);
2197 g_free(buddy);
2201 * Unassociates buddy from group first.
2202 * Then see if no groups left, removes buddy completely.
2203 * Otherwise updates buddy groups on server.
2205 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2207 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2208 struct sipe_buddy *b;
2209 struct sipe_group *g = NULL;
2211 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2212 if (!buddy) return;
2214 b = g_hash_table_lookup(sip->buddies, buddy->name);
2215 if (!b) return;
2217 if (group) {
2218 g = sipe_group_find_by_name(sip, group->name);
2221 if (g) {
2222 b->groups = g_slist_remove(b->groups, g);
2223 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2226 if (g_slist_length(b->groups) < 1) {
2227 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2228 sipe_cancel_scheduled_action(sip, action_name);
2229 g_free(action_name);
2231 g_hash_table_remove(sip->buddies, buddy->name);
2233 if (b->name) {
2234 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2235 send_soap_request(sip, body);
2236 g_free(body);
2239 sipe_free_buddy(b);
2240 } else {
2241 //updates groups on server
2242 sipe_group_set_user(sip, b->name);
2247 static void
2248 sipe_rename_group(PurpleConnection *gc,
2249 const char *old_name,
2250 PurpleGroup *group,
2251 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2253 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2254 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2255 if (s_group) {
2256 sipe_group_rename(sip, s_group, group->name);
2257 } else {
2258 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2262 static void
2263 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2265 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2266 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2267 if (s_group) {
2268 gchar *body;
2269 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2270 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2271 send_soap_request(sip, body);
2272 g_free(body);
2274 sip->groups = g_slist_remove(sip->groups, s_group);
2275 g_free(s_group->name);
2276 g_free(s_group);
2277 } else {
2278 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2282 /** All statuses need message attribute to pass Note */
2283 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2285 PurpleStatusType *type;
2286 GList *types = NULL;
2288 /* Macros to reduce code repetition.
2289 Translators: noun */
2290 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2291 prim, id, name, \
2292 TRUE, user, FALSE, \
2293 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2294 NULL); \
2295 types = g_list_append(types, type);
2297 /* Online */
2298 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2299 NULL,
2300 NULL,
2301 TRUE);
2303 /* Busy */
2304 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2305 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2306 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2307 TRUE);
2309 /* Do Not Disturb */
2310 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2311 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2312 NULL,
2313 TRUE);
2315 /* Away */
2316 /* Goes first in the list as
2317 * purple picks the first status with the AWAY type
2318 * for idle.
2320 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2321 NULL,
2322 NULL,
2323 TRUE);
2325 /* Be Right Back */
2326 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2327 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2328 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2329 TRUE);
2331 /* Appear Offline */
2332 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2333 NULL,
2334 NULL,
2335 TRUE);
2337 /* Offline (not user settable) */
2338 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE,
2339 NULL,
2340 NULL,
2341 FALSE);
2343 return types;
2347 * A callback for g_hash_table_foreach
2349 static void
2350 sipe_buddy_subscribe_cb(char *buddy_name,
2351 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2352 struct sipe_account_data *sip)
2354 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2355 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2356 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2357 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2359 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2360 g_free(action_name);
2364 * Removes entries from purple buddy list
2365 * that does not correspond ones in the roaming contact list.
2367 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2368 GSList *buddies = purple_find_buddies(sip->account, NULL);
2369 GSList *entry = buddies;
2370 struct sipe_buddy *buddy;
2371 PurpleBuddy *b;
2372 PurpleGroup *g;
2374 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2375 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2376 while (entry) {
2377 b = entry->data;
2378 g = purple_buddy_get_group(b);
2379 buddy = g_hash_table_lookup(sip->buddies, b->name);
2380 if(buddy) {
2381 gboolean in_sipe_groups = FALSE;
2382 GSList *entry2 = buddy->groups;
2383 while (entry2) {
2384 struct sipe_group *group = entry2->data;
2385 if (!strcmp(group->name, g->name)) {
2386 in_sipe_groups = TRUE;
2387 break;
2389 entry2 = entry2->next;
2391 if(!in_sipe_groups) {
2392 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2393 purple_blist_remove_buddy(b);
2395 } else {
2396 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2397 purple_blist_remove_buddy(b);
2399 entry = entry->next;
2401 g_slist_free(buddies);
2404 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2406 int len = msg->bodylen;
2408 gchar *tmp = sipmsg_find_header(msg, "Event");
2409 xmlnode *item;
2410 xmlnode *isc;
2411 const gchar *contacts_delta;
2412 xmlnode *group_node;
2413 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
2414 return FALSE;
2417 /* Convert the contact from XML to Purple Buddies */
2418 isc = xmlnode_from_str(msg->body, len);
2419 if (!isc) {
2420 return FALSE;
2423 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2424 if (contacts_delta) {
2425 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2428 if (!strcmp(isc->name, "contactList")) {
2430 /* Parse groups */
2431 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2432 struct sipe_group * group = g_new0(struct sipe_group, 1);
2433 const char *name = xmlnode_get_attrib(group_node, "name");
2435 if (!strncmp(name, "~", 1)) {
2436 name = _("Other Contacts");
2438 group->name = g_strdup(name);
2439 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2441 sipe_group_add(sip, group);
2444 // Make sure we have at least one group
2445 if (g_slist_length(sip->groups) == 0) {
2446 struct sipe_group * group = g_new0(struct sipe_group, 1);
2447 PurpleGroup *purple_group;
2448 group->name = g_strdup(_("Other Contacts"));
2449 group->id = 1;
2450 purple_group = purple_group_new(group->name);
2451 purple_blist_add_group(purple_group, NULL);
2452 sip->groups = g_slist_append(sip->groups, group);
2455 /* Parse contacts */
2456 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2457 const gchar *uri = xmlnode_get_attrib(item, "uri");
2458 const gchar *name = xmlnode_get_attrib(item, "name");
2459 gchar *buddy_name;
2460 struct sipe_buddy *buddy = NULL;
2461 gchar *tmp;
2462 gchar **item_groups;
2463 int i = 0;
2465 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2466 tmp = sip_uri_from_name(uri);
2467 buddy_name = g_ascii_strdown(tmp, -1);
2468 g_free(tmp);
2470 /* assign to group Other Contacts if nothing else received */
2471 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2472 if(!tmp || !strcmp("", tmp) ) {
2473 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2474 g_free(tmp);
2475 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2477 item_groups = g_strsplit(tmp, " ", 0);
2478 g_free(tmp);
2480 while (item_groups[i]) {
2481 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2483 // If couldn't find the right group for this contact, just put them in the first group we have
2484 if (group == NULL && g_slist_length(sip->groups) > 0) {
2485 group = sip->groups->data;
2488 if (group != NULL) {
2489 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2490 if (!b){
2491 b = purple_buddy_new(sip->account, buddy_name, uri);
2492 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2494 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2497 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2498 if (name != NULL && strlen(name) != 0) {
2499 purple_blist_alias_buddy(b, name);
2501 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2505 if (!buddy) {
2506 buddy = g_new0(struct sipe_buddy, 1);
2507 buddy->name = g_strdup(b->name);
2508 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2511 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2513 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2514 } else {
2515 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2516 name);
2519 i++;
2520 } // while, contact groups
2521 g_strfreev(item_groups);
2522 g_free(buddy_name);
2524 } // for, contacts
2526 sipe_cleanup_local_blist(sip);
2528 /* Add self-contact if not there yet. 2005 systems. */
2529 /* This will resemble subscription to roaming_self in 2007 systems */
2530 if (!sip->ocs2007) {
2531 gchar *self_uri = sip_uri_self(sip);
2532 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2534 if (!buddy) {
2535 buddy = g_new0(struct sipe_buddy, 1);
2536 buddy->name = g_strdup(self_uri);
2537 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2539 g_free(self_uri);
2542 xmlnode_free(isc);
2544 /* subscribe to buddies */
2545 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2546 if (sip->batched_support) {
2547 sipe_subscribe_presence_batched(sip, NULL);
2548 } else {
2549 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2551 sip->subscribed_buddies = TRUE;
2553 /* for 2005 systems schedule contacts' status update
2554 * based on their calendar information
2556 if (!sip->ocs2007) {
2557 sipe_sched_calendar_status_update(sip, time(NULL));
2560 return 0;
2564 * Subscribe roaming contacts
2566 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2568 gchar *to = sip_uri_self(sip);
2569 gchar *tmp = get_contact(sip);
2570 gchar *hdr = g_strdup_printf(
2571 "Event: vnd-microsoft-roaming-contacts\r\n"
2572 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2573 "Supported: com.microsoft.autoextend\r\n"
2574 "Supported: ms-benotify\r\n"
2575 "Proxy-Require: ms-benotify\r\n"
2576 "Supported: ms-piggyback-first-notify\r\n"
2577 "Contact: %s\r\n", tmp);
2578 g_free(tmp);
2580 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2581 g_free(to);
2582 g_free(hdr);
2585 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2586 SIPE_UNUSED_PARAMETER void *unused)
2588 gchar *key;
2589 struct sip_dialog *dialog;
2590 gchar *to = sip_uri_self(sip);
2591 gchar *tmp = get_contact(sip);
2592 gchar *hdr = g_strdup_printf(
2593 "Event: presence.wpending\r\n"
2594 "Accept: text/xml+msrtc.wpending\r\n"
2595 "Supported: com.microsoft.autoextend\r\n"
2596 "Supported: ms-benotify\r\n"
2597 "Proxy-Require: ms-benotify\r\n"
2598 "Supported: ms-piggyback-first-notify\r\n"
2599 "Contact: %s\r\n", tmp);
2600 g_free(tmp);
2602 /* Subscription is identified by <event> key */
2603 key = g_strdup_printf("<%s>", "presence.wpending");
2604 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2605 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2607 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2609 g_free(to);
2610 g_free(hdr);
2611 g_free(key);
2615 * Fires on deregistration event initiated by server.
2616 * [MS-SIPREGE] SIP extension.
2619 // 2007 Example
2621 // Content-Type: text/registration-event
2622 // subscription-state: terminated;expires=0
2623 // ms-diagnostics-public: 4141;reason="User disabled"
2625 // deregistered;event=rejected
2627 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2629 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2630 gchar *event = NULL;
2631 gchar *reason = NULL;
2632 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2634 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2635 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2637 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2638 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2639 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2640 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2641 } else {
2642 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2643 return;
2646 if (warning != NULL) {
2647 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2648 } else { // for LCS2005
2649 int error_id = 0;
2650 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2651 error_id = 4140; // [MS-SIPREGE]
2652 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2653 reason = g_strdup(_("you are already signed in at another location"));
2654 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2655 error_id = 4141;
2656 reason = g_strdup(_("user disabled")); // [MS-OCER]
2657 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2658 error_id = 4142;
2659 reason = g_strdup(_("user moved")); // [MS-OCER]
2662 g_free(event);
2663 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2664 g_free(reason);
2666 sip->gc->wants_to_die = TRUE;
2667 purple_connection_error(sip->gc, warning);
2668 g_free(warning);
2672 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2674 xmlnode *xn_provision_group_list;
2675 xmlnode *node;
2677 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2679 /* provisionGroup */
2680 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2681 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2682 g_free(sip->focus_factory_uri);
2683 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2684 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2685 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2686 break;
2689 xmlnode_free(xn_provision_group_list);
2692 /** for 2005 system */
2693 static void
2694 sipe_process_provisioning(struct sipe_account_data *sip,
2695 struct sipmsg *msg)
2697 xmlnode *xn_provision;
2698 xmlnode *node;
2700 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2701 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2702 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2703 if ((node = xmlnode_get_child(node, "line"))) {
2704 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2705 const gchar *server = xmlnode_get_attrib(node, "server");
2706 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2707 sip_csta_open(sip, line_uri, server);
2710 xmlnode_free(xn_provision);
2713 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2715 const gchar *contacts_delta;
2716 xmlnode *xml;
2718 xml = xmlnode_from_str(msg->body, msg->bodylen);
2719 if (!xml)
2721 return;
2724 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2725 if (contacts_delta)
2727 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2730 xmlnode_free(xml);
2733 static void
2734 free_container(struct sipe_container *container)
2736 GSList *entry;
2738 if (!container) return;
2740 entry = container->members;
2741 while (entry) {
2742 g_free(entry->data);
2743 entry = g_slist_remove(entry, entry->data);
2745 g_free(container);
2749 * Finds locally stored MS-PRES container member
2751 static struct sipe_container_member *
2752 sipe_find_container_member(struct sipe_container *container,
2753 const gchar *type,
2754 const gchar *value)
2756 struct sipe_container_member *member;
2757 GSList *entry;
2759 if (container == NULL || type == NULL) {
2760 return NULL;
2763 entry = container->members;
2764 while (entry) {
2765 member = entry->data;
2766 if (!g_strcasecmp(member->type, type)
2767 && ((!member->value && !value)
2768 || (value && member->value && !g_strcasecmp(member->value, value)))
2770 return member;
2772 entry = entry->next;
2774 return NULL;
2778 * Finds locally stored MS-PRES container by id
2780 static struct sipe_container *
2781 sipe_find_container(struct sipe_account_data *sip,
2782 guint id)
2784 struct sipe_container *container;
2785 GSList *entry;
2787 if (sip == NULL) {
2788 return NULL;
2791 entry = sip->containers;
2792 while (entry) {
2793 container = entry->data;
2794 if (id == container->id) {
2795 return container;
2797 entry = entry->next;
2799 return NULL;
2803 * Access Levels
2804 * 32000 - Blocked
2805 * 400 - Personal
2806 * 300 - Team
2807 * 200 - Company
2808 * 100 - Public
2810 static int
2811 sipe_find_access_level(struct sipe_account_data *sip,
2812 const gchar *type,
2813 const gchar *value)
2815 guint containers[] = {32000, 400, 300, 200, 100};
2816 int i = 0;
2818 for (i = 0; i < 5; i++) {
2819 struct sipe_container_member *member;
2820 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2821 if (!container) continue;
2823 member = sipe_find_container_member(container, type, value);
2824 if (member) {
2825 return containers[i];
2829 return -1;
2832 static void
2833 sipe_send_set_container_members(struct sipe_account_data *sip,
2834 guint container_id,
2835 guint container_version,
2836 const gchar* action,
2837 const gchar* type,
2838 const gchar* value)
2840 gchar *self = sip_uri_self(sip);
2841 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2842 gchar *contact;
2843 gchar *hdr;
2844 gchar *body = g_strdup_printf(
2845 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2846 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2847 "</setContainerMembers>",
2848 container_id,
2849 container_version,
2850 action,
2851 type,
2852 value_str);
2853 g_free(value_str);
2855 contact = get_contact(sip);
2856 hdr = g_strdup_printf("Contact: %s\r\n"
2857 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2858 g_free(contact);
2860 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2862 g_free(hdr);
2863 g_free(body);
2864 g_free(self);
2867 static void
2868 free_publication(struct sipe_publication *publication)
2870 g_free(publication->category);
2871 g_free(publication->cal_event_hash);
2872 g_free(publication->note);
2874 g_free(publication->working_hours_xml_str);
2875 g_free(publication->fb_start_str);
2876 g_free(publication->free_busy_base64);
2878 g_free(publication);
2881 /* key is <category><instance><container> */
2882 static gboolean
2883 sipe_is_our_publication(struct sipe_account_data *sip,
2884 const gchar *key)
2886 GSList *entry;
2888 /* filling keys for our publications if not yet cached */
2889 if (!sip->our_publication_keys) {
2890 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2891 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2892 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2893 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2894 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2895 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2896 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2898 purple_debug_info("sipe", "* Out Publication Instances *\n");
2899 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2900 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2901 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2902 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2903 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2904 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2905 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2906 purple_debug_info("sipe", "\tNote : %u\n", 0);
2907 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2909 /* device */
2910 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2911 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2913 /* state:machineState */
2914 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2915 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2916 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2917 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2919 /* state:userState */
2920 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2921 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2922 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2923 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2925 /* state:calendarState */
2926 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2927 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2928 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2929 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2931 /* state:calendarState OOF */
2932 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2933 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2934 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2935 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2937 /* note */
2938 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2939 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2940 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2941 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2942 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2943 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2945 /* note OOF */
2946 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2947 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2948 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2949 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2950 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2951 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2953 /* calendarData:WorkingHours */
2954 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2955 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2956 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2957 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2958 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2959 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2960 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2961 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2962 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2963 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2964 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2965 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2967 /* calendarData:FreeBusy */
2968 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2969 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2970 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2971 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2972 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2973 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2974 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2975 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2976 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2977 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2978 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2979 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2981 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2982 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2985 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
2987 entry = sip->our_publication_keys;
2988 while (entry) {
2989 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
2990 if (!strcmp(entry->data, key)) {
2991 return TRUE;
2993 entry = entry->next;
2995 return FALSE;
2998 /** Property names to store in blist.xml */
2999 #define ALIAS_PROP "alias"
3000 #define EMAIL_PROP "email"
3001 #define PHONE_PROP "phone"
3002 #define PHONE_DISPLAY_PROP "phone-display"
3003 #define PHONE_MOBILE_PROP "phone-mobile"
3004 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3005 #define PHONE_HOME_PROP "phone-home"
3006 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3007 #define PHONE_OTHER_PROP "phone-other"
3008 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3009 #define PHONE_CUSTOM1_PROP "phone-custom1"
3010 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3011 #define SITE_PROP "site"
3012 #define COMPANY_PROP "company"
3013 #define DEPARTMENT_PROP "department"
3014 #define TITLE_PROP "title"
3015 #define OFFICE_PROP "office"
3016 /** implies work address */
3017 #define ADDRESS_STREET_PROP "address-street"
3018 #define ADDRESS_CITY_PROP "address-city"
3019 #define ADDRESS_STATE_PROP "address-state"
3020 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3021 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3023 * Update user information
3025 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3026 * @param property_name
3027 * @param property_value may be modified to strip white space
3029 static void
3030 sipe_update_user_info(struct sipe_account_data *sip,
3031 const char *uri,
3032 const char *property_name,
3033 char *property_value)
3035 GSList *buddies, *entry;
3037 if (!property_name || strlen(property_name) == 0) return;
3039 if (property_value)
3040 property_value = g_strstrip(property_value);
3042 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3043 while (entry) {
3044 const char *prop_str;
3045 const char *server_alias;
3046 PurpleBuddy *p_buddy = entry->data;
3048 /* for Display Name */
3049 if (!strcmp(property_name, ALIAS_PROP)) {
3050 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3051 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3052 purple_blist_alias_buddy(p_buddy, property_value);
3055 server_alias = purple_buddy_get_server_alias(p_buddy);
3056 if (property_value && strlen(property_value) > 0 &&
3057 ( (server_alias && strcmp(property_value, server_alias))
3058 || !server_alias || strlen(server_alias) == 0 )
3060 purple_blist_server_alias_buddy(p_buddy, property_value);
3063 /* for other properties */
3064 else {
3065 if (property_value && strlen(property_value) > 0) {
3066 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3067 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3068 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3073 entry = entry->next;
3075 g_slist_free(buddies);
3079 * Update user phone
3080 * Suitable for both 2005 and 2007 systems.
3082 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3083 * @param phone_type
3084 * @param phone may be modified to strip white space
3085 * @param phone_display_string may be modified to strip white space
3087 static void
3088 sipe_update_user_phone(struct sipe_account_data *sip,
3089 const char *uri,
3090 const gchar *phone_type,
3091 gchar *phone,
3092 gchar *phone_display_string)
3094 const char *phone_node = PHONE_PROP; /* work phone by default */
3095 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3097 if(!phone || strlen(phone) == 0) return;
3099 if (phone_type && (!strcmp(phone_type, "mobile") || !strcmp(phone_type, "cell"))) {
3100 phone_node = PHONE_MOBILE_PROP;
3101 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3102 } else if (phone_type && !strcmp(phone_type, "home")) {
3103 phone_node = PHONE_HOME_PROP;
3104 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3105 } else if (phone_type && !strcmp(phone_type, "other")) {
3106 phone_node = PHONE_OTHER_PROP;
3107 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3108 } else if (phone_type && !strcmp(phone_type, "custom1")) {
3109 phone_node = PHONE_CUSTOM1_PROP;
3110 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3113 sipe_update_user_info(sip, uri, phone_node, phone);
3114 if (phone_display_string) {
3115 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3119 static void
3120 sipe_update_calendar(struct sipe_account_data *sip)
3122 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3124 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3126 if (!strcmp(calendar, "EXCH")) {
3127 sipe_ews_update_calendar(sip);
3130 /* schedule repeat */
3131 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3133 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3137 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3138 * by using standard Purple's means of signals and saved statuses.
3140 * Thus all UI elements get updated: Status Button with Note, docklet.
3141 * This is ablolutely important as both our status and note can come
3142 * inbound (roaming) or be updated programmatically (e.g. based on our
3143 * calendar data).
3145 static void
3146 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3147 const char *status_id,
3148 const char *message,
3149 time_t do_not_publish[])
3151 PurpleStatus *status = purple_account_get_active_status(account);
3152 gboolean changed = TRUE;
3154 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3155 purple_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3157 changed = FALSE;
3160 if (changed) {
3161 PurpleSavedStatus *saved_status;
3162 const PurpleStatusType *acct_status_type =
3163 purple_status_type_find_with_id(account->status_types, status_id);
3164 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3165 sipe_activity activity = sipe_get_activity_by_token(status_id);
3167 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3168 if (saved_status) {
3169 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3172 /* If this type+message is unique then create a new transient saved status
3173 * Ref: gtkstatusbox.c
3175 if (!saved_status) {
3176 GList *tmp;
3177 GList *active_accts = purple_accounts_get_all_active();
3179 saved_status = purple_savedstatus_new(NULL, primitive);
3180 purple_savedstatus_set_message(saved_status, message);
3182 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3183 purple_savedstatus_set_substatus(saved_status,
3184 (PurpleAccount *)tmp->data, acct_status_type, message);
3186 g_list_free(active_accts);
3189 do_not_publish[activity] = time(NULL);
3190 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3191 status_id, (int)do_not_publish[activity]);
3193 /* Set the status for each account */
3194 purple_savedstatus_activate(saved_status);
3198 struct hash_table_delete_payload {
3199 GHashTable *hash_table;
3200 guint container;
3203 static void
3204 sipe_remove_category_container_publications_cb(const char *name,
3205 struct sipe_publication *publication,
3206 struct hash_table_delete_payload *payload)
3208 if (publication->container == payload->container) {
3209 g_hash_table_remove(payload->hash_table, name);
3212 static void
3213 sipe_remove_category_container_publications(GHashTable *our_publications,
3214 const char *category,
3215 guint container)
3217 struct hash_table_delete_payload payload;
3218 payload.hash_table = g_hash_table_lookup(our_publications, category);
3220 if (!payload.hash_table) return;
3222 payload.container = container;
3223 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3226 static void
3227 send_publish_category_initial(struct sipe_account_data *sip);
3230 * When we receive some self (BE) NOTIFY with a new subscriber
3231 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3234 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3236 gchar *contact;
3237 gchar *to;
3238 xmlnode *xml;
3239 xmlnode *node;
3240 xmlnode *node2;
3241 char *display_name = NULL;
3242 char *uri;
3243 GSList *category_names = NULL;
3244 int aggreg_avail = 0;
3245 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3246 gboolean do_update_status = FALSE;
3247 gboolean has_note_cleaned = FALSE;
3249 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3251 xml = xmlnode_from_str(msg->body, msg->bodylen);
3252 if (!xml) return;
3254 contact = get_contact(sip);
3255 to = sip_uri_self(sip);
3258 /* categories */
3259 /* set list of categories participating in this XML */
3260 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3261 const gchar *name = xmlnode_get_attrib(node, "name");
3262 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3264 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3265 category_names ? (int) g_slist_length(category_names) : -1);
3266 /* drop category information */
3267 if (category_names) {
3268 GSList *entry = category_names;
3269 while (entry) {
3270 GHashTable *cat_publications;
3271 const gchar *category = entry->data;
3272 entry = entry->next;
3273 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3274 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3275 if (cat_publications) {
3276 g_hash_table_remove(sip->our_publications, category);
3277 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3281 g_slist_free(category_names);
3282 /* filling our categories reflected in roaming data */
3283 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3284 const char *tmp;
3285 const gchar *name = xmlnode_get_attrib(node, "name");
3286 guint container = xmlnode_get_int_attrib(node, "container", -1);
3287 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3288 guint version = xmlnode_get_int_attrib(node, "version", 0);
3289 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3290 sipe_utils_str_to_time(tmp) : 0;
3291 gchar *key;
3292 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3294 /* Ex. clear note: <category name="note"/> */
3295 if (container == (guint)-1) {
3296 g_free(sip->note);
3297 sip->note = NULL;
3298 do_update_status = TRUE;
3299 continue;
3302 /* Ex. clear note: <category name="note" container="200"/> */
3303 if (instance == (guint)-1) {
3304 if (container == 200) {
3305 g_free(sip->note);
3306 sip->note = NULL;
3307 do_update_status = TRUE;
3309 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3310 sipe_remove_category_container_publications(
3311 sip->our_publications, name, container);
3312 continue;
3315 /* key is <category><instance><container> */
3316 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3317 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3319 /* capture all userState publication for later clean up if required */
3320 if (!strcmp(name, "state") && (container == 2 || container == 3)) {
3321 xmlnode *xn_state = xmlnode_get_child(node, "state");
3323 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3324 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3325 publication->category = g_strdup(name);
3326 publication->instance = instance;
3327 publication->container = container;
3328 publication->version = version;
3330 if (!sip->user_state_publications) {
3331 sip->user_state_publications = g_hash_table_new_full(
3332 g_str_hash, g_str_equal,
3333 g_free, (GDestroyNotify)free_publication);
3335 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3336 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3337 key, version);
3341 if (sipe_is_our_publication(sip, key)) {
3342 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3344 publication->category = g_strdup(name);
3345 publication->instance = instance;
3346 publication->container = container;
3347 publication->version = version;
3349 /* filling publication->availability */
3350 if (!strcmp(name, "state")) {
3351 xmlnode *xn_state = xmlnode_get_child(node, "state");
3352 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3354 if (xn_avail) {
3355 gchar *avail_str = xmlnode_get_data(xn_avail);
3356 if (avail_str) {
3357 publication->availability = atoi(avail_str);
3359 g_free(avail_str);
3361 /* for calendarState */
3362 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3363 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3364 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3366 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3367 if (xn_activity) {
3368 if (!strcmp(xmlnode_get_attrib(xn_activity, "token"),
3369 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3371 event->is_meeting = TRUE;
3374 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3375 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3377 publication->cal_event_hash = sipe_cal_event_hash(event);
3378 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3379 publication->cal_event_hash);
3380 sipe_cal_event_free(event);
3383 /* filling publication->note */
3384 if (!strcmp(name, "note")) {
3385 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3387 if (!has_note_cleaned) {
3388 has_note_cleaned = TRUE;
3390 g_free(sip->note);
3391 sip->note = NULL;
3392 sip->note_since = publish_time;
3394 do_update_status = TRUE;
3397 g_free(publication->note);
3398 publication->note = NULL;
3399 if (xn_body) {
3400 char *tmp;
3402 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3403 g_free(tmp);
3404 if (publish_time >= sip->note_since) {
3405 g_free(sip->note);
3406 sip->note = g_strdup(publication->note);
3407 sip->note_since = publish_time;
3408 sip->is_oof_note = !strcmp(xmlnode_get_attrib(xn_body, "type"), "OOF");
3410 do_update_status = TRUE;
3415 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3416 if (!strcmp(name, "calendarData") && (publication->container == 300)) {
3417 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3418 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3419 if (xn_free_busy) {
3420 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3421 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3423 if (xn_working_hours) {
3424 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3428 if (!cat_publications) {
3429 cat_publications = g_hash_table_new_full(
3430 g_str_hash, g_str_equal,
3431 g_free, (GDestroyNotify)free_publication);
3432 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3433 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3435 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3436 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3438 g_free(key);
3440 /* aggregateState (not an our publication) from 2-nd container */
3441 if (!strcmp(name, "state") && container == 2) {
3442 xmlnode *xn_state = xmlnode_get_child(node, "state");
3444 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3445 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3446 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3448 if (xn_avail) {
3449 gchar *avail_str = xmlnode_get_data(xn_avail);
3450 if (avail_str) {
3451 aggreg_avail = atoi(avail_str);
3453 g_free(avail_str);
3456 if (xn_activity) {
3457 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3459 aggreg_activity = sipe_get_activity_by_token(activity_token);
3462 do_update_status = TRUE;
3466 /* userProperties published by server from AD */
3467 if (!sip->csta && !strcmp(name, "userProperties")) {
3468 xmlnode *line;
3469 /* line, for Remote Call Control (RCC) */
3470 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3471 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3472 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3473 gchar *line_uri;
3475 if (!line_server || (strcmp(line_type, "Rcc") && strcmp(line_type, "Dual"))) continue;
3477 line_uri = xmlnode_get_data(line);
3478 if (line_uri) {
3479 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3480 sip_csta_open(sip, line_uri, line_server);
3482 g_free(line_uri);
3484 break;
3488 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3489 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3491 /* containers */
3492 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3493 guint id = xmlnode_get_int_attrib(node, "id", 0);
3494 struct sipe_container *container = sipe_find_container(sip, id);
3496 if (container) {
3497 sip->containers = g_slist_remove(sip->containers, container);
3498 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3499 free_container(container);
3501 container = g_new0(struct sipe_container, 1);
3502 container->id = id;
3503 container->version = xmlnode_get_int_attrib(node, "version", 0);
3504 sip->containers = g_slist_append(sip->containers, container);
3505 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3507 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3508 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3509 member->type = xmlnode_get_attrib(node2, "type");
3510 member->value = xmlnode_get_attrib(node2, "value");
3511 container->members = g_slist_append(container->members, member);
3512 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3513 member->type, member->value ? member->value : "");
3517 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3518 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3519 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3520 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3521 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3522 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3523 /* initial set-up to let counterparties see your status */
3524 if (sameEnterpriseAL < 0) {
3525 struct sipe_container *container = sipe_find_container(sip, 200);
3526 guint version = container ? container->version : 0;
3527 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3529 if (federatedAL < 0) {
3530 struct sipe_container *container = sipe_find_container(sip, 100);
3531 guint version = container ? container->version : 0;
3532 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3534 sip->access_level_set = TRUE;
3537 /* subscribers */
3538 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3539 const char *user;
3540 const char *acknowledged;
3541 gchar *hdr;
3542 gchar *body;
3544 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3545 if (!user) continue;
3546 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3547 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3548 uri = sip_uri_from_name(user);
3550 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3552 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3553 if(!g_ascii_strcasecmp(acknowledged,"false")){
3554 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3555 if (!purple_find_buddy(sip->account, uri)) {
3556 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3559 hdr = g_strdup_printf(
3560 "Contact: %s\r\n"
3561 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3563 body = g_strdup_printf(
3564 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3565 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3566 "</setSubscribers>", user);
3568 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3569 g_free(body);
3570 g_free(hdr);
3572 g_free(display_name);
3573 g_free(uri);
3576 g_free(contact);
3577 xmlnode_free(xml);
3579 /* Publish initial state if not yet.
3580 * Assuming this happens on initial responce to subscription to roaming-self
3581 * so we've already updated our roaming data in full.
3582 * Only for 2007+
3584 if (!sip->initial_state_published) {
3585 send_publish_category_initial(sip);
3586 sip->initial_state_published = TRUE;
3587 /* dalayed run */
3588 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3589 do_update_status = FALSE;
3590 } else if (aggreg_avail) {
3592 g_free(sip->status);
3593 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3594 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3595 } else {
3596 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3600 if (do_update_status) {
3601 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3602 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3605 g_free(to);
3608 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3610 gchar *to = sip_uri_self(sip);
3611 gchar *tmp = get_contact(sip);
3612 gchar *hdr = g_strdup_printf(
3613 "Event: vnd-microsoft-roaming-ACL\r\n"
3614 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3615 "Supported: com.microsoft.autoextend\r\n"
3616 "Supported: ms-benotify\r\n"
3617 "Proxy-Require: ms-benotify\r\n"
3618 "Supported: ms-piggyback-first-notify\r\n"
3619 "Contact: %s\r\n", tmp);
3620 g_free(tmp);
3622 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3623 g_free(to);
3624 g_free(hdr);
3628 * To request for presence information about the user, access level settings that have already been configured by the user
3629 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3630 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3633 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3635 gchar *to = sip_uri_self(sip);
3636 gchar *tmp = get_contact(sip);
3637 gchar *hdr = g_strdup_printf(
3638 "Event: vnd-microsoft-roaming-self\r\n"
3639 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3640 "Supported: ms-benotify\r\n"
3641 "Proxy-Require: ms-benotify\r\n"
3642 "Supported: ms-piggyback-first-notify\r\n"
3643 "Contact: %s\r\n"
3644 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3646 gchar *body=g_strdup(
3647 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3648 "<roaming type=\"categories\"/>"
3649 "<roaming type=\"containers\"/>"
3650 "<roaming type=\"subscribers\"/></roamingList>");
3652 g_free(tmp);
3653 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3654 g_free(body);
3655 g_free(to);
3656 g_free(hdr);
3660 * For 2005 version
3662 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3664 gchar *to = sip_uri_self(sip);
3665 gchar *tmp = get_contact(sip);
3666 gchar *hdr = g_strdup_printf(
3667 "Event: vnd-microsoft-provisioning\r\n"
3668 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3669 "Supported: com.microsoft.autoextend\r\n"
3670 "Supported: ms-benotify\r\n"
3671 "Proxy-Require: ms-benotify\r\n"
3672 "Supported: ms-piggyback-first-notify\r\n"
3673 "Expires: 0\r\n"
3674 "Contact: %s\r\n", tmp);
3676 g_free(tmp);
3677 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3678 g_free(to);
3679 g_free(hdr);
3682 /** Subscription for provisioning information to help with initial
3683 * configuration. This subscription is a one-time query (denoted by the Expires header,
3684 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3685 * configuration, meeting policies, and policy settings that Communicator must enforce.
3686 * TODO: for what we need this information.
3689 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3691 gchar *to = sip_uri_self(sip);
3692 gchar *tmp = get_contact(sip);
3693 gchar *hdr = g_strdup_printf(
3694 "Event: vnd-microsoft-provisioning-v2\r\n"
3695 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3696 "Supported: com.microsoft.autoextend\r\n"
3697 "Supported: ms-benotify\r\n"
3698 "Proxy-Require: ms-benotify\r\n"
3699 "Supported: ms-piggyback-first-notify\r\n"
3700 "Expires: 0\r\n"
3701 "Contact: %s\r\n"
3702 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3703 gchar *body = g_strdup(
3704 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3705 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3706 "<provisioningGroup name=\"ucPolicy\"/>"
3707 "</provisioningGroupList>");
3709 g_free(tmp);
3710 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3711 g_free(body);
3712 g_free(to);
3713 g_free(hdr);
3716 static void
3717 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3718 gpointer value, gpointer user_data)
3720 struct sip_subscription *subscription = value;
3721 struct sip_dialog *dialog = &subscription->dialog;
3722 struct sipe_account_data *sip = user_data;
3723 gchar *tmp = get_contact(sip);
3724 gchar *hdr = g_strdup_printf(
3725 "Event: %s\r\n"
3726 "Expires: 0\r\n"
3727 "Contact: %s\r\n", subscription->event, tmp);
3728 g_free(tmp);
3730 /* Rate limit to max. 25 requests per seconds */
3731 g_usleep(1000000 / 25);
3733 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3734 g_free(hdr);
3737 /* IM Session (INVITE and MESSAGE methods) */
3739 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3740 static gchar *
3741 get_end_points (struct sipe_account_data *sip,
3742 struct sip_session *session)
3744 gchar *res;
3746 if (session == NULL) {
3747 return NULL;
3750 res = g_strdup_printf("<sip:%s>", sip->username);
3752 SIPE_DIALOG_FOREACH {
3753 gchar *tmp = res;
3754 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3755 g_free(tmp);
3757 if (dialog->theirepid) {
3758 tmp = res;
3759 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3760 g_free(tmp);
3762 } SIPE_DIALOG_FOREACH_END;
3764 return res;
3767 static gboolean
3768 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3769 struct sipmsg *msg,
3770 SIPE_UNUSED_PARAMETER struct transaction *trans)
3772 gboolean ret = TRUE;
3774 if (msg->response != 200) {
3775 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3776 return FALSE;
3779 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3781 return ret;
3785 * Asks UA/proxy about its capabilities.
3787 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3789 gchar *to = sip_uri(who);
3790 gchar *contact = get_contact(sip);
3791 gchar *request = g_strdup_printf(
3792 "Accept: application/sdp\r\n"
3793 "Contact: %s\r\n", contact);
3794 g_free(contact);
3796 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3798 g_free(to);
3799 g_free(request);
3802 static void
3803 sipe_notify_user(struct sipe_account_data *sip,
3804 struct sip_session *session,
3805 PurpleMessageFlags flags,
3806 const gchar *message)
3808 PurpleConversation *conv;
3810 if (!session->conv) {
3811 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3812 } else {
3813 conv = session->conv;
3815 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3818 void
3819 sipe_present_info(struct sipe_account_data *sip,
3820 struct sip_session *session,
3821 const gchar *message)
3823 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3826 static void
3827 sipe_present_err(struct sipe_account_data *sip,
3828 struct sip_session *session,
3829 const gchar *message)
3831 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3834 void
3835 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3836 struct sip_session *session,
3837 int sip_error,
3838 const gchar *who,
3839 const gchar *message)
3841 char *msg, *msg_tmp, *msg_tmp2;
3842 const char *label;
3844 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
3845 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3846 g_free(msg_tmp);
3847 /* Service unavailable; Server Internal Error; Server Time-out */
3848 if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3849 label = _("This message was not delivered to %s because the service is not available");
3850 } else if (sip_error == 486) { /* Busy Here */
3851 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3852 } else {
3853 label = _("This message was not delivered to %s because one or more recipients are offline");
3856 msg_tmp = g_strdup_printf( "%s:\n%s" ,
3857 msg_tmp2 = g_strdup_printf(label, who ? who : ""), msg ? msg : "");
3858 sipe_present_err(sip, session, msg_tmp);
3859 g_free(msg_tmp2);
3860 g_free(msg_tmp);
3861 g_free(msg);
3865 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
3867 static gboolean
3868 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
3869 SIPE_UNUSED_PARAMETER struct transaction *trans)
3871 gboolean ret = TRUE;
3872 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3873 struct sip_session *session = sipe_session_find_im(sip, with);
3874 struct sip_dialog *dialog;
3875 gchar *cseq;
3876 char *key;
3877 gchar *message;
3879 if (!session) {
3880 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3881 g_free(with);
3882 return FALSE;
3885 dialog = sipe_dialog_find(session, with);
3886 if (!dialog) {
3887 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3888 g_free(with);
3889 return FALSE;
3892 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3893 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3894 g_free(cseq);
3895 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3897 if (msg->response >= 400) {
3898 PurpleBuddy *pbuddy;
3899 gchar *alias = with;
3901 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3903 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3904 alias = (gchar *)purple_buddy_get_alias(pbuddy);
3907 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
3908 ret = FALSE;
3909 } else {
3910 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3911 if (message_id) {
3912 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
3913 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
3914 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3917 g_hash_table_remove(session->unconfirmed_messages, key);
3918 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
3919 key, g_hash_table_size(session->unconfirmed_messages));
3922 g_free(key);
3923 g_free(with);
3925 if (ret) sipe_im_process_queue(sip, session);
3926 return ret;
3929 static gboolean
3930 sipe_is_election_finished(struct sip_session *session);
3932 static void
3933 sipe_election_result(struct sipe_account_data *sip,
3934 void *sess);
3936 static gboolean
3937 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
3938 SIPE_UNUSED_PARAMETER struct transaction *trans)
3940 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3941 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3942 struct sip_dialog *dialog;
3943 struct sip_session *session;
3945 session = sipe_session_find_chat_by_callid(sip, callid);
3946 if (!session) {
3947 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
3948 return FALSE;
3951 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
3952 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3953 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
3954 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
3956 if (xn_request_rm_response) {
3957 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
3958 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
3960 dialog = sipe_dialog_find(session, with);
3961 if (!dialog) {
3962 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
3963 return FALSE;
3966 if (allow && !g_strcasecmp(allow, "true")) {
3967 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
3968 dialog->election_vote = 1;
3969 } else if (allow && !g_strcasecmp(allow, "false")) {
3970 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
3971 dialog->election_vote = -1;
3974 if (sipe_is_election_finished(session)) {
3975 sipe_election_result(sip, session);
3978 } else if (xn_set_rm_response) {
3981 xmlnode_free(xn_action);
3985 return TRUE;
3988 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
3990 gchar *hdr;
3991 gchar *tmp;
3992 char *msgformat;
3993 char *msgtext;
3994 gchar *msgr_value;
3995 gchar *msgr;
3997 sipe_parse_html(msg, &msgformat, &msgtext);
3998 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4000 msgr_value = sipmsg_get_msgr_string(msgformat);
4001 g_free(msgformat);
4002 if (msgr_value) {
4003 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4004 g_free(msgr_value);
4005 } else {
4006 msgr = g_strdup("");
4009 tmp = get_contact(sip);
4010 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4011 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4012 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4013 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
4014 g_free(tmp);
4015 g_free(msgr);
4017 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4018 g_free(msgtext);
4019 g_free(hdr);
4023 static void
4024 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4026 GSList *entry2 = session->outgoing_message_queue;
4027 while (entry2) {
4028 char *queued_msg = entry2->data;
4030 /* for multiparty chat or conference */
4031 if (session->is_multiparty || session->focus_uri) {
4032 gchar *who = sip_uri_self(sip);
4033 serv_got_chat_in(sip->gc, session->chat_id, who,
4034 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
4035 g_free(who);
4038 SIPE_DIALOG_FOREACH {
4039 char *key;
4041 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4043 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4044 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
4045 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4046 key, g_hash_table_size(session->unconfirmed_messages));
4047 g_free(key);
4049 sipe_send_message(sip, dialog, queued_msg);
4050 } SIPE_DIALOG_FOREACH_END;
4052 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
4053 g_free(queued_msg);
4057 static void
4058 sipe_refer_notify(struct sipe_account_data *sip,
4059 struct sip_session *session,
4060 const gchar *who,
4061 int status,
4062 const gchar *desc)
4064 gchar *hdr;
4065 gchar *body;
4066 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4068 hdr = g_strdup_printf(
4069 "Event: refer\r\n"
4070 "Subscription-State: %s\r\n"
4071 "Content-Type: message/sipfrag\r\n",
4072 status >= 200 ? "terminated" : "active");
4074 body = g_strdup_printf(
4075 "SIP/2.0 %d %s\r\n",
4076 status, desc);
4078 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4080 g_free(hdr);
4081 g_free(body);
4084 static gboolean
4085 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4087 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4088 struct sip_session *session;
4089 struct sip_dialog *dialog;
4090 char *cseq;
4091 char *key;
4092 gchar *message;
4093 struct sipmsg *request_msg = trans->msg;
4095 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4096 gchar *referred_by;
4098 session = sipe_session_find_chat_by_callid(sip, callid);
4099 if (!session) {
4100 session = sipe_session_find_im(sip, with);
4102 if (!session) {
4103 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4104 g_free(with);
4105 return FALSE;
4108 dialog = sipe_dialog_find(session, with);
4109 if (!dialog) {
4110 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4111 g_free(with);
4112 return FALSE;
4115 sipe_dialog_parse(dialog, msg, TRUE);
4117 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4118 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4119 g_free(cseq);
4120 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4122 if (msg->response != 200) {
4123 PurpleBuddy *pbuddy;
4124 gchar *alias = with;
4126 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4128 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4129 alias = (gchar *)purple_buddy_get_alias(pbuddy);
4132 if (message) {
4133 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
4134 } else {
4135 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4136 sipe_present_err(sip, session, tmp_msg);
4137 g_free(tmp_msg);
4140 sipe_dialog_remove(session, with);
4142 g_free(key);
4143 g_free(with);
4144 return FALSE;
4147 dialog->cseq = 0;
4148 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4149 dialog->outgoing_invite = NULL;
4150 dialog->is_established = TRUE;
4152 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4153 if (referred_by) {
4154 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4155 g_free(referred_by);
4158 /* add user to chat if it is a multiparty session */
4159 if (session->is_multiparty) {
4160 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4161 with, NULL,
4162 PURPLE_CBFLAGS_NONE, TRUE);
4165 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4166 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4167 if (session->outgoing_message_queue) {
4168 char *queued_msg = session->outgoing_message_queue->data;
4169 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
4170 g_free(queued_msg);
4174 sipe_im_process_queue(sip, session);
4176 g_hash_table_remove(session->unconfirmed_messages, key);
4177 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4178 key, g_hash_table_size(session->unconfirmed_messages));
4180 g_free(key);
4181 g_free(with);
4182 return TRUE;
4186 void
4187 sipe_invite(struct sipe_account_data *sip,
4188 struct sip_session *session,
4189 const gchar *who,
4190 const gchar *msg_body,
4191 const gchar *referred_by,
4192 const gboolean is_triggered)
4194 gchar *hdr;
4195 gchar *to;
4196 gchar *contact;
4197 gchar *body;
4198 gchar *self;
4199 char *ms_text_format = NULL;
4200 gchar *roster_manager;
4201 gchar *end_points;
4202 gchar *referred_by_str;
4203 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4205 if (dialog && dialog->is_established) {
4206 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4207 return;
4210 if (!dialog) {
4211 dialog = sipe_dialog_add(session);
4212 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4213 dialog->with = g_strdup(who);
4216 if (!(dialog->ourtag)) {
4217 dialog->ourtag = gentag();
4220 to = sip_uri(who);
4222 if (msg_body) {
4223 char *msgformat;
4224 char *msgtext;
4225 char *base64_msg;
4226 gchar *msgr_value;
4227 gchar *msgr;
4228 char *key;
4230 sipe_parse_html(msg_body, &msgformat, &msgtext);
4231 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4233 msgr_value = sipmsg_get_msgr_string(msgformat);
4234 g_free(msgformat);
4235 msgr = "";
4236 if (msgr_value) {
4237 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4238 g_free(msgr_value);
4241 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4242 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
4243 g_free(msgtext);
4244 g_free(msgr);
4245 g_free(base64_msg);
4247 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4248 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
4249 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4250 key, g_hash_table_size(session->unconfirmed_messages));
4251 g_free(key);
4254 contact = get_contact(sip);
4255 end_points = get_end_points(sip, session);
4256 self = sip_uri_self(sip);
4257 roster_manager = g_strdup_printf(
4258 "Roster-Manager: %s\r\n"
4259 "EndPoints: %s\r\n",
4260 self,
4261 end_points);
4262 referred_by_str = referred_by ?
4263 g_strdup_printf(
4264 "Referred-By: %s\r\n",
4265 referred_by)
4266 : g_strdup("");
4267 hdr = g_strdup_printf(
4268 "Supported: ms-sender\r\n"
4269 "%s"
4270 "%s"
4271 "%s"
4272 "%s"
4273 "Contact: %s\r\n%s"
4274 "Content-Type: application/sdp\r\n",
4275 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
4276 referred_by_str,
4277 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4278 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4279 contact,
4280 ms_text_format ? ms_text_format : "");
4281 g_free(ms_text_format);
4282 g_free(self);
4284 body = g_strdup_printf(
4285 "v=0\r\n"
4286 "o=- 0 0 IN IP4 %s\r\n"
4287 "s=session\r\n"
4288 "c=IN IP4 %s\r\n"
4289 "t=0 0\r\n"
4290 "m=%s %d sip null\r\n"
4291 "a=accept-types:text/plain text/html image/gif "
4292 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4293 purple_network_get_my_ip(-1),
4294 purple_network_get_my_ip(-1),
4295 sip->ocs2007 ? "message" : "x-ms-message",
4296 sip->realport);
4298 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4299 to, to, hdr, body, dialog, process_invite_response);
4301 g_free(to);
4302 g_free(roster_manager);
4303 g_free(end_points);
4304 g_free(referred_by_str);
4305 g_free(body);
4306 g_free(hdr);
4307 g_free(contact);
4310 static void
4311 sipe_refer(struct sipe_account_data *sip,
4312 struct sip_session *session,
4313 const gchar *who)
4315 gchar *hdr;
4316 gchar *contact;
4317 gchar *epid = get_epid(sip);
4318 struct sip_dialog *dialog = sipe_dialog_find(session,
4319 session->roster_manager);
4320 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4322 contact = get_contact(sip);
4323 hdr = g_strdup_printf(
4324 "Contact: %s\r\n"
4325 "Refer-to: <%s>\r\n"
4326 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4327 "Require: com.microsoft.rtc-multiparty\r\n",
4328 contact,
4329 who,
4330 sip->username,
4331 ourtag ? ";tag=" : "",
4332 ourtag ? ourtag : "",
4333 epid);
4334 g_free(epid);
4336 send_sip_request(sip->gc, "REFER",
4337 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4339 g_free(hdr);
4340 g_free(contact);
4343 static void
4344 sipe_send_election_request_rm(struct sipe_account_data *sip,
4345 struct sip_dialog *dialog,
4346 int bid)
4348 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4350 gchar *body = g_strdup_printf(
4351 "<?xml version=\"1.0\"?>\r\n"
4352 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4353 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4354 sip->username, bid);
4356 send_sip_request(sip->gc, "INFO",
4357 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4359 g_free(body);
4362 static void
4363 sipe_send_election_set_rm(struct sipe_account_data *sip,
4364 struct sip_dialog *dialog)
4366 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4368 gchar *body = g_strdup_printf(
4369 "<?xml version=\"1.0\"?>\r\n"
4370 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4371 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4372 sip->username);
4374 send_sip_request(sip->gc, "INFO",
4375 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4377 g_free(body);
4380 static void
4381 sipe_session_close(struct sipe_account_data *sip,
4382 struct sip_session * session)
4384 if (session && session->focus_uri) {
4385 sipe_conf_immcu_closed(sip, session);
4386 conf_session_close(sip, session);
4389 if (session) {
4390 SIPE_DIALOG_FOREACH {
4391 /* @TODO slow down BYE message sending rate */
4392 /* @see single subscription code */
4393 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4394 } SIPE_DIALOG_FOREACH_END;
4396 sipe_session_remove(sip, session);
4400 static void
4401 sipe_session_close_all(struct sipe_account_data *sip)
4403 GSList *entry;
4404 while ((entry = sip->sessions) != NULL) {
4405 sipe_session_close(sip, entry->data);
4409 static void
4410 sipe_convo_closed(PurpleConnection * gc, const char *who)
4412 struct sipe_account_data *sip = gc->proto_data;
4414 purple_debug_info("sipe", "conversation with %s closed\n", who);
4415 sipe_session_close(sip, sipe_session_find_im(sip, who));
4418 static void
4419 sipe_chat_leave (PurpleConnection *gc, int id)
4421 struct sipe_account_data *sip = gc->proto_data;
4422 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4424 sipe_session_close(sip, session);
4427 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4428 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4430 struct sipe_account_data *sip = gc->proto_data;
4431 struct sip_session *session;
4432 struct sip_dialog *dialog;
4433 gchar *uri = sip_uri(who);
4435 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4437 session = sipe_session_find_or_add_im(sip, uri);
4438 dialog = sipe_dialog_find(session, uri);
4440 // Queue the message
4441 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
4443 if (dialog && !dialog->outgoing_invite) {
4444 sipe_im_process_queue(sip, session);
4445 } else if (!dialog || !dialog->outgoing_invite) {
4446 // Need to send the INVITE to get the outgoing dialog setup
4447 sipe_invite(sip, session, uri, what, NULL, FALSE);
4450 g_free(uri);
4451 return 1;
4454 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4455 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4457 struct sipe_account_data *sip = gc->proto_data;
4458 struct sip_session *session;
4460 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4462 session = sipe_session_find_chat_by_id(sip, id);
4464 // Queue the message
4465 if (session && session->dialogs) {
4466 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4467 g_strdup(what));
4468 sipe_im_process_queue(sip, session);
4469 } else if (sip) {
4470 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4471 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4473 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4474 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4476 if (sip->ocs2007) {
4477 struct sip_session *session = sipe_session_add_chat(sip);
4479 session->is_multiparty = FALSE;
4480 session->focus_uri = g_strdup(proto_chat_id);
4481 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4482 g_strdup(what));
4483 sipe_invite_conf_focus(sip, session);
4487 return 1;
4490 /* End IM Session (INVITE and MESSAGE methods) */
4492 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4494 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4495 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4496 gchar *from;
4497 struct sip_session *session;
4499 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4501 /* Call Control protocol */
4502 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4504 process_incoming_info_csta(sip, msg);
4505 return;
4508 from = parse_from(sipmsg_find_header(msg, "From"));
4509 session = sipe_session_find_chat_by_callid(sip, callid);
4510 if (!session) {
4511 session = sipe_session_find_im(sip, from);
4513 if (!session) {
4514 g_free(from);
4515 return;
4518 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4520 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4521 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4522 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4524 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4526 if (xn_request_rm) {
4527 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4528 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4529 gchar *body = g_strdup_printf(
4530 "<?xml version=\"1.0\"?>\r\n"
4531 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4532 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4533 sip->username,
4534 session->bid < bid ? "true" : "false");
4535 send_sip_response(sip->gc, msg, 200, "OK", body);
4536 g_free(body);
4537 } else if (xn_set_rm) {
4538 gchar *body;
4539 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4540 g_free(session->roster_manager);
4541 session->roster_manager = g_strdup(rm);
4543 body = g_strdup_printf(
4544 "<?xml version=\"1.0\"?>\r\n"
4545 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4546 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4547 sip->username);
4548 send_sip_response(sip->gc, msg, 200, "OK", body);
4549 g_free(body);
4551 xmlnode_free(xn_action);
4554 else
4556 /* looks like purple lacks typing notification for chat */
4557 if (!session->is_multiparty && !session->focus_uri) {
4558 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4559 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4560 "status");
4561 if (status && !strcmp(status, "type")) {
4562 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4563 } else if (status && !strcmp(status, "idle")) {
4564 serv_got_typing_stopped(sip->gc, from);
4566 xmlnode_free(xn_keyboard_activity);
4569 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4571 g_free(from);
4574 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4576 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4577 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4578 struct sip_session *session;
4579 struct sip_dialog *dialog;
4581 /* collect dialog identification
4582 * we need callid, ourtag and theirtag to unambiguously identify dialog
4584 /* take data before 'msg' will be modified by send_sip_response */
4585 dialog = g_new0(struct sip_dialog, 1);
4586 dialog->callid = g_strdup(callid);
4587 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4588 dialog->with = g_strdup(from);
4589 sipe_dialog_parse(dialog, msg, FALSE);
4591 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4593 session = sipe_session_find_chat_by_callid(sip, callid);
4594 if (!session) {
4595 session = sipe_session_find_im(sip, from);
4597 if (!session) {
4598 sipe_dialog_free(dialog);
4599 g_free(from);
4600 return;
4603 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4604 g_free(session->roster_manager);
4605 session->roster_manager = NULL;
4608 /* This what BYE is essentially for - terminating dialog */
4609 sipe_dialog_remove_3(session, dialog);
4610 sipe_dialog_free(dialog);
4611 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4612 sipe_conf_immcu_closed(sip, session);
4613 } else if (session->is_multiparty) {
4614 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4617 g_free(from);
4620 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4622 gchar *self = sip_uri_self(sip);
4623 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4624 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4625 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4626 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4627 struct sip_session *session;
4628 struct sip_dialog *dialog;
4630 session = sipe_session_find_chat_by_callid(sip, callid);
4631 dialog = sipe_dialog_find(session, from);
4633 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
4634 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4635 } else {
4636 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4638 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
4641 g_free(self);
4642 g_free(from);
4643 g_free(refer_to);
4644 g_free(referred_by);
4647 static unsigned int
4648 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4650 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4651 struct sip_session *session;
4652 struct sip_dialog *dialog;
4654 if (state == PURPLE_NOT_TYPING)
4655 return 0;
4657 session = sipe_session_find_im(sip, who);
4658 dialog = sipe_dialog_find(session, who);
4660 if (session && dialog && dialog->is_established) {
4661 send_sip_request(gc, "INFO", who, who,
4662 "Content-Type: application/xml\r\n",
4663 SIPE_SEND_TYPING, dialog, NULL);
4665 return SIPE_TYPING_SEND_TIMEOUT;
4668 static gboolean resend_timeout(struct sipe_account_data *sip)
4670 GSList *tmp = sip->transactions;
4671 time_t currtime = time(NULL);
4672 while (tmp) {
4673 struct transaction *trans = tmp->data;
4674 tmp = tmp->next;
4675 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4676 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4677 /* TODO 408 */
4678 } else {
4679 if ((currtime - trans->time > 2) && trans->retries == 0) {
4680 trans->retries++;
4681 sendout_sipmsg(sip, trans->msg);
4685 return TRUE;
4688 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4689 SIPE_UNUSED_PARAMETER void *unused)
4691 /* register again when security token expires */
4692 /* we have to start a new authentication as the security token
4693 * is almost expired by sending a not signed REGISTER message */
4694 purple_debug_info("sipe", "do a full reauthentication\n");
4695 sipe_auth_free(&sip->registrar);
4696 sipe_auth_free(&sip->proxy);
4697 sip->registerstatus = 0;
4698 do_register(sip);
4699 sip->reauthenticate_set = FALSE;
4702 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4704 gchar *from;
4705 gchar *contenttype;
4706 gboolean found = FALSE;
4708 from = parse_from(sipmsg_find_header(msg, "From"));
4710 if (!from) return;
4712 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4714 contenttype = sipmsg_find_header(msg, "Content-Type");
4715 if (!strncmp(contenttype, "text/plain", 10)
4716 || !strncmp(contenttype, "text/html", 9)
4717 || !strncmp(contenttype, "multipart/related", 17)
4718 || !strncmp(contenttype, "multipart/alternative", 21))
4720 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4721 gchar *html = get_html_message(contenttype, msg->body);
4723 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4724 if (!session) {
4725 session = sipe_session_find_im(sip, from);
4728 if (session && session->focus_uri) { /* a conference */
4729 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4730 gchar *sender = parse_from(tmp);
4731 g_free(tmp);
4732 serv_got_chat_in(sip->gc, session->chat_id, sender,
4733 PURPLE_MESSAGE_RECV, html, time(NULL));
4734 g_free(sender);
4735 } else if (session && session->is_multiparty) { /* a multiparty chat */
4736 serv_got_chat_in(sip->gc, session->chat_id, from,
4737 PURPLE_MESSAGE_RECV, html, time(NULL));
4738 } else {
4739 serv_got_im(sip->gc, from, html, 0, time(NULL));
4741 g_free(html);
4742 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4743 found = TRUE;
4745 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
4746 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4747 xmlnode *state;
4748 gchar *statedata;
4750 if (!isc) {
4751 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4752 return;
4755 state = xmlnode_get_child(isc, "state");
4757 if (!state) {
4758 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4759 xmlnode_free(isc);
4760 return;
4763 statedata = xmlnode_get_data(state);
4764 if (statedata) {
4765 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4766 else serv_got_typing_stopped(sip->gc, from);
4768 g_free(statedata);
4770 xmlnode_free(isc);
4771 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4772 found = TRUE;
4774 if (!found) {
4775 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4776 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4777 if (!session) {
4778 session = sipe_session_find_im(sip, from);
4780 if (session) {
4781 gchar *msg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4782 from);
4783 sipe_present_err(sip, session, msg);
4784 g_free(msg);
4787 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
4788 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
4790 g_free(from);
4793 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
4795 gchar *body;
4796 gchar *newTag;
4797 gchar *oldHeader;
4798 gchar *newHeader;
4799 gboolean is_multiparty = FALSE;
4800 gboolean is_triggered = FALSE;
4801 gboolean was_multiparty = TRUE;
4802 gboolean just_joined = FALSE;
4803 gchar *from;
4804 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4805 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4806 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4807 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4808 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4809 GSList *end_points = NULL;
4810 char *tmp = NULL;
4811 struct sip_session *session;
4813 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
4814 g_free(tmp);
4816 /* Invitation to join conference */
4817 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
4818 process_incoming_invite_conf(sip, msg);
4819 return;
4822 /* Only accept text invitations */
4823 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4824 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4825 return;
4828 // TODO There *must* be a better way to clean up the To header to add a tag...
4829 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4830 oldHeader = sipmsg_find_header(msg, "To");
4831 newTag = gentag();
4832 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4833 sipmsg_remove_header_now(msg, "To");
4834 sipmsg_add_header_now(msg, "To", newHeader);
4835 g_free(newHeader);
4837 if (end_points_hdr) {
4838 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4840 if (g_slist_length(end_points) > 2) {
4841 is_multiparty = TRUE;
4844 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4845 is_triggered = TRUE;
4846 is_multiparty = TRUE;
4849 session = sipe_session_find_chat_by_callid(sip, callid);
4850 /* Convert to multiparty */
4851 if (session && is_multiparty && !session->is_multiparty) {
4852 g_free(session->with);
4853 session->with = NULL;
4854 was_multiparty = FALSE;
4855 session->is_multiparty = TRUE;
4856 session->chat_id = rand();
4859 if (!session && is_multiparty) {
4860 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
4862 /* IM session */
4863 from = parse_from(sipmsg_find_header(msg, "From"));
4864 if (!session) {
4865 session = sipe_session_find_or_add_im(sip, from);
4868 if (session) {
4869 g_free(session->callid);
4870 session->callid = g_strdup(callid);
4872 session->is_multiparty = is_multiparty;
4873 if (roster_manager) {
4874 session->roster_manager = g_strdup(roster_manager);
4878 if (is_multiparty && end_points) {
4879 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
4880 GSList *entry = end_points;
4881 while (entry) {
4882 struct sip_dialog *dialog;
4883 struct sipendpoint *end_point = entry->data;
4884 entry = entry->next;
4886 if (!g_strcasecmp(from, end_point->contact) ||
4887 !g_strcasecmp(to, end_point->contact))
4888 continue;
4890 dialog = sipe_dialog_find(session, end_point->contact);
4891 if (dialog) {
4892 g_free(dialog->theirepid);
4893 dialog->theirepid = end_point->epid;
4894 end_point->epid = NULL;
4895 } else {
4896 dialog = sipe_dialog_add(session);
4898 dialog->callid = g_strdup(session->callid);
4899 dialog->with = end_point->contact;
4900 end_point->contact = NULL;
4901 dialog->theirepid = end_point->epid;
4902 end_point->epid = NULL;
4904 just_joined = TRUE;
4906 /* send triggered INVITE */
4907 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
4910 g_free(to);
4913 if (end_points) {
4914 GSList *entry = end_points;
4915 while (entry) {
4916 struct sipendpoint *end_point = entry->data;
4917 entry = entry->next;
4918 g_free(end_point->contact);
4919 g_free(end_point->epid);
4920 g_free(end_point);
4922 g_slist_free(end_points);
4925 if (session) {
4926 struct sip_dialog *dialog = sipe_dialog_find(session, from);
4927 if (dialog) {
4928 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
4929 } else {
4930 dialog = sipe_dialog_add(session);
4932 dialog->callid = g_strdup(session->callid);
4933 dialog->with = g_strdup(from);
4934 sipe_dialog_parse(dialog, msg, FALSE);
4936 if (!dialog->ourtag) {
4937 dialog->ourtag = newTag;
4938 newTag = NULL;
4941 just_joined = TRUE;
4943 } else {
4944 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
4946 g_free(newTag);
4948 if (is_multiparty && !session->conv) {
4949 gchar *chat_title = sipe_chat_get_name(callid);
4950 gchar *self = sip_uri_self(sip);
4951 /* create prpl chat */
4952 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
4953 session->chat_title = g_strdup(chat_title);
4954 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
4955 /* add self */
4956 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4957 self, NULL,
4958 PURPLE_CBFLAGS_NONE, FALSE);
4959 g_free(chat_title);
4960 g_free(self);
4963 if (is_multiparty && !was_multiparty) {
4964 /* add current IM counterparty to chat */
4965 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4966 sipe_dialog_first(session)->with, NULL,
4967 PURPLE_CBFLAGS_NONE, FALSE);
4970 /* add inviting party to chat */
4971 if (just_joined && session->conv) {
4972 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4973 from, NULL,
4974 PURPLE_CBFLAGS_NONE, TRUE);
4977 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4979 /* This used only in 2005 official client, not 2007 or Reuters.
4980 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4981 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4983 if (is_multiparty) {
4984 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4985 gchar *ms_text_format = sipmsg_find_header(msg, "ms-text-format");
4986 if (ms_text_format) {
4987 if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html")) {
4989 gchar *html = get_html_message(ms_text_format, NULL);
4990 if (html) {
4991 if (is_multiparty) {
4992 serv_got_chat_in(sip->gc, session->chat_id, from,
4993 PURPLE_MESSAGE_RECV, html, time(NULL));
4994 } else {
4995 serv_got_im(sip->gc, from, html, 0, time(NULL));
4997 g_free(html);
4998 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5005 g_free(from);
5007 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5008 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5009 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5011 body = g_strdup_printf(
5012 "v=0\r\n"
5013 "o=- 0 0 IN IP4 %s\r\n"
5014 "s=session\r\n"
5015 "c=IN IP4 %s\r\n"
5016 "t=0 0\r\n"
5017 "m=%s %d sip sip:%s\r\n"
5018 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5019 purple_network_get_my_ip(-1),
5020 purple_network_get_my_ip(-1),
5021 sip->ocs2007 ? "message" : "x-ms-message",
5022 sip->realport,
5023 sip->username);
5024 send_sip_response(sip->gc, msg, 200, "OK", body);
5025 g_free(body);
5028 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5030 gchar *body;
5032 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5033 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5034 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5036 body = g_strdup_printf(
5037 "v=0\r\n"
5038 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5039 "s=session\r\n"
5040 "c=IN IP4 0.0.0.0\r\n"
5041 "t=0 0\r\n"
5042 "m=%s %d sip sip:%s\r\n"
5043 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5044 sip->ocs2007 ? "message" : "x-ms-message",
5045 sip->realport,
5046 sip->username);
5047 send_sip_response(sip->gc, msg, 200, "OK", body);
5048 g_free(body);
5051 static void sipe_connection_cleanup(struct sipe_account_data *);
5052 static void create_connection(struct sipe_account_data *, gchar *, int);
5054 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5055 SIPE_UNUSED_PARAMETER struct transaction *trans)
5057 gchar *tmp;
5058 const gchar *expires_header;
5059 int expires, i;
5060 GSList *hdr = msg->headers;
5061 struct siphdrelement *elem;
5063 expires_header = sipmsg_find_header(msg, "Expires");
5064 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5065 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5067 switch (msg->response) {
5068 case 200:
5069 if (expires == 0) {
5070 sip->registerstatus = 0;
5071 } else {
5072 gchar *contact_hdr = NULL;
5073 gchar *gruu = NULL;
5074 gchar *epid;
5075 gchar *uuid;
5076 gchar *timeout;
5077 gchar *server_hdr = sipmsg_find_header(msg, "Server");
5079 if (!sip->reregister_set) {
5080 gchar *action_name = g_strdup_printf("<%s>", "registration");
5081 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5082 g_free(action_name);
5083 sip->reregister_set = TRUE;
5086 sip->registerstatus = 3;
5088 if (server_hdr && !sip->server_version) {
5089 sip->server_version = g_strdup(server_hdr);
5090 g_free(default_ua);
5091 default_ua = NULL;
5094 #ifdef USE_KERBEROS
5095 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5096 #endif
5097 tmp = sipmsg_find_auth_header(msg, "NTLM");
5098 #ifdef USE_KERBEROS
5099 } else {
5100 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5102 #endif
5103 if (tmp) {
5104 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5105 fill_auth(tmp, &sip->registrar);
5108 if (!sip->reauthenticate_set) {
5109 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5110 guint reauth_timeout;
5111 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5112 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5113 reauth_timeout = sip->registrar.expires - 300;
5114 } else {
5115 /* NTLM: we have to reauthenticate as our security token expires
5116 after eight hours (be five minutes early) */
5117 reauth_timeout = (8 * 3600) - 300;
5119 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5120 g_free(action_name);
5121 sip->reauthenticate_set = TRUE;
5124 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5126 epid = get_epid(sip);
5127 uuid = generateUUIDfromEPID(epid);
5128 g_free(epid);
5130 // There can be multiple Contact headers (one per location where the user is logged in) so
5131 // make sure to only get the one for this uuid
5132 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5133 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5134 if (valid_contact) {
5135 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5136 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5137 g_free(valid_contact);
5138 break;
5139 } else {
5140 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5143 g_free(uuid);
5145 g_free(sip->contact);
5146 if(gruu) {
5147 sip->contact = g_strdup_printf("<%s>", gruu);
5148 g_free(gruu);
5149 } else {
5150 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5151 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);
5153 sip->ocs2007 = FALSE;
5154 sip->batched_support = FALSE;
5156 while(hdr)
5158 elem = hdr->data;
5159 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5160 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5161 /* We interpret this as OCS2007+ indicator */
5162 sip->ocs2007 = TRUE;
5163 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5165 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5166 sip->batched_support = TRUE;
5167 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5170 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5171 gchar **caps = g_strsplit(elem->value,",",0);
5172 i = 0;
5173 while (caps[i]) {
5174 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5175 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5176 i++;
5178 g_strfreev(caps);
5180 hdr = g_slist_next(hdr);
5183 /* rejoin open chats to be able to use them by continue to send messages */
5184 purple_conversation_foreach(sipe_rejoin_chat);
5186 /* subscriptions */
5187 if (!sip->subscribed) { //do it just once, not every re-register
5189 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5190 (GCompareFunc)g_ascii_strcasecmp)) {
5191 sipe_subscribe_roaming_contacts(sip);
5194 /* For 2007+ it does not make sence to subscribe to:
5195 * vnd-microsoft-roaming-ACL
5196 * vnd-microsoft-provisioning (not v2)
5197 * presence.wpending
5198 * These are for backward compatibility.
5200 if (sip->ocs2007)
5202 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5203 (GCompareFunc)g_ascii_strcasecmp)) {
5204 sipe_subscribe_roaming_self(sip);
5206 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5207 (GCompareFunc)g_ascii_strcasecmp)) {
5208 sipe_subscribe_roaming_provisioning_v2(sip);
5211 /* For 2005- servers */
5212 else
5214 //sipe_options_request(sip, sip->sipdomain);
5216 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5217 (GCompareFunc)g_ascii_strcasecmp)) {
5218 sipe_subscribe_roaming_acl(sip);
5220 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5221 (GCompareFunc)g_ascii_strcasecmp)) {
5222 sipe_subscribe_roaming_provisioning(sip);
5224 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5225 (GCompareFunc)g_ascii_strcasecmp)) {
5226 sipe_subscribe_presence_wpending(sip, msg);
5229 /* For 2007+ we publish our initial statuses and calendar data only after
5230 * received our existing publications in sipe_process_roaming_self()
5231 * Only in this case we know versions of current publications made
5232 * on our behalf.
5234 /* For 2005- we publish our initial statuses only after
5235 * received our existing UserInfo data in response to
5236 * self subscription.
5237 * Only in this case we won't override existing UserInfo data
5238 * set earlier or by other client on our behalf.
5242 sip->subscribed = TRUE;
5245 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5246 "timeout=", ";", NULL);
5247 if (timeout != NULL) {
5248 sscanf(timeout, "%u", &sip->keepalive_timeout);
5249 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5250 sip->keepalive_timeout);
5251 g_free(timeout);
5254 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5256 break;
5257 case 301:
5259 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5261 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5262 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5263 gchar **tmp;
5264 gchar *hostname;
5265 int port = 0;
5266 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5267 int i = 1;
5269 tmp = g_strsplit(parts[0], ":", 0);
5270 hostname = g_strdup(tmp[0]);
5271 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5272 g_strfreev(tmp);
5274 while (parts[i]) {
5275 tmp = g_strsplit(parts[i], "=", 0);
5276 if (tmp[1]) {
5277 if (g_strcasecmp("transport", tmp[0]) == 0) {
5278 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5279 transport = SIPE_TRANSPORT_TCP;
5280 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5281 transport = SIPE_TRANSPORT_UDP;
5285 g_strfreev(tmp);
5286 i++;
5288 g_strfreev(parts);
5290 /* Close old connection */
5291 sipe_connection_cleanup(sip);
5293 /* Create new connection */
5294 sip->transport = transport;
5295 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5296 hostname, port, TRANSPORT_DESCRIPTOR);
5297 create_connection(sip, hostname, port);
5299 g_free(redirect);
5301 break;
5302 case 401:
5303 if (sip->registerstatus != 2) {
5304 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5305 if (sip->registrar.retries > 3) {
5306 sip->gc->wants_to_die = TRUE;
5307 purple_connection_error(sip->gc, _("Wrong password"));
5308 return TRUE;
5310 #ifdef USE_KERBEROS
5311 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5312 #endif
5313 tmp = sipmsg_find_auth_header(msg, "NTLM");
5314 #ifdef USE_KERBEROS
5315 } else {
5316 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5318 #endif
5319 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5320 fill_auth(tmp, &sip->registrar);
5321 sip->registerstatus = 2;
5322 if (sip->account->disconnecting) {
5323 do_register_exp(sip, 0);
5324 } else {
5325 do_register(sip);
5328 break;
5329 case 403:
5331 gchar *warning = sipmsg_find_header(msg, "Warning");
5332 gchar **reason = NULL;
5333 if (warning != NULL) {
5334 /* Example header:
5335 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5337 reason = g_strsplit(warning, "\"", 0);
5339 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5340 (reason && reason[1]) ? reason[1] : _("no reason given"));
5341 g_strfreev(reason);
5343 sip->gc->wants_to_die = TRUE;
5344 purple_connection_error(sip->gc, warning);
5345 g_free(warning);
5346 return TRUE;
5348 break;
5349 case 404:
5351 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5352 gchar *reason = NULL;
5353 if (warning != NULL) {
5354 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5356 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5357 warning ? (reason ? reason : _("no reason given")) :
5358 _("SIP is either not enabled for the destination URI or it does not exist"));
5359 g_free(reason);
5361 sip->gc->wants_to_die = TRUE;
5362 purple_connection_error(sip->gc, warning);
5363 g_free(warning);
5364 return TRUE;
5366 break;
5367 case 503:
5368 case 504: /* Server time-out */
5370 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5371 gchar *reason = NULL;
5372 if (warning != NULL) {
5373 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5375 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : "<a href=\"http://www.reuters.com\">http://www.reuters.com</a>"/*_("no reason given")*/);
5376 g_free(reason);
5378 sip->gc->wants_to_die = TRUE;
5379 purple_connection_error(sip->gc, warning);
5380 g_free(warning);
5381 return TRUE;
5383 break;
5385 return TRUE;
5389 * Returns 2005-style activity and Availability.
5391 * @param status Sipe statis id.
5393 static void
5394 sipe_get_act_avail_by_status_2005(const char *status,
5395 int *activity,
5396 int *availability)
5398 int avail = 300; /* online */
5399 int act = 400; /* Available */
5401 if (!strcmp(status, SIPE_STATUS_ID_AWAY)) {
5402 act = 100;
5403 //} else if (!strcmp(status, SIPE_STATUS_ID_LUNCH)) {
5404 // act = 150;
5405 } else if (!strcmp(status, SIPE_STATUS_ID_BRB)) {
5406 act = 300;
5407 } else if (!strcmp(status, SIPE_STATUS_ID_AVAILABLE)) {
5408 act = 400;
5409 //} else if (!strcmp(status, SIPE_STATUS_ID_ON_PHONE)) {
5410 // act = 500;
5411 } else if (!strcmp(status, SIPE_STATUS_ID_BUSY) ||
5412 !strcmp(status, SIPE_STATUS_ID_DND)) {
5413 act = 600;
5414 } else if (!strcmp(status, SIPE_STATUS_ID_INVISIBLE) ||
5415 !strcmp(status, SIPE_STATUS_ID_OFFLINE)) {
5416 avail = 0; /* offline */
5417 act = 100;
5418 } else {
5419 act = 400; /* Available */
5422 if (activity) *activity = act;
5423 if (availability) *availability = avail;
5427 * [MS-SIP] 2.2.1
5429 * @param activity 2005 aggregated activity. Ex.: 600
5430 * @param availablity 2005 aggregated availablity. Ex.: 300
5432 static const char *
5433 sipe_get_status_by_act_avail_2005(const int activity,
5434 const int availablity,
5435 char **activity_desc)
5437 const char *status_id = NULL;
5438 const char *act = NULL;
5440 if (activity < 150) {
5441 status_id = SIPE_STATUS_ID_AWAY;
5442 } else if (activity < 200) {
5443 //status_id = SIPE_STATUS_ID_LUNCH;
5444 status_id = SIPE_STATUS_ID_AWAY;
5445 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5446 } else if (activity < 300) {
5447 //status_id = SIPE_STATUS_ID_IDLE;
5448 status_id = SIPE_STATUS_ID_AWAY;
5449 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5450 } else if (activity < 400) {
5451 status_id = SIPE_STATUS_ID_BRB;
5452 } else if (activity < 500) {
5453 status_id = SIPE_STATUS_ID_AVAILABLE;
5454 } else if (activity < 600) {
5455 //status_id = SIPE_STATUS_ID_ON_PHONE;
5456 status_id = SIPE_STATUS_ID_BUSY;
5457 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5458 } else if (activity < 700) {
5459 status_id = SIPE_STATUS_ID_BUSY;
5460 } else if (activity < 800) {
5461 status_id = SIPE_STATUS_ID_AWAY;
5462 } else {
5463 status_id = SIPE_STATUS_ID_AVAILABLE;
5466 if (availablity < 100)
5467 status_id = SIPE_STATUS_ID_OFFLINE;
5469 if (activity_desc && act) {
5470 g_free(*activity_desc);
5471 *activity_desc = g_strdup(act);
5474 return status_id;
5478 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5480 static const char*
5481 sipe_get_status_by_availability(int avail,
5482 char** activity_desc)
5484 const char *status;
5485 const char *act = NULL;
5487 if (avail < 3000) {
5488 status = SIPE_STATUS_ID_OFFLINE;
5489 } else if (avail < 4500) {
5490 status = SIPE_STATUS_ID_AVAILABLE;
5491 } else if (avail < 6000) {
5492 //status = SIPE_STATUS_ID_IDLE;
5493 status = SIPE_STATUS_ID_AVAILABLE;
5494 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5495 } else if (avail < 7500) {
5496 status = SIPE_STATUS_ID_BUSY;
5497 } else if (avail < 9000) {
5498 //status = SIPE_STATUS_ID_BUSYIDLE;
5499 status = SIPE_STATUS_ID_BUSY;
5500 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5501 } else if (avail < 12000) {
5502 status = SIPE_STATUS_ID_DND;
5503 } else if (avail < 15000) {
5504 status = SIPE_STATUS_ID_BRB;
5505 } else if (avail < 18000) {
5506 status = SIPE_STATUS_ID_AWAY;
5507 } else {
5508 status = SIPE_STATUS_ID_OFFLINE;
5511 if (activity_desc && act) {
5512 g_free(*activity_desc);
5513 *activity_desc = g_strdup(act);
5516 return status;
5520 * Returns 2007-style availability value
5522 * @param sipe_status_id (in)
5523 * @param activity_token (out) Must be g_free()'d after use if consumed.
5525 static int
5526 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5528 int availability;
5529 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5531 if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5532 availability = 15500;
5533 if (!activity_token || !(*activity_token)) {
5534 activity = SIPE_ACTIVITY_AWAY;
5536 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5537 availability = 12500;
5538 activity = SIPE_ACTIVITY_BRB;
5539 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_DND)) {
5540 availability = 9500;
5541 activity = SIPE_ACTIVITY_DND;
5542 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5543 availability = 6500;
5544 if (!activity_token || !(*activity_token)) {
5545 activity = SIPE_ACTIVITY_BUSY;
5547 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5548 availability = 3500;
5549 activity = SIPE_ACTIVITY_ONLINE;
5550 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5551 availability = 0;
5552 } else {
5553 // Offline or invisible
5554 availability = 18500;
5555 activity = SIPE_ACTIVITY_OFFLINE;
5558 if (activity_token) {
5559 *activity_token = g_strdup(sipe_activity_map[activity].token);
5561 return availability;
5564 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5566 const char *uri;
5567 xmlnode *xn_categories;
5568 xmlnode *xn_category;
5569 xmlnode *xn_node;
5570 const char *status = NULL;
5571 gboolean do_update_status = FALSE;
5572 gboolean has_note_cleaned = FALSE;
5573 gboolean has_free_busy_cleaned = FALSE;
5575 xn_categories = xmlnode_from_str(data, len);
5576 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5578 for (xn_category = xmlnode_get_child(xn_categories, "category");
5579 xn_category ;
5580 xn_category = xmlnode_get_next_twin(xn_category) )
5582 const char *tmp;
5583 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5584 time_t publish_time = (tmp = xmlnode_get_attrib(xn_category, "publishTime")) ?
5585 sipe_utils_str_to_time(tmp) : 0;
5587 /* contactCard */
5588 if (!strcmp(attrVar, "contactCard"))
5590 xmlnode *node;
5591 /* identity - Display Name and email */
5592 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5593 if (node) {
5594 char* display_name = xmlnode_get_data(
5595 xmlnode_get_descendant(node, "name", "displayName", NULL));
5596 char* email = xmlnode_get_data(
5597 xmlnode_get_child(node, "email"));
5599 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5600 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5602 g_free(display_name);
5603 g_free(email);
5605 /* company */
5606 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5607 if (node) {
5608 char* company = xmlnode_get_data(node);
5609 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5610 g_free(company);
5612 /* department */
5613 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5614 if (node) {
5615 char* department = xmlnode_get_data(node);
5616 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5617 g_free(department);
5619 /* title */
5620 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5621 if (node) {
5622 char* title = xmlnode_get_data(node);
5623 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5624 g_free(title);
5626 /* office */
5627 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5628 if (node) {
5629 char* office = xmlnode_get_data(node);
5630 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5631 g_free(office);
5633 /* site (url) */
5634 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5635 if (node) {
5636 char* site = xmlnode_get_data(node);
5637 sipe_update_user_info(sip, uri, SITE_PROP, site);
5638 g_free(site);
5640 /* phone */
5641 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5642 node;
5643 node = xmlnode_get_next_twin(node))
5645 const char *phone_type = xmlnode_get_attrib(node, "type");
5646 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5647 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5649 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5651 g_free(phone);
5652 g_free(phone_display_string);
5654 /* address */
5655 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5656 node;
5657 node = xmlnode_get_next_twin(node))
5659 if (!strcmp(xmlnode_get_attrib(node, "type"), "work")) {
5660 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5661 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5662 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5663 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5664 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5666 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5667 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5668 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5669 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5670 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5672 g_free(street);
5673 g_free(city);
5674 g_free(state);
5675 g_free(zipcode);
5676 g_free(country_code);
5678 break;
5682 /* note */
5683 else if (!strcmp(attrVar, "note"))
5685 if (uri) {
5686 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5688 if (!has_note_cleaned) {
5689 has_note_cleaned = TRUE;
5691 g_free(sbuddy->note);
5692 sbuddy->note = NULL;
5693 sbuddy->is_oof_note = FALSE;
5694 sbuddy->note_since = publish_time;
5696 do_update_status = TRUE;
5698 if (sbuddy && (publish_time >= sbuddy->note_since)) {
5699 /* clean up in case no 'note' element is supplied
5700 * which indicate note removal in client
5702 g_free(sbuddy->note);
5703 sbuddy->note = NULL;
5704 sbuddy->is_oof_note = FALSE;
5705 sbuddy->note_since = publish_time;
5707 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
5708 if (xn_node) {
5709 char *tmp;
5710 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
5711 g_free(tmp);
5712 sbuddy->is_oof_note = !strcmp(xmlnode_get_attrib(xn_node, "type"), "OOF");
5713 sbuddy->note_since = publish_time;
5715 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
5716 uri, sbuddy->note ? sbuddy->note : "");
5718 /* to trigger UI refresh in case no status info is supplied in this update */
5719 do_update_status = TRUE;
5723 /* state */
5724 else if(!strcmp(attrVar, "state"))
5726 char *data;
5727 int availability;
5728 xmlnode *xn_availability;
5729 xmlnode *xn_activity;
5730 xmlnode *xn_meeting_subject;
5731 xmlnode *xn_meeting_location;
5732 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5734 xn_node = xmlnode_get_child(xn_category, "state");
5735 if (!xn_node) continue;
5736 xn_availability = xmlnode_get_child(xn_node, "availability");
5737 if (!xn_availability) continue;
5738 xn_activity = xmlnode_get_child(xn_node, "activity");
5739 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
5740 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
5742 data = xmlnode_get_data(xn_availability);
5743 availability = atoi(data);
5744 g_free(data);
5746 /* activity, meeting_subject, meeting_location */
5747 if (sbuddy) {
5748 char *tmp = NULL;
5750 /* activity */
5751 g_free(sbuddy->activity);
5752 sbuddy->activity = NULL;
5753 if (xn_activity) {
5754 const char *token = xmlnode_get_attrib(xn_activity, "token");
5755 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
5757 /* from token */
5758 if (!is_empty(token)) {
5759 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
5761 /* from custom element */
5762 if (xn_custom) {
5763 char *custom = xmlnode_get_data(xn_custom);
5765 if (!is_empty(custom)) {
5766 sbuddy->activity = custom;
5767 custom = NULL;
5769 g_free(custom);
5772 /* meeting_subject */
5773 g_free(sbuddy->meeting_subject);
5774 sbuddy->meeting_subject = NULL;
5775 if (xn_meeting_subject) {
5776 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
5778 if (!is_empty(meeting_subject)) {
5779 sbuddy->meeting_subject = meeting_subject;
5780 meeting_subject = NULL;
5782 g_free(meeting_subject);
5784 /* meeting_location */
5785 g_free(sbuddy->meeting_location);
5786 sbuddy->meeting_location = NULL;
5787 if (xn_meeting_location) {
5788 char *meeting_location = xmlnode_get_data(xn_meeting_location);
5790 if (!is_empty(meeting_location)) {
5791 sbuddy->meeting_location = meeting_location;
5792 meeting_location = NULL;
5794 g_free(meeting_location);
5797 status = sipe_get_status_by_availability(availability, &tmp);
5798 if (sbuddy->activity && tmp) {
5799 char *tmp2 = sbuddy->activity;
5801 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
5802 g_free(tmp);
5803 g_free(tmp2);
5804 } else if (tmp) {
5805 sbuddy->activity = tmp;
5809 do_update_status = TRUE;
5811 /* calendarData */
5812 else if(!strcmp(attrVar, "calendarData"))
5814 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5815 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
5816 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
5818 if (sbuddy && xn_free_busy) {
5819 if (!has_free_busy_cleaned) {
5820 has_free_busy_cleaned = TRUE;
5822 g_free(sbuddy->cal_start_time);
5823 sbuddy->cal_start_time = NULL;
5825 g_free(sbuddy->cal_free_busy_base64);
5826 sbuddy->cal_free_busy_base64 = NULL;
5828 g_free(sbuddy->cal_free_busy);
5829 sbuddy->cal_free_busy = NULL;
5831 sbuddy->cal_free_busy_published = publish_time;
5834 if (publish_time >= sbuddy->cal_free_busy_published) {
5835 g_free(sbuddy->cal_start_time);
5836 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
5838 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
5839 15 : 0;
5841 g_free(sbuddy->cal_free_busy_base64);
5842 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
5844 g_free(sbuddy->cal_free_busy);
5845 sbuddy->cal_free_busy = NULL;
5847 sbuddy->cal_free_busy_published = publish_time;
5849 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);
5853 if (sbuddy && xn_working_hours) {
5854 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
5859 if (do_update_status) {
5860 if (!status) { /* no status category in this update, using contact's current status */
5861 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
5862 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
5863 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
5864 status = purple_status_get_id(pstatus);
5867 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
5868 sipe_got_user_status(sip, uri, status);
5871 xmlnode_free(xn_categories);
5874 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
5876 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5877 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
5878 payload->host = g_strdup(host);
5879 payload->buddies = server;
5880 sipe_subscribe_presence_batched_routed(sip, payload);
5881 sipe_subscribe_presence_batched_routed_free(payload);
5884 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
5886 xmlnode *xn_list;
5887 xmlnode *xn_resource;
5888 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
5889 g_free, NULL);
5890 GSList *server;
5891 gchar *host;
5893 xn_list = xmlnode_from_str(data, len);
5895 for (xn_resource = xmlnode_get_child(xn_list, "resource");
5896 xn_resource;
5897 xn_resource = xmlnode_get_next_twin(xn_resource) )
5899 const char *uri, *state;
5900 xmlnode *xn_instance;
5902 xn_instance = xmlnode_get_child(xn_resource, "instance");
5903 if (!xn_instance) continue;
5905 uri = xmlnode_get_attrib(xn_resource, "uri");
5906 state = xmlnode_get_attrib(xn_instance, "state");
5907 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
5909 if (strstr(state, "resubscribe")) {
5910 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
5912 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
5913 gchar *user = g_strdup(uri);
5914 host = g_strdup(poolFqdn);
5915 server = g_hash_table_lookup(servers, host);
5916 server = g_slist_append(server, user);
5917 g_hash_table_insert(servers, host, server);
5918 } else {
5919 sipe_subscribe_presence_single(sip, (void *) uri);
5924 /* Send out any deferred poolFqdn subscriptions */
5925 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
5926 g_hash_table_destroy(servers);
5928 xmlnode_free(xn_list);
5931 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
5933 gchar *uri;
5934 gchar *getbasic;
5935 gchar *activity = NULL;
5936 xmlnode *pidf;
5937 xmlnode *basicstatus = NULL, *tuple, *status;
5938 gboolean isonline = FALSE;
5939 xmlnode *display_name_node;
5941 pidf = xmlnode_from_str(data, len);
5942 if (!pidf) {
5943 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
5944 return;
5947 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
5949 if ((tuple = xmlnode_get_child(pidf, "tuple")))
5951 if ((status = xmlnode_get_child(tuple, "status"))) {
5952 basicstatus = xmlnode_get_child(status, "basic");
5956 if (!basicstatus) {
5957 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
5958 xmlnode_free(pidf);
5959 return;
5962 getbasic = xmlnode_get_data(basicstatus);
5963 if (!getbasic) {
5964 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
5965 xmlnode_free(pidf);
5966 return;
5969 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
5970 if (strstr(getbasic, "open")) {
5971 isonline = TRUE;
5973 g_free(getbasic);
5975 display_name_node = xmlnode_get_child(pidf, "display-name");
5976 if (display_name_node) {
5977 char * display_name = xmlnode_get_data(display_name_node);
5979 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5980 g_free(display_name);
5983 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
5984 if ((status = xmlnode_get_child(tuple, "status"))) {
5985 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
5986 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
5987 activity = xmlnode_get_data(basicstatus);
5988 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
5994 if (isonline) {
5995 const gchar * status_id = NULL;
5996 if (activity) {
5997 if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
5998 status_id = SIPE_STATUS_ID_BUSY;
5999 } else if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6000 status_id = SIPE_STATUS_ID_AWAY;
6004 if (!status_id) {
6005 status_id = SIPE_STATUS_ID_AVAILABLE;
6008 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6009 sipe_got_user_status(sip, uri, status_id);
6010 } else {
6011 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6014 g_free(activity);
6015 g_free(uri);
6016 xmlnode_free(pidf);
6019 /** 2005 */
6020 static void
6021 sipe_user_info_has_updated(struct sipe_account_data *sip,
6022 xmlnode *xn_userinfo)
6024 if (sip->user_info) {
6025 xmlnode_free(sip->user_info);
6027 sip->user_info = xmlnode_copy(xn_userinfo);
6029 /* Publish initial state if not yet.
6030 * Assuming this happens on initial responce to self subscription
6031 * so we've already updated our UserInfo.
6033 if (!sip->initial_state_published) {
6034 send_presence_soap(sip, FALSE);
6035 /* dalayed run */
6036 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6040 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6042 char *activity = NULL;
6043 const char *epid;
6044 const char *status_id = NULL;
6045 const char *name;
6046 char *uri;
6047 char *self_uri = sip_uri_self(sip);
6048 int avl;
6049 int act;
6050 const char *device_name = NULL;
6051 const char *cal_start_time = NULL;
6052 const char *cal_granularity = NULL;
6053 char *cal_free_busy_base64 = NULL;
6054 struct sipe_buddy *sbuddy;
6055 xmlnode *node;
6056 xmlnode *xn_presentity;
6057 xmlnode *xn_availability;
6058 xmlnode *xn_activity;
6059 xmlnode *xn_display_name;
6060 xmlnode *xn_email;
6061 xmlnode *xn_phone_number;
6062 xmlnode *xn_userinfo;
6063 xmlnode *xn_note;
6064 xmlnode *xn_oof;
6065 xmlnode *xn_state;
6066 xmlnode *xn_contact;
6067 char *note;
6068 char *free_activity;
6069 int user_avail;
6070 const char *user_avail_nil;
6071 int res_avail;
6072 time_t user_avail_since = 0;
6073 time_t activity_since = 0;
6075 /* fix for Reuters environment on Linux */
6076 if (data && strstr(data, "encoding=\"utf-16\"")) {
6077 char *tmp_data;
6078 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6079 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6080 g_free(tmp_data);
6081 } else {
6082 xn_presentity = xmlnode_from_str(data, len);
6085 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6086 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6087 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6088 xn_email = xmlnode_get_child(xn_presentity, "email");
6089 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6090 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6091 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6092 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6093 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6094 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6095 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6096 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6097 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6098 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6100 if (user_avail_nil && !strcmp(user_avail_nil, "true")) { /* null-ed */
6101 user_avail = 0;
6102 user_avail_since = 0;
6105 free_activity = NULL;
6107 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6108 uri = sip_uri_from_name(name);
6109 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6110 epid = xmlnode_get_attrib(xn_availability, "epid");
6111 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6113 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6114 res_avail = sipe_get_availability_by_status(status_id, NULL);
6115 if (user_avail > res_avail) {
6116 res_avail = user_avail;
6117 status_id = sipe_get_status_by_availability(user_avail, NULL);
6120 if (xn_display_name) {
6121 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6122 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6123 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6124 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6125 char *tel_uri = sip_to_tel_uri(phone_number);
6127 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6128 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6129 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6130 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6132 g_free(tel_uri);
6133 g_free(phone_label);
6134 g_free(phone_number);
6135 g_free(email);
6136 g_free(display_name);
6139 if (xn_contact) {
6140 /* tel */
6141 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6143 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6144 const char *phone_type = xmlnode_get_attrib(node, "type");
6145 char* phone = xmlnode_get_data(node);
6147 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6149 g_free(phone);
6153 /* devicePresence */
6154 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6155 xmlnode *xn_device_name;
6156 xmlnode *xn_calendar_info;
6157 xmlnode *xn_state;
6158 char *state;
6160 /* deviceName */
6161 if (!strcmp(xmlnode_get_attrib(node, "epid"), epid)) {
6162 xn_device_name = xmlnode_get_child(node, "deviceName");
6163 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6166 /* calendarInfo */
6167 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6168 if (xn_calendar_info) {
6169 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6171 if (cal_start_time) {
6172 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6173 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6175 if (cal_start_time_t_tmp > cal_start_time_t) {
6176 cal_start_time = cal_start_time_tmp;
6177 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6178 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6180 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);
6182 } else {
6183 cal_start_time = cal_start_time_tmp;
6184 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6185 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6187 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);
6191 /* state */
6192 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6193 if (xn_state) {
6194 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6195 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6197 state = xmlnode_get_data(xn_state);
6198 if (dev_avail_since > user_avail_since &&
6199 dev_avail >= res_avail)
6201 res_avail = dev_avail;
6202 if (!is_empty(state))
6204 if (!strcmp(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6205 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6206 } else if (!strcmp(state, "presenting")) {
6207 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6208 } else {
6209 activity = state;
6211 activity_since = dev_avail_since;
6213 status_id = sipe_get_status_by_availability(res_avail, &activity);
6215 g_free(state);
6219 /* oof */
6220 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6221 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6222 activity_since = 0;
6225 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6226 if (sbuddy)
6228 g_free(sbuddy->activity);
6229 sbuddy->activity = activity;
6231 sbuddy->activity_since = activity_since;
6233 sbuddy->user_avail = user_avail;
6234 sbuddy->user_avail_since = user_avail_since;
6236 g_free(sbuddy->note);
6237 sbuddy->note = NULL;
6238 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6240 sbuddy->is_oof_note = (xn_oof != NULL);
6242 g_free(sbuddy->device_name);
6243 sbuddy->device_name = NULL;
6244 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6246 if (!is_empty(cal_free_busy_base64)) {
6247 g_free(sbuddy->cal_start_time);
6248 sbuddy->cal_start_time = g_strdup(cal_start_time);
6250 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6252 g_free(sbuddy->cal_free_busy_base64);
6253 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6255 g_free(sbuddy->cal_free_busy);
6256 sbuddy->cal_free_busy = NULL;
6259 sbuddy->last_non_cal_status_id = status_id;
6260 g_free(sbuddy->last_non_cal_activity);
6261 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6263 if (!strcmp(sbuddy->name, self_uri)) {
6264 if (!(sbuddy->note && sip->note && !strcmp(sbuddy->note, sip->note))) /* not same */
6266 sip->is_oof_note = sbuddy->is_oof_note;
6268 g_free(sip->note);
6269 sip->note = g_strdup(sbuddy->note);
6271 sip->note_since = time(NULL);
6274 g_free(sip->status);
6275 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6279 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6280 sipe_got_user_status(sip, uri, status_id);
6282 if (!sip->ocs2007 && !strcmp(self_uri, uri)) {
6283 sipe_user_info_has_updated(sip, xn_userinfo);
6286 g_free(note);
6287 xmlnode_free(xn_presentity);
6288 g_free(uri);
6289 g_free(self_uri);
6292 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6294 char *ctype = sipmsg_find_header(msg, "Content-Type");
6296 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6298 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6299 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6301 const char *content = msg->body;
6302 unsigned length = msg->bodylen;
6303 PurpleMimeDocument *mime = NULL;
6305 if (strstr(ctype, "multipart"))
6307 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6308 const char *content_type;
6309 GList* parts;
6310 mime = purple_mime_document_parse(doc);
6311 parts = purple_mime_document_get_parts(mime);
6312 while(parts) {
6313 content = purple_mime_part_get_data(parts->data);
6314 length = purple_mime_part_get_length(parts->data);
6315 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6316 if(content_type && strstr(content_type,"application/rlmi+xml"))
6318 process_incoming_notify_rlmi_resub(sip, content, length);
6320 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6322 process_incoming_notify_msrtc(sip, content, length);
6324 else
6326 process_incoming_notify_rlmi(sip, content, length);
6328 parts = parts->next;
6330 g_free(doc);
6332 if (mime)
6334 purple_mime_document_free(mime);
6337 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6339 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6341 else if(strstr(ctype, "application/rlmi+xml"))
6343 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6346 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6348 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6350 else
6352 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6356 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6358 char *ctype = sipmsg_find_header(msg, "Content-Type");
6359 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6361 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6363 if (ctype &&
6364 strstr(ctype, "multipart") &&
6365 (strstr(ctype, "application/rlmi+xml") ||
6366 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6367 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6368 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6369 GList *parts = purple_mime_document_get_parts(mime);
6370 GSList *buddies = NULL;
6371 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6373 while (parts) {
6374 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6375 purple_mime_part_get_length(parts->data));
6377 if (strcmp(xml->name, "list")) {
6378 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6380 buddies = g_slist_append(buddies, uri);
6382 xmlnode_free(xml);
6384 parts = parts->next;
6386 g_free(doc);
6387 if (mime) purple_mime_document_free(mime);
6389 payload->host = g_strdup(who);
6390 payload->buddies = buddies;
6391 sipe_schedule_action(action_name, timeout,
6392 sipe_subscribe_presence_batched_routed,
6393 sipe_subscribe_presence_batched_routed_free,
6394 sip, payload);
6395 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6397 } else {
6398 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6399 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6401 g_free(action_name);
6405 * Dispatcher for all incoming subscription information
6406 * whether it comes from NOTIFY, BENOTIFY requests or
6407 * piggy-backed to subscription's OK responce.
6409 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6410 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6412 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6414 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6415 gchar *event = sipmsg_find_header(msg, "Event");
6416 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6417 char *tmp;
6418 int timeout = 0;
6420 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6421 event ? event : "",
6422 tmp = fix_newlines(msg->body));
6423 g_free(tmp);
6424 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6426 /* implicit subscriptions */
6427 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6428 sipe_process_imdn(sip, msg);
6431 if (!request)
6433 const gchar *expires_header;
6434 expires_header = sipmsg_find_header(msg, "Expires");
6435 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6436 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6437 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout; // 2 min ahead of expiration
6440 /* for one off subscriptions (send with Expire: 0) */
6441 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6443 sipe_process_provisioning_v2(sip, msg);
6445 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6447 sipe_process_provisioning(sip, msg);
6450 if (!subscription_state || strstr(subscription_state, "active"))
6452 if (event && !g_ascii_strcasecmp(event, "presence"))
6454 sipe_process_presence(sip, msg);
6456 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6458 sipe_process_roaming_contacts(sip, msg);
6460 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6462 sipe_process_roaming_self(sip, msg);
6464 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6466 sipe_process_roaming_acl(sip, msg);
6468 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
6470 sipe_process_presence_wpending(sip, msg);
6472 else if (event && !g_ascii_strcasecmp(event, "conference"))
6474 sipe_process_conference(sip, msg);
6478 /* The server sends status 'terminated' */
6479 if (subscription_state && strstr(subscription_state, "terminated") ) {
6480 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6481 gchar *key = sipe_get_subscription_key(event, who);
6483 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6484 g_free(who);
6486 if (g_hash_table_lookup(sip->subscriptions, key)) {
6487 g_hash_table_remove(sip->subscriptions, key);
6488 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6491 g_free(key);
6494 if (timeout && event) {// For LSC 2005 and OCS 2007
6495 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
6496 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
6498 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
6499 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
6500 g_free(action_name);
6502 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
6503 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
6505 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
6506 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
6507 g_free(action_name);
6509 else*/
6510 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6511 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6513 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6514 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6515 g_free(action_name);
6517 else if (!g_ascii_strcasecmp(event, "presence") &&
6518 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6520 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6521 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6522 if (sip->batched_support) {
6523 sipe_process_presence_timeout(sip, msg, who, timeout);
6525 else {
6526 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6527 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6529 g_free(action_name);
6530 g_free(who);
6534 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
6536 sipe_process_registration_notify(sip, msg);
6539 /* The client responses on received a NOTIFY message */
6540 if (request && !benotify)
6542 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6547 * Whether user manually changed status or
6548 * it was changed automatically due to user
6549 * became inactive/active again
6551 static gboolean
6552 sipe_is_user_state(struct sipe_account_data *sip)
6554 gboolean res;
6555 time_t now = time(NULL);
6557 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6558 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6560 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6562 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6563 return res;
6566 static void
6567 send_presence_soap0(struct sipe_account_data *sip,
6568 gboolean do_publish_calendar,
6569 gboolean do_reset_status)
6571 struct sipe_ews* ews = sip->ews;
6572 int availability = 0;
6573 int activity = 0;
6574 gchar *body;
6575 gchar *tmp;
6576 gchar *tmp2 = NULL;
6577 gchar *res_note = NULL;
6578 gchar *res_oof = NULL;
6579 const gchar *note_pub = NULL;
6580 gchar *states = NULL;
6581 gchar *calendar_data = NULL;
6582 gchar *epid = get_epid(sip);
6583 time_t now = time(NULL);
6584 gchar *since_time_str = sipe_utils_time_to_str(now);
6585 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6586 const char *user_input;
6587 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6589 if (oof_note && sip->note) {
6590 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6591 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6594 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6596 if (!sip->initial_state_published ||
6597 do_reset_status)
6599 g_free(sip->status);
6600 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6603 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6605 /* Note */
6606 if (pub_oof) {
6607 note_pub = oof_note;
6608 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6609 ews->published = TRUE;
6610 } else if (sip->note) {
6611 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6612 g_free(sip->note);
6613 sip->note = NULL;
6614 sip->is_oof_note = FALSE;
6615 sip->note_since = 0;
6616 } else {
6617 note_pub = sip->note;
6618 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6622 if (note_pub)
6624 /* to protocol internal plain text format */
6625 tmp = purple_markup_strip_html(note_pub);
6626 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6627 g_free(tmp);
6630 /* User State */
6631 if (!do_reset_status) {
6632 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6634 gchar *activity_token = NULL;
6635 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6637 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6638 avail_2007,
6639 since_time_str,
6640 epid,
6641 activity_token);
6642 g_free(activity_token);
6644 else /* preserve existing publication */
6646 xmlnode *xn_states;
6647 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6648 states = xmlnode_to_str(xn_states, NULL);
6649 /* this is a hack-around to remove added newline after inner element,
6650 * state in this case, where it shouldn't be.
6651 * After several use of xmlnode_to_str, amount of added newlines
6652 * grows significantly.
6654 purple_str_strip_char(states, '\n');
6655 //purple_str_strip_char(states, '\r');
6658 } else {
6659 /* do nothing - then User state will be erased */
6661 sip->initial_state_published = TRUE;
6663 /* CalendarInfo */
6664 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6666 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6667 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6668 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6669 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6670 fb_start_str,
6671 free_busy_base64);
6672 g_free(fb_start_str);
6673 g_free(free_busy_base64);
6676 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6678 /* forming resulting XML */
6679 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6680 sip->username,
6681 availability,
6682 activity,
6683 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6684 res_note ? res_note : "",
6685 res_oof ? res_oof : "",
6686 states ? states : "",
6687 calendar_data ? calendar_data : "",
6688 epid,
6689 since_time_str,
6690 since_time_str,
6691 user_input);
6692 g_free(tmp);
6693 g_free(tmp2);
6694 g_free(res_note);
6695 g_free(states);
6696 g_free(calendar_data);
6698 send_soap_request(sip, body);
6700 g_free(body);
6701 g_free(since_time_str);
6702 g_free(epid);
6705 void
6706 send_presence_soap(struct sipe_account_data *sip,
6707 gboolean do_publish_calendar)
6709 return send_presence_soap0(sip, do_publish_calendar, FALSE);
6713 static gboolean
6714 process_send_presence_category_publish_response(struct sipe_account_data *sip,
6715 struct sipmsg *msg,
6716 struct transaction *trans)
6718 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
6720 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
6721 xmlnode *xml;
6722 xmlnode *node;
6723 gchar *fault_code;
6724 GHashTable *faults;
6725 int index_our;
6726 gboolean has_device_publication = FALSE;
6728 xml = xmlnode_from_str(msg->body, msg->bodylen);
6730 /* test if version mismatch fault */
6731 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
6732 if (strcmp(fault_code, "Client.BadCall.WrongDelta")) {
6733 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
6734 g_free(fault_code);
6735 xmlnode_free(xml);
6736 return TRUE;
6738 g_free(fault_code);
6740 /* accumulating information about faulty versions */
6741 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
6742 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
6743 node;
6744 node = xmlnode_get_next_twin(node))
6746 const gchar *index = xmlnode_get_attrib(node, "index");
6747 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
6749 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
6750 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
6752 xmlnode_free(xml);
6754 /* here we are parsing own request to figure out what publication
6755 * referensed here only by index went wrong
6757 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
6759 /* publication */
6760 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
6761 index_our = 1; /* starts with 1 - our first publication */
6762 node;
6763 node = xmlnode_get_next_twin(node), index_our++)
6765 gchar *idx = g_strdup_printf("%d", index_our);
6766 const gchar *curVersion = g_hash_table_lookup(faults, idx);
6767 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
6768 g_free(idx);
6770 if (!strcmp("device", categoryName)) {
6771 has_device_publication = TRUE;
6774 if (curVersion) { /* fault exist on this index */
6775 const gchar *container = xmlnode_get_attrib(node, "container");
6776 const gchar *instance = xmlnode_get_attrib(node, "instance");
6777 /* key is <category><instance><container> */
6778 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
6779 struct sipe_publication *publication =
6780 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
6782 purple_debug_info("sipe", "key is %s\n", key);
6784 if (publication) {
6785 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6786 key, curVersion, publication->version);
6787 /* updating publication's version to the correct one */
6788 publication->version = atoi(curVersion);
6790 g_free(key);
6793 xmlnode_free(xml);
6794 g_hash_table_destroy(faults);
6796 /* rebublishing with right versions */
6797 if (has_device_publication) {
6798 send_publish_category_initial(sip);
6799 } else {
6800 send_presence_status(sip);
6803 return TRUE;
6807 * Returns 'device' XML part for publication.
6808 * Must be g_free'd after use.
6810 static gchar *
6811 sipe_publish_get_category_device(struct sipe_account_data *sip)
6813 gchar *uri;
6814 gchar *doc;
6815 gchar *epid = get_epid(sip);
6816 gchar *uuid = generateUUIDfromEPID(epid);
6817 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
6818 /* key is <category><instance><container> */
6819 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
6820 struct sipe_publication *publication =
6821 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
6823 g_free(key);
6824 g_free(epid);
6826 uri = sip_uri_self(sip);
6827 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
6828 device_instance,
6829 publication ? publication->version : 0,
6830 uuid,
6831 uri,
6832 "00:00:00+01:00", /* @TODO make timezone real*/
6833 sipe_get_host_name()
6836 g_free(uri);
6837 g_free(uuid);
6839 return doc;
6843 * A service method - use
6844 * - send_publish_get_category_state_machine and
6845 * - send_publish_get_category_state_user instead.
6846 * Must be g_free'd after use.
6848 static gchar *
6849 sipe_publish_get_category_state(struct sipe_account_data *sip,
6850 gboolean is_user_state)
6852 int availability = sipe_get_availability_by_status(sip->status, NULL);
6853 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
6854 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
6855 /* key is <category><instance><container> */
6856 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6857 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6858 struct sipe_publication *publication_2 =
6859 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6860 struct sipe_publication *publication_3 =
6861 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6863 g_free(key_2);
6864 g_free(key_3);
6866 if (publication_2 && (publication_2->availability == availability))
6868 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6869 return NULL; /* nothing to update */
6872 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
6873 instance,
6874 publication_2 ? publication_2->version : 0,
6875 availability,
6876 instance,
6877 publication_3 ? publication_3->version : 0,
6878 availability);
6882 * Only Busy and OOF calendar event are published.
6883 * Different instances are used for that.
6885 * Must be g_free'd after use.
6887 static gchar *
6888 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
6889 struct sipe_cal_event *event,
6890 const char *uri,
6891 int cal_satus)
6893 gchar *start_time_str;
6894 int availability = 0;
6895 gchar *res;
6896 guint instance = (cal_satus == SIPE_CAL_OOF) ?
6897 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
6898 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
6900 /* key is <category><instance><container> */
6901 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6902 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6903 struct sipe_publication *publication_2 =
6904 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6905 struct sipe_publication *publication_3 =
6906 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6908 g_free(key_2);
6909 g_free(key_3);
6911 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
6912 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6913 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
6914 return NULL;
6917 if (event &&
6918 publication_3 &&
6919 (publication_3->availability == availability) &&
6920 !strcmp(publication_3->cal_event_hash, sipe_cal_event_hash(event)))
6922 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6923 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
6924 return NULL; /* nothing to update */
6927 if (event &&
6928 (event->cal_status == SIPE_CAL_BUSY ||
6929 event->cal_status == SIPE_CAL_OOF))
6931 gchar *availability_xml_str = NULL;
6932 gchar *activity_xml_str = NULL;
6934 if (event->cal_status == SIPE_CAL_BUSY) {
6935 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
6938 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
6939 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6940 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
6941 "minAvailability=\"6500\"",
6942 "maxAvailability=\"8999\"");
6943 } else if (event->cal_status == SIPE_CAL_OOF) {
6944 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6945 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
6946 "minAvailability=\"12000\"",
6947 "");
6949 start_time_str = sipe_utils_time_to_str(event->start_time);
6951 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
6952 instance,
6953 publication_2 ? publication_2->version : 0,
6954 uri,
6955 start_time_str,
6956 availability_xml_str ? availability_xml_str : "",
6957 activity_xml_str ? activity_xml_str : "",
6958 event->subject ? event->subject : "",
6959 event->location ? event->location : "",
6961 instance,
6962 publication_3 ? publication_3->version : 0,
6963 uri,
6964 start_time_str,
6965 availability_xml_str ? availability_xml_str : "",
6966 activity_xml_str ? activity_xml_str : "",
6967 event->subject ? event->subject : "",
6968 event->location ? event->location : ""
6970 g_free(start_time_str);
6971 g_free(availability_xml_str);
6972 g_free(activity_xml_str);
6975 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
6977 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
6978 instance,
6979 publication_2 ? publication_2->version : 0,
6981 instance,
6982 publication_3 ? publication_3->version : 0
6986 return res;
6990 * Returns 'machineState' XML part for publication.
6991 * Must be g_free'd after use.
6993 static gchar *
6994 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
6996 return sipe_publish_get_category_state(sip, FALSE);
7000 * Returns 'userState' XML part for publication.
7001 * Must be g_free'd after use.
7003 static gchar *
7004 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7006 return sipe_publish_get_category_state(sip, TRUE);
7010 * Compares two strings even in case both are NULL/empty
7012 static gboolean
7013 sipe_is_equal(const char* n1, const char* n2) {
7014 return ((!n1 || !strlen(n1)) && (!n2 || !strlen(n2))) /* both empty */
7015 || (n1 && n2 && !strcmp(n1, n2)); /* or not empty and equal */
7019 * Returns 'note' XML part for publication.
7020 * Must be g_free'd after use.
7022 * Protocol format for Note is plain text.
7024 * @param note a note in Sipe internal HTML format
7025 * @param note_type either personal or OOF
7027 static gchar *
7028 sipe_publish_get_category_note(struct sipe_account_data *sip,
7029 const char *note, /* html */
7030 const char *note_type,
7031 time_t note_start,
7032 time_t note_end)
7034 guint instance = !strcmp("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7035 /* key is <category><instance><container> */
7036 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7037 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7038 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7040 struct sipe_publication *publication_note_200 =
7041 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7042 struct sipe_publication *publication_note_300 =
7043 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7044 struct sipe_publication *publication_note_400 =
7045 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7047 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7048 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7049 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7050 char *res, *tmp1, *tmp2, *tmp3;
7051 char *start_time_attr;
7052 char *end_time_attr;
7054 g_free(tmp);
7055 g_free(key_note_200);
7056 g_free(key_note_300);
7057 g_free(key_note_400);
7059 /* we even need to republish empty note */
7060 if (n1 && n2 && !strcmp(n1, n2))
7062 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7063 g_free(n1);
7064 return NULL; /* nothing to update */
7067 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7068 g_free(tmp);
7069 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7070 g_free(tmp);
7072 if (n1) {
7073 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7074 instance,
7075 200,
7076 publication_note_200 ? publication_note_200->version : 0,
7077 note_type,
7078 start_time_attr ? start_time_attr : "",
7079 end_time_attr ? end_time_attr : "",
7080 n1);
7082 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7083 instance,
7084 300,
7085 publication_note_300 ? publication_note_300->version : 0,
7086 note_type,
7087 start_time_attr ? start_time_attr : "",
7088 end_time_attr ? end_time_attr : "",
7089 n1);
7091 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7092 instance,
7093 400,
7094 publication_note_400 ? publication_note_400->version : 0,
7095 note_type,
7096 start_time_attr ? start_time_attr : "",
7097 end_time_attr ? end_time_attr : "",
7098 n1);
7099 } else {
7100 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7101 "note",
7102 instance,
7103 200,
7104 publication_note_200 ? publication_note_200->version : 0,
7105 "static");
7106 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7107 "note",
7108 instance,
7109 300,
7110 publication_note_200 ? publication_note_200->version : 0,
7111 "static");
7112 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7113 "note",
7114 instance,
7115 400,
7116 publication_note_200 ? publication_note_200->version : 0,
7117 "static");
7119 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7121 g_free(start_time_attr);
7122 g_free(end_time_attr);
7123 g_free(tmp1);
7124 g_free(tmp2);
7125 g_free(tmp3);
7126 g_free(n1);
7128 return res;
7132 * Returns 'calendarData' XML part with WorkingHours for publication.
7133 * Must be g_free'd after use.
7135 static gchar *
7136 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7138 struct sipe_ews* ews = sip->ews;
7140 /* key is <category><instance><container> */
7141 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7142 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7143 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7144 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7145 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7146 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7148 struct sipe_publication *publication_cal_1 =
7149 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7150 struct sipe_publication *publication_cal_100 =
7151 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7152 struct sipe_publication *publication_cal_200 =
7153 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7154 struct sipe_publication *publication_cal_300 =
7155 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7156 struct sipe_publication *publication_cal_400 =
7157 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7158 struct sipe_publication *publication_cal_32000 =
7159 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7161 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7162 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7164 g_free(key_cal_1);
7165 g_free(key_cal_100);
7166 g_free(key_cal_200);
7167 g_free(key_cal_300);
7168 g_free(key_cal_400);
7169 g_free(key_cal_32000);
7171 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7172 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7173 return NULL;
7176 if (sipe_is_equal(n1, n2))
7178 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7179 return NULL; /* nothing to update */
7182 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7183 /* 1 */
7184 publication_cal_1 ? publication_cal_1->version : 0,
7185 ews->email,
7186 ews->working_hours_xml_str,
7187 /* 100 - Public */
7188 publication_cal_100 ? publication_cal_100->version : 0,
7189 /* 200 - Company */
7190 publication_cal_200 ? publication_cal_200->version : 0,
7191 ews->email,
7192 ews->working_hours_xml_str,
7193 /* 300 - Team */
7194 publication_cal_300 ? publication_cal_300->version : 0,
7195 ews->email,
7196 ews->working_hours_xml_str,
7197 /* 400 - Personal */
7198 publication_cal_400 ? publication_cal_400->version : 0,
7199 ews->email,
7200 ews->working_hours_xml_str,
7201 /* 32000 - Blocked */
7202 publication_cal_32000 ? publication_cal_32000->version : 0
7207 * Returns 'calendarData' XML part with FreeBusy for publication.
7208 * Must be g_free'd after use.
7210 static gchar *
7211 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7213 struct sipe_ews* ews = sip->ews;
7214 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7215 char *fb_start_str;
7216 char *free_busy_base64;
7217 const char *st;
7218 const char *fb;
7219 char *res;
7221 /* key is <category><instance><container> */
7222 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7223 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7224 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7225 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7226 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7227 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7229 struct sipe_publication *publication_cal_1 =
7230 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7231 struct sipe_publication *publication_cal_100 =
7232 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7233 struct sipe_publication *publication_cal_200 =
7234 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7235 struct sipe_publication *publication_cal_300 =
7236 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7237 struct sipe_publication *publication_cal_400 =
7238 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7239 struct sipe_publication *publication_cal_32000 =
7240 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7242 g_free(key_cal_1);
7243 g_free(key_cal_100);
7244 g_free(key_cal_200);
7245 g_free(key_cal_300);
7246 g_free(key_cal_400);
7247 g_free(key_cal_32000);
7249 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7250 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7251 return NULL;
7254 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7255 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7257 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7258 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7260 /* we will rebuplish the same data to refresh publication time,
7261 * so if data from multiple sources, most recent will be choosen
7263 //if (sipe_is_equal(st, fb_start_str) && sipe_is_equal(fb, free_busy_base64))
7265 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7266 // g_free(fb_start_str);
7267 // g_free(free_busy_base64);
7268 // return NULL; /* nothing to update */
7271 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7272 /* 1 */
7273 cal_data_instance,
7274 publication_cal_1 ? publication_cal_1->version : 0,
7275 /* 100 - Public */
7276 cal_data_instance,
7277 publication_cal_100 ? publication_cal_100->version : 0,
7278 /* 200 - Company */
7279 cal_data_instance,
7280 publication_cal_200 ? publication_cal_200->version : 0,
7281 ews->email,
7282 fb_start_str,
7283 free_busy_base64,
7284 /* 300 - Team */
7285 cal_data_instance,
7286 publication_cal_300 ? publication_cal_300->version : 0,
7287 ews->email,
7288 fb_start_str,
7289 free_busy_base64,
7290 /* 400 - Personal */
7291 cal_data_instance,
7292 publication_cal_400 ? publication_cal_400->version : 0,
7293 ews->email,
7294 fb_start_str,
7295 free_busy_base64,
7296 /* 32000 - Blocked */
7297 cal_data_instance,
7298 publication_cal_32000 ? publication_cal_32000->version : 0
7301 g_free(fb_start_str);
7302 g_free(free_busy_base64);
7303 return res;
7306 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7308 gchar *uri;
7309 gchar *doc;
7310 gchar *tmp;
7311 gchar *hdr;
7313 uri = sip_uri_self(sip);
7314 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7315 uri,
7316 publications);
7318 tmp = get_contact(sip);
7319 hdr = g_strdup_printf("Contact: %s\r\n"
7320 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7322 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7324 g_free(tmp);
7325 g_free(hdr);
7326 g_free(uri);
7327 g_free(doc);
7330 static void
7331 send_publish_category_initial(struct sipe_account_data *sip)
7333 gchar *pub_device = sipe_publish_get_category_device(sip);
7334 gchar *pub_machine;
7335 gchar *publications;
7337 g_free(sip->status);
7338 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7340 pub_machine = sipe_publish_get_category_state_machine(sip);
7341 publications = g_strdup_printf("%s%s",
7342 pub_device,
7343 pub_machine ? pub_machine : "");
7344 g_free(pub_device);
7345 g_free(pub_machine);
7347 send_presence_publish(sip, publications);
7348 g_free(publications);
7351 static void
7352 send_presence_category_publish(struct sipe_account_data *sip)
7354 gchar *pub_state = sipe_is_user_state(sip) ?
7355 sipe_publish_get_category_state_user(sip) :
7356 sipe_publish_get_category_state_machine(sip);
7357 gchar *pub_note = sipe_publish_get_category_note(sip,
7358 sip->note,
7359 sip->is_oof_note ? "OOF" : "personal",
7362 gchar *publications;
7364 if (!pub_state && !pub_note) {
7365 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7366 return;
7369 publications = g_strdup_printf("%s%s",
7370 pub_state ? pub_state : "",
7371 pub_note ? pub_note : "");
7373 g_free(pub_state);
7374 g_free(pub_note);
7376 send_presence_publish(sip, publications);
7377 g_free(publications);
7381 * Publishes self status
7382 * based on own calendar information.
7384 * For 2007+
7386 void
7387 publish_calendar_status_self(struct sipe_account_data *sip)
7389 struct sipe_cal_event* event = NULL;
7390 gchar *pub_cal_working_hours = NULL;
7391 gchar *pub_cal_free_busy = NULL;
7392 gchar *pub_calendar = NULL;
7393 gchar *pub_calendar2 = NULL;
7394 gchar *pub_oof_note = NULL;
7395 const gchar *oof_note;
7396 time_t oof_start = 0;
7397 time_t oof_end = 0;
7399 if (!sip->ews) {
7400 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7401 return;
7404 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7405 if (sip->ews->cal_events) {
7406 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7409 if (!event) {
7410 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7411 } else {
7412 char *desc = sipe_cal_event_describe(event);
7413 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7414 g_free(desc);
7417 /* Logic
7418 if OOF
7419 OOF publish, Busy clean
7420 ilse if Busy
7421 OOF clean, Busy publish
7422 else
7423 OOF clean, Busy clean
7425 if (event && event->cal_status == SIPE_CAL_OOF) {
7426 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7427 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7428 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7429 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7430 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7431 } else {
7432 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7433 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7436 oof_note = sipe_ews_get_oof_note(sip->ews);
7437 if (!strcmp("Scheduled", sip->ews->oof_state)) {
7438 oof_start = sip->ews->oof_start;
7439 oof_end = sip->ews->oof_end;
7441 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7443 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7444 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7446 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7447 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7448 } else {
7449 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7450 pub_cal_working_hours ? pub_cal_working_hours : "",
7451 pub_cal_free_busy ? pub_cal_free_busy : "",
7452 pub_calendar ? pub_calendar : "",
7453 pub_calendar2 ? pub_calendar2 : "",
7454 pub_oof_note ? pub_oof_note : "");
7456 send_presence_publish(sip, publications);
7457 g_free(publications);
7460 g_free(pub_cal_working_hours);
7461 g_free(pub_cal_free_busy);
7462 g_free(pub_calendar);
7463 g_free(pub_calendar2);
7464 g_free(pub_oof_note);
7466 /* repeat scheduling */
7467 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7470 static void send_presence_status(struct sipe_account_data *sip)
7472 PurpleStatus * status = purple_account_get_active_status(sip->account);
7474 if (!status) return;
7476 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7477 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7478 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7480 if (sip->ocs2007) {
7481 send_presence_category_publish(sip);
7482 } else {
7483 send_presence_soap(sip, FALSE);
7487 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7489 gboolean found = FALSE;
7490 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
7491 if (msg->response == 0) { /* request */
7492 if (!strcmp(msg->method, "MESSAGE")) {
7493 process_incoming_message(sip, msg);
7494 found = TRUE;
7495 } else if (!strcmp(msg->method, "NOTIFY")) {
7496 purple_debug_info("sipe","send->process_incoming_notify\n");
7497 process_incoming_notify(sip, msg, TRUE, FALSE);
7498 found = TRUE;
7499 } else if (!strcmp(msg->method, "BENOTIFY")) {
7500 purple_debug_info("sipe","send->process_incoming_benotify\n");
7501 process_incoming_notify(sip, msg, TRUE, TRUE);
7502 found = TRUE;
7503 } else if (!strcmp(msg->method, "INVITE")) {
7504 process_incoming_invite(sip, msg);
7505 found = TRUE;
7506 } else if (!strcmp(msg->method, "REFER")) {
7507 process_incoming_refer(sip, msg);
7508 found = TRUE;
7509 } else if (!strcmp(msg->method, "OPTIONS")) {
7510 process_incoming_options(sip, msg);
7511 found = TRUE;
7512 } else if (!strcmp(msg->method, "INFO")) {
7513 process_incoming_info(sip, msg);
7514 found = TRUE;
7515 } else if (!strcmp(msg->method, "ACK")) {
7516 // ACK's don't need any response
7517 found = TRUE;
7518 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
7519 // LCS 2005 sends us these - just respond 200 OK
7520 found = TRUE;
7521 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7522 } else if (!strcmp(msg->method, "BYE")) {
7523 process_incoming_bye(sip, msg);
7524 found = TRUE;
7525 } else {
7526 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7528 } else { /* response */
7529 struct transaction *trans = transactions_find(sip, msg);
7530 if (trans) {
7531 if (msg->response == 407) {
7532 gchar *resend, *auth, *ptmp;
7534 if (sip->proxy.retries > 30) return;
7535 sip->proxy.retries++;
7536 /* do proxy authentication */
7538 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7540 fill_auth(ptmp, &sip->proxy);
7541 auth = auth_header(sip, &sip->proxy, trans->msg);
7542 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7543 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7544 g_free(auth);
7545 resend = sipmsg_to_string(trans->msg);
7546 /* resend request */
7547 sendout_pkt(sip->gc, resend);
7548 g_free(resend);
7549 } else {
7550 if (msg->response < 200) {
7551 /* ignore provisional response */
7552 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7553 } else {
7554 sip->proxy.retries = 0;
7555 if (!strcmp(trans->msg->method, "REGISTER")) {
7556 if (msg->response == 401)
7558 sip->registrar.retries++;
7560 else
7562 sip->registrar.retries = 0;
7564 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7565 } else {
7566 if (msg->response == 401) {
7567 gchar *resend, *auth, *ptmp;
7569 if (sip->registrar.retries > 4) return;
7570 sip->registrar.retries++;
7572 #ifdef USE_KERBEROS
7573 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
7574 #endif
7575 ptmp = sipmsg_find_auth_header(msg, "NTLM");
7576 #ifdef USE_KERBEROS
7577 } else {
7578 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
7580 #endif
7582 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp);
7584 fill_auth(ptmp, &sip->registrar);
7585 auth = auth_header(sip, &sip->registrar, trans->msg);
7586 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7587 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7589 //sipmsg_remove_header_now(trans->msg, "Authorization");
7590 //sipmsg_add_header(trans->msg, "Authorization", auth);
7591 g_free(auth);
7592 resend = sipmsg_to_string(trans->msg);
7593 /* resend request */
7594 sendout_pkt(sip->gc, resend);
7595 g_free(resend);
7599 if (trans->callback) {
7600 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7601 /* call the callback to process response*/
7602 (trans->callback)(sip, msg, trans);
7605 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7606 transactions_remove(sip, trans);
7610 found = TRUE;
7611 } else {
7612 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7615 if (!found) {
7616 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
7620 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7622 char *cur;
7623 char *dummy;
7624 char *tmp;
7625 struct sipmsg *msg;
7626 int restlen;
7627 cur = conn->inbuf;
7629 /* according to the RFC remove CRLF at the beginning */
7630 while (*cur == '\r' || *cur == '\n') {
7631 cur++;
7633 if (cur != conn->inbuf) {
7634 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7635 conn->inbufused = strlen(conn->inbuf);
7638 /* Received a full Header? */
7639 sip->processing_input = TRUE;
7640 while (sip->processing_input &&
7641 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7642 time_t currtime = time(NULL);
7643 cur += 2;
7644 cur[0] = '\0';
7645 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7646 g_free(tmp);
7647 msg = sipmsg_parse_header(conn->inbuf);
7648 cur[0] = '\r';
7649 cur += 2;
7650 restlen = conn->inbufused - (cur - conn->inbuf);
7651 if (msg && restlen >= msg->bodylen) {
7652 dummy = g_malloc(msg->bodylen + 1);
7653 memcpy(dummy, cur, msg->bodylen);
7654 dummy[msg->bodylen] = '\0';
7655 msg->body = dummy;
7656 cur += msg->bodylen;
7657 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7658 conn->inbufused = strlen(conn->inbuf);
7659 } else {
7660 if (msg){
7661 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7662 sipmsg_free(msg);
7664 return;
7667 /*if (msg->body) {
7668 purple_debug_info("sipe", "body:\n%s", msg->body);
7671 // Verify the signature before processing it
7672 if (sip->registrar.gssapi_context) {
7673 struct sipmsg_breakdown msgbd;
7674 gchar *signature_input_str;
7675 gchar *rspauth;
7676 msgbd.msg = msg;
7677 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7678 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
7680 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7682 if (rspauth != NULL) {
7683 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7684 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7685 process_input_message(sip, msg);
7686 } else {
7687 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7688 purple_connection_error(sip->gc, _("Invalid message signature received"));
7689 sip->gc->wants_to_die = TRUE;
7691 } else if (msg->response == 401) {
7692 purple_connection_error(sip->gc, _("Wrong password"));
7693 sip->gc->wants_to_die = TRUE;
7695 g_free(signature_input_str);
7697 g_free(rspauth);
7698 sipmsg_breakdown_free(&msgbd);
7699 } else {
7700 process_input_message(sip, msg);
7703 sipmsg_free(msg);
7707 static void sipe_udp_process(gpointer data, gint source,
7708 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
7710 PurpleConnection *gc = data;
7711 struct sipe_account_data *sip = gc->proto_data;
7712 int len;
7714 static char buffer[65536];
7715 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
7716 time_t currtime = time(NULL);
7717 struct sipmsg *msg;
7718 buffer[len] = '\0';
7719 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
7720 msg = sipmsg_parse_msg(buffer);
7721 if (msg) process_input_message(sip, msg);
7725 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
7727 struct sipe_account_data *sip = gc->proto_data;
7728 PurpleSslConnection *gsc = sip->gsc;
7730 purple_debug_error("sipe", "%s",debug);
7731 purple_connection_error(gc, msg);
7733 /* Invalidate this connection. Next send will open a new one */
7734 if (gsc) {
7735 connection_remove(sip, gsc->fd);
7736 purple_ssl_close(gsc);
7738 sip->gsc = NULL;
7739 sip->fd = -1;
7742 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7743 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7745 PurpleConnection *gc = data;
7746 struct sipe_account_data *sip;
7747 struct sip_connection *conn;
7748 int readlen, len;
7749 gboolean firstread = TRUE;
7751 /* NOTE: This check *IS* necessary */
7752 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
7753 purple_ssl_close(gsc);
7754 return;
7757 sip = gc->proto_data;
7758 conn = connection_find(sip, gsc->fd);
7759 if (conn == NULL) {
7760 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7761 gc->wants_to_die = TRUE;
7762 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
7763 return;
7766 /* Read all available data from the SSL connection */
7767 do {
7768 /* Increase input buffer size as needed */
7769 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7770 conn->inbuflen += SIMPLE_BUF_INC;
7771 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7772 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
7775 /* Try to read as much as there is space left in the buffer */
7776 readlen = conn->inbuflen - conn->inbufused - 1;
7777 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
7779 if (len < 0 && errno == EAGAIN) {
7780 /* Try again later */
7781 return;
7782 } else if (len < 0) {
7783 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
7784 return;
7785 } else if (firstread && (len == 0)) {
7786 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
7787 return;
7790 conn->inbufused += len;
7791 firstread = FALSE;
7793 /* Equivalence indicates that there is possibly more data to read */
7794 } while (len == readlen);
7796 conn->inbuf[conn->inbufused] = '\0';
7797 process_input(sip, conn);
7801 static void sipe_input_cb(gpointer data, gint source,
7802 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7804 PurpleConnection *gc = data;
7805 struct sipe_account_data *sip = gc->proto_data;
7806 int len;
7807 struct sip_connection *conn = connection_find(sip, source);
7808 if (!conn) {
7809 purple_debug_error("sipe", "Connection not found!\n");
7810 return;
7813 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7814 conn->inbuflen += SIMPLE_BUF_INC;
7815 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7818 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
7820 if (len < 0 && errno == EAGAIN)
7821 return;
7822 else if (len <= 0) {
7823 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7824 connection_remove(sip, source);
7825 if (sip->fd == source) sip->fd = -1;
7826 return;
7829 conn->inbufused += len;
7830 conn->inbuf[conn->inbufused] = '\0';
7832 process_input(sip, conn);
7835 /* Callback for new connections on incoming TCP port */
7836 static void sipe_newconn_cb(gpointer data, gint source,
7837 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7839 PurpleConnection *gc = data;
7840 struct sipe_account_data *sip = gc->proto_data;
7841 struct sip_connection *conn;
7843 int newfd = accept(source, NULL, NULL);
7845 conn = connection_create(sip, newfd);
7847 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7850 static void login_cb(gpointer data, gint source,
7851 SIPE_UNUSED_PARAMETER const gchar *error_message)
7853 PurpleConnection *gc = data;
7854 struct sipe_account_data *sip;
7855 struct sip_connection *conn;
7857 if (!PURPLE_CONNECTION_IS_VALID(gc))
7859 if (source >= 0)
7860 close(source);
7861 return;
7864 if (source < 0) {
7865 purple_connection_error(gc, _("Could not connect"));
7866 return;
7869 sip = gc->proto_data;
7870 sip->fd = source;
7871 sip->last_keepalive = time(NULL);
7873 conn = connection_create(sip, source);
7875 do_register(sip);
7877 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7880 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7881 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7883 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
7884 if (sip == NULL) return;
7886 do_register(sip);
7889 static guint sipe_ht_hash_nick(const char *nick)
7891 char *lc = g_utf8_strdown(nick, -1);
7892 guint bucket = g_str_hash(lc);
7893 g_free(lc);
7895 return bucket;
7898 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
7900 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
7903 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
7905 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7907 sip->listen_data = NULL;
7909 if (listenfd == -1) {
7910 purple_connection_error(sip->gc, _("Could not create listen socket"));
7911 return;
7914 sip->fd = listenfd;
7916 sip->listenport = purple_network_get_port_from_fd(sip->fd);
7917 sip->listenfd = sip->fd;
7919 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
7921 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
7922 do_register(sip);
7925 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
7926 SIPE_UNUSED_PARAMETER const char *error_message)
7928 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7930 sip->query_data = NULL;
7932 if (!hosts || !hosts->data) {
7933 purple_connection_error(sip->gc, _("Could not resolve hostname"));
7934 return;
7937 hosts = g_slist_remove(hosts, hosts->data);
7938 g_free(sip->serveraddr);
7939 sip->serveraddr = hosts->data;
7940 hosts = g_slist_remove(hosts, hosts->data);
7941 while (hosts) {
7942 hosts = g_slist_remove(hosts, hosts->data);
7943 g_free(hosts->data);
7944 hosts = g_slist_remove(hosts, hosts->data);
7947 /* create socket for incoming connections */
7948 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
7949 sipe_udp_host_resolved_listen_cb, sip);
7950 if (sip->listen_data == NULL) {
7951 purple_connection_error(sip->gc, _("Could not create listen socket"));
7952 return;
7956 static const struct sipe_service_data *current_service = NULL;
7958 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
7959 PurpleSslErrorType error,
7960 gpointer data)
7962 PurpleConnection *gc = data;
7963 struct sipe_account_data *sip;
7965 /* If the connection is already disconnected, we don't need to do anything else */
7966 if (!PURPLE_CONNECTION_IS_VALID(gc))
7967 return;
7969 sip = gc->proto_data;
7970 current_service = sip->service_data;
7971 if (current_service) {
7972 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
7973 current_service->transport ? current_service->transport : "NULL",
7974 current_service->service ? current_service->service : "NULL");
7977 sip->fd = -1;
7978 sip->gsc = NULL;
7980 switch(error) {
7981 case PURPLE_SSL_CONNECT_FAILED:
7982 purple_connection_error(gc, _("Connection failed"));
7983 break;
7984 case PURPLE_SSL_HANDSHAKE_FAILED:
7985 purple_connection_error(gc, _("SSL handshake failed"));
7986 break;
7987 case PURPLE_SSL_CERTIFICATE_INVALID:
7988 purple_connection_error(gc, _("SSL certificate invalid"));
7989 break;
7993 static void
7994 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
7996 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7997 PurpleProxyConnectData *connect_data;
7999 sip->listen_data = NULL;
8001 sip->listenfd = listenfd;
8002 if (sip->listenfd == -1) {
8003 purple_connection_error(sip->gc, _("Could not create listen socket"));
8004 return;
8007 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8008 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8009 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8010 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8011 sipe_newconn_cb, sip->gc);
8012 purple_debug_info("sipe", "connecting to %s port %d\n",
8013 sip->realhostname, sip->realport);
8014 /* open tcp connection to the server */
8015 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8016 sip->realport, login_cb, sip->gc);
8018 if (connect_data == NULL) {
8019 purple_connection_error(sip->gc, _("Could not create socket"));
8023 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8025 PurpleAccount *account = sip->account;
8026 PurpleConnection *gc = sip->gc;
8028 if (port == 0) {
8029 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8032 sip->realhostname = hostname;
8033 sip->realport = port;
8035 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8036 hostname, port);
8038 /* TODO: is there a good default grow size? */
8039 if (sip->transport != SIPE_TRANSPORT_UDP)
8040 sip->txbuf = purple_circ_buffer_new(0);
8042 if (sip->transport == SIPE_TRANSPORT_TLS) {
8043 /* SSL case */
8044 if (!purple_ssl_is_supported()) {
8045 gc->wants_to_die = TRUE;
8046 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8047 return;
8050 purple_debug_info("sipe", "using SSL\n");
8052 sip->gsc = purple_ssl_connect(account, hostname, port,
8053 login_cb_ssl, sipe_ssl_connect_failure, gc);
8054 if (sip->gsc == NULL) {
8055 purple_connection_error(gc, _("Could not create SSL context"));
8056 return;
8058 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8059 /* UDP case */
8060 purple_debug_info("sipe", "using UDP\n");
8062 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8063 if (sip->query_data == NULL) {
8064 purple_connection_error(gc, _("Could not resolve hostname"));
8066 } else {
8067 /* TCP case */
8068 purple_debug_info("sipe", "using TCP\n");
8069 /* create socket for incoming connections */
8070 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8071 sipe_tcp_connect_listen_cb, sip);
8072 if (sip->listen_data == NULL) {
8073 purple_connection_error(gc, _("Could not create listen socket"));
8074 return;
8079 /* Service list for autodection */
8080 static const struct sipe_service_data service_autodetect[] = {
8081 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8082 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8083 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8084 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8085 { NULL, NULL, 0 }
8088 /* Service list for SSL/TLS */
8089 static const struct sipe_service_data service_tls[] = {
8090 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8091 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8092 { NULL, NULL, 0 }
8095 /* Service list for TCP */
8096 static const struct sipe_service_data service_tcp[] = {
8097 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8098 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8099 { NULL, NULL, 0 }
8102 /* Service list for UDP */
8103 static const struct sipe_service_data service_udp[] = {
8104 { "sip", "udp", SIPE_TRANSPORT_UDP },
8105 { NULL, NULL, 0 }
8108 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8109 static void resolve_next_service(struct sipe_account_data *sip,
8110 const struct sipe_service_data *start)
8112 if (start) {
8113 sip->service_data = start;
8114 } else {
8115 sip->service_data++;
8116 if (sip->service_data->service == NULL) {
8117 gchar *hostname;
8118 /* Try connecting to the SIP hostname directly */
8119 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8120 if (sip->auto_transport) {
8121 // If SSL is supported, default to using it; OCS servers aren't configured
8122 // by default to accept TCP
8123 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8124 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8125 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8128 hostname = g_strdup(sip->sipdomain);
8129 create_connection(sip, hostname, 0);
8130 return;
8134 /* Try to resolve next service */
8135 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8136 sip->service_data->transport,
8137 sip->sipdomain,
8138 srvresolved, sip);
8141 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8143 struct sipe_account_data *sip = data;
8145 sip->srv_query_data = NULL;
8147 /* find the host to connect to */
8148 if (results) {
8149 gchar *hostname = g_strdup(resp->hostname);
8150 int port = resp->port;
8151 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8152 hostname, port);
8153 g_free(resp);
8155 sip->transport = sip->service_data->type;
8157 create_connection(sip, hostname, port);
8158 } else {
8159 resolve_next_service(sip, NULL);
8163 static void sipe_login(PurpleAccount *account)
8165 PurpleConnection *gc;
8166 struct sipe_account_data *sip;
8167 gchar **signinname_login, **userserver;
8168 const char *transport;
8169 const char *email;
8171 const char *username = purple_account_get_username(account);
8172 gc = purple_account_get_connection(account);
8174 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8176 if (strpbrk(username, "\t\v\r\n") != NULL) {
8177 gc->wants_to_die = TRUE;
8178 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8179 return;
8182 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8183 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8184 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8185 sip->gc = gc;
8186 sip->account = account;
8187 sip->reregister_set = FALSE;
8188 sip->reauthenticate_set = FALSE;
8189 sip->subscribed = FALSE;
8190 sip->subscribed_buddies = FALSE;
8191 sip->initial_state_published = FALSE;
8193 /* username format: <username>,[<optional login>] */
8194 signinname_login = g_strsplit(username, ",", 2);
8195 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8197 /* ensure that username format is name@domain */
8198 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8199 g_strfreev(signinname_login);
8200 gc->wants_to_die = TRUE;
8201 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8202 return;
8204 sip->username = g_strdup(signinname_login[0]);
8206 /* ensure that email format is name@domain if provided */
8207 email = purple_account_get_string(sip->account, "email", NULL);
8208 if (!is_empty(email) &&
8209 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8211 gc->wants_to_die = TRUE;
8212 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8213 return;
8215 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8217 /* login name specified? */
8218 if (signinname_login[1] && strlen(signinname_login[1])) {
8219 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8220 gboolean has_domain = domain_user[1] != NULL;
8221 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8222 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8223 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8224 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8225 sip->authdomain ? sip->authdomain : "", sip->authuser);
8226 g_strfreev(domain_user);
8229 userserver = g_strsplit(signinname_login[0], "@", 2);
8230 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8231 purple_connection_set_display_name(gc, userserver[0]);
8232 sip->sipdomain = g_strdup(userserver[1]);
8233 g_strfreev(userserver);
8234 g_strfreev(signinname_login);
8236 if (strchr(sip->username, ' ') != NULL) {
8237 gc->wants_to_die = TRUE;
8238 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8239 return;
8242 sip->password = g_strdup(purple_connection_get_password(gc));
8244 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8245 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8246 g_free, (GDestroyNotify)g_hash_table_destroy);
8247 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8248 g_free, (GDestroyNotify)sipe_subscription_free);
8250 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8252 g_free(sip->status);
8253 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8255 sip->auto_transport = FALSE;
8256 transport = purple_account_get_string(account, "transport", "auto");
8257 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8258 if (userserver[0]) {
8259 /* Use user specified server[:port] */
8260 int port = 0;
8262 if (userserver[1])
8263 port = atoi(userserver[1]);
8265 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8266 userserver[0], port);
8268 if (strcmp(transport, "auto") == 0) {
8269 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8270 } else if (strcmp(transport, "tls") == 0) {
8271 sip->transport = SIPE_TRANSPORT_TLS;
8272 } else if (strcmp(transport, "tcp") == 0) {
8273 sip->transport = SIPE_TRANSPORT_TCP;
8274 } else {
8275 sip->transport = SIPE_TRANSPORT_UDP;
8278 create_connection(sip, g_strdup(userserver[0]), port);
8279 } else {
8280 /* Server auto-discovery */
8281 if (strcmp(transport, "auto") == 0) {
8282 sip->auto_transport = TRUE;
8283 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8284 current_service++;
8285 resolve_next_service(sip, current_service);
8286 } else {
8287 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8289 } else if (strcmp(transport, "tls") == 0) {
8290 resolve_next_service(sip, service_tls);
8291 } else if (strcmp(transport, "tcp") == 0) {
8292 resolve_next_service(sip, service_tcp);
8293 } else {
8294 resolve_next_service(sip, service_udp);
8297 g_strfreev(userserver);
8300 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8302 connection_free_all(sip);
8304 g_free(sip->epid);
8305 sip->epid = NULL;
8307 if (sip->query_data != NULL)
8308 purple_dnsquery_destroy(sip->query_data);
8309 sip->query_data = NULL;
8311 if (sip->srv_query_data != NULL)
8312 purple_srv_cancel(sip->srv_query_data);
8313 sip->srv_query_data = NULL;
8315 if (sip->listen_data != NULL)
8316 purple_network_listen_cancel(sip->listen_data);
8317 sip->listen_data = NULL;
8319 if (sip->gsc != NULL)
8320 purple_ssl_close(sip->gsc);
8321 sip->gsc = NULL;
8323 sipe_auth_free(&sip->registrar);
8324 sipe_auth_free(&sip->proxy);
8326 if (sip->txbuf)
8327 purple_circ_buffer_destroy(sip->txbuf);
8328 sip->txbuf = NULL;
8330 g_free(sip->realhostname);
8331 sip->realhostname = NULL;
8333 g_free(sip->server_version);
8334 sip->server_version = NULL;
8336 if (sip->listenpa)
8337 purple_input_remove(sip->listenpa);
8338 sip->listenpa = 0;
8339 if (sip->tx_handler)
8340 purple_input_remove(sip->tx_handler);
8341 sip->tx_handler = 0;
8342 if (sip->resendtimeout)
8343 purple_timeout_remove(sip->resendtimeout);
8344 sip->resendtimeout = 0;
8345 if (sip->timeouts) {
8346 GSList *entry = sip->timeouts;
8347 while (entry) {
8348 struct scheduled_action *sched_action = entry->data;
8349 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8350 purple_timeout_remove(sched_action->timeout_handler);
8351 if (sched_action->destroy) {
8352 (*sched_action->destroy)(sched_action->payload);
8354 g_free(sched_action->name);
8355 g_free(sched_action);
8356 entry = entry->next;
8359 g_slist_free(sip->timeouts);
8361 if (sip->allow_events) {
8362 GSList *entry = sip->allow_events;
8363 while (entry) {
8364 g_free(entry->data);
8365 entry = entry->next;
8368 g_slist_free(sip->allow_events);
8370 if (sip->containers) {
8371 GSList *entry = sip->containers;
8372 while (entry) {
8373 free_container((struct sipe_container *)entry->data);
8374 entry = entry->next;
8377 g_slist_free(sip->containers);
8379 if (sip->contact)
8380 g_free(sip->contact);
8381 sip->contact = NULL;
8382 if (sip->regcallid)
8383 g_free(sip->regcallid);
8384 sip->regcallid = NULL;
8386 if (sip->serveraddr)
8387 g_free(sip->serveraddr);
8388 sip->serveraddr = NULL;
8390 if (sip->focus_factory_uri)
8391 g_free(sip->focus_factory_uri);
8392 sip->focus_factory_uri = NULL;
8394 sip->fd = -1;
8395 sip->processing_input = FALSE;
8397 if (sip->ews) {
8398 sipe_ews_free(sip->ews);
8400 sip->ews = NULL;
8404 * A callback for g_hash_table_foreach_remove
8406 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8407 SIPE_UNUSED_PARAMETER gpointer user_data)
8409 sipe_free_buddy((struct sipe_buddy *) buddy);
8411 /* We must return TRUE as the key/value have already been deleted */
8412 return(TRUE);
8415 static void sipe_close(PurpleConnection *gc)
8417 struct sipe_account_data *sip = gc->proto_data;
8419 if (sip) {
8420 /* leave all conversations */
8421 sipe_session_close_all(sip);
8422 sipe_session_remove_all(sip);
8424 if (sip->csta) {
8425 sip_csta_close(sip);
8428 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8429 /* unsubscribe all */
8430 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8432 /* unregister */
8433 do_register_exp(sip, 0);
8436 sipe_connection_cleanup(sip);
8437 g_free(sip->sipdomain);
8438 g_free(sip->username);
8439 g_free(sip->email);
8440 g_free(sip->password);
8441 g_free(sip->authdomain);
8442 g_free(sip->authuser);
8443 g_free(sip->status);
8444 g_free(sip->note);
8446 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8447 g_hash_table_destroy(sip->buddies);
8448 g_hash_table_destroy(sip->our_publications);
8449 g_hash_table_destroy(sip->user_state_publications);
8450 g_hash_table_destroy(sip->subscriptions);
8452 if (sip->groups) {
8453 GSList *entry = sip->groups;
8454 while (entry) {
8455 struct sipe_group *group = entry->data;
8456 g_free(group->name);
8457 g_free(group);
8458 entry = entry->next;
8461 g_slist_free(sip->groups);
8463 if (sip->our_publication_keys) {
8464 GSList *entry = sip->our_publication_keys;
8465 while (entry) {
8466 g_free(entry->data);
8467 entry = entry->next;
8470 g_slist_free(sip->our_publication_keys);
8472 while (sip->transactions)
8473 transactions_remove(sip, sip->transactions->data);
8475 g_free(gc->proto_data);
8476 gc->proto_data = NULL;
8479 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8480 SIPE_UNUSED_PARAMETER void *user_data)
8482 PurpleAccount *acct = purple_connection_get_account(gc);
8483 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8484 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8485 if (conv == NULL)
8486 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8487 purple_conversation_present(conv);
8488 g_free(id);
8491 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8492 SIPE_UNUSED_PARAMETER void *user_data)
8495 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8496 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8499 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8500 SIPE_UNUSED_PARAMETER struct transaction *trans)
8502 PurpleNotifySearchResults *results;
8503 PurpleNotifySearchColumn *column;
8504 xmlnode *searchResults;
8505 xmlnode *mrow;
8506 int match_count = 0;
8507 gboolean more = FALSE;
8508 gchar *secondary;
8510 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8512 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8513 if (!searchResults) {
8514 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8515 return FALSE;
8518 results = purple_notify_searchresults_new();
8520 if (results == NULL) {
8521 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8522 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8524 xmlnode_free(searchResults);
8525 return FALSE;
8528 column = purple_notify_searchresults_column_new(_("User name"));
8529 purple_notify_searchresults_column_add(results, column);
8531 column = purple_notify_searchresults_column_new(_("Name"));
8532 purple_notify_searchresults_column_add(results, column);
8534 column = purple_notify_searchresults_column_new(_("Company"));
8535 purple_notify_searchresults_column_add(results, column);
8537 column = purple_notify_searchresults_column_new(_("Country"));
8538 purple_notify_searchresults_column_add(results, column);
8540 column = purple_notify_searchresults_column_new(_("Email"));
8541 purple_notify_searchresults_column_add(results, column);
8543 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8544 GList *row = NULL;
8546 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8547 row = g_list_append(row, g_strdup(uri_parts[1]));
8548 g_strfreev(uri_parts);
8550 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8551 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8552 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8553 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8555 purple_notify_searchresults_row_add(results, row);
8556 match_count++;
8559 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8560 char *data = xmlnode_get_data_unescaped(mrow);
8561 more = (g_strcasecmp(data, "true") == 0);
8562 g_free(data);
8565 secondary = g_strdup_printf(
8566 dngettext(GETTEXT_PACKAGE,
8567 "Found %d contact%s:",
8568 "Found %d contacts%s:", match_count),
8569 match_count, more ? _(" (more matched your query)") : "");
8571 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8572 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8573 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8575 g_free(secondary);
8576 xmlnode_free(searchResults);
8577 return TRUE;
8580 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8582 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8583 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8584 unsigned i = 0;
8586 do {
8587 PurpleRequestField *field = entries->data;
8588 const char *id = purple_request_field_get_id(field);
8589 const char *value = purple_request_field_string_get_value(field);
8591 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8593 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8594 } while ((entries = g_list_next(entries)) != NULL);
8595 attrs[i] = NULL;
8597 if (i > 0) {
8598 struct sipe_account_data *sip = gc->proto_data;
8599 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8600 gchar *query = g_strjoinv(NULL, attrs);
8601 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8602 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8603 send_soap_request_with_cb(sip, domain_uri, body,
8604 (TransCallback) process_search_contact_response, NULL);
8605 g_free(domain_uri);
8606 g_free(body);
8607 g_free(query);
8610 g_strfreev(attrs);
8613 static void sipe_show_find_contact(PurplePluginAction *action)
8615 PurpleConnection *gc = (PurpleConnection *) action->context;
8616 PurpleRequestFields *fields;
8617 PurpleRequestFieldGroup *group;
8618 PurpleRequestField *field;
8620 fields = purple_request_fields_new();
8621 group = purple_request_field_group_new(NULL);
8622 purple_request_fields_add_group(fields, group);
8624 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8625 purple_request_field_group_add_field(group, field);
8626 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8627 purple_request_field_group_add_field(group, field);
8628 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8629 purple_request_field_group_add_field(group, field);
8630 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8631 purple_request_field_group_add_field(group, field);
8633 purple_request_fields(gc,
8634 _("Search"),
8635 _("Search for a contact"),
8636 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8637 fields,
8638 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8639 _("_Cancel"), NULL,
8640 purple_connection_get_account(gc), NULL, NULL, gc);
8643 static void sipe_show_about_plugin(PurplePluginAction *action)
8645 PurpleConnection *gc = (PurpleConnection *) action->context;
8646 char *tmp = g_strdup_printf(
8648 * Non-translatable parts, like markup, are hard-coded
8649 * into the format string. This requires more translatable
8650 * texts but it makes the translations less error prone.
8652 "<b><font size=\"+1\">SIPE " SIPE_VERSION " </font></b><br/>"
8653 "<br/>"
8654 /* 1 */ "%s:<br/>"
8655 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8656 "<li> - MS Office Communications Server 2007</li><br/>"
8657 "<li> - MS Live Communications Server 2005</li><br/>"
8658 "<li> - MS Live Communications Server 2003</li><br/>"
8659 "<li> - Reuters Messaging</li><br/>"
8660 "<br/>"
8661 /* 2 */ "%s: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8662 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8663 /* 5 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8664 /* 6 */ "%s: GPLv2+<br/>"
8665 "<br/>"
8666 /* 7 */ "%s:<br/>"
8667 " - CERN<br/>"
8668 " - Reuters Messaging network<br/>"
8669 " - Deutsche Bank<br/>"
8670 " - Merrill Lynch<br/>"
8671 " - Wachovia<br/>"
8672 " - Intel<br/>"
8673 " - Nokia<br/>"
8674 " - HP<br/>"
8675 " - Siemens<br/>"
8676 " - Alcatel-Lucent<br/>"
8677 " - BT<br/>"
8678 "<br/>"
8679 /* 8,9 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
8680 "<br/>"
8681 /* 10 */ "<b>%s:</b><br/>"
8682 " - Anibal Avelar<br/>"
8683 " - Gabriel Burt<br/>"
8684 " - Stefan Becker<br/>"
8685 " - pier11<br/>"
8686 "<br/>"
8687 /* 11 */ "%s<br/>"
8689 /* The next 11 texts make up the SIPE about note text */
8690 /* About note, part 1/11: introduction */
8691 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
8692 /* About note, part 2/11: home page URL (label) */
8693 _("Home"),
8694 /* About note, part 3/11: support forum URL (label) */
8695 _("Support"),
8696 /* About note, part 4/11: support forum name (hyperlink text) */
8697 _("Help Forum"),
8698 /* About note, part 5/11: translation service URL (label) */
8699 _("Translations"),
8700 /* About note, part 6/11: license type (label) */
8701 _("License"),
8702 /* About note, part 7/11: known users */
8703 _("We support users in such organizations as"),
8704 /* About note, part 8/11: translation request, text before Transifex.net URL */
8705 /* append a space if text is not empty */
8706 _("Please help us to translate SIPE to your native language here at "),
8707 /* About note, part 9/11: translation request, text after Transifex.net URL */
8708 /* start with a space if text is not empty */
8709 _(" using convenient web interface"),
8710 /* About note, part 10/11: author list (header) */
8711 _("Authors"),
8712 /* About note, part 11/11: Localization credit */
8713 /* PLEASE NOTE: do *NOT* simply translate the english original */
8714 /* but write something similar to the following sentence: */
8715 /* "Localization for <language name> (<language code>): <name>" */
8716 _("Original texts in English (en): SIPE developers")
8718 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
8719 g_free(tmp);
8722 static void sipe_republish_calendar(PurplePluginAction *action)
8724 PurpleConnection *gc = (PurpleConnection *) action->context;
8725 struct sipe_account_data *sip = gc->proto_data;
8727 sipe_update_calendar(sip);
8730 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
8731 gpointer value,
8732 GString* str)
8734 struct sipe_publication *publication = value;
8736 g_string_append_printf( str,
8737 SIPE_PUB_XML_PUBLICATION_CLEAR,
8738 publication->category,
8739 publication->instance,
8740 publication->container,
8741 publication->version,
8742 "static");
8745 static void sipe_reset_status(PurplePluginAction *action)
8747 PurpleConnection *gc = (PurpleConnection *) action->context;
8748 struct sipe_account_data *sip = gc->proto_data;
8750 if (sip->ocs2007) /* 2007+ */
8752 GString* str = g_string_new(NULL);
8753 gchar *publications;
8755 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
8756 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8757 return;
8760 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
8761 publications = g_string_free(str, FALSE);
8763 send_presence_publish(sip, publications);
8764 g_free(publications);
8766 else /* 2005 */
8768 send_presence_soap0(sip, FALSE, TRUE);
8772 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
8773 gpointer context)
8775 PurpleConnection *gc = (PurpleConnection *)context;
8776 struct sipe_account_data *sip = gc->proto_data;
8777 GList *menu = NULL;
8778 PurplePluginAction *act;
8779 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
8781 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
8782 menu = g_list_prepend(menu, act);
8784 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
8785 menu = g_list_prepend(menu, act);
8787 if (!strcmp(calendar, "EXCH")) {
8788 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
8789 menu = g_list_prepend(menu, act);
8792 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
8793 menu = g_list_prepend(menu, act);
8795 menu = g_list_reverse(menu);
8797 return menu;
8800 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
8804 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8806 return TRUE;
8810 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8812 return TRUE;
8816 static char *sipe_status_text(PurpleBuddy *buddy)
8818 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8819 const PurpleStatus *status = purple_presence_get_active_status(presence);
8820 const char *status_id = purple_status_get_id(status);
8821 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
8822 struct sipe_buddy *sbuddy;
8823 char *text = NULL;
8825 if (!sip) return NULL; /* happens on pidgin exit */
8827 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8828 if (sbuddy) {
8829 const char *activity_str = sbuddy->activity ?
8830 sbuddy->activity :
8831 !strcmp(status_id, SIPE_STATUS_ID_BUSY) || !strcmp(status_id, SIPE_STATUS_ID_BRB) ?
8832 purple_status_get_name(status) : NULL;
8834 if (activity_str && sbuddy->note)
8836 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
8838 else if (activity_str)
8840 text = g_strdup(activity_str);
8842 else if (sbuddy->note)
8844 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
8848 return text;
8851 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
8853 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8854 const PurpleStatus *status = purple_presence_get_active_status(presence);
8855 struct sipe_account_data *sip;
8856 struct sipe_buddy *sbuddy;
8857 char *note = NULL;
8858 gboolean is_oof_note = FALSE;
8859 char *activity = NULL;
8860 char *calendar = NULL;
8861 char *meeting_subject = NULL;
8862 char *meeting_location = NULL;
8864 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
8865 if (sip) //happens on pidgin exit
8867 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8868 if (sbuddy)
8870 note = sbuddy->note;
8871 is_oof_note = sbuddy->is_oof_note;
8872 activity = sbuddy->activity;
8873 calendar = sipe_cal_get_description(sbuddy);
8874 meeting_subject = sbuddy->meeting_subject;
8875 meeting_location = sbuddy->meeting_location;
8879 //Layout
8880 if (purple_presence_is_online(presence))
8882 const char *status_str = activity ? activity : purple_status_get_name(status);
8884 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
8886 if (purple_presence_is_online(presence) &&
8887 !is_empty(calendar))
8889 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
8891 g_free(calendar);
8892 if (!is_empty(meeting_location))
8894 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
8896 if (!is_empty(meeting_subject))
8898 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
8901 if (note)
8903 char *tmp = g_strdup_printf("<i>%s</i>", note);
8904 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
8906 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
8907 g_free(tmp);
8912 #if PURPLE_VERSION_CHECK(2,5,0)
8913 static GHashTable *
8914 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
8916 GHashTable *table;
8917 table = g_hash_table_new(g_str_hash, g_str_equal);
8918 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
8919 return table;
8921 #endif
8923 static PurpleBuddy *
8924 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
8926 PurpleBuddy *clone;
8927 const gchar *server_alias, *email;
8928 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
8930 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
8932 purple_blist_add_buddy(clone, NULL, group, NULL);
8934 server_alias = purple_buddy_get_server_alias(buddy);
8935 if (server_alias) {
8936 purple_blist_server_alias_buddy(clone, server_alias);
8939 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8940 if (email) {
8941 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
8944 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
8945 //for UI to update;
8946 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
8947 return clone;
8950 static void
8951 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
8953 PurpleBuddy *buddy, *b;
8954 PurpleConnection *gc;
8955 PurpleGroup * group = purple_find_group(group_name);
8957 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
8959 buddy = (PurpleBuddy *)node;
8961 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
8962 gc = purple_account_get_connection(buddy->account);
8964 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
8965 if (!b){
8966 purple_blist_add_buddy_clone(group, buddy);
8969 sipe_group_buddy(gc, buddy->name, NULL, group_name);
8972 static void
8973 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
8975 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8977 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
8979 /* 2007+ conference */
8980 if (sip->ocs2007)
8982 sipe_conf_add(sip, buddy->name);
8984 else /* 2005- multiparty chat */
8986 gchar *self = sip_uri_self(sip);
8987 struct sip_session *session;
8989 session = sipe_session_add_chat(sip);
8990 session->chat_title = sipe_chat_get_name(session->callid);
8991 session->roster_manager = g_strdup(self);
8993 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
8994 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
8995 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
8996 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
8998 g_free(self);
9002 static gboolean
9003 sipe_is_election_finished(struct sip_session *session)
9005 gboolean res = TRUE;
9007 SIPE_DIALOG_FOREACH {
9008 if (dialog->election_vote == 0) {
9009 res = FALSE;
9010 break;
9012 } SIPE_DIALOG_FOREACH_END;
9014 if (res) {
9015 session->is_voting_in_progress = FALSE;
9017 return res;
9020 static void
9021 sipe_election_start(struct sipe_account_data *sip,
9022 struct sip_session *session)
9024 int election_timeout;
9026 if (session->is_voting_in_progress) {
9027 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9028 return;
9029 } else {
9030 session->is_voting_in_progress = TRUE;
9032 session->bid = rand();
9034 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9036 SIPE_DIALOG_FOREACH {
9037 /* reset election_vote for each chat participant */
9038 dialog->election_vote = 0;
9040 /* send RequestRM to each chat participant*/
9041 sipe_send_election_request_rm(sip, dialog, session->bid);
9042 } SIPE_DIALOG_FOREACH_END;
9044 election_timeout = 15; /* sec */
9045 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9049 * @param who a URI to whom to invite to chat
9051 void
9052 sipe_invite_to_chat(struct sipe_account_data *sip,
9053 struct sip_session *session,
9054 const gchar *who)
9056 /* a conference */
9057 if (session->focus_uri)
9059 sipe_invite_conf(sip, session, who);
9061 else /* a multi-party chat */
9063 gchar *self = sip_uri_self(sip);
9064 if (session->roster_manager) {
9065 if (!strcmp(session->roster_manager, self)) {
9066 sipe_invite(sip, session, who, NULL, NULL, FALSE);
9067 } else {
9068 sipe_refer(sip, session, who);
9070 } else {
9071 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9073 session->pending_invite_queue = slist_insert_unique_sorted(
9074 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9076 sipe_election_start(sip, session);
9078 g_free(self);
9082 void
9083 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9084 struct sip_session *session)
9086 gchar *invitee;
9087 GSList *entry = session->pending_invite_queue;
9089 while (entry) {
9090 invitee = entry->data;
9091 sipe_invite_to_chat(sip, session, invitee);
9092 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9093 g_free(invitee);
9097 static void
9098 sipe_election_result(struct sipe_account_data *sip,
9099 void *sess)
9101 struct sip_session *session = (struct sip_session *)sess;
9102 gchar *rival;
9103 gboolean has_won = TRUE;
9105 if (session->roster_manager) {
9106 purple_debug_info("sipe",
9107 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9108 return;
9111 session->is_voting_in_progress = FALSE;
9113 SIPE_DIALOG_FOREACH {
9114 if (dialog->election_vote < 0) {
9115 has_won = FALSE;
9116 rival = dialog->with;
9117 break;
9119 } SIPE_DIALOG_FOREACH_END;
9121 if (has_won) {
9122 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9124 session->roster_manager = sip_uri_self(sip);
9126 SIPE_DIALOG_FOREACH {
9127 /* send SetRM to each chat participant*/
9128 sipe_send_election_set_rm(sip, dialog);
9129 } SIPE_DIALOG_FOREACH_END;
9130 } else {
9131 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9133 session->bid = 0;
9135 sipe_process_pending_invite_queue(sip, session);
9139 * For 2007+ conference only.
9141 static void
9142 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9144 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9145 struct sip_session *session;
9147 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9148 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9150 session = sipe_session_find_chat_by_title(sip, chat_title);
9152 sipe_conf_modify_user_role(sip, session, buddy->name);
9156 * For 2007+ conference only.
9158 static void
9159 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9161 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9162 struct sip_session *session;
9164 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9165 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9167 session = sipe_session_find_chat_by_title(sip, chat_title);
9169 sipe_conf_delete_user(sip, session, buddy->name);
9172 static void
9173 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9175 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9176 struct sip_session *session;
9178 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9179 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9181 session = sipe_session_find_chat_by_title(sip, chat_title);
9183 sipe_invite_to_chat(sip, session, buddy->name);
9186 static void
9187 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9189 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9191 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9192 if (phone) {
9193 char *tel_uri = sip_to_tel_uri(phone);
9195 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9196 sip_csta_make_call(sip, tel_uri);
9198 g_free(tel_uri);
9202 static void
9203 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9205 const gchar *email;
9206 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9208 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9209 if (email)
9211 char *mailto = g_strdup_printf("mailto:%s", email);
9212 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9213 #ifndef _WIN32
9215 pid_t pid;
9216 char *const parmList[] = {"xdg-email", mailto, NULL};
9217 if ((pid = fork()) == -1)
9219 purple_debug_info("sipe", "fork() error\n");
9221 else if (pid == 0)
9223 execvp(parmList[0], parmList);
9224 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9227 #else
9229 BOOL ret;
9230 _flushall();
9231 errno = 0;
9232 //@TODO resolve env variable %WINDIR% first
9233 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9234 if (errno)
9236 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9239 #endif
9241 g_free(mailto);
9243 else
9245 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9250 * A menu which appear when right-clicking on buddy in contact list.
9252 static GList *
9253 sipe_buddy_menu(PurpleBuddy *buddy)
9255 PurpleBlistNode *g_node;
9256 PurpleGroup *group, *gr_parent;
9257 PurpleMenuAction *act;
9258 GList *menu = NULL;
9259 GList *menu_groups = NULL;
9260 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9261 const char *email;
9262 const char *phone;
9263 const char *phone_disp_str;
9264 gchar *self = sip_uri_self(sip);
9266 SIPE_SESSION_FOREACH {
9267 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9269 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9271 PurpleConvChatBuddyFlags flags;
9272 PurpleConvChatBuddyFlags flags_us;
9274 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9275 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9276 if (session->focus_uri
9277 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9278 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9280 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9281 act = purple_menu_action_new(label,
9282 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9283 session->chat_title, NULL);
9284 g_free(label);
9285 menu = g_list_prepend(menu, act);
9288 if (session->focus_uri
9289 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9291 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9292 act = purple_menu_action_new(label,
9293 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9294 session->chat_title, NULL);
9295 g_free(label);
9296 menu = g_list_prepend(menu, act);
9299 else
9301 if (!session->focus_uri
9302 || (session->focus_uri && !session->locked))
9304 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9305 act = purple_menu_action_new(label,
9306 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9307 session->chat_title, NULL);
9308 g_free(label);
9309 menu = g_list_prepend(menu, act);
9313 } SIPE_SESSION_FOREACH_END;
9315 act = purple_menu_action_new(_("New chat"),
9316 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9317 NULL, NULL);
9318 menu = g_list_prepend(menu, act);
9320 if (sip->csta && !sip->csta->line_status) {
9321 gchar *tmp = NULL;
9322 /* work phone */
9323 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9324 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9325 if (phone) {
9326 gchar *label = g_strdup_printf(_("Work %s"),
9327 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9328 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9329 g_free(tmp);
9330 tmp = NULL;
9331 g_free(label);
9332 menu = g_list_prepend(menu, act);
9335 /* mobile phone */
9336 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9337 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9338 if (phone) {
9339 gchar *label = g_strdup_printf(_("Mobile %s"),
9340 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9341 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9342 g_free(tmp);
9343 tmp = NULL;
9344 g_free(label);
9345 menu = g_list_prepend(menu, act);
9348 /* home phone */
9349 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9350 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9351 if (phone) {
9352 gchar *label = g_strdup_printf(_("Home %s"),
9353 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9354 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9355 g_free(tmp);
9356 tmp = NULL;
9357 g_free(label);
9358 menu = g_list_prepend(menu, act);
9361 /* other phone */
9362 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9363 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9364 if (phone) {
9365 gchar *label = g_strdup_printf(_("Other %s"),
9366 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9367 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9368 g_free(tmp);
9369 tmp = NULL;
9370 g_free(label);
9371 menu = g_list_prepend(menu, act);
9374 /* custom1 phone */
9375 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9376 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9377 if (phone) {
9378 gchar *label = g_strdup_printf(_("Custom1 %s"),
9379 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9380 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9381 g_free(tmp);
9382 tmp = NULL;
9383 g_free(label);
9384 menu = g_list_prepend(menu, act);
9388 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9389 if (email) {
9390 act = purple_menu_action_new(_("Send email..."),
9391 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9392 NULL, NULL);
9393 menu = g_list_prepend(menu, act);
9396 gr_parent = purple_buddy_get_group(buddy);
9397 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9398 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9399 continue;
9401 group = (PurpleGroup *)g_node;
9402 if (group == gr_parent)
9403 continue;
9405 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9406 continue;
9408 act = purple_menu_action_new(purple_group_get_name(group),
9409 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9410 group->name, NULL);
9411 menu_groups = g_list_prepend(menu_groups, act);
9413 menu_groups = g_list_reverse(menu_groups);
9415 act = purple_menu_action_new(_("Copy to"),
9416 NULL,
9417 NULL, menu_groups);
9418 menu = g_list_prepend(menu, act);
9419 menu = g_list_reverse(menu);
9421 g_free(self);
9422 return menu;
9425 static void
9426 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9428 struct sipe_account_data *sip = chat->account->gc->proto_data;
9429 struct sip_session *session;
9431 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9432 sipe_conf_modify_conference_lock(sip, session, locked);
9435 static void
9436 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9438 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9439 sipe_conf_modify_lock(chat, FALSE);
9442 static void
9443 sipe_chat_menu_lock_cb(PurpleChat *chat)
9445 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9446 sipe_conf_modify_lock(chat, TRUE);
9449 static GList *
9450 sipe_chat_menu(PurpleChat *chat)
9452 PurpleMenuAction *act;
9453 PurpleConvChatBuddyFlags flags_us;
9454 GList *menu = NULL;
9455 struct sipe_account_data *sip = chat->account->gc->proto_data;
9456 struct sip_session *session;
9457 gchar *self;
9459 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9460 if (!session) return NULL;
9462 self = sip_uri_self(sip);
9463 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9465 if (session->focus_uri
9466 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9468 if (session->locked) {
9469 act = purple_menu_action_new(_("Unlock"),
9470 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9471 NULL, NULL);
9472 menu = g_list_prepend(menu, act);
9473 } else {
9474 act = purple_menu_action_new(_("Lock"),
9475 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9476 NULL, NULL);
9477 menu = g_list_prepend(menu, act);
9481 menu = g_list_reverse(menu);
9483 g_free(self);
9484 return menu;
9487 static GList *
9488 sipe_blist_node_menu(PurpleBlistNode *node)
9490 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9491 return sipe_buddy_menu((PurpleBuddy *) node);
9492 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9493 return sipe_chat_menu((PurpleChat *)node);
9494 } else {
9495 return NULL;
9499 static gboolean
9500 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9502 char *uri = trans->payload->data;
9504 PurpleNotifyUserInfo *info;
9505 PurpleBuddy *pbuddy = NULL;
9506 struct sipe_buddy *sbuddy;
9507 const char *alias = NULL;
9508 char *device_name = NULL;
9509 char *server_alias = NULL;
9510 char *phone_number = NULL;
9511 char *email = NULL;
9512 const char *site;
9514 if (!sip) return FALSE;
9516 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9518 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9519 alias = purple_buddy_get_local_alias(pbuddy);
9521 //will query buddy UA's capabilities and send answer to log
9522 sipe_options_request(sip, uri);
9524 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9525 if (sbuddy) {
9526 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9529 info = purple_notify_user_info_new();
9531 if (msg->response != 200) {
9532 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9533 } else {
9534 xmlnode *searchResults;
9535 xmlnode *mrow;
9537 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9538 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9539 if (!searchResults) {
9540 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9541 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9542 const char *value;
9543 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9544 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9545 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9547 /* For 2007 system we will take this from ContactCard -
9548 * it has cleaner tel: URIs at least
9550 if (!sip->ocs2007) {
9551 char *tel_uri = sip_to_tel_uri(phone_number);
9552 /* trims its parameters, so call first */
9553 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9554 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9555 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9556 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9557 g_free(tel_uri);
9560 if (server_alias && strlen(server_alias) > 0) {
9561 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9563 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9564 purple_notify_user_info_add_pair(info, _("Job title"), value);
9566 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9567 purple_notify_user_info_add_pair(info, _("Office"), value);
9569 if (phone_number && strlen(phone_number) > 0) {
9570 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9572 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9573 purple_notify_user_info_add_pair(info, _("Company"), value);
9575 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9576 purple_notify_user_info_add_pair(info, _("City"), value);
9578 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9579 purple_notify_user_info_add_pair(info, _("State"), value);
9581 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9582 purple_notify_user_info_add_pair(info, _("Country"), value);
9584 if (email && strlen(email) > 0) {
9585 purple_notify_user_info_add_pair(info, _("Email address"), email);
9589 xmlnode_free(searchResults);
9592 purple_notify_user_info_add_section_break(info);
9594 if (!server_alias || !strcmp("", server_alias)) {
9595 g_free(server_alias);
9596 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9597 if (server_alias) {
9598 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9602 /* present alias if it differs from server alias */
9603 if (alias && (!server_alias || strcmp(alias, server_alias)))
9605 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9608 if (!email || !strcmp("", email)) {
9609 g_free(email);
9610 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9611 if (email) {
9612 purple_notify_user_info_add_pair(info, _("Email address"), email);
9616 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9617 if (site) {
9618 purple_notify_user_info_add_pair(info, _("Site"), site);
9621 if (device_name) {
9622 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9625 /* show a buddy's user info in a nice dialog box */
9626 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9627 uri, /* buddy's URI */
9628 info, /* body */
9629 NULL, /* callback called when dialog closed */
9630 NULL); /* userdata for callback */
9632 g_free(phone_number);
9633 g_free(server_alias);
9634 g_free(email);
9635 g_free(device_name);
9637 return TRUE;
9641 * AD search first, LDAP based
9643 static void sipe_get_info(PurpleConnection *gc, const char *username)
9645 struct sipe_account_data *sip = gc->proto_data;
9646 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9647 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9648 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9649 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9651 payload->destroy = g_free;
9652 payload->data = g_strdup(username);
9654 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9655 send_soap_request_with_cb(sip, domain_uri, body,
9656 (TransCallback) process_get_info_response, payload);
9657 g_free(domain_uri);
9658 g_free(body);
9659 g_free(row);
9662 static PurplePlugin *my_protocol = NULL;
9664 static PurplePluginProtocolInfo prpl_info =
9666 OPT_PROTO_CHAT_TOPIC,
9667 NULL, /* user_splits */
9668 NULL, /* protocol_options */
9669 NO_BUDDY_ICONS, /* icon_spec */
9670 sipe_list_icon, /* list_icon */
9671 NULL, /* list_emblems */
9672 sipe_status_text, /* status_text */
9673 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9674 sipe_status_types, /* away_states */
9675 sipe_blist_node_menu, /* blist_node_menu */
9676 NULL, /* chat_info */
9677 NULL, /* chat_info_defaults */
9678 sipe_login, /* login */
9679 sipe_close, /* close */
9680 sipe_im_send, /* send_im */
9681 NULL, /* set_info */ // TODO maybe
9682 sipe_send_typing, /* send_typing */
9683 sipe_get_info, /* get_info */
9684 sipe_set_status, /* set_status */
9685 sipe_set_idle, /* set_idle */
9686 NULL, /* change_passwd */
9687 sipe_add_buddy, /* add_buddy */
9688 NULL, /* add_buddies */
9689 sipe_remove_buddy, /* remove_buddy */
9690 NULL, /* remove_buddies */
9691 sipe_add_permit, /* add_permit */
9692 sipe_add_deny, /* add_deny */
9693 sipe_add_deny, /* rem_permit */
9694 sipe_add_permit, /* rem_deny */
9695 dummy_permit_deny, /* set_permit_deny */
9696 NULL, /* join_chat */
9697 NULL, /* reject_chat */
9698 NULL, /* get_chat_name */
9699 sipe_chat_invite, /* chat_invite */
9700 sipe_chat_leave, /* chat_leave */
9701 NULL, /* chat_whisper */
9702 sipe_chat_send, /* chat_send */
9703 sipe_keep_alive, /* keepalive */
9704 NULL, /* register_user */
9705 NULL, /* get_cb_info */ // deprecated
9706 NULL, /* get_cb_away */ // deprecated
9707 sipe_alias_buddy, /* alias_buddy */
9708 sipe_group_buddy, /* group_buddy */
9709 sipe_rename_group, /* rename_group */
9710 NULL, /* buddy_free */
9711 sipe_convo_closed, /* convo_closed */
9712 purple_normalize_nocase, /* normalize */
9713 NULL, /* set_buddy_icon */
9714 sipe_remove_group, /* remove_group */
9715 NULL, /* get_cb_real_name */ // TODO?
9716 NULL, /* set_chat_topic */
9717 NULL, /* find_blist_chat */
9718 NULL, /* roomlist_get_list */
9719 NULL, /* roomlist_cancel */
9720 NULL, /* roomlist_expand_category */
9721 NULL, /* can_receive_file */
9722 NULL, /* send_file */
9723 NULL, /* new_xfer */
9724 NULL, /* offline_message */
9725 NULL, /* whiteboard_prpl_ops */
9726 sipe_send_raw, /* send_raw */
9727 NULL, /* roomlist_room_serialize */
9728 NULL, /* unregister_user */
9729 NULL, /* send_attention */
9730 NULL, /* get_attention_types */
9731 #if !PURPLE_VERSION_CHECK(2,5,0)
9732 /* Backward compatibility when compiling against 2.4.x API */
9733 (void (*)(void)) /* _purple_reserved4 */
9734 #endif
9735 sizeof(PurplePluginProtocolInfo), /* struct_size */
9736 #if PURPLE_VERSION_CHECK(2,5,0)
9737 sipe_get_account_text_table, /* get_account_text_table */
9738 #if PURPLE_VERSION_CHECK(2,6,0)
9739 NULL, /* initiate_media */
9740 NULL, /* get_media_caps */
9741 #endif
9742 #endif
9746 static PurplePluginInfo info = {
9747 PURPLE_PLUGIN_MAGIC,
9748 PURPLE_MAJOR_VERSION,
9749 PURPLE_MINOR_VERSION,
9750 PURPLE_PLUGIN_PROTOCOL, /**< type */
9751 NULL, /**< ui_requirement */
9752 0, /**< flags */
9753 NULL, /**< dependencies */
9754 PURPLE_PRIORITY_DEFAULT, /**< priority */
9755 "prpl-sipe", /**< id */
9756 "Office Communicator", /**< name */
9757 SIPE_VERSION, /**< version */
9758 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9759 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9760 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9761 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9762 "Gabriel Burt <gburt@novell.com>, " /**< author */
9763 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9764 "pier11 <pier11@operamail.com>", /**< author */
9765 "http://sipe.sourceforge.net/", /**< homepage */
9766 sipe_plugin_load, /**< load */
9767 sipe_plugin_unload, /**< unload */
9768 sipe_plugin_destroy, /**< destroy */
9769 NULL, /**< ui_info */
9770 &prpl_info, /**< extra_info */
9771 NULL,
9772 sipe_actions,
9773 NULL,
9774 NULL,
9775 NULL,
9776 NULL
9779 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9781 GList *entry;
9783 entry = prpl_info.protocol_options;
9784 while (entry) {
9785 purple_account_option_destroy(entry->data);
9786 entry = g_list_delete_link(entry, entry);
9788 prpl_info.protocol_options = NULL;
9790 entry = prpl_info.user_splits;
9791 while (entry) {
9792 purple_account_user_split_destroy(entry->data);
9793 entry = g_list_delete_link(entry, entry);
9795 prpl_info.user_splits = NULL;
9798 static void init_plugin(PurplePlugin *plugin)
9800 PurpleAccountUserSplit *split;
9801 PurpleAccountOption *option;
9803 srand(time(NULL));
9805 #ifdef ENABLE_NLS
9806 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
9807 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
9808 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
9809 textdomain(GETTEXT_PACKAGE);
9810 #endif
9812 purple_plugin_register(plugin);
9814 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
9815 purple_account_user_split_set_reverse(split, FALSE);
9816 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
9818 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
9819 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9821 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
9822 purple_account_option_add_list_item(option, _("Auto"), "auto");
9823 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
9824 purple_account_option_add_list_item(option, _("TCP"), "tcp");
9825 purple_account_option_add_list_item(option, _("UDP"), "udp");
9826 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9828 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
9829 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
9831 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
9832 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9834 #ifdef USE_KERBEROS
9835 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
9836 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9838 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
9839 * No login/password is taken into account if this option present,
9840 * instead used default credentials stored in OS.
9842 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
9843 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9844 #endif
9846 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
9847 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
9848 purple_account_option_add_list_item(option, _("None"), "NONE");
9849 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9851 /** Example: https://server.company.com/EWS/Exchange.asmx */
9852 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
9853 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9855 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
9856 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9858 /** Example: DOMAIN\user or user@company.com */
9859 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
9860 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9862 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
9863 purple_account_option_set_masked(option, TRUE);
9864 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9866 my_protocol = plugin;
9869 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
9872 Local Variables:
9873 mode: c
9874 c-file-style: "bsd"
9875 indent-tabs-mode: t
9876 tab-width: 8
9877 End: