presence-05: fix for OOF note propagation
[siplcs.git] / src / core / sipe.c
blob83ba57a3ab05a52bbf851553b03a5655ddeba4ea
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: 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);
2028 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2029 status_id, (int)sip->do_not_publish[activity], (int)now);
2031 sip->do_not_publish[activity] = 0;
2032 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2033 status_id, (int)sip->do_not_publish[activity]);
2035 if (do_not_publish)
2037 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2038 return;
2041 g_free(sip->status);
2042 sip->status = g_strdup(status_id);
2044 /* hack to escape apostrof before comparison */
2045 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2047 /* this will preserve OOF flag as well */
2048 if (!(tmp && sip->note && !strcmp(tmp, sip->note))) {
2049 sip->is_oof_note = FALSE;
2050 g_free(sip->note);
2051 sip->note = g_strdup(note);
2052 sip->note_since = time(NULL);
2054 g_free(tmp);
2056 /* schedule 2 sec to capture idle flag */
2057 action_name = g_strdup_printf("<%s>", "+set-status");
2058 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2059 g_free(action_name);
2063 static void
2064 sipe_set_idle(PurpleConnection * gc,
2065 int interval)
2067 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2069 if (gc) {
2070 struct sipe_account_data *sip = gc->proto_data;
2072 if (sip) {
2073 sip->idle_switch = time(NULL);
2074 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2079 static void
2080 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2081 SIPE_UNUSED_PARAMETER const char *alias)
2083 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2084 sipe_group_set_user(sip, name);
2087 static void
2088 sipe_group_buddy(PurpleConnection *gc,
2089 const char *who,
2090 const char *old_group_name,
2091 const char *new_group_name)
2093 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2094 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2095 struct sipe_group * old_group = NULL;
2096 struct sipe_group * new_group;
2098 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2099 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2101 if(!buddy) { // buddy not in roaming list
2102 return;
2105 if (old_group_name) {
2106 old_group = sipe_group_find_by_name(sip, old_group_name);
2108 new_group = sipe_group_find_by_name(sip, new_group_name);
2110 if (old_group) {
2111 buddy->groups = g_slist_remove(buddy->groups, old_group);
2112 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2115 if (!new_group) {
2116 sipe_group_create(sip, new_group_name, who);
2117 } else {
2118 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2119 sipe_group_set_user(sip, who);
2123 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2125 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2127 /* libpurple can call us with undefined buddy or group */
2128 if (buddy && group) {
2129 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2131 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2132 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2133 purple_blist_rename_buddy(buddy, buddy_name);
2134 g_free(buddy_name);
2136 /* Prepend sip: if needed */
2137 if (strncmp("sip:", buddy->name, 4)) {
2138 gchar *buf = sip_uri_from_name(buddy->name);
2139 purple_blist_rename_buddy(buddy, buf);
2140 g_free(buf);
2143 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2144 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2145 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2146 b->name = g_strdup(buddy->name);
2147 b->just_added = TRUE;
2148 g_hash_table_insert(sip->buddies, b->name, b);
2149 sipe_group_buddy(gc, b->name, NULL, group->name);
2150 /* @TODO should go to callback */
2151 sipe_subscribe_presence_single(sip, b->name);
2152 } else {
2153 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2158 static void sipe_free_buddy(struct sipe_buddy *buddy)
2160 #ifndef _WIN32
2162 * We are calling g_hash_table_foreach_steal(). That means that no
2163 * key/value deallocation functions are called. Therefore the glib
2164 * hash code does not touch the key (buddy->name) or value (buddy)
2165 * of the to-be-deleted hash node at all. It follows that we
2167 * - MUST free the memory for the key ourselves and
2168 * - ARE allowed to do it in this function
2170 * Conclusion: glib must be broken on the Windows platform if sipe
2171 * crashes with SIGTRAP when closing. You'll have to live
2172 * with the memory leak until this is fixed.
2174 g_free(buddy->name);
2175 #endif
2176 g_free(buddy->activity);
2177 g_free(buddy->meeting_subject);
2178 g_free(buddy->meeting_location);
2179 g_free(buddy->note);
2181 g_free(buddy->cal_start_time);
2182 g_free(buddy->cal_free_busy_base64);
2183 g_free(buddy->cal_free_busy);
2184 g_free(buddy->last_non_cal_activity);
2186 sipe_cal_free_working_hours(buddy->cal_working_hours);
2188 g_free(buddy->device_name);
2189 g_slist_free(buddy->groups);
2190 g_free(buddy);
2194 * Unassociates buddy from group first.
2195 * Then see if no groups left, removes buddy completely.
2196 * Otherwise updates buddy groups on server.
2198 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2200 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2201 struct sipe_buddy *b;
2202 struct sipe_group *g = NULL;
2204 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2205 if (!buddy) return;
2207 b = g_hash_table_lookup(sip->buddies, buddy->name);
2208 if (!b) return;
2210 if (group) {
2211 g = sipe_group_find_by_name(sip, group->name);
2214 if (g) {
2215 b->groups = g_slist_remove(b->groups, g);
2216 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2219 if (g_slist_length(b->groups) < 1) {
2220 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2221 sipe_cancel_scheduled_action(sip, action_name);
2222 g_free(action_name);
2224 g_hash_table_remove(sip->buddies, buddy->name);
2226 if (b->name) {
2227 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2228 send_soap_request(sip, body);
2229 g_free(body);
2232 sipe_free_buddy(b);
2233 } else {
2234 //updates groups on server
2235 sipe_group_set_user(sip, b->name);
2240 static void
2241 sipe_rename_group(PurpleConnection *gc,
2242 const char *old_name,
2243 PurpleGroup *group,
2244 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2246 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2247 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2248 if (s_group) {
2249 sipe_group_rename(sip, s_group, group->name);
2250 } else {
2251 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2255 static void
2256 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2258 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2259 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2260 if (s_group) {
2261 gchar *body;
2262 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2263 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2264 send_soap_request(sip, body);
2265 g_free(body);
2267 sip->groups = g_slist_remove(sip->groups, s_group);
2268 g_free(s_group->name);
2269 g_free(s_group);
2270 } else {
2271 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2275 /** All statuses need message attribute to pass Note */
2276 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2278 PurpleStatusType *type;
2279 GList *types = NULL;
2281 /* Macros to reduce code repetition.
2282 Translators: noun */
2283 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2284 prim, id, name, \
2285 TRUE, user, FALSE, \
2286 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2287 NULL); \
2288 types = g_list_append(types, type);
2290 /* Online */
2291 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2292 NULL,
2293 NULL,
2294 TRUE);
2296 /* Busy */
2297 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2298 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2299 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2300 TRUE);
2302 /* Do Not Disturb */
2303 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2304 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2305 NULL,
2306 TRUE);
2308 /* Away */
2309 /* Goes first in the list as
2310 * purple picks the first status with the AWAY type
2311 * for idle.
2313 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2314 NULL,
2315 NULL,
2316 TRUE);
2318 /* Be Right Back */
2319 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2320 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2321 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2322 TRUE);
2324 /* Appear Offline */
2325 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2326 NULL,
2327 NULL,
2328 TRUE);
2330 /* Offline (not user settable) */
2331 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE,
2332 NULL,
2333 NULL,
2334 FALSE);
2336 return types;
2340 * A callback for g_hash_table_foreach
2342 static void
2343 sipe_buddy_subscribe_cb(char *buddy_name,
2344 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2345 struct sipe_account_data *sip)
2347 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2348 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2349 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2350 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2352 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2353 g_free(action_name);
2357 * Removes entries from purple buddy list
2358 * that does not correspond ones in the roaming contact list.
2360 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2361 GSList *buddies = purple_find_buddies(sip->account, NULL);
2362 GSList *entry = buddies;
2363 struct sipe_buddy *buddy;
2364 PurpleBuddy *b;
2365 PurpleGroup *g;
2367 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2368 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2369 while (entry) {
2370 b = entry->data;
2371 g = purple_buddy_get_group(b);
2372 buddy = g_hash_table_lookup(sip->buddies, b->name);
2373 if(buddy) {
2374 gboolean in_sipe_groups = FALSE;
2375 GSList *entry2 = buddy->groups;
2376 while (entry2) {
2377 struct sipe_group *group = entry2->data;
2378 if (!strcmp(group->name, g->name)) {
2379 in_sipe_groups = TRUE;
2380 break;
2382 entry2 = entry2->next;
2384 if(!in_sipe_groups) {
2385 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2386 purple_blist_remove_buddy(b);
2388 } else {
2389 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2390 purple_blist_remove_buddy(b);
2392 entry = entry->next;
2394 g_slist_free(buddies);
2397 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2399 int len = msg->bodylen;
2401 gchar *tmp = sipmsg_find_header(msg, "Event");
2402 xmlnode *item;
2403 xmlnode *isc;
2404 const gchar *contacts_delta;
2405 xmlnode *group_node;
2406 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
2407 return FALSE;
2410 /* Convert the contact from XML to Purple Buddies */
2411 isc = xmlnode_from_str(msg->body, len);
2412 if (!isc) {
2413 return FALSE;
2416 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2417 if (contacts_delta) {
2418 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2421 if (!strcmp(isc->name, "contactList")) {
2423 /* Parse groups */
2424 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2425 struct sipe_group * group = g_new0(struct sipe_group, 1);
2426 const char *name = xmlnode_get_attrib(group_node, "name");
2428 if (!strncmp(name, "~", 1)) {
2429 name = _("Other Contacts");
2431 group->name = g_strdup(name);
2432 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2434 sipe_group_add(sip, group);
2437 // Make sure we have at least one group
2438 if (g_slist_length(sip->groups) == 0) {
2439 struct sipe_group * group = g_new0(struct sipe_group, 1);
2440 PurpleGroup *purple_group;
2441 group->name = g_strdup(_("Other Contacts"));
2442 group->id = 1;
2443 purple_group = purple_group_new(group->name);
2444 purple_blist_add_group(purple_group, NULL);
2445 sip->groups = g_slist_append(sip->groups, group);
2448 /* Parse contacts */
2449 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2450 const gchar *uri = xmlnode_get_attrib(item, "uri");
2451 const gchar *name = xmlnode_get_attrib(item, "name");
2452 gchar *buddy_name;
2453 struct sipe_buddy *buddy = NULL;
2454 gchar *tmp;
2455 gchar **item_groups;
2456 int i = 0;
2458 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2459 tmp = sip_uri_from_name(uri);
2460 buddy_name = g_ascii_strdown(tmp, -1);
2461 g_free(tmp);
2463 /* assign to group Other Contacts if nothing else received */
2464 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2465 if(!tmp || !strcmp("", tmp) ) {
2466 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2467 g_free(tmp);
2468 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2470 item_groups = g_strsplit(tmp, " ", 0);
2471 g_free(tmp);
2473 while (item_groups[i]) {
2474 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2476 // If couldn't find the right group for this contact, just put them in the first group we have
2477 if (group == NULL && g_slist_length(sip->groups) > 0) {
2478 group = sip->groups->data;
2481 if (group != NULL) {
2482 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2483 if (!b){
2484 b = purple_buddy_new(sip->account, buddy_name, uri);
2485 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2487 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2490 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2491 if (name != NULL && strlen(name) != 0) {
2492 purple_blist_alias_buddy(b, name);
2494 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2498 if (!buddy) {
2499 buddy = g_new0(struct sipe_buddy, 1);
2500 buddy->name = g_strdup(b->name);
2501 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2504 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2506 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2507 } else {
2508 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2509 name);
2512 i++;
2513 } // while, contact groups
2514 g_strfreev(item_groups);
2515 g_free(buddy_name);
2517 } // for, contacts
2519 sipe_cleanup_local_blist(sip);
2521 /* Add self-contact if not there yet. 2005 systems. */
2522 /* This will resemble subscription to roaming_self in 2007 systems */
2523 if (!sip->ocs2007) {
2524 gchar *self_uri = sip_uri_self(sip);
2525 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2527 if (!buddy) {
2528 buddy = g_new0(struct sipe_buddy, 1);
2529 buddy->name = g_strdup(self_uri);
2530 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2532 g_free(self_uri);
2535 xmlnode_free(isc);
2537 /* subscribe to buddies */
2538 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2539 if (sip->batched_support) {
2540 sipe_subscribe_presence_batched(sip, NULL);
2541 } else {
2542 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2544 sip->subscribed_buddies = TRUE;
2546 /* for 2005 systems schedule contacts' status update
2547 * based on their calendar information
2549 if (!sip->ocs2007) {
2550 sipe_sched_calendar_status_update(sip, time(NULL));
2553 return 0;
2557 * Subscribe roaming contacts
2559 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2561 gchar *to = sip_uri_self(sip);
2562 gchar *tmp = get_contact(sip);
2563 gchar *hdr = g_strdup_printf(
2564 "Event: vnd-microsoft-roaming-contacts\r\n"
2565 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2566 "Supported: com.microsoft.autoextend\r\n"
2567 "Supported: ms-benotify\r\n"
2568 "Proxy-Require: ms-benotify\r\n"
2569 "Supported: ms-piggyback-first-notify\r\n"
2570 "Contact: %s\r\n", tmp);
2571 g_free(tmp);
2573 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2574 g_free(to);
2575 g_free(hdr);
2578 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2579 SIPE_UNUSED_PARAMETER void *unused)
2581 gchar *key;
2582 struct sip_dialog *dialog;
2583 gchar *to = sip_uri_self(sip);
2584 gchar *tmp = get_contact(sip);
2585 gchar *hdr = g_strdup_printf(
2586 "Event: presence.wpending\r\n"
2587 "Accept: text/xml+msrtc.wpending\r\n"
2588 "Supported: com.microsoft.autoextend\r\n"
2589 "Supported: ms-benotify\r\n"
2590 "Proxy-Require: ms-benotify\r\n"
2591 "Supported: ms-piggyback-first-notify\r\n"
2592 "Contact: %s\r\n", tmp);
2593 g_free(tmp);
2595 /* Subscription is identified by <event> key */
2596 key = g_strdup_printf("<%s>", "presence.wpending");
2597 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2598 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2600 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2602 g_free(to);
2603 g_free(hdr);
2604 g_free(key);
2608 * Fires on deregistration event initiated by server.
2609 * [MS-SIPREGE] SIP extension.
2612 // 2007 Example
2614 // Content-Type: text/registration-event
2615 // subscription-state: terminated;expires=0
2616 // ms-diagnostics-public: 4141;reason="User disabled"
2618 // deregistered;event=rejected
2620 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2622 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2623 gchar *event = NULL;
2624 gchar *reason = NULL;
2625 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2627 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2628 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2630 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2631 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2632 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2633 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2634 } else {
2635 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2636 return;
2639 if (warning != NULL) {
2640 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2641 } else { // for LCS2005
2642 int error_id = 0;
2643 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2644 error_id = 4140; // [MS-SIPREGE]
2645 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2646 reason = g_strdup(_("you are already signed in at another location"));
2647 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2648 error_id = 4141;
2649 reason = g_strdup(_("user disabled")); // [MS-OCER]
2650 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2651 error_id = 4142;
2652 reason = g_strdup(_("user moved")); // [MS-OCER]
2655 g_free(event);
2656 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2657 g_free(reason);
2659 sip->gc->wants_to_die = TRUE;
2660 purple_connection_error(sip->gc, warning);
2661 g_free(warning);
2665 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2667 xmlnode *xn_provision_group_list;
2668 xmlnode *node;
2670 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2672 /* provisionGroup */
2673 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2674 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2675 g_free(sip->focus_factory_uri);
2676 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2677 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2678 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2679 break;
2682 xmlnode_free(xn_provision_group_list);
2685 /** for 2005 system */
2686 static void
2687 sipe_process_provisioning(struct sipe_account_data *sip,
2688 struct sipmsg *msg)
2690 xmlnode *xn_provision;
2691 xmlnode *node;
2693 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2694 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2695 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2696 if ((node = xmlnode_get_child(node, "line"))) {
2697 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2698 const gchar *server = xmlnode_get_attrib(node, "server");
2699 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2700 sip_csta_open(sip, line_uri, server);
2703 xmlnode_free(xn_provision);
2706 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2708 const gchar *contacts_delta;
2709 xmlnode *xml;
2711 xml = xmlnode_from_str(msg->body, msg->bodylen);
2712 if (!xml)
2714 return;
2717 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2718 if (contacts_delta)
2720 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2723 xmlnode_free(xml);
2726 static void
2727 free_container(struct sipe_container *container)
2729 GSList *entry;
2731 if (!container) return;
2733 entry = container->members;
2734 while (entry) {
2735 g_free(entry->data);
2736 entry = g_slist_remove(entry, entry->data);
2738 g_free(container);
2742 * Finds locally stored MS-PRES container member
2744 static struct sipe_container_member *
2745 sipe_find_container_member(struct sipe_container *container,
2746 const gchar *type,
2747 const gchar *value)
2749 struct sipe_container_member *member;
2750 GSList *entry;
2752 if (container == NULL || type == NULL) {
2753 return NULL;
2756 entry = container->members;
2757 while (entry) {
2758 member = entry->data;
2759 if (!g_strcasecmp(member->type, type)
2760 && ((!member->value && !value)
2761 || (value && member->value && !g_strcasecmp(member->value, value)))
2763 return member;
2765 entry = entry->next;
2767 return NULL;
2771 * Finds locally stored MS-PRES container by id
2773 static struct sipe_container *
2774 sipe_find_container(struct sipe_account_data *sip,
2775 guint id)
2777 struct sipe_container *container;
2778 GSList *entry;
2780 if (sip == NULL) {
2781 return NULL;
2784 entry = sip->containers;
2785 while (entry) {
2786 container = entry->data;
2787 if (id == container->id) {
2788 return container;
2790 entry = entry->next;
2792 return NULL;
2796 * Access Levels
2797 * 32000 - Blocked
2798 * 400 - Personal
2799 * 300 - Team
2800 * 200 - Company
2801 * 100 - Public
2803 static int
2804 sipe_find_access_level(struct sipe_account_data *sip,
2805 const gchar *type,
2806 const gchar *value)
2808 guint containers[] = {32000, 400, 300, 200, 100};
2809 int i = 0;
2811 for (i = 0; i < 5; i++) {
2812 struct sipe_container_member *member;
2813 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2814 if (!container) continue;
2816 member = sipe_find_container_member(container, type, value);
2817 if (member) {
2818 return containers[i];
2822 return -1;
2825 static void
2826 sipe_send_set_container_members(struct sipe_account_data *sip,
2827 guint container_id,
2828 guint container_version,
2829 const gchar* action,
2830 const gchar* type,
2831 const gchar* value)
2833 gchar *self = sip_uri_self(sip);
2834 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2835 gchar *contact;
2836 gchar *hdr;
2837 gchar *body = g_strdup_printf(
2838 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2839 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2840 "</setContainerMembers>",
2841 container_id,
2842 container_version,
2843 action,
2844 type,
2845 value_str);
2846 g_free(value_str);
2848 contact = get_contact(sip);
2849 hdr = g_strdup_printf("Contact: %s\r\n"
2850 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2851 g_free(contact);
2853 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2855 g_free(hdr);
2856 g_free(body);
2857 g_free(self);
2860 static void
2861 free_publication(struct sipe_publication *publication)
2863 g_free(publication->category);
2864 g_free(publication->cal_event_hash);
2865 g_free(publication->note);
2867 g_free(publication->working_hours_xml_str);
2868 g_free(publication->fb_start_str);
2869 g_free(publication->free_busy_base64);
2871 g_free(publication);
2874 /* key is <category><instance><container> */
2875 static gboolean
2876 sipe_is_our_publication(struct sipe_account_data *sip,
2877 const gchar *key)
2879 GSList *entry;
2881 /* filling keys for our publications if not yet cached */
2882 if (!sip->our_publication_keys) {
2883 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2884 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2885 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2886 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2887 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2888 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2889 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2891 purple_debug_info("sipe", "* Out Publication Instances *\n");
2892 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2893 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2894 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2895 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2896 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2897 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2898 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2899 purple_debug_info("sipe", "\tNote : %u\n", 0);
2900 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2902 /* device */
2903 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2904 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2906 /* state:machineState */
2907 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2908 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2909 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2910 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2912 /* state:userState */
2913 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2914 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2915 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2916 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2918 /* state:calendarState */
2919 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2920 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2921 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2922 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2924 /* state:calendarState OOF */
2925 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2926 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2927 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2928 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2930 /* note */
2931 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2932 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2933 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2934 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2935 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2936 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2938 /* note OOF */
2939 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2940 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2941 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2942 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2943 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2944 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2946 /* calendarData:WorkingHours */
2947 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2948 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2949 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2950 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2951 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2952 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2953 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2954 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2955 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2956 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2957 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2958 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2960 /* calendarData:FreeBusy */
2961 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2962 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2963 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2964 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2965 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2966 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2967 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2968 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2969 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2970 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2971 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2972 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2974 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2975 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2978 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
2980 entry = sip->our_publication_keys;
2981 while (entry) {
2982 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
2983 if (!strcmp(entry->data, key)) {
2984 return TRUE;
2986 entry = entry->next;
2988 return FALSE;
2991 /** Property names to store in blist.xml */
2992 #define ALIAS_PROP "alias"
2993 #define EMAIL_PROP "email"
2994 #define PHONE_PROP "phone"
2995 #define PHONE_DISPLAY_PROP "phone-display"
2996 #define PHONE_MOBILE_PROP "phone-mobile"
2997 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2998 #define PHONE_HOME_PROP "phone-home"
2999 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3000 #define PHONE_OTHER_PROP "phone-other"
3001 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3002 #define PHONE_CUSTOM1_PROP "phone-custom1"
3003 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3004 #define SITE_PROP "site"
3005 #define COMPANY_PROP "company"
3006 #define DEPARTMENT_PROP "department"
3007 #define TITLE_PROP "title"
3008 #define OFFICE_PROP "office"
3009 /** implies work address */
3010 #define ADDRESS_STREET_PROP "address-street"
3011 #define ADDRESS_CITY_PROP "address-city"
3012 #define ADDRESS_STATE_PROP "address-state"
3013 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3014 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3016 * Update user information
3018 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3019 * @param property_name
3020 * @param property_value may be modified to strip white space
3022 static void
3023 sipe_update_user_info(struct sipe_account_data *sip,
3024 const char *uri,
3025 const char *property_name,
3026 char *property_value)
3028 GSList *buddies, *entry;
3030 if (!property_name || strlen(property_name) == 0) return;
3032 if (property_value)
3033 property_value = g_strstrip(property_value);
3035 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3036 while (entry) {
3037 const char *prop_str;
3038 const char *server_alias;
3039 PurpleBuddy *p_buddy = entry->data;
3041 /* for Display Name */
3042 if (!strcmp(property_name, ALIAS_PROP)) {
3043 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3044 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3045 purple_blist_alias_buddy(p_buddy, property_value);
3048 server_alias = purple_buddy_get_server_alias(p_buddy);
3049 if (property_value && strlen(property_value) > 0 &&
3050 ( (server_alias && strcmp(property_value, server_alias))
3051 || !server_alias || strlen(server_alias) == 0 )
3053 purple_blist_server_alias_buddy(p_buddy, property_value);
3056 /* for other properties */
3057 else {
3058 if (property_value && strlen(property_value) > 0) {
3059 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3060 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3061 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3066 entry = entry->next;
3068 g_slist_free(buddies);
3072 * Update user phone
3073 * Suitable for both 2005 and 2007 systems.
3075 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3076 * @param phone_type
3077 * @param phone may be modified to strip white space
3078 * @param phone_display_string may be modified to strip white space
3080 static void
3081 sipe_update_user_phone(struct sipe_account_data *sip,
3082 const char *uri,
3083 const gchar *phone_type,
3084 gchar *phone,
3085 gchar *phone_display_string)
3087 const char *phone_node = PHONE_PROP; /* work phone by default */
3088 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3090 if(!phone || strlen(phone) == 0) return;
3092 if (phone_type && (!strcmp(phone_type, "mobile") || !strcmp(phone_type, "cell"))) {
3093 phone_node = PHONE_MOBILE_PROP;
3094 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3095 } else if (phone_type && !strcmp(phone_type, "home")) {
3096 phone_node = PHONE_HOME_PROP;
3097 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3098 } else if (phone_type && !strcmp(phone_type, "other")) {
3099 phone_node = PHONE_OTHER_PROP;
3100 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3101 } else if (phone_type && !strcmp(phone_type, "custom1")) {
3102 phone_node = PHONE_CUSTOM1_PROP;
3103 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3106 sipe_update_user_info(sip, uri, phone_node, phone);
3107 if (phone_display_string) {
3108 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3112 static void
3113 sipe_update_calendar(struct sipe_account_data *sip)
3115 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3117 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3119 if (!strcmp(calendar, "EXCH")) {
3120 sipe_ews_update_calendar(sip);
3123 /* schedule repeat */
3124 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3126 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3130 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3131 * by using standard Purple's means of signals and saved statuses.
3133 * Thus all UI elements get updated: Status Button with Note, docklet.
3134 * This is ablolutely important as both our status and note can come
3135 * inbound (roaming) or be updated programmatically (e.g. based on our
3136 * calendar data).
3138 static void
3139 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3140 const char *status_id,
3141 const char *message,
3142 time_t do_not_publish[])
3144 PurpleStatus *status = purple_account_get_active_status(account);
3145 gboolean changed = TRUE;
3147 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3148 purple_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3150 changed = FALSE;
3153 if (changed) {
3154 PurpleSavedStatus *saved_status;
3155 const PurpleStatusType *acct_status_type =
3156 purple_status_type_find_with_id(account->status_types, status_id);
3157 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3158 sipe_activity activity = sipe_get_activity_by_token(status_id);
3160 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3161 if (saved_status) {
3162 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3165 /* If this type+message is unique then create a new transient saved status
3166 * Ref: gtkstatusbox.c
3168 if (!saved_status) {
3169 GList *tmp;
3170 GList *active_accts = purple_accounts_get_all_active();
3172 saved_status = purple_savedstatus_new(NULL, primitive);
3173 purple_savedstatus_set_message(saved_status, message);
3175 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3176 purple_savedstatus_set_substatus(saved_status,
3177 (PurpleAccount *)tmp->data, acct_status_type, message);
3179 g_list_free(active_accts);
3182 do_not_publish[activity] = time(NULL);
3183 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3184 status_id, (int)do_not_publish[activity]);
3186 /* Set the status for each account */
3187 purple_savedstatus_activate(saved_status);
3191 struct hash_table_delete_payload {
3192 GHashTable *hash_table;
3193 guint container;
3196 static void
3197 sipe_remove_category_container_publications_cb(const char *name,
3198 struct sipe_publication *publication,
3199 struct hash_table_delete_payload *payload)
3201 if (publication->container == payload->container) {
3202 g_hash_table_remove(payload->hash_table, name);
3205 static void
3206 sipe_remove_category_container_publications(GHashTable *our_publications,
3207 const char *category,
3208 guint container)
3210 struct hash_table_delete_payload payload;
3211 payload.hash_table = g_hash_table_lookup(our_publications, category);
3213 if (!payload.hash_table) return;
3215 payload.container = container;
3216 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3219 static void
3220 send_publish_category_initial(struct sipe_account_data *sip);
3223 * When we receive some self (BE) NOTIFY with a new subscriber
3224 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3227 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3229 gchar *contact;
3230 gchar *to;
3231 xmlnode *xml;
3232 xmlnode *node;
3233 xmlnode *node2;
3234 char *display_name = NULL;
3235 char *uri;
3236 GSList *category_names = NULL;
3237 int aggreg_avail = 0;
3238 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3239 gboolean do_update_status = FALSE;
3240 gboolean has_note_cleaned = FALSE;
3242 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3244 xml = xmlnode_from_str(msg->body, msg->bodylen);
3245 if (!xml) return;
3247 contact = get_contact(sip);
3248 to = sip_uri_self(sip);
3251 /* categories */
3252 /* set list of categories participating in this XML */
3253 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3254 const gchar *name = xmlnode_get_attrib(node, "name");
3255 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3257 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3258 category_names ? (int) g_slist_length(category_names) : -1);
3259 /* drop category information */
3260 if (category_names) {
3261 GSList *entry = category_names;
3262 while (entry) {
3263 GHashTable *cat_publications;
3264 const gchar *category = entry->data;
3265 entry = entry->next;
3266 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3267 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3268 if (cat_publications) {
3269 g_hash_table_remove(sip->our_publications, category);
3270 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3274 g_slist_free(category_names);
3275 /* filling our categories reflected in roaming data */
3276 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3277 const char *tmp;
3278 const gchar *name = xmlnode_get_attrib(node, "name");
3279 guint container = (tmp = xmlnode_get_attrib(node, "container")) ? atoi(tmp) : -1;
3280 guint instance = (tmp = xmlnode_get_attrib(node, "instance")) ? atoi(tmp) : -1;
3281 guint version = atoi(xmlnode_get_attrib(node, "version"));
3282 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3283 purple_str_to_time(tmp, FALSE, NULL, NULL, NULL) : 0;
3284 gchar *key;
3285 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3287 /* Ex. clear note: <category name="note"/> */
3288 if (container == (guint)-1) {
3289 g_free(sip->note);
3290 sip->note = NULL;
3291 do_update_status = TRUE;
3292 continue;
3295 /* Ex. clear note: <category name="note" container="200"/> */
3296 if (instance == (guint)-1) {
3297 if (container == 200) {
3298 g_free(sip->note);
3299 sip->note = NULL;
3300 do_update_status = TRUE;
3302 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3303 sipe_remove_category_container_publications(
3304 sip->our_publications, name, container);
3305 continue;
3308 /* key is <category><instance><container> */
3309 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3310 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3312 /* capture all userState publication for later clean up if required */
3313 if (!strcmp(name, "state") && (container == 2 || container == 3)) {
3314 xmlnode *xn_state = xmlnode_get_child(node, "state");
3316 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3317 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3318 publication->category = g_strdup(name);
3319 publication->instance = instance;
3320 publication->container = container;
3321 publication->version = version;
3323 if (!sip->user_state_publications) {
3324 sip->user_state_publications = g_hash_table_new_full(
3325 g_str_hash, g_str_equal,
3326 g_free, (GDestroyNotify)free_publication);
3328 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3329 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3330 key, version);
3334 if (sipe_is_our_publication(sip, key)) {
3335 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3337 publication->category = g_strdup(name);
3338 publication->instance = instance;
3339 publication->container = container;
3340 publication->version = version;
3342 /* filling publication->availability */
3343 if (!strcmp(name, "state")) {
3344 xmlnode *xn_state = xmlnode_get_child(node, "state");
3345 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3347 if (xn_avail) {
3348 gchar *avail_str = xmlnode_get_data(xn_avail);
3349 if (avail_str) {
3350 publication->availability = atoi(avail_str);
3352 g_free(avail_str);
3354 /* for calendarState */
3355 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3356 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3357 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3359 event->start_time = purple_str_to_time(xmlnode_get_attrib(xn_state, "startTime"),
3360 FALSE, NULL, NULL, NULL);
3361 if (xn_activity) {
3362 if (!strcmp(xmlnode_get_attrib(xn_activity, "token"),
3363 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3365 event->is_meeting = TRUE;
3368 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3369 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3371 publication->cal_event_hash = sipe_cal_event_hash(event);
3372 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3373 publication->cal_event_hash);
3374 sipe_cal_event_free(event);
3377 /* filling publication->note */
3378 if (!strcmp(name, "note")) {
3379 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3381 if (!has_note_cleaned) {
3382 has_note_cleaned = TRUE;
3384 g_free(sip->note);
3385 sip->note = NULL;
3386 sip->note_since = publish_time;
3388 do_update_status = TRUE;
3391 g_free(publication->note);
3392 publication->note = NULL;
3393 if (xn_body) {
3394 char *tmp;
3396 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3397 g_free(tmp);
3398 if (publish_time >= sip->note_since) {
3399 g_free(sip->note);
3400 sip->note = g_strdup(publication->note);
3401 sip->note_since = publish_time;
3402 sip->is_oof_note = !strcmp(xmlnode_get_attrib(xn_body, "type"), "OOF");
3404 do_update_status = TRUE;
3409 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3410 if (!strcmp(name, "calendarData") && (publication->container == 300)) {
3411 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3412 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3413 if (xn_free_busy) {
3414 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3415 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3417 if (xn_working_hours) {
3418 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3422 if (!cat_publications) {
3423 cat_publications = g_hash_table_new_full(
3424 g_str_hash, g_str_equal,
3425 g_free, (GDestroyNotify)free_publication);
3426 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3427 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3429 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3430 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3432 g_free(key);
3434 /* aggregateState (not an our publication) from 2-nd container */
3435 if (!strcmp(name, "state") && container == 2) {
3436 xmlnode *xn_state = xmlnode_get_child(node, "state");
3438 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3439 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3440 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3442 if (xn_avail) {
3443 gchar *avail_str = xmlnode_get_data(xn_avail);
3444 if (avail_str) {
3445 aggreg_avail = atoi(avail_str);
3447 g_free(avail_str);
3450 if (xn_activity) {
3451 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3453 aggreg_activity = sipe_get_activity_by_token(activity_token);
3456 do_update_status = TRUE;
3460 /* userProperties published by server from AD */
3461 if (!sip->csta && !strcmp(name, "userProperties")) {
3462 xmlnode *line;
3463 /* line, for Remote Call Control (RCC) */
3464 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3465 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3466 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3467 gchar *line_uri;
3469 if (!line_server || (strcmp(line_type, "Rcc") && strcmp(line_type, "Dual"))) continue;
3471 line_uri = xmlnode_get_data(line);
3472 if (line_uri) {
3473 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3474 sip_csta_open(sip, line_uri, line_server);
3476 g_free(line_uri);
3478 break;
3482 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3483 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3485 /* containers */
3486 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3487 guint id = atoi(xmlnode_get_attrib(node, "id"));
3488 struct sipe_container *container = sipe_find_container(sip, id);
3490 if (container) {
3491 sip->containers = g_slist_remove(sip->containers, container);
3492 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3493 free_container(container);
3495 container = g_new0(struct sipe_container, 1);
3496 container->id = id;
3497 container->version = atoi(xmlnode_get_attrib(node, "version"));
3498 sip->containers = g_slist_append(sip->containers, container);
3499 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3501 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3502 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3503 member->type = xmlnode_get_attrib(node2, "type");
3504 member->value = xmlnode_get_attrib(node2, "value");
3505 container->members = g_slist_append(container->members, member);
3506 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3507 member->type, member->value ? member->value : "");
3511 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3512 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3513 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3514 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3515 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3516 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3517 /* initial set-up to let counterparties see your status */
3518 if (sameEnterpriseAL < 0) {
3519 struct sipe_container *container = sipe_find_container(sip, 200);
3520 guint version = container ? container->version : 0;
3521 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3523 if (federatedAL < 0) {
3524 struct sipe_container *container = sipe_find_container(sip, 100);
3525 guint version = container ? container->version : 0;
3526 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3528 sip->access_level_set = TRUE;
3531 /* subscribers */
3532 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3533 const char *user;
3534 const char *acknowledged;
3535 gchar *hdr;
3536 gchar *body;
3538 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3539 if (!user) continue;
3540 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3541 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3542 uri = sip_uri_from_name(user);
3544 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3546 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3547 if(!g_ascii_strcasecmp(acknowledged,"false")){
3548 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3549 if (!purple_find_buddy(sip->account, uri)) {
3550 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3553 hdr = g_strdup_printf(
3554 "Contact: %s\r\n"
3555 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3557 body = g_strdup_printf(
3558 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3559 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3560 "</setSubscribers>", user);
3562 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3563 g_free(body);
3564 g_free(hdr);
3566 g_free(display_name);
3567 g_free(uri);
3570 g_free(contact);
3571 xmlnode_free(xml);
3573 /* Publish initial state if not yet.
3574 * Assuming this happens on initial responce to subscription to roaming-self
3575 * so we've already updated our roaming data in full.
3576 * Only for 2007+
3578 if (!sip->initial_state_published) {
3579 send_publish_category_initial(sip);
3580 sip->initial_state_published = TRUE;
3581 /* dalayed run */
3582 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3583 do_update_status = FALSE;
3584 } else if (aggreg_avail) {
3586 g_free(sip->status);
3587 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3588 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3589 } else {
3590 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3594 if (do_update_status) {
3595 purple_debug_info("sipe", "sipe_process_roaming_self: to %s for the account\n", sip->status);
3596 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3599 g_free(to);
3602 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3604 gchar *to = sip_uri_self(sip);
3605 gchar *tmp = get_contact(sip);
3606 gchar *hdr = g_strdup_printf(
3607 "Event: vnd-microsoft-roaming-ACL\r\n"
3608 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3609 "Supported: com.microsoft.autoextend\r\n"
3610 "Supported: ms-benotify\r\n"
3611 "Proxy-Require: ms-benotify\r\n"
3612 "Supported: ms-piggyback-first-notify\r\n"
3613 "Contact: %s\r\n", tmp);
3614 g_free(tmp);
3616 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3617 g_free(to);
3618 g_free(hdr);
3622 * To request for presence information about the user, access level settings that have already been configured by the user
3623 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3624 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3627 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3629 gchar *to = sip_uri_self(sip);
3630 gchar *tmp = get_contact(sip);
3631 gchar *hdr = g_strdup_printf(
3632 "Event: vnd-microsoft-roaming-self\r\n"
3633 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3634 "Supported: ms-benotify\r\n"
3635 "Proxy-Require: ms-benotify\r\n"
3636 "Supported: ms-piggyback-first-notify\r\n"
3637 "Contact: %s\r\n"
3638 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3640 gchar *body=g_strdup(
3641 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3642 "<roaming type=\"categories\"/>"
3643 "<roaming type=\"containers\"/>"
3644 "<roaming type=\"subscribers\"/></roamingList>");
3646 g_free(tmp);
3647 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3648 g_free(body);
3649 g_free(to);
3650 g_free(hdr);
3654 * For 2005 version
3656 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3658 gchar *to = sip_uri_self(sip);
3659 gchar *tmp = get_contact(sip);
3660 gchar *hdr = g_strdup_printf(
3661 "Event: vnd-microsoft-provisioning\r\n"
3662 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3663 "Supported: com.microsoft.autoextend\r\n"
3664 "Supported: ms-benotify\r\n"
3665 "Proxy-Require: ms-benotify\r\n"
3666 "Supported: ms-piggyback-first-notify\r\n"
3667 "Expires: 0\r\n"
3668 "Contact: %s\r\n", tmp);
3670 g_free(tmp);
3671 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3672 g_free(to);
3673 g_free(hdr);
3676 /** Subscription for provisioning information to help with initial
3677 * configuration. This subscription is a one-time query (denoted by the Expires header,
3678 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3679 * configuration, meeting policies, and policy settings that Communicator must enforce.
3680 * TODO: for what we need this information.
3683 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3685 gchar *to = sip_uri_self(sip);
3686 gchar *tmp = get_contact(sip);
3687 gchar *hdr = g_strdup_printf(
3688 "Event: vnd-microsoft-provisioning-v2\r\n"
3689 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3690 "Supported: com.microsoft.autoextend\r\n"
3691 "Supported: ms-benotify\r\n"
3692 "Proxy-Require: ms-benotify\r\n"
3693 "Supported: ms-piggyback-first-notify\r\n"
3694 "Expires: 0\r\n"
3695 "Contact: %s\r\n"
3696 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3697 gchar *body = g_strdup(
3698 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3699 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3700 "<provisioningGroup name=\"ucPolicy\"/>"
3701 "</provisioningGroupList>");
3703 g_free(tmp);
3704 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3705 g_free(body);
3706 g_free(to);
3707 g_free(hdr);
3710 static void
3711 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3712 gpointer value, gpointer user_data)
3714 struct sip_subscription *subscription = value;
3715 struct sip_dialog *dialog = &subscription->dialog;
3716 struct sipe_account_data *sip = user_data;
3717 gchar *tmp = get_contact(sip);
3718 gchar *hdr = g_strdup_printf(
3719 "Event: %s\r\n"
3720 "Expires: 0\r\n"
3721 "Contact: %s\r\n", subscription->event, tmp);
3722 g_free(tmp);
3724 /* Rate limit to max. 25 requests per seconds */
3725 g_usleep(1000000 / 25);
3727 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3728 g_free(hdr);
3731 /* IM Session (INVITE and MESSAGE methods) */
3733 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3734 static gchar *
3735 get_end_points (struct sipe_account_data *sip,
3736 struct sip_session *session)
3738 gchar *res;
3740 if (session == NULL) {
3741 return NULL;
3744 res = g_strdup_printf("<sip:%s>", sip->username);
3746 SIPE_DIALOG_FOREACH {
3747 gchar *tmp = res;
3748 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3749 g_free(tmp);
3751 if (dialog->theirepid) {
3752 tmp = res;
3753 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3754 g_free(tmp);
3756 } SIPE_DIALOG_FOREACH_END;
3758 return res;
3761 static gboolean
3762 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3763 struct sipmsg *msg,
3764 SIPE_UNUSED_PARAMETER struct transaction *trans)
3766 gboolean ret = TRUE;
3768 if (msg->response != 200) {
3769 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3770 return FALSE;
3773 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3775 return ret;
3779 * Asks UA/proxy about its capabilities.
3781 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3783 gchar *to = sip_uri(who);
3784 gchar *contact = get_contact(sip);
3785 gchar *request = g_strdup_printf(
3786 "Accept: application/sdp\r\n"
3787 "Contact: %s\r\n", contact);
3788 g_free(contact);
3790 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3792 g_free(to);
3793 g_free(request);
3796 static void
3797 sipe_notify_user(struct sipe_account_data *sip,
3798 struct sip_session *session,
3799 PurpleMessageFlags flags,
3800 const gchar *message)
3802 PurpleConversation *conv;
3804 if (!session->conv) {
3805 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3806 } else {
3807 conv = session->conv;
3809 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3812 void
3813 sipe_present_info(struct sipe_account_data *sip,
3814 struct sip_session *session,
3815 const gchar *message)
3817 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3820 static void
3821 sipe_present_err(struct sipe_account_data *sip,
3822 struct sip_session *session,
3823 const gchar *message)
3825 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3828 void
3829 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3830 struct sip_session *session,
3831 int sip_error,
3832 const gchar *who,
3833 const gchar *message)
3835 char *msg, *msg_tmp, *msg_tmp2;
3836 const char *label;
3838 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
3839 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3840 g_free(msg_tmp);
3841 /* Service unavailable; Server Internal Error; Server Time-out */
3842 if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3843 label = _("This message was not delivered to %s because the service is not available");
3844 } else if (sip_error == 486) { /* Busy Here */
3845 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3846 } else {
3847 label = _("This message was not delivered to %s because one or more recipients are offline");
3850 msg_tmp = g_strdup_printf( "%s:\n%s" ,
3851 msg_tmp2 = g_strdup_printf(label, who ? who : ""), msg ? msg : "");
3852 sipe_present_err(sip, session, msg_tmp);
3853 g_free(msg_tmp2);
3854 g_free(msg_tmp);
3855 g_free(msg);
3859 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
3861 static gboolean
3862 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
3863 SIPE_UNUSED_PARAMETER struct transaction *trans)
3865 gboolean ret = TRUE;
3866 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3867 struct sip_session *session = sipe_session_find_im(sip, with);
3868 struct sip_dialog *dialog;
3869 gchar *cseq;
3870 char *key;
3871 gchar *message;
3873 if (!session) {
3874 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3875 g_free(with);
3876 return FALSE;
3879 dialog = sipe_dialog_find(session, with);
3880 if (!dialog) {
3881 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3882 g_free(with);
3883 return FALSE;
3886 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3887 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3888 g_free(cseq);
3889 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3891 if (msg->response >= 400) {
3892 PurpleBuddy *pbuddy;
3893 gchar *alias = with;
3895 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3897 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3898 alias = (gchar *)purple_buddy_get_alias(pbuddy);
3901 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
3902 ret = FALSE;
3903 } else {
3904 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3905 if (message_id) {
3906 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
3907 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
3908 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3911 g_hash_table_remove(session->unconfirmed_messages, key);
3912 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
3913 key, g_hash_table_size(session->unconfirmed_messages));
3916 g_free(key);
3917 g_free(with);
3919 if (ret) sipe_im_process_queue(sip, session);
3920 return ret;
3923 static gboolean
3924 sipe_is_election_finished(struct sip_session *session);
3926 static void
3927 sipe_election_result(struct sipe_account_data *sip,
3928 void *sess);
3930 static gboolean
3931 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
3932 SIPE_UNUSED_PARAMETER struct transaction *trans)
3934 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3935 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3936 struct sip_dialog *dialog;
3937 struct sip_session *session;
3939 session = sipe_session_find_chat_by_callid(sip, callid);
3940 if (!session) {
3941 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
3942 return FALSE;
3945 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
3946 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3947 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
3948 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
3950 if (xn_request_rm_response) {
3951 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
3952 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
3954 dialog = sipe_dialog_find(session, with);
3955 if (!dialog) {
3956 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
3957 return FALSE;
3960 if (allow && !g_strcasecmp(allow, "true")) {
3961 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
3962 dialog->election_vote = 1;
3963 } else if (allow && !g_strcasecmp(allow, "false")) {
3964 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
3965 dialog->election_vote = -1;
3968 if (sipe_is_election_finished(session)) {
3969 sipe_election_result(sip, session);
3972 } else if (xn_set_rm_response) {
3975 xmlnode_free(xn_action);
3979 return TRUE;
3982 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
3984 gchar *hdr;
3985 gchar *tmp;
3986 char *msgformat;
3987 char *msgtext;
3988 gchar *msgr_value;
3989 gchar *msgr;
3991 sipe_parse_html(msg, &msgformat, &msgtext);
3992 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
3994 msgr_value = sipmsg_get_msgr_string(msgformat);
3995 g_free(msgformat);
3996 if (msgr_value) {
3997 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3998 g_free(msgr_value);
3999 } else {
4000 msgr = g_strdup("");
4003 tmp = get_contact(sip);
4004 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4005 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4006 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4007 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
4008 g_free(tmp);
4009 g_free(msgr);
4011 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4012 g_free(msgtext);
4013 g_free(hdr);
4017 static void
4018 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4020 GSList *entry2 = session->outgoing_message_queue;
4021 while (entry2) {
4022 char *queued_msg = entry2->data;
4024 /* for multiparty chat or conference */
4025 if (session->is_multiparty || session->focus_uri) {
4026 gchar *who = sip_uri_self(sip);
4027 serv_got_chat_in(sip->gc, session->chat_id, who,
4028 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
4029 g_free(who);
4032 SIPE_DIALOG_FOREACH {
4033 char *key;
4035 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4037 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4038 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
4039 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4040 key, g_hash_table_size(session->unconfirmed_messages));
4041 g_free(key);
4043 sipe_send_message(sip, dialog, queued_msg);
4044 } SIPE_DIALOG_FOREACH_END;
4046 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
4047 g_free(queued_msg);
4051 static void
4052 sipe_refer_notify(struct sipe_account_data *sip,
4053 struct sip_session *session,
4054 const gchar *who,
4055 int status,
4056 const gchar *desc)
4058 gchar *hdr;
4059 gchar *body;
4060 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4062 hdr = g_strdup_printf(
4063 "Event: refer\r\n"
4064 "Subscription-State: %s\r\n"
4065 "Content-Type: message/sipfrag\r\n",
4066 status >= 200 ? "terminated" : "active");
4068 body = g_strdup_printf(
4069 "SIP/2.0 %d %s\r\n",
4070 status, desc);
4072 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4074 g_free(hdr);
4075 g_free(body);
4078 static gboolean
4079 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4081 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4082 struct sip_session *session;
4083 struct sip_dialog *dialog;
4084 char *cseq;
4085 char *key;
4086 gchar *message;
4087 struct sipmsg *request_msg = trans->msg;
4089 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4090 gchar *referred_by;
4092 session = sipe_session_find_chat_by_callid(sip, callid);
4093 if (!session) {
4094 session = sipe_session_find_im(sip, with);
4096 if (!session) {
4097 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4098 g_free(with);
4099 return FALSE;
4102 dialog = sipe_dialog_find(session, with);
4103 if (!dialog) {
4104 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4105 g_free(with);
4106 return FALSE;
4109 sipe_dialog_parse(dialog, msg, TRUE);
4111 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4112 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4113 g_free(cseq);
4114 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4116 if (msg->response != 200) {
4117 PurpleBuddy *pbuddy;
4118 gchar *alias = with;
4120 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4122 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4123 alias = (gchar *)purple_buddy_get_alias(pbuddy);
4126 if (message) {
4127 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
4128 } else {
4129 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4130 sipe_present_err(sip, session, tmp_msg);
4131 g_free(tmp_msg);
4134 sipe_dialog_remove(session, with);
4136 g_free(key);
4137 g_free(with);
4138 return FALSE;
4141 dialog->cseq = 0;
4142 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4143 dialog->outgoing_invite = NULL;
4144 dialog->is_established = TRUE;
4146 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4147 if (referred_by) {
4148 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4149 g_free(referred_by);
4152 /* add user to chat if it is a multiparty session */
4153 if (session->is_multiparty) {
4154 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4155 with, NULL,
4156 PURPLE_CBFLAGS_NONE, TRUE);
4159 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4160 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4161 if (session->outgoing_message_queue) {
4162 char *queued_msg = session->outgoing_message_queue->data;
4163 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
4164 g_free(queued_msg);
4168 sipe_im_process_queue(sip, session);
4170 g_hash_table_remove(session->unconfirmed_messages, key);
4171 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4172 key, g_hash_table_size(session->unconfirmed_messages));
4174 g_free(key);
4175 g_free(with);
4176 return TRUE;
4180 void
4181 sipe_invite(struct sipe_account_data *sip,
4182 struct sip_session *session,
4183 const gchar *who,
4184 const gchar *msg_body,
4185 const gchar *referred_by,
4186 const gboolean is_triggered)
4188 gchar *hdr;
4189 gchar *to;
4190 gchar *contact;
4191 gchar *body;
4192 gchar *self;
4193 char *ms_text_format = NULL;
4194 gchar *roster_manager;
4195 gchar *end_points;
4196 gchar *referred_by_str;
4197 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4199 if (dialog && dialog->is_established) {
4200 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4201 return;
4204 if (!dialog) {
4205 dialog = sipe_dialog_add(session);
4206 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4207 dialog->with = g_strdup(who);
4210 if (!(dialog->ourtag)) {
4211 dialog->ourtag = gentag();
4214 to = sip_uri(who);
4216 if (msg_body) {
4217 char *msgformat;
4218 char *msgtext;
4219 char *base64_msg;
4220 gchar *msgr_value;
4221 gchar *msgr;
4222 char *key;
4224 sipe_parse_html(msg_body, &msgformat, &msgtext);
4225 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4227 msgr_value = sipmsg_get_msgr_string(msgformat);
4228 g_free(msgformat);
4229 msgr = "";
4230 if (msgr_value) {
4231 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4232 g_free(msgr_value);
4235 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4236 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
4237 g_free(msgtext);
4238 g_free(msgr);
4239 g_free(base64_msg);
4241 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4242 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
4243 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4244 key, g_hash_table_size(session->unconfirmed_messages));
4245 g_free(key);
4248 contact = get_contact(sip);
4249 end_points = get_end_points(sip, session);
4250 self = sip_uri_self(sip);
4251 roster_manager = g_strdup_printf(
4252 "Roster-Manager: %s\r\n"
4253 "EndPoints: %s\r\n",
4254 self,
4255 end_points);
4256 referred_by_str = referred_by ?
4257 g_strdup_printf(
4258 "Referred-By: %s\r\n",
4259 referred_by)
4260 : g_strdup("");
4261 hdr = g_strdup_printf(
4262 "Supported: ms-sender\r\n"
4263 "%s"
4264 "%s"
4265 "%s"
4266 "%s"
4267 "Contact: %s\r\n%s"
4268 "Content-Type: application/sdp\r\n",
4269 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
4270 referred_by_str,
4271 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4272 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4273 contact,
4274 ms_text_format ? ms_text_format : "");
4275 g_free(ms_text_format);
4276 g_free(self);
4278 body = g_strdup_printf(
4279 "v=0\r\n"
4280 "o=- 0 0 IN IP4 %s\r\n"
4281 "s=session\r\n"
4282 "c=IN IP4 %s\r\n"
4283 "t=0 0\r\n"
4284 "m=%s %d sip null\r\n"
4285 "a=accept-types:text/plain text/html image/gif "
4286 "multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4287 purple_network_get_my_ip(-1),
4288 purple_network_get_my_ip(-1),
4289 sip->ocs2007 ? "message" : "x-ms-message",
4290 sip->realport);
4292 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4293 to, to, hdr, body, dialog, process_invite_response);
4295 g_free(to);
4296 g_free(roster_manager);
4297 g_free(end_points);
4298 g_free(referred_by_str);
4299 g_free(body);
4300 g_free(hdr);
4301 g_free(contact);
4304 static void
4305 sipe_refer(struct sipe_account_data *sip,
4306 struct sip_session *session,
4307 const gchar *who)
4309 gchar *hdr;
4310 gchar *contact;
4311 gchar *epid = get_epid(sip);
4312 struct sip_dialog *dialog = sipe_dialog_find(session,
4313 session->roster_manager);
4314 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4316 contact = get_contact(sip);
4317 hdr = g_strdup_printf(
4318 "Contact: %s\r\n"
4319 "Refer-to: <%s>\r\n"
4320 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4321 "Require: com.microsoft.rtc-multiparty\r\n",
4322 contact,
4323 who,
4324 sip->username,
4325 ourtag ? ";tag=" : "",
4326 ourtag ? ourtag : "",
4327 epid);
4328 g_free(epid);
4330 send_sip_request(sip->gc, "REFER",
4331 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4333 g_free(hdr);
4334 g_free(contact);
4337 static void
4338 sipe_send_election_request_rm(struct sipe_account_data *sip,
4339 struct sip_dialog *dialog,
4340 int bid)
4342 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4344 gchar *body = g_strdup_printf(
4345 "<?xml version=\"1.0\"?>\r\n"
4346 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4347 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4348 sip->username, bid);
4350 send_sip_request(sip->gc, "INFO",
4351 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4353 g_free(body);
4356 static void
4357 sipe_send_election_set_rm(struct sipe_account_data *sip,
4358 struct sip_dialog *dialog)
4360 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4362 gchar *body = g_strdup_printf(
4363 "<?xml version=\"1.0\"?>\r\n"
4364 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4365 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4366 sip->username);
4368 send_sip_request(sip->gc, "INFO",
4369 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4371 g_free(body);
4374 static void
4375 sipe_session_close(struct sipe_account_data *sip,
4376 struct sip_session * session)
4378 if (session && session->focus_uri) {
4379 sipe_conf_immcu_closed(sip, session);
4380 conf_session_close(sip, session);
4383 if (session) {
4384 SIPE_DIALOG_FOREACH {
4385 /* @TODO slow down BYE message sending rate */
4386 /* @see single subscription code */
4387 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4388 } SIPE_DIALOG_FOREACH_END;
4390 sipe_session_remove(sip, session);
4394 static void
4395 sipe_session_close_all(struct sipe_account_data *sip)
4397 GSList *entry;
4398 while ((entry = sip->sessions) != NULL) {
4399 sipe_session_close(sip, entry->data);
4403 static void
4404 sipe_convo_closed(PurpleConnection * gc, const char *who)
4406 struct sipe_account_data *sip = gc->proto_data;
4408 purple_debug_info("sipe", "conversation with %s closed\n", who);
4409 sipe_session_close(sip, sipe_session_find_im(sip, who));
4412 static void
4413 sipe_chat_leave (PurpleConnection *gc, int id)
4415 struct sipe_account_data *sip = gc->proto_data;
4416 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4418 sipe_session_close(sip, session);
4421 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4422 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4424 struct sipe_account_data *sip = gc->proto_data;
4425 struct sip_session *session;
4426 struct sip_dialog *dialog;
4427 gchar *uri = sip_uri(who);
4429 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4431 session = sipe_session_find_or_add_im(sip, uri);
4432 dialog = sipe_dialog_find(session, uri);
4434 // Queue the message
4435 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
4437 if (dialog && !dialog->outgoing_invite) {
4438 sipe_im_process_queue(sip, session);
4439 } else if (!dialog || !dialog->outgoing_invite) {
4440 // Need to send the INVITE to get the outgoing dialog setup
4441 sipe_invite(sip, session, uri, what, NULL, FALSE);
4444 g_free(uri);
4445 return 1;
4448 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4449 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4451 struct sipe_account_data *sip = gc->proto_data;
4452 struct sip_session *session;
4454 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4456 session = sipe_session_find_chat_by_id(sip, id);
4458 // Queue the message
4459 if (session && session->dialogs) {
4460 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4461 g_strdup(what));
4462 sipe_im_process_queue(sip, session);
4463 } else if (sip) {
4464 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4465 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4467 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4468 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4470 if (sip->ocs2007) {
4471 struct sip_session *session = sipe_session_add_chat(sip);
4473 session->is_multiparty = FALSE;
4474 session->focus_uri = g_strdup(proto_chat_id);
4475 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4476 g_strdup(what));
4477 sipe_invite_conf_focus(sip, session);
4481 return 1;
4484 /* End IM Session (INVITE and MESSAGE methods) */
4486 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4488 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4489 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4490 gchar *from;
4491 struct sip_session *session;
4493 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4495 /* Call Control protocol */
4496 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4498 process_incoming_info_csta(sip, msg);
4499 return;
4502 from = parse_from(sipmsg_find_header(msg, "From"));
4503 session = sipe_session_find_chat_by_callid(sip, callid);
4504 if (!session) {
4505 session = sipe_session_find_im(sip, from);
4507 if (!session) {
4508 g_free(from);
4509 return;
4512 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4514 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4515 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4516 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4518 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4520 if (xn_request_rm) {
4521 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4522 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
4523 gchar *body = g_strdup_printf(
4524 "<?xml version=\"1.0\"?>\r\n"
4525 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4526 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4527 sip->username,
4528 session->bid < bid ? "true" : "false");
4529 send_sip_response(sip->gc, msg, 200, "OK", body);
4530 g_free(body);
4531 } else if (xn_set_rm) {
4532 gchar *body;
4533 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4534 g_free(session->roster_manager);
4535 session->roster_manager = g_strdup(rm);
4537 body = g_strdup_printf(
4538 "<?xml version=\"1.0\"?>\r\n"
4539 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4540 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4541 sip->username);
4542 send_sip_response(sip->gc, msg, 200, "OK", body);
4543 g_free(body);
4545 xmlnode_free(xn_action);
4548 else
4550 /* looks like purple lacks typing notification for chat */
4551 if (!session->is_multiparty && !session->focus_uri) {
4552 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4553 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4554 "status");
4555 if (status && !strcmp(status, "type")) {
4556 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4557 } else if (status && !strcmp(status, "idle")) {
4558 serv_got_typing_stopped(sip->gc, from);
4560 xmlnode_free(xn_keyboard_activity);
4563 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4565 g_free(from);
4568 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4570 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4571 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4572 struct sip_session *session;
4573 struct sip_dialog *dialog;
4575 /* collect dialog identification
4576 * we need callid, ourtag and theirtag to unambiguously identify dialog
4578 /* take data before 'msg' will be modified by send_sip_response */
4579 dialog = g_new0(struct sip_dialog, 1);
4580 dialog->callid = g_strdup(callid);
4581 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4582 dialog->with = g_strdup(from);
4583 sipe_dialog_parse(dialog, msg, FALSE);
4585 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4587 session = sipe_session_find_chat_by_callid(sip, callid);
4588 if (!session) {
4589 session = sipe_session_find_im(sip, from);
4591 if (!session) {
4592 g_free(from);
4593 return;
4596 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4597 g_free(session->roster_manager);
4598 session->roster_manager = NULL;
4601 /* This what BYE is essentially for - terminating dialog */
4602 sipe_dialog_remove_3(session, dialog);
4603 sipe_dialog_free(dialog);
4604 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4605 sipe_conf_immcu_closed(sip, session);
4606 } else if (session->is_multiparty) {
4607 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4610 g_free(from);
4613 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4615 gchar *self = sip_uri_self(sip);
4616 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4617 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4618 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4619 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4620 struct sip_session *session;
4621 struct sip_dialog *dialog;
4623 session = sipe_session_find_chat_by_callid(sip, callid);
4624 dialog = sipe_dialog_find(session, from);
4626 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
4627 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4628 } else {
4629 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4631 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
4634 g_free(self);
4635 g_free(from);
4636 g_free(refer_to);
4637 g_free(referred_by);
4640 static unsigned int
4641 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4643 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4644 struct sip_session *session;
4645 struct sip_dialog *dialog;
4647 if (state == PURPLE_NOT_TYPING)
4648 return 0;
4650 session = sipe_session_find_im(sip, who);
4651 dialog = sipe_dialog_find(session, who);
4653 if (session && dialog && dialog->is_established) {
4654 send_sip_request(gc, "INFO", who, who,
4655 "Content-Type: application/xml\r\n",
4656 SIPE_SEND_TYPING, dialog, NULL);
4658 return SIPE_TYPING_SEND_TIMEOUT;
4661 static gboolean resend_timeout(struct sipe_account_data *sip)
4663 GSList *tmp = sip->transactions;
4664 time_t currtime = time(NULL);
4665 while (tmp) {
4666 struct transaction *trans = tmp->data;
4667 tmp = tmp->next;
4668 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4669 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4670 /* TODO 408 */
4671 } else {
4672 if ((currtime - trans->time > 2) && trans->retries == 0) {
4673 trans->retries++;
4674 sendout_sipmsg(sip, trans->msg);
4678 return TRUE;
4681 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4682 SIPE_UNUSED_PARAMETER void *unused)
4684 /* register again when security token expires */
4685 /* we have to start a new authentication as the security token
4686 * is almost expired by sending a not signed REGISTER message */
4687 purple_debug_info("sipe", "do a full reauthentication\n");
4688 sipe_auth_free(&sip->registrar);
4689 sipe_auth_free(&sip->proxy);
4690 sip->registerstatus = 0;
4691 do_register(sip);
4692 sip->reauthenticate_set = FALSE;
4695 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4697 gchar *from;
4698 gchar *contenttype;
4699 gboolean found = FALSE;
4701 from = parse_from(sipmsg_find_header(msg, "From"));
4703 if (!from) return;
4705 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4707 contenttype = sipmsg_find_header(msg, "Content-Type");
4708 if (!strncmp(contenttype, "text/plain", 10)
4709 || !strncmp(contenttype, "text/html", 9)
4710 || !strncmp(contenttype, "multipart/related", 17)
4711 || !strncmp(contenttype, "multipart/alternative", 21))
4713 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4714 gchar *html = get_html_message(contenttype, msg->body);
4716 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4717 if (!session) {
4718 session = sipe_session_find_im(sip, from);
4721 if (session && session->focus_uri) { /* a conference */
4722 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4723 gchar *sender = parse_from(tmp);
4724 g_free(tmp);
4725 serv_got_chat_in(sip->gc, session->chat_id, sender,
4726 PURPLE_MESSAGE_RECV, html, time(NULL));
4727 g_free(sender);
4728 } else if (session && session->is_multiparty) { /* a multiparty chat */
4729 serv_got_chat_in(sip->gc, session->chat_id, from,
4730 PURPLE_MESSAGE_RECV, html, time(NULL));
4731 } else {
4732 serv_got_im(sip->gc, from, html, 0, time(NULL));
4734 g_free(html);
4735 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4736 found = TRUE;
4738 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
4739 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4740 xmlnode *state;
4741 gchar *statedata;
4743 if (!isc) {
4744 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4745 return;
4748 state = xmlnode_get_child(isc, "state");
4750 if (!state) {
4751 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4752 xmlnode_free(isc);
4753 return;
4756 statedata = xmlnode_get_data(state);
4757 if (statedata) {
4758 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4759 else serv_got_typing_stopped(sip->gc, from);
4761 g_free(statedata);
4763 xmlnode_free(isc);
4764 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4765 found = TRUE;
4767 if (!found) {
4768 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4769 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4770 if (!session) {
4771 session = sipe_session_find_im(sip, from);
4773 if (session) {
4774 gchar *msg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4775 from);
4776 sipe_present_err(sip, session, msg);
4777 g_free(msg);
4780 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
4781 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
4783 g_free(from);
4786 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
4788 gchar *body;
4789 gchar *newTag;
4790 gchar *oldHeader;
4791 gchar *newHeader;
4792 gboolean is_multiparty = FALSE;
4793 gboolean is_triggered = FALSE;
4794 gboolean was_multiparty = TRUE;
4795 gboolean just_joined = FALSE;
4796 gchar *from;
4797 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4798 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4799 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4800 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4801 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4802 GSList *end_points = NULL;
4803 char *tmp = NULL;
4804 struct sip_session *session;
4806 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
4807 g_free(tmp);
4809 /* Invitation to join conference */
4810 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
4811 process_incoming_invite_conf(sip, msg);
4812 return;
4815 /* Only accept text invitations */
4816 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4817 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4818 return;
4821 // TODO There *must* be a better way to clean up the To header to add a tag...
4822 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4823 oldHeader = sipmsg_find_header(msg, "To");
4824 newTag = gentag();
4825 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4826 sipmsg_remove_header_now(msg, "To");
4827 sipmsg_add_header_now(msg, "To", newHeader);
4828 g_free(newHeader);
4830 if (end_points_hdr) {
4831 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4833 if (g_slist_length(end_points) > 2) {
4834 is_multiparty = TRUE;
4837 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4838 is_triggered = TRUE;
4839 is_multiparty = TRUE;
4842 session = sipe_session_find_chat_by_callid(sip, callid);
4843 /* Convert to multiparty */
4844 if (session && is_multiparty && !session->is_multiparty) {
4845 g_free(session->with);
4846 session->with = NULL;
4847 was_multiparty = FALSE;
4848 session->is_multiparty = TRUE;
4849 session->chat_id = rand();
4852 if (!session && is_multiparty) {
4853 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
4855 /* IM session */
4856 from = parse_from(sipmsg_find_header(msg, "From"));
4857 if (!session) {
4858 session = sipe_session_find_or_add_im(sip, from);
4861 if (session) {
4862 g_free(session->callid);
4863 session->callid = g_strdup(callid);
4865 session->is_multiparty = is_multiparty;
4866 if (roster_manager) {
4867 session->roster_manager = g_strdup(roster_manager);
4871 if (is_multiparty && end_points) {
4872 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
4873 GSList *entry = end_points;
4874 while (entry) {
4875 struct sip_dialog *dialog;
4876 struct sipendpoint *end_point = entry->data;
4877 entry = entry->next;
4879 if (!g_strcasecmp(from, end_point->contact) ||
4880 !g_strcasecmp(to, end_point->contact))
4881 continue;
4883 dialog = sipe_dialog_find(session, end_point->contact);
4884 if (dialog) {
4885 g_free(dialog->theirepid);
4886 dialog->theirepid = end_point->epid;
4887 end_point->epid = NULL;
4888 } else {
4889 dialog = sipe_dialog_add(session);
4891 dialog->callid = g_strdup(session->callid);
4892 dialog->with = end_point->contact;
4893 end_point->contact = NULL;
4894 dialog->theirepid = end_point->epid;
4895 end_point->epid = NULL;
4897 just_joined = TRUE;
4899 /* send triggered INVITE */
4900 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
4903 g_free(to);
4906 if (end_points) {
4907 GSList *entry = end_points;
4908 while (entry) {
4909 struct sipendpoint *end_point = entry->data;
4910 entry = entry->next;
4911 g_free(end_point->contact);
4912 g_free(end_point->epid);
4913 g_free(end_point);
4915 g_slist_free(end_points);
4918 if (session) {
4919 struct sip_dialog *dialog = sipe_dialog_find(session, from);
4920 if (dialog) {
4921 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
4922 } else {
4923 dialog = sipe_dialog_add(session);
4925 dialog->callid = g_strdup(session->callid);
4926 dialog->with = g_strdup(from);
4927 sipe_dialog_parse(dialog, msg, FALSE);
4929 if (!dialog->ourtag) {
4930 dialog->ourtag = newTag;
4931 newTag = NULL;
4934 just_joined = TRUE;
4936 } else {
4937 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
4939 g_free(newTag);
4941 if (is_multiparty && !session->conv) {
4942 gchar *chat_title = sipe_chat_get_name(callid);
4943 gchar *self = sip_uri_self(sip);
4944 /* create prpl chat */
4945 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
4946 session->chat_title = g_strdup(chat_title);
4947 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
4948 /* add self */
4949 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4950 self, NULL,
4951 PURPLE_CBFLAGS_NONE, FALSE);
4952 g_free(chat_title);
4953 g_free(self);
4956 if (is_multiparty && !was_multiparty) {
4957 /* add current IM counterparty to chat */
4958 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4959 sipe_dialog_first(session)->with, NULL,
4960 PURPLE_CBFLAGS_NONE, FALSE);
4963 /* add inviting party to chat */
4964 if (just_joined && session->conv) {
4965 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4966 from, NULL,
4967 PURPLE_CBFLAGS_NONE, TRUE);
4970 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4972 /* This used only in 2005 official client, not 2007 or Reuters.
4973 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4974 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4976 if (is_multiparty) {
4977 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4978 gchar *ms_text_format = sipmsg_find_header(msg, "ms-text-format");
4979 if (ms_text_format) {
4980 if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html")) {
4982 gchar *html = get_html_message(ms_text_format, NULL);
4983 if (html) {
4984 if (is_multiparty) {
4985 serv_got_chat_in(sip->gc, session->chat_id, from,
4986 PURPLE_MESSAGE_RECV, html, time(NULL));
4987 } else {
4988 serv_got_im(sip->gc, from, html, 0, time(NULL));
4990 g_free(html);
4991 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
4998 g_free(from);
5000 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5001 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5002 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5004 body = g_strdup_printf(
5005 "v=0\r\n"
5006 "o=- 0 0 IN IP4 %s\r\n"
5007 "s=session\r\n"
5008 "c=IN IP4 %s\r\n"
5009 "t=0 0\r\n"
5010 "m=%s %d sip sip:%s\r\n"
5011 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5012 purple_network_get_my_ip(-1),
5013 purple_network_get_my_ip(-1),
5014 sip->ocs2007 ? "message" : "x-ms-message",
5015 sip->realport,
5016 sip->username);
5017 send_sip_response(sip->gc, msg, 200, "OK", body);
5018 g_free(body);
5021 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5023 gchar *body;
5025 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5026 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5027 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5029 body = g_strdup_printf(
5030 "v=0\r\n"
5031 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5032 "s=session\r\n"
5033 "c=IN IP4 0.0.0.0\r\n"
5034 "t=0 0\r\n"
5035 "m=%s %d sip sip:%s\r\n"
5036 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5037 sip->ocs2007 ? "message" : "x-ms-message",
5038 sip->realport,
5039 sip->username);
5040 send_sip_response(sip->gc, msg, 200, "OK", body);
5041 g_free(body);
5044 static void sipe_connection_cleanup(struct sipe_account_data *);
5045 static void create_connection(struct sipe_account_data *, gchar *, int);
5047 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5048 SIPE_UNUSED_PARAMETER struct transaction *trans)
5050 gchar *tmp;
5051 const gchar *expires_header;
5052 int expires, i;
5053 GSList *hdr = msg->headers;
5054 struct siphdrelement *elem;
5056 expires_header = sipmsg_find_header(msg, "Expires");
5057 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5058 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5060 switch (msg->response) {
5061 case 200:
5062 if (expires == 0) {
5063 sip->registerstatus = 0;
5064 } else {
5065 gchar *contact_hdr = NULL;
5066 gchar *gruu = NULL;
5067 gchar *epid;
5068 gchar *uuid;
5069 gchar *timeout;
5070 gchar *server_hdr = sipmsg_find_header(msg, "Server");
5072 if (!sip->reregister_set) {
5073 gchar *action_name = g_strdup_printf("<%s>", "registration");
5074 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5075 g_free(action_name);
5076 sip->reregister_set = TRUE;
5079 sip->registerstatus = 3;
5081 if (server_hdr && !sip->server_version) {
5082 sip->server_version = g_strdup(server_hdr);
5083 g_free(default_ua);
5084 default_ua = NULL;
5087 #ifdef USE_KERBEROS
5088 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5089 #endif
5090 tmp = sipmsg_find_auth_header(msg, "NTLM");
5091 #ifdef USE_KERBEROS
5092 } else {
5093 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5095 #endif
5096 if (tmp) {
5097 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5098 fill_auth(tmp, &sip->registrar);
5101 if (!sip->reauthenticate_set) {
5102 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5103 guint reauth_timeout;
5104 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5105 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5106 reauth_timeout = sip->registrar.expires - 300;
5107 } else {
5108 /* NTLM: we have to reauthenticate as our security token expires
5109 after eight hours (be five minutes early) */
5110 reauth_timeout = (8 * 3600) - 300;
5112 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5113 g_free(action_name);
5114 sip->reauthenticate_set = TRUE;
5117 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5119 epid = get_epid(sip);
5120 uuid = generateUUIDfromEPID(epid);
5121 g_free(epid);
5123 // There can be multiple Contact headers (one per location where the user is logged in) so
5124 // make sure to only get the one for this uuid
5125 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5126 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5127 if (valid_contact) {
5128 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5129 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5130 g_free(valid_contact);
5131 break;
5132 } else {
5133 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5136 g_free(uuid);
5138 g_free(sip->contact);
5139 if(gruu) {
5140 sip->contact = g_strdup_printf("<%s>", gruu);
5141 g_free(gruu);
5142 } else {
5143 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5144 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);
5146 sip->ocs2007 = FALSE;
5147 sip->batched_support = FALSE;
5149 while(hdr)
5151 elem = hdr->data;
5152 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5153 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5154 /* We interpret this as OCS2007+ indicator */
5155 sip->ocs2007 = TRUE;
5156 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5158 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5159 sip->batched_support = TRUE;
5160 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5163 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5164 gchar **caps = g_strsplit(elem->value,",",0);
5165 i = 0;
5166 while (caps[i]) {
5167 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5168 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5169 i++;
5171 g_strfreev(caps);
5173 hdr = g_slist_next(hdr);
5176 /* rejoin open chats to be able to use them by continue to send messages */
5177 purple_conversation_foreach(sipe_rejoin_chat);
5179 /* subscriptions */
5180 if (!sip->subscribed) { //do it just once, not every re-register
5182 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5183 (GCompareFunc)g_ascii_strcasecmp)) {
5184 sipe_subscribe_roaming_contacts(sip);
5187 /* For 2007+ it does not make sence to subscribe to:
5188 * vnd-microsoft-roaming-ACL
5189 * vnd-microsoft-provisioning (not v2)
5190 * presence.wpending
5191 * These are for backward compatibility.
5193 if (sip->ocs2007)
5195 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5196 (GCompareFunc)g_ascii_strcasecmp)) {
5197 sipe_subscribe_roaming_self(sip);
5199 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5200 (GCompareFunc)g_ascii_strcasecmp)) {
5201 sipe_subscribe_roaming_provisioning_v2(sip);
5204 /* For 2005- servers */
5205 else
5207 //sipe_options_request(sip, sip->sipdomain);
5209 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5210 (GCompareFunc)g_ascii_strcasecmp)) {
5211 sipe_subscribe_roaming_acl(sip);
5213 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5214 (GCompareFunc)g_ascii_strcasecmp)) {
5215 sipe_subscribe_roaming_provisioning(sip);
5217 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5218 (GCompareFunc)g_ascii_strcasecmp)) {
5219 sipe_subscribe_presence_wpending(sip, msg);
5222 /* For 2007+ we publish our initial statuses and calendar data only after
5223 * received our existing publications in sipe_process_roaming_self()
5224 * Only in this case we know versions of current publications made
5225 * on our behalf.
5227 /* For 2005- we publish our initial statuses only after
5228 * received our existing UserInfo data in response to
5229 * self subscription.
5230 * Only in this case we won't override existing UserInfo data
5231 * set earlier or by other client on our behalf.
5235 sip->subscribed = TRUE;
5238 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5239 "timeout=", ";", NULL);
5240 if (timeout != NULL) {
5241 sscanf(timeout, "%u", &sip->keepalive_timeout);
5242 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5243 sip->keepalive_timeout);
5244 g_free(timeout);
5247 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5249 break;
5250 case 301:
5252 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5254 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5255 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5256 gchar **tmp;
5257 gchar *hostname;
5258 int port = 0;
5259 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5260 int i = 1;
5262 tmp = g_strsplit(parts[0], ":", 0);
5263 hostname = g_strdup(tmp[0]);
5264 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5265 g_strfreev(tmp);
5267 while (parts[i]) {
5268 tmp = g_strsplit(parts[i], "=", 0);
5269 if (tmp[1]) {
5270 if (g_strcasecmp("transport", tmp[0]) == 0) {
5271 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5272 transport = SIPE_TRANSPORT_TCP;
5273 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5274 transport = SIPE_TRANSPORT_UDP;
5278 g_strfreev(tmp);
5279 i++;
5281 g_strfreev(parts);
5283 /* Close old connection */
5284 sipe_connection_cleanup(sip);
5286 /* Create new connection */
5287 sip->transport = transport;
5288 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5289 hostname, port, TRANSPORT_DESCRIPTOR);
5290 create_connection(sip, hostname, port);
5292 g_free(redirect);
5294 break;
5295 case 401:
5296 if (sip->registerstatus != 2) {
5297 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5298 if (sip->registrar.retries > 3) {
5299 sip->gc->wants_to_die = TRUE;
5300 purple_connection_error(sip->gc, _("Wrong password"));
5301 return TRUE;
5303 #ifdef USE_KERBEROS
5304 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5305 #endif
5306 tmp = sipmsg_find_auth_header(msg, "NTLM");
5307 #ifdef USE_KERBEROS
5308 } else {
5309 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5311 #endif
5312 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5313 fill_auth(tmp, &sip->registrar);
5314 sip->registerstatus = 2;
5315 if (sip->account->disconnecting) {
5316 do_register_exp(sip, 0);
5317 } else {
5318 do_register(sip);
5321 break;
5322 case 403:
5324 gchar *warning = sipmsg_find_header(msg, "Warning");
5325 gchar **reason = NULL;
5326 if (warning != NULL) {
5327 /* Example header:
5328 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5330 reason = g_strsplit(warning, "\"", 0);
5332 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5333 (reason && reason[1]) ? reason[1] : _("no reason given"));
5334 g_strfreev(reason);
5336 sip->gc->wants_to_die = TRUE;
5337 purple_connection_error(sip->gc, warning);
5338 g_free(warning);
5339 return TRUE;
5341 break;
5342 case 404:
5344 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5345 gchar *reason = NULL;
5346 if (warning != NULL) {
5347 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5349 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5350 warning ? (reason ? reason : _("no reason given")) :
5351 _("SIP is either not enabled for the destination URI or it does not exist"));
5352 g_free(reason);
5354 sip->gc->wants_to_die = TRUE;
5355 purple_connection_error(sip->gc, warning);
5356 g_free(warning);
5357 return TRUE;
5359 break;
5360 case 503:
5361 case 504: /* Server time-out */
5363 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5364 gchar *reason = NULL;
5365 if (warning != NULL) {
5366 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5368 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : "<a href=\"http://www.reuters.com\">http://www.reuters.com</a>"/*_("no reason given")*/);
5369 g_free(reason);
5371 sip->gc->wants_to_die = TRUE;
5372 purple_connection_error(sip->gc, warning);
5373 g_free(warning);
5374 return TRUE;
5376 break;
5378 return TRUE;
5382 * Returns 2005-style activity and Availability.
5384 * @param status Sipe statis id.
5386 static void
5387 sipe_get_act_avail_by_status_2005(const char *status,
5388 int *activity,
5389 int *availability)
5391 int avail = 300; /* online */
5392 int act = 400; /* Available */
5394 if (!strcmp(status, SIPE_STATUS_ID_AWAY)) {
5395 act = 100;
5396 //} else if (!strcmp(status, SIPE_STATUS_ID_LUNCH)) {
5397 // act = 150;
5398 } else if (!strcmp(status, SIPE_STATUS_ID_BRB)) {
5399 act = 300;
5400 } else if (!strcmp(status, SIPE_STATUS_ID_AVAILABLE)) {
5401 act = 400;
5402 //} else if (!strcmp(status, SIPE_STATUS_ID_ON_PHONE)) {
5403 // act = 500;
5404 } else if (!strcmp(status, SIPE_STATUS_ID_BUSY) ||
5405 !strcmp(status, SIPE_STATUS_ID_DND)) {
5406 act = 600;
5407 } else if (!strcmp(status, SIPE_STATUS_ID_INVISIBLE) ||
5408 !strcmp(status, SIPE_STATUS_ID_OFFLINE)) {
5409 avail = 0; /* offline */
5410 act = 100;
5411 } else {
5412 act = 400; /* Available */
5415 if (activity) *activity = act;
5416 if (availability) *availability = avail;
5420 * [MS-SIP] 2.2.1
5422 * @param activity 2005 aggregated activity. Ex.: 600
5423 * @param availablity 2005 aggregated availablity. Ex.: 300
5425 static const char *
5426 sipe_get_status_by_act_avail_2005(const int activity,
5427 const int availablity,
5428 char **activity_desc)
5430 const char *status_id = NULL;
5431 const char *act = NULL;
5433 if (activity < 150) {
5434 status_id = SIPE_STATUS_ID_AWAY;
5435 } else if (activity < 200) {
5436 //status_id = SIPE_STATUS_ID_LUNCH;
5437 status_id = SIPE_STATUS_ID_AWAY;
5438 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5439 } else if (activity < 300) {
5440 //status_id = SIPE_STATUS_ID_IDLE;
5441 status_id = SIPE_STATUS_ID_AWAY;
5442 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5443 } else if (activity < 400) {
5444 status_id = SIPE_STATUS_ID_BRB;
5445 } else if (activity < 500) {
5446 status_id = SIPE_STATUS_ID_AVAILABLE;
5447 } else if (activity < 600) {
5448 //status_id = SIPE_STATUS_ID_ON_PHONE;
5449 status_id = SIPE_STATUS_ID_BUSY;
5450 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5451 } else if (activity < 700) {
5452 status_id = SIPE_STATUS_ID_BUSY;
5453 } else if (activity < 800) {
5454 status_id = SIPE_STATUS_ID_AWAY;
5455 } else {
5456 status_id = SIPE_STATUS_ID_AVAILABLE;
5459 if (availablity < 100)
5460 status_id = SIPE_STATUS_ID_OFFLINE;
5462 if (activity_desc && act) {
5463 g_free(*activity_desc);
5464 *activity_desc = g_strdup(act);
5467 return status_id;
5471 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5473 static const char*
5474 sipe_get_status_by_availability(int avail,
5475 char** activity_desc)
5477 const char *status;
5478 const char *act = NULL;
5480 if (avail < 3000) {
5481 status = SIPE_STATUS_ID_OFFLINE;
5482 } else if (avail < 4500) {
5483 status = SIPE_STATUS_ID_AVAILABLE;
5484 } else if (avail < 6000) {
5485 //status = SIPE_STATUS_ID_IDLE;
5486 status = SIPE_STATUS_ID_AVAILABLE;
5487 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5488 } else if (avail < 7500) {
5489 status = SIPE_STATUS_ID_BUSY;
5490 } else if (avail < 9000) {
5491 //status = SIPE_STATUS_ID_BUSYIDLE;
5492 status = SIPE_STATUS_ID_BUSY;
5493 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5494 } else if (avail < 12000) {
5495 status = SIPE_STATUS_ID_DND;
5496 } else if (avail < 15000) {
5497 status = SIPE_STATUS_ID_BRB;
5498 } else if (avail < 18000) {
5499 status = SIPE_STATUS_ID_AWAY;
5500 } else {
5501 status = SIPE_STATUS_ID_OFFLINE;
5504 if (activity_desc && act) {
5505 g_free(*activity_desc);
5506 *activity_desc = g_strdup(act);
5509 return status;
5513 * Returns 2007-style availability value
5515 * @param sipe_status_id (in)
5516 * @param activity_token (out) Must be g_free()'d after use if consumed.
5518 static int
5519 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5521 int availability;
5522 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5524 if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5525 availability = 15500;
5526 if (!activity_token || !(*activity_token)) {
5527 activity = SIPE_ACTIVITY_AWAY;
5529 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5530 availability = 12500;
5531 activity = SIPE_ACTIVITY_BRB;
5532 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_DND)) {
5533 availability = 9500;
5534 activity = SIPE_ACTIVITY_DND;
5535 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5536 availability = 6500;
5537 if (!activity_token || !(*activity_token)) {
5538 activity = SIPE_ACTIVITY_BUSY;
5540 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5541 availability = 3500;
5542 activity = SIPE_ACTIVITY_ONLINE;
5543 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5544 availability = 0;
5545 } else {
5546 // Offline or invisible
5547 availability = 18500;
5548 activity = SIPE_ACTIVITY_OFFLINE;
5551 if (activity_token) {
5552 *activity_token = g_strdup(sipe_activity_map[activity].token);
5554 return availability;
5557 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5559 const char *uri;
5560 xmlnode *xn_categories;
5561 xmlnode *xn_category;
5562 xmlnode *xn_node;
5563 const char *status = NULL;
5564 gboolean do_update_status = FALSE;
5565 gboolean has_note_cleaned = FALSE;
5566 gboolean has_free_busy_cleaned = FALSE;
5568 xn_categories = xmlnode_from_str(data, len);
5569 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5571 for (xn_category = xmlnode_get_child(xn_categories, "category");
5572 xn_category ;
5573 xn_category = xmlnode_get_next_twin(xn_category) )
5575 const char *tmp;
5576 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5577 time_t publish_time = (tmp = xmlnode_get_attrib(xn_category, "publishTime")) ?
5578 purple_str_to_time(tmp, FALSE, NULL, NULL, NULL) : 0;
5580 /* contactCard */
5581 if (!strcmp(attrVar, "contactCard"))
5583 xmlnode *node;
5584 /* identity - Display Name and email */
5585 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5586 if (node) {
5587 char* display_name = xmlnode_get_data(
5588 xmlnode_get_descendant(node, "name", "displayName", NULL));
5589 char* email = xmlnode_get_data(
5590 xmlnode_get_child(node, "email"));
5592 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5593 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5595 g_free(display_name);
5596 g_free(email);
5598 /* company */
5599 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5600 if (node) {
5601 char* company = xmlnode_get_data(node);
5602 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5603 g_free(company);
5605 /* department */
5606 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5607 if (node) {
5608 char* department = xmlnode_get_data(node);
5609 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5610 g_free(department);
5612 /* title */
5613 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5614 if (node) {
5615 char* title = xmlnode_get_data(node);
5616 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5617 g_free(title);
5619 /* office */
5620 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5621 if (node) {
5622 char* office = xmlnode_get_data(node);
5623 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5624 g_free(office);
5626 /* site (url) */
5627 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5628 if (node) {
5629 char* site = xmlnode_get_data(node);
5630 sipe_update_user_info(sip, uri, SITE_PROP, site);
5631 g_free(site);
5633 /* phone */
5634 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5635 node;
5636 node = xmlnode_get_next_twin(node))
5638 const char *phone_type = xmlnode_get_attrib(node, "type");
5639 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5640 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5642 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5644 g_free(phone);
5645 g_free(phone_display_string);
5647 /* address */
5648 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5649 node;
5650 node = xmlnode_get_next_twin(node))
5652 if (!strcmp(xmlnode_get_attrib(node, "type"), "work")) {
5653 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5654 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5655 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5656 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5657 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5659 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5660 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5661 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5662 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5663 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5665 g_free(street);
5666 g_free(city);
5667 g_free(state);
5668 g_free(zipcode);
5669 g_free(country_code);
5671 break;
5675 /* note */
5676 else if (!strcmp(attrVar, "note"))
5678 if (uri) {
5679 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5681 if (!has_note_cleaned) {
5682 has_note_cleaned = TRUE;
5684 g_free(sbuddy->note);
5685 sbuddy->note = NULL;
5686 sbuddy->is_oof_note = FALSE;
5687 sbuddy->note_since = publish_time;
5689 do_update_status = TRUE;
5691 if (sbuddy && (publish_time >= sbuddy->note_since)) {
5692 /* clean up in case no 'note' element is supplied
5693 * which indicate note removal in client
5695 g_free(sbuddy->note);
5696 sbuddy->note = NULL;
5697 sbuddy->is_oof_note = FALSE;
5698 sbuddy->note_since = publish_time;
5700 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
5701 if (xn_node) {
5702 char *tmp;
5703 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
5704 g_free(tmp);
5705 sbuddy->is_oof_note = !strcmp(xmlnode_get_attrib(xn_node, "type"), "OOF");
5706 sbuddy->note_since = publish_time;
5708 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
5709 uri, sbuddy->note ? sbuddy->note : "");
5711 /* to trigger UI refresh in case no status info is supplied in this update */
5712 do_update_status = TRUE;
5716 /* state */
5717 else if(!strcmp(attrVar, "state"))
5719 char *data;
5720 int availability;
5721 xmlnode *xn_availability;
5722 xmlnode *xn_activity;
5723 xmlnode *xn_meeting_subject;
5724 xmlnode *xn_meeting_location;
5725 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5727 xn_node = xmlnode_get_child(xn_category, "state");
5728 if (!xn_node) continue;
5729 xn_availability = xmlnode_get_child(xn_node, "availability");
5730 if (!xn_availability) continue;
5731 xn_activity = xmlnode_get_child(xn_node, "activity");
5732 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
5733 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
5735 data = xmlnode_get_data(xn_availability);
5736 availability = atoi(data);
5737 g_free(data);
5739 /* activity, meeting_subject, meeting_location */
5740 if (sbuddy) {
5741 char *tmp = NULL;
5743 /* activity */
5744 g_free(sbuddy->activity);
5745 sbuddy->activity = NULL;
5746 if (xn_activity) {
5747 const char *token = xmlnode_get_attrib(xn_activity, "token");
5748 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
5750 /* from token */
5751 if (!is_empty(token)) {
5752 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
5754 /* from custom element */
5755 if (xn_custom) {
5756 char *custom = xmlnode_get_data(xn_custom);
5758 if (!is_empty(custom)) {
5759 sbuddy->activity = custom;
5760 custom = NULL;
5762 g_free(custom);
5765 /* meeting_subject */
5766 g_free(sbuddy->meeting_subject);
5767 sbuddy->meeting_subject = NULL;
5768 if (xn_meeting_subject) {
5769 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
5771 if (!is_empty(meeting_subject)) {
5772 sbuddy->meeting_subject = meeting_subject;
5773 meeting_subject = NULL;
5775 g_free(meeting_subject);
5777 /* meeting_location */
5778 g_free(sbuddy->meeting_location);
5779 sbuddy->meeting_location = NULL;
5780 if (xn_meeting_location) {
5781 char *meeting_location = xmlnode_get_data(xn_meeting_location);
5783 if (!is_empty(meeting_location)) {
5784 sbuddy->meeting_location = meeting_location;
5785 meeting_location = NULL;
5787 g_free(meeting_location);
5790 status = sipe_get_status_by_availability(availability, &tmp);
5791 if (sbuddy->activity && tmp) {
5792 char *tmp2 = sbuddy->activity;
5794 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
5795 g_free(tmp);
5796 g_free(tmp2);
5797 } else if (tmp) {
5798 sbuddy->activity = tmp;
5802 do_update_status = TRUE;
5804 /* calendarData */
5805 else if(!strcmp(attrVar, "calendarData"))
5807 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5808 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
5809 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
5811 if (sbuddy && xn_free_busy) {
5812 if (!has_free_busy_cleaned) {
5813 has_free_busy_cleaned = TRUE;
5815 g_free(sbuddy->cal_start_time);
5816 sbuddy->cal_start_time = NULL;
5818 g_free(sbuddy->cal_free_busy_base64);
5819 sbuddy->cal_free_busy_base64 = NULL;
5821 g_free(sbuddy->cal_free_busy);
5822 sbuddy->cal_free_busy = NULL;
5824 sbuddy->cal_free_busy_published = publish_time;
5827 if (publish_time >= sbuddy->cal_free_busy_published) {
5828 g_free(sbuddy->cal_start_time);
5829 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
5831 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
5832 15 : 0;
5834 g_free(sbuddy->cal_free_busy_base64);
5835 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
5837 g_free(sbuddy->cal_free_busy);
5838 sbuddy->cal_free_busy = NULL;
5840 sbuddy->cal_free_busy_published = publish_time;
5842 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);
5846 if (sbuddy && xn_working_hours) {
5847 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
5852 if (do_update_status) {
5853 if (!status) { /* no status category in this update, using contact's current status */
5854 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
5855 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
5856 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
5857 status = purple_status_get_id(pstatus);
5860 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
5861 sipe_got_user_status(sip, uri, status);
5864 xmlnode_free(xn_categories);
5867 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
5869 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5870 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
5871 payload->host = g_strdup(host);
5872 payload->buddies = server;
5873 sipe_subscribe_presence_batched_routed(sip, payload);
5874 sipe_subscribe_presence_batched_routed_free(payload);
5877 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
5879 xmlnode *xn_list;
5880 xmlnode *xn_resource;
5881 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
5882 g_free, NULL);
5883 GSList *server;
5884 gchar *host;
5886 xn_list = xmlnode_from_str(data, len);
5888 for (xn_resource = xmlnode_get_child(xn_list, "resource");
5889 xn_resource;
5890 xn_resource = xmlnode_get_next_twin(xn_resource) )
5892 const char *uri, *state;
5893 xmlnode *xn_instance;
5895 xn_instance = xmlnode_get_child(xn_resource, "instance");
5896 if (!xn_instance) continue;
5898 uri = xmlnode_get_attrib(xn_resource, "uri");
5899 state = xmlnode_get_attrib(xn_instance, "state");
5900 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
5902 if (strstr(state, "resubscribe")) {
5903 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
5905 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
5906 gchar *user = g_strdup(uri);
5907 host = g_strdup(poolFqdn);
5908 server = g_hash_table_lookup(servers, host);
5909 server = g_slist_append(server, user);
5910 g_hash_table_insert(servers, host, server);
5911 } else {
5912 sipe_subscribe_presence_single(sip, (void *) uri);
5917 /* Send out any deferred poolFqdn subscriptions */
5918 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
5919 g_hash_table_destroy(servers);
5921 xmlnode_free(xn_list);
5924 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
5926 gchar *uri;
5927 gchar *getbasic;
5928 gchar *activity = NULL;
5929 xmlnode *pidf;
5930 xmlnode *basicstatus = NULL, *tuple, *status;
5931 gboolean isonline = FALSE;
5932 xmlnode *display_name_node;
5934 pidf = xmlnode_from_str(data, len);
5935 if (!pidf) {
5936 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
5937 return;
5940 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
5942 if ((tuple = xmlnode_get_child(pidf, "tuple")))
5944 if ((status = xmlnode_get_child(tuple, "status"))) {
5945 basicstatus = xmlnode_get_child(status, "basic");
5949 if (!basicstatus) {
5950 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
5951 xmlnode_free(pidf);
5952 return;
5955 getbasic = xmlnode_get_data(basicstatus);
5956 if (!getbasic) {
5957 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
5958 xmlnode_free(pidf);
5959 return;
5962 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
5963 if (strstr(getbasic, "open")) {
5964 isonline = TRUE;
5966 g_free(getbasic);
5968 display_name_node = xmlnode_get_child(pidf, "display-name");
5969 if (display_name_node) {
5970 char * display_name = xmlnode_get_data(display_name_node);
5972 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5973 g_free(display_name);
5976 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
5977 if ((status = xmlnode_get_child(tuple, "status"))) {
5978 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
5979 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
5980 activity = xmlnode_get_data(basicstatus);
5981 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
5987 if (isonline) {
5988 const gchar * status_id = NULL;
5989 if (activity) {
5990 if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
5991 status_id = SIPE_STATUS_ID_BUSY;
5992 } else if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
5993 status_id = SIPE_STATUS_ID_AWAY;
5997 if (!status_id) {
5998 status_id = SIPE_STATUS_ID_AVAILABLE;
6001 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6002 sipe_got_user_status(sip, uri, status_id);
6003 } else {
6004 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6007 g_free(activity);
6008 g_free(uri);
6009 xmlnode_free(pidf);
6012 /** 2005 */
6013 static void
6014 sipe_user_info_has_updated(struct sipe_account_data *sip,
6015 xmlnode *xn_userinfo)
6017 if (sip->user_info) {
6018 xmlnode_free(sip->user_info);
6020 sip->user_info = xmlnode_copy(xn_userinfo);
6022 /* Publish initial state if not yet.
6023 * Assuming this happens on initial responce to self subscription
6024 * so we've already updated our UserInfo.
6026 if (!sip->initial_state_published) {
6027 send_presence_soap(sip, FALSE);
6028 /* dalayed run */
6029 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6033 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6035 char *activity = NULL;
6036 const char *epid;
6037 const char *status_id = NULL;
6038 const char *name;
6039 char *uri;
6040 char *self_uri = sip_uri_self(sip);
6041 int avl;
6042 int act;
6043 const char *device_name = NULL;
6044 const char *cal_start_time = NULL;
6045 const char *cal_granularity = NULL;
6046 char *cal_free_busy_base64 = NULL;
6047 struct sipe_buddy *sbuddy;
6048 xmlnode *node;
6049 xmlnode *xn_presentity;
6050 xmlnode *xn_availability;
6051 xmlnode *xn_activity;
6052 xmlnode *xn_display_name;
6053 xmlnode *xn_email;
6054 xmlnode *xn_phone_number;
6055 xmlnode *xn_userinfo;
6056 xmlnode *xn_note;
6057 xmlnode *xn_oof;
6058 xmlnode *xn_state;
6059 xmlnode *xn_contact;
6060 char *note;
6061 char *free_activity;
6062 int user_avail;
6063 const char *user_avail_nil;
6064 int res_avail;
6065 time_t user_avail_since = 0;
6066 time_t activity_since = 0;
6068 /* fix for Reuters environment on Linux */
6069 if (data && strstr(data, "encoding=\"utf-16\"")) {
6070 char *tmp_data;
6071 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6072 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6073 g_free(tmp_data);
6074 } else {
6075 xn_presentity = xmlnode_from_str(data, len);
6078 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6079 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6080 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6081 xn_email = xmlnode_get_child(xn_presentity, "email");
6082 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6083 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6084 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6085 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6086 user_avail = xn_state ? atoi(xmlnode_get_attrib(xn_state, "avail")) : 0;
6087 user_avail_since = xn_state ? purple_str_to_time(xmlnode_get_attrib(xn_state, "since"), FALSE, NULL, NULL, NULL) : 0;
6088 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6089 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6090 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6091 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6093 if (user_avail_nil && !strcmp(user_avail_nil, "true")) { /* null-ed */
6094 user_avail = 0;
6095 user_avail_since = 0;
6098 free_activity = NULL;
6100 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6101 uri = sip_uri_from_name(name);
6102 avl = atoi(xmlnode_get_attrib(xn_availability, "aggregate"));
6103 epid = xmlnode_get_attrib(xn_availability, "epid");
6104 act = atoi(xmlnode_get_attrib(xn_activity, "aggregate"));
6106 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6107 res_avail = sipe_get_availability_by_status(status_id, NULL);
6108 if (user_avail > res_avail) {
6109 res_avail = user_avail;
6110 status_id = sipe_get_status_by_availability(user_avail, NULL);
6113 if (xn_display_name) {
6114 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6115 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6116 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6117 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6118 char *tel_uri = sip_to_tel_uri(phone_number);
6120 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6121 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6122 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6123 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6125 g_free(tel_uri);
6126 g_free(phone_label);
6127 g_free(phone_number);
6128 g_free(email);
6129 g_free(display_name);
6132 if (xn_contact) {
6133 /* tel */
6134 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6136 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6137 const char *phone_type = xmlnode_get_attrib(node, "type");
6138 char* phone = xmlnode_get_data(node);
6140 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6142 g_free(phone);
6146 /* devicePresence */
6147 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6148 xmlnode *xn_device_name;
6149 xmlnode *xn_calendar_info;
6150 xmlnode *xn_state;
6151 char *state;
6153 /* deviceName */
6154 if (!strcmp(xmlnode_get_attrib(node, "epid"), epid)) {
6155 xn_device_name = xmlnode_get_child(node, "deviceName");
6156 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6159 /* calendarInfo */
6160 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6161 if (xn_calendar_info) {
6162 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6164 if (cal_start_time) {
6165 time_t cal_start_time_t = purple_str_to_time(cal_start_time, FALSE, NULL, NULL, NULL);
6166 time_t cal_start_time_t_tmp = purple_str_to_time(cal_start_time_tmp, FALSE, NULL, NULL, NULL);
6168 if (cal_start_time_t_tmp > cal_start_time_t) {
6169 cal_start_time = cal_start_time_tmp;
6170 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6171 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6173 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);
6175 } else {
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);
6184 /* state */
6185 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6186 if (xn_state) {
6187 int dev_avail = atoi(xmlnode_get_attrib(xn_state, "avail"));
6188 time_t dev_avail_since = purple_str_to_time(xmlnode_get_attrib(xn_state, "since"), FALSE, NULL, NULL, NULL);
6190 state = xmlnode_get_data(xn_state);
6191 if (dev_avail_since > user_avail_since &&
6192 dev_avail >= res_avail)
6194 res_avail = dev_avail;
6195 if (!is_empty(state))
6197 if (!strcmp(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6198 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6199 } else if (!strcmp(state, "presenting")) {
6200 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6201 } else {
6202 activity = state;
6204 activity_since = dev_avail_since;
6206 status_id = sipe_get_status_by_availability(res_avail, &activity);
6208 g_free(state);
6212 /* oof */
6213 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6214 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6215 activity_since = 0;
6218 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6219 if (sbuddy)
6221 g_free(sbuddy->activity);
6222 sbuddy->activity = activity;
6224 sbuddy->activity_since = activity_since;
6226 sbuddy->user_avail = user_avail;
6227 sbuddy->user_avail_since = user_avail_since;
6229 g_free(sbuddy->note);
6230 sbuddy->note = NULL;
6231 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6233 sbuddy->is_oof_note = (xn_oof != NULL);
6235 g_free(sbuddy->device_name);
6236 sbuddy->device_name = NULL;
6237 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6239 if (!is_empty(cal_free_busy_base64)) {
6240 g_free(sbuddy->cal_start_time);
6241 sbuddy->cal_start_time = g_strdup(cal_start_time);
6243 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6245 g_free(sbuddy->cal_free_busy_base64);
6246 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6248 g_free(sbuddy->cal_free_busy);
6249 sbuddy->cal_free_busy = NULL;
6252 sbuddy->last_non_cal_status_id = status_id;
6253 g_free(sbuddy->last_non_cal_activity);
6254 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6256 if (!strcmp(sbuddy->name, self_uri)) {
6257 if (!(sbuddy->note && sip->note && !strcmp(sbuddy->note, sip->note))) /* not same */
6259 sip->is_oof_note = sbuddy->is_oof_note;
6261 g_free(sip->note);
6262 sip->note = g_strdup(sbuddy->note);
6264 sip->note_since = time(NULL);
6267 g_free(sip->status);
6268 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6272 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6273 sipe_got_user_status(sip, uri, status_id);
6275 if (!sip->ocs2007 && !strcmp(self_uri, uri)) {
6276 sipe_user_info_has_updated(sip, xn_userinfo);
6279 g_free(note);
6280 xmlnode_free(xn_presentity);
6281 g_free(uri);
6282 g_free(self_uri);
6285 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6287 char *ctype = sipmsg_find_header(msg, "Content-Type");
6289 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6291 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6292 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6294 const char *content = msg->body;
6295 unsigned length = msg->bodylen;
6296 PurpleMimeDocument *mime = NULL;
6298 if (strstr(ctype, "multipart"))
6300 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6301 const char *content_type;
6302 GList* parts;
6303 mime = purple_mime_document_parse(doc);
6304 parts = purple_mime_document_get_parts(mime);
6305 while(parts) {
6306 content = purple_mime_part_get_data(parts->data);
6307 length = purple_mime_part_get_length(parts->data);
6308 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6309 if(content_type && strstr(content_type,"application/rlmi+xml"))
6311 process_incoming_notify_rlmi_resub(sip, content, length);
6313 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6315 process_incoming_notify_msrtc(sip, content, length);
6317 else
6319 process_incoming_notify_rlmi(sip, content, length);
6321 parts = parts->next;
6323 g_free(doc);
6325 if (mime)
6327 purple_mime_document_free(mime);
6330 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6332 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6334 else if(strstr(ctype, "application/rlmi+xml"))
6336 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6339 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6341 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6343 else
6345 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6349 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6351 char *ctype = sipmsg_find_header(msg, "Content-Type");
6352 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6354 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6356 if (ctype &&
6357 strstr(ctype, "multipart") &&
6358 (strstr(ctype, "application/rlmi+xml") ||
6359 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6360 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6361 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6362 GList *parts = purple_mime_document_get_parts(mime);
6363 GSList *buddies = NULL;
6364 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6366 while (parts) {
6367 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6368 purple_mime_part_get_length(parts->data));
6370 if (strcmp(xml->name, "list")) {
6371 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6373 buddies = g_slist_append(buddies, uri);
6375 xmlnode_free(xml);
6377 parts = parts->next;
6379 g_free(doc);
6380 if (mime) purple_mime_document_free(mime);
6382 payload->host = g_strdup(who);
6383 payload->buddies = buddies;
6384 sipe_schedule_action(action_name, timeout,
6385 sipe_subscribe_presence_batched_routed,
6386 sipe_subscribe_presence_batched_routed_free,
6387 sip, payload);
6388 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6390 } else {
6391 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6392 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6394 g_free(action_name);
6398 * Dispatcher for all incoming subscription information
6399 * whether it comes from NOTIFY, BENOTIFY requests or
6400 * piggy-backed to subscription's OK responce.
6402 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6403 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6405 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6407 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6408 gchar *event = sipmsg_find_header(msg, "Event");
6409 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6410 char *tmp;
6411 int timeout = 0;
6413 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6414 event ? event : "",
6415 tmp = fix_newlines(msg->body));
6416 g_free(tmp);
6417 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6419 /* implicit subscriptions */
6420 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6421 sipe_process_imdn(sip, msg);
6424 if (!request)
6426 const gchar *expires_header;
6427 expires_header = sipmsg_find_header(msg, "Expires");
6428 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6429 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6430 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout; // 2 min ahead of expiration
6433 /* for one off subscriptions (send with Expire: 0) */
6434 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6436 sipe_process_provisioning_v2(sip, msg);
6438 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6440 sipe_process_provisioning(sip, msg);
6443 if (!subscription_state || strstr(subscription_state, "active"))
6445 if (event && !g_ascii_strcasecmp(event, "presence"))
6447 sipe_process_presence(sip, msg);
6449 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6451 sipe_process_roaming_contacts(sip, msg);
6453 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6455 sipe_process_roaming_self(sip, msg);
6457 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6459 sipe_process_roaming_acl(sip, msg);
6461 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
6463 sipe_process_presence_wpending(sip, msg);
6465 else if (event && !g_ascii_strcasecmp(event, "conference"))
6467 sipe_process_conference(sip, msg);
6471 /* The server sends status 'terminated' */
6472 if (subscription_state && strstr(subscription_state, "terminated") ) {
6473 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6474 gchar *key = sipe_get_subscription_key(event, who);
6476 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6477 g_free(who);
6479 if (g_hash_table_lookup(sip->subscriptions, key)) {
6480 g_hash_table_remove(sip->subscriptions, key);
6481 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6484 g_free(key);
6487 if (timeout && event) {// For LSC 2005 and OCS 2007
6488 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
6489 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
6491 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
6492 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
6493 g_free(action_name);
6495 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
6496 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
6498 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
6499 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
6500 g_free(action_name);
6502 else*/
6503 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6504 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6506 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6507 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6508 g_free(action_name);
6510 else if (!g_ascii_strcasecmp(event, "presence") &&
6511 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6513 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6514 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6515 if (sip->batched_support) {
6516 sipe_process_presence_timeout(sip, msg, who, timeout);
6518 else {
6519 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6520 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6522 g_free(action_name);
6523 g_free(who);
6527 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
6529 sipe_process_registration_notify(sip, msg);
6532 /* The client responses on received a NOTIFY message */
6533 if (request && !benotify)
6535 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6540 * Whether user manually changed status or
6541 * it was changed automatically due to user
6542 * became inactive/active again
6544 static gboolean
6545 sipe_is_user_state(struct sipe_account_data *sip)
6547 gboolean res;
6548 time_t now = time(NULL);
6550 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6551 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6553 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6555 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6556 return res;
6559 static void
6560 send_presence_soap0(struct sipe_account_data *sip,
6561 gboolean do_publish_calendar,
6562 gboolean do_reset_status)
6564 struct sipe_ews* ews = sip->ews;
6565 int availability = 0;
6566 int activity = 0;
6567 gchar *body;
6568 gchar *tmp;
6569 gchar *tmp2 = NULL;
6570 gchar *res_note = NULL;
6571 gchar *res_oof = NULL;
6572 const gchar *note_pub = NULL;
6573 gchar *states = NULL;
6574 gchar *calendar_data = NULL;
6575 gchar *epid = get_epid(sip);
6576 time_t now = time(NULL);
6577 gchar *since_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&now)));
6578 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6579 const char *user_input;
6580 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6582 if (oof_note && sip->note) {
6583 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6584 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6587 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6589 if (!sip->initial_state_published ||
6590 do_reset_status)
6592 g_free(sip->status);
6593 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6596 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6598 /* Note */
6599 if (pub_oof) {
6600 note_pub = oof_note;
6601 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6602 ews->published = TRUE;
6603 } else if (sip->note) {
6604 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6605 g_free(sip->note);
6606 sip->note = NULL;
6607 sip->is_oof_note = FALSE;
6608 sip->note_since = 0;
6609 } else {
6610 note_pub = sip->note;
6611 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6615 if (note_pub)
6617 /* to protocol internal plain text format */
6618 tmp = purple_markup_strip_html(note_pub);
6619 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6620 g_free(tmp);
6623 /* User State */
6624 if (!do_reset_status) {
6625 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6627 gchar *activity_token = NULL;
6628 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6630 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6631 avail_2007,
6632 since_time_str,
6633 epid,
6634 activity_token);
6635 g_free(activity_token);
6637 else /* preserve existing publication */
6639 xmlnode *xn_states;
6640 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6641 states = xmlnode_to_str(xn_states, NULL);
6642 /* this is a hack-around to remove added newline after inner element,
6643 * state in this case, where it shouldn't be.
6644 * After several use of xmlnode_to_str, amount of added newlines
6645 * grows significantly.
6647 purple_str_strip_char(states, '\n');
6648 //purple_str_strip_char(states, '\r');
6651 } else {
6652 /* do nothing - then User state will be erased */
6654 sip->initial_state_published = TRUE;
6656 /* CalendarInfo */
6657 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6659 char *fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
6660 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6661 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6662 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6663 fb_start_str,
6664 free_busy_base64);
6665 g_free(fb_start_str);
6666 g_free(free_busy_base64);
6669 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6671 /* forming resulting XML */
6672 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6673 sip->username,
6674 availability,
6675 activity,
6676 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6677 res_note ? res_note : "",
6678 res_oof ? res_oof : "",
6679 states ? states : "",
6680 calendar_data ? calendar_data : "",
6681 epid,
6682 since_time_str,
6683 since_time_str,
6684 user_input);
6685 g_free(tmp);
6686 g_free(tmp2);
6687 g_free(res_note);
6688 g_free(states);
6689 g_free(calendar_data);
6691 send_soap_request(sip, body);
6693 g_free(body);
6694 g_free(since_time_str);
6695 g_free(epid);
6698 void
6699 send_presence_soap(struct sipe_account_data *sip,
6700 gboolean do_publish_calendar)
6702 return send_presence_soap0(sip, do_publish_calendar, FALSE);
6706 static gboolean
6707 process_send_presence_category_publish_response(struct sipe_account_data *sip,
6708 struct sipmsg *msg,
6709 struct transaction *trans)
6711 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
6713 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
6714 xmlnode *xml;
6715 xmlnode *node;
6716 gchar *fault_code;
6717 GHashTable *faults;
6718 int index_our;
6719 gboolean has_device_publication = FALSE;
6721 xml = xmlnode_from_str(msg->body, msg->bodylen);
6723 /* test if version mismatch fault */
6724 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
6725 if (strcmp(fault_code, "Client.BadCall.WrongDelta")) {
6726 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
6727 g_free(fault_code);
6728 xmlnode_free(xml);
6729 return TRUE;
6731 g_free(fault_code);
6733 /* accumulating information about faulty versions */
6734 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
6735 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
6736 node;
6737 node = xmlnode_get_next_twin(node))
6739 const gchar *index = xmlnode_get_attrib(node, "index");
6740 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
6742 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
6743 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
6745 xmlnode_free(xml);
6747 /* here we are parsing own request to figure out what publication
6748 * referensed here only by index went wrong
6750 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
6752 /* publication */
6753 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
6754 index_our = 1; /* starts with 1 - our first publication */
6755 node;
6756 node = xmlnode_get_next_twin(node), index_our++)
6758 gchar *idx = g_strdup_printf("%d", index_our);
6759 const gchar *curVersion = g_hash_table_lookup(faults, idx);
6760 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
6761 g_free(idx);
6763 if (!strcmp("device", categoryName)) {
6764 has_device_publication = TRUE;
6767 if (curVersion) { /* fault exist on this index */
6768 const gchar *container = xmlnode_get_attrib(node, "container");
6769 const gchar *instance = xmlnode_get_attrib(node, "instance");
6770 /* key is <category><instance><container> */
6771 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
6772 struct sipe_publication *publication =
6773 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
6775 purple_debug_info("sipe", "key is %s\n", key);
6777 if (publication) {
6778 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6779 key, curVersion, publication->version);
6780 /* updating publication's version to the correct one */
6781 publication->version = atoi(curVersion);
6783 g_free(key);
6786 xmlnode_free(xml);
6787 g_hash_table_destroy(faults);
6789 /* rebublishing with right versions */
6790 if (has_device_publication) {
6791 send_publish_category_initial(sip);
6792 } else {
6793 send_presence_status(sip);
6796 return TRUE;
6800 * Returns 'device' XML part for publication.
6801 * Must be g_free'd after use.
6803 static gchar *
6804 sipe_publish_get_category_device(struct sipe_account_data *sip)
6806 gchar *uri;
6807 gchar *doc;
6808 gchar *epid = get_epid(sip);
6809 gchar *uuid = generateUUIDfromEPID(epid);
6810 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
6811 /* key is <category><instance><container> */
6812 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
6813 struct sipe_publication *publication =
6814 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
6816 g_free(key);
6817 g_free(epid);
6819 uri = sip_uri_self(sip);
6820 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
6821 device_instance,
6822 publication ? publication->version : 0,
6823 uuid,
6824 uri,
6825 "00:00:00+01:00", /* @TODO make timezone real*/
6826 sipe_get_host_name()
6829 g_free(uri);
6830 g_free(uuid);
6832 return doc;
6836 * A service method - use
6837 * - send_publish_get_category_state_machine and
6838 * - send_publish_get_category_state_user instead.
6839 * Must be g_free'd after use.
6841 static gchar *
6842 sipe_publish_get_category_state(struct sipe_account_data *sip,
6843 gboolean is_user_state)
6845 int availability = sipe_get_availability_by_status(sip->status, NULL);
6846 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
6847 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
6848 /* key is <category><instance><container> */
6849 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6850 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6851 struct sipe_publication *publication_2 =
6852 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6853 struct sipe_publication *publication_3 =
6854 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6856 g_free(key_2);
6857 g_free(key_3);
6859 if (publication_2 && (publication_2->availability == availability))
6861 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6862 return NULL; /* nothing to update */
6865 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
6866 instance,
6867 publication_2 ? publication_2->version : 0,
6868 availability,
6869 instance,
6870 publication_3 ? publication_3->version : 0,
6871 availability);
6875 * Only Busy and OOF calendar event are published.
6876 * Different instances are used for that.
6878 * Must be g_free'd after use.
6880 static gchar *
6881 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
6882 struct sipe_cal_event *event,
6883 const char *uri,
6884 int cal_satus)
6886 gchar *start_time_str;
6887 int availability = 0;
6888 gchar *res;
6889 guint instance = (cal_satus == SIPE_CAL_OOF) ?
6890 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
6891 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
6893 /* key is <category><instance><container> */
6894 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6895 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6896 struct sipe_publication *publication_2 =
6897 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6898 struct sipe_publication *publication_3 =
6899 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6901 g_free(key_2);
6902 g_free(key_3);
6904 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
6905 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6906 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
6907 return NULL;
6910 if (event &&
6911 publication_3 &&
6912 (publication_3->availability == availability) &&
6913 !strcmp(publication_3->cal_event_hash, sipe_cal_event_hash(event)))
6915 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6916 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
6917 return NULL; /* nothing to update */
6920 if (event &&
6921 (event->cal_status == SIPE_CAL_BUSY ||
6922 event->cal_status == SIPE_CAL_OOF))
6924 gchar *availability_xml_str = NULL;
6925 gchar *activity_xml_str = NULL;
6927 if (event->cal_status == SIPE_CAL_BUSY) {
6928 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
6931 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
6932 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6933 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
6934 "minAvailability=\"6500\"",
6935 "maxAvailability=\"8999\"");
6936 } else if (event->cal_status == SIPE_CAL_OOF) {
6937 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6938 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
6939 "minAvailability=\"12000\"",
6940 "");
6942 start_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&event->start_time)));
6944 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
6945 instance,
6946 publication_2 ? publication_2->version : 0,
6947 uri,
6948 start_time_str,
6949 availability_xml_str ? availability_xml_str : "",
6950 activity_xml_str ? activity_xml_str : "",
6951 event->subject ? event->subject : "",
6952 event->location ? event->location : "",
6954 instance,
6955 publication_3 ? publication_3->version : 0,
6956 uri,
6957 start_time_str,
6958 availability_xml_str ? availability_xml_str : "",
6959 activity_xml_str ? activity_xml_str : "",
6960 event->subject ? event->subject : "",
6961 event->location ? event->location : ""
6963 g_free(start_time_str);
6964 g_free(availability_xml_str);
6965 g_free(activity_xml_str);
6968 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
6970 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
6971 instance,
6972 publication_2 ? publication_2->version : 0,
6974 instance,
6975 publication_3 ? publication_3->version : 0
6979 return res;
6983 * Returns 'machineState' XML part for publication.
6984 * Must be g_free'd after use.
6986 static gchar *
6987 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
6989 return sipe_publish_get_category_state(sip, FALSE);
6993 * Returns 'userState' XML part for publication.
6994 * Must be g_free'd after use.
6996 static gchar *
6997 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
6999 return sipe_publish_get_category_state(sip, TRUE);
7003 * Compares two strings even in case both are NULL/empty
7005 static gboolean
7006 sipe_is_equal(const char* n1, const char* n2) {
7007 return ((!n1 || !strlen(n1)) && (!n2 || !strlen(n2))) /* both empty */
7008 || (n1 && n2 && !strcmp(n1, n2)); /* or not empty and equal */
7012 * Returns 'note' XML part for publication.
7013 * Must be g_free'd after use.
7015 * Protocol format for Note is plain text.
7017 * @param note a note in Sipe internal HTML format
7018 * @param note_type either personal or OOF
7020 static gchar *
7021 sipe_publish_get_category_note(struct sipe_account_data *sip,
7022 const char *note, /* html */
7023 const char *note_type,
7024 time_t note_start,
7025 time_t note_end)
7027 guint instance = !strcmp("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7028 /* key is <category><instance><container> */
7029 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7030 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7031 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7033 struct sipe_publication *publication_note_200 =
7034 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7035 struct sipe_publication *publication_note_300 =
7036 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7037 struct sipe_publication *publication_note_400 =
7038 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7040 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7041 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7042 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7043 char *res, *tmp1, *tmp2, *tmp3;
7044 char *start_time_attr;
7045 char *end_time_attr;
7047 g_free(tmp);
7048 g_free(key_note_200);
7049 g_free(key_note_300);
7050 g_free(key_note_400);
7052 /* we even need to republish empty note */
7053 if (n1 && n2 && !strcmp(n1, n2))
7055 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7056 return NULL; /* nothing to update */
7059 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"",
7060 purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&note_start))) : NULL;
7061 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"",
7062 purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&note_end))) : NULL;
7064 if (n1) {
7065 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7066 instance,
7067 200,
7068 publication_note_200 ? publication_note_200->version : 0,
7069 note_type,
7070 start_time_attr ? start_time_attr : "",
7071 end_time_attr ? end_time_attr : "",
7072 n1);
7074 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7075 instance,
7076 300,
7077 publication_note_300 ? publication_note_300->version : 0,
7078 note_type,
7079 start_time_attr ? start_time_attr : "",
7080 end_time_attr ? end_time_attr : "",
7081 n1);
7083 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7084 instance,
7085 400,
7086 publication_note_400 ? publication_note_400->version : 0,
7087 note_type,
7088 start_time_attr ? start_time_attr : "",
7089 end_time_attr ? end_time_attr : "",
7090 n1);
7091 } else {
7092 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7093 "note",
7094 instance,
7095 200,
7096 publication_note_200 ? publication_note_200->version : 0,
7097 "static");
7098 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7099 "note",
7100 instance,
7101 300,
7102 publication_note_200 ? publication_note_200->version : 0,
7103 "static");
7104 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7105 "note",
7106 instance,
7107 400,
7108 publication_note_200 ? publication_note_200->version : 0,
7109 "static");
7111 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7113 g_free(start_time_attr);
7114 g_free(end_time_attr);
7115 g_free(tmp1);
7116 g_free(tmp2);
7117 g_free(tmp3);
7118 g_free(n1);
7120 return res;
7124 * Returns 'calendarData' XML part with WorkingHours for publication.
7125 * Must be g_free'd after use.
7127 static gchar *
7128 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7130 struct sipe_ews* ews = sip->ews;
7132 /* key is <category><instance><container> */
7133 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7134 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7135 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7136 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7137 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7138 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7140 struct sipe_publication *publication_cal_1 =
7141 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7142 struct sipe_publication *publication_cal_100 =
7143 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7144 struct sipe_publication *publication_cal_200 =
7145 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7146 struct sipe_publication *publication_cal_300 =
7147 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7148 struct sipe_publication *publication_cal_400 =
7149 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7150 struct sipe_publication *publication_cal_32000 =
7151 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7153 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7154 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7156 g_free(key_cal_1);
7157 g_free(key_cal_100);
7158 g_free(key_cal_200);
7159 g_free(key_cal_300);
7160 g_free(key_cal_400);
7161 g_free(key_cal_32000);
7163 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7164 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7165 return NULL;
7168 if (sipe_is_equal(n1, n2))
7170 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7171 return NULL; /* nothing to update */
7174 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7175 /* 1 */
7176 publication_cal_1 ? publication_cal_1->version : 0,
7177 ews->email,
7178 ews->working_hours_xml_str,
7179 /* 100 - Public */
7180 publication_cal_100 ? publication_cal_100->version : 0,
7181 /* 200 - Company */
7182 publication_cal_200 ? publication_cal_200->version : 0,
7183 ews->email,
7184 ews->working_hours_xml_str,
7185 /* 300 - Team */
7186 publication_cal_300 ? publication_cal_300->version : 0,
7187 ews->email,
7188 ews->working_hours_xml_str,
7189 /* 400 - Personal */
7190 publication_cal_400 ? publication_cal_400->version : 0,
7191 ews->email,
7192 ews->working_hours_xml_str,
7193 /* 32000 - Blocked */
7194 publication_cal_32000 ? publication_cal_32000->version : 0
7199 * Returns 'calendarData' XML part with FreeBusy for publication.
7200 * Must be g_free'd after use.
7202 static gchar *
7203 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7205 struct sipe_ews* ews = sip->ews;
7206 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7207 char *fb_start_str;
7208 char *free_busy_base64;
7209 const char *st;
7210 const char *fb;
7211 char *res;
7213 /* key is <category><instance><container> */
7214 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7215 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7216 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7217 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7218 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7219 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7221 struct sipe_publication *publication_cal_1 =
7222 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7223 struct sipe_publication *publication_cal_100 =
7224 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7225 struct sipe_publication *publication_cal_200 =
7226 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7227 struct sipe_publication *publication_cal_300 =
7228 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7229 struct sipe_publication *publication_cal_400 =
7230 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7231 struct sipe_publication *publication_cal_32000 =
7232 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7234 g_free(key_cal_1);
7235 g_free(key_cal_100);
7236 g_free(key_cal_200);
7237 g_free(key_cal_300);
7238 g_free(key_cal_400);
7239 g_free(key_cal_32000);
7241 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7242 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7243 return NULL;
7246 fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
7247 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7249 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7250 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7252 /* we will rebuplish the same data to refresh publication time,
7253 * so if data from multiple sources, most recent will be choosen
7255 //if (sipe_is_equal(st, fb_start_str) && sipe_is_equal(fb, free_busy_base64))
7257 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7258 // g_free(fb_start_str);
7259 // g_free(free_busy_base64);
7260 // return NULL; /* nothing to update */
7263 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7264 /* 1 */
7265 cal_data_instance,
7266 publication_cal_1 ? publication_cal_1->version : 0,
7267 /* 100 - Public */
7268 cal_data_instance,
7269 publication_cal_100 ? publication_cal_100->version : 0,
7270 /* 200 - Company */
7271 cal_data_instance,
7272 publication_cal_200 ? publication_cal_200->version : 0,
7273 ews->email,
7274 fb_start_str,
7275 free_busy_base64,
7276 /* 300 - Team */
7277 cal_data_instance,
7278 publication_cal_300 ? publication_cal_300->version : 0,
7279 ews->email,
7280 fb_start_str,
7281 free_busy_base64,
7282 /* 400 - Personal */
7283 cal_data_instance,
7284 publication_cal_400 ? publication_cal_400->version : 0,
7285 ews->email,
7286 fb_start_str,
7287 free_busy_base64,
7288 /* 32000 - Blocked */
7289 cal_data_instance,
7290 publication_cal_32000 ? publication_cal_32000->version : 0
7293 g_free(fb_start_str);
7294 g_free(free_busy_base64);
7295 return res;
7298 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7300 gchar *uri;
7301 gchar *doc;
7302 gchar *tmp;
7303 gchar *hdr;
7305 uri = sip_uri_self(sip);
7306 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7307 uri,
7308 publications);
7310 tmp = get_contact(sip);
7311 hdr = g_strdup_printf("Contact: %s\r\n"
7312 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7314 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7316 g_free(tmp);
7317 g_free(hdr);
7318 g_free(uri);
7319 g_free(doc);
7322 static void
7323 send_publish_category_initial(struct sipe_account_data *sip)
7325 gchar *pub_device = sipe_publish_get_category_device(sip);
7326 gchar *pub_machine;
7327 gchar *publications;
7329 g_free(sip->status);
7330 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7332 pub_machine = sipe_publish_get_category_state_machine(sip);
7333 publications = g_strdup_printf("%s%s",
7334 pub_device,
7335 pub_machine ? pub_machine : "");
7336 g_free(pub_device);
7337 g_free(pub_machine);
7339 send_presence_publish(sip, publications);
7340 g_free(publications);
7343 static void
7344 send_presence_category_publish(struct sipe_account_data *sip)
7346 gchar *pub_state = sipe_is_user_state(sip) ?
7347 sipe_publish_get_category_state_user(sip) :
7348 sipe_publish_get_category_state_machine(sip);
7349 gchar *pub_note = sipe_publish_get_category_note(sip,
7350 sip->note,
7351 sip->is_oof_note ? "OOF" : "personal",
7354 gchar *publications;
7356 if (!pub_state && !pub_note) {
7357 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7358 return;
7361 publications = g_strdup_printf("%s%s",
7362 pub_state ? pub_state : "",
7363 pub_note ? pub_note : "");
7365 g_free(pub_state);
7366 g_free(pub_note);
7368 send_presence_publish(sip, publications);
7369 g_free(publications);
7373 * Publishes self status
7374 * based on own calendar information.
7376 * For 2007+
7378 void
7379 publish_calendar_status_self(struct sipe_account_data *sip)
7381 struct sipe_cal_event* event = NULL;
7382 gchar *pub_cal_working_hours = NULL;
7383 gchar *pub_cal_free_busy = NULL;
7384 gchar *pub_calendar = NULL;
7385 gchar *pub_calendar2 = NULL;
7386 gchar *pub_oof_note = NULL;
7387 const gchar *oof_note;
7388 time_t oof_start = 0;
7389 time_t oof_end = 0;
7391 if (!sip->ews) {
7392 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7393 return;
7396 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7397 if (sip->ews->cal_events) {
7398 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7401 if (!event) {
7402 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7403 } else {
7404 char *desc = sipe_cal_event_describe(event);
7405 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7406 g_free(desc);
7409 /* Logic
7410 if OOF
7411 OOF publish, Busy clean
7412 ilse if Busy
7413 OOF clean, Busy publish
7414 else
7415 OOF clean, Busy clean
7417 if (event && event->cal_status == SIPE_CAL_OOF) {
7418 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7419 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7420 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7421 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7422 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7423 } else {
7424 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7425 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7428 oof_note = sipe_ews_get_oof_note(sip->ews);
7429 if (!strcmp("Scheduled", sip->ews->oof_state)) {
7430 oof_start = sip->ews->oof_start;
7431 oof_end = sip->ews->oof_end;
7433 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7435 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7436 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7438 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7439 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7440 } else {
7441 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7442 pub_cal_working_hours ? pub_cal_working_hours : "",
7443 pub_cal_free_busy ? pub_cal_free_busy : "",
7444 pub_calendar ? pub_calendar : "",
7445 pub_calendar2 ? pub_calendar2 : "",
7446 pub_oof_note ? pub_oof_note : "");
7448 send_presence_publish(sip, publications);
7449 g_free(publications);
7452 g_free(pub_cal_working_hours);
7453 g_free(pub_cal_free_busy);
7454 g_free(pub_calendar);
7455 g_free(pub_calendar2);
7456 g_free(pub_oof_note);
7458 /* repeat scheduling */
7459 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7462 static void send_presence_status(struct sipe_account_data *sip)
7464 PurpleStatus * status = purple_account_get_active_status(sip->account);
7466 if (!status) return;
7468 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7469 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7470 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7472 if (sip->ocs2007) {
7473 send_presence_category_publish(sip);
7474 } else {
7475 send_presence_soap(sip, FALSE);
7479 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7481 gboolean found = FALSE;
7482 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
7483 if (msg->response == 0) { /* request */
7484 if (!strcmp(msg->method, "MESSAGE")) {
7485 process_incoming_message(sip, msg);
7486 found = TRUE;
7487 } else if (!strcmp(msg->method, "NOTIFY")) {
7488 purple_debug_info("sipe","send->process_incoming_notify\n");
7489 process_incoming_notify(sip, msg, TRUE, FALSE);
7490 found = TRUE;
7491 } else if (!strcmp(msg->method, "BENOTIFY")) {
7492 purple_debug_info("sipe","send->process_incoming_benotify\n");
7493 process_incoming_notify(sip, msg, TRUE, TRUE);
7494 found = TRUE;
7495 } else if (!strcmp(msg->method, "INVITE")) {
7496 process_incoming_invite(sip, msg);
7497 found = TRUE;
7498 } else if (!strcmp(msg->method, "REFER")) {
7499 process_incoming_refer(sip, msg);
7500 found = TRUE;
7501 } else if (!strcmp(msg->method, "OPTIONS")) {
7502 process_incoming_options(sip, msg);
7503 found = TRUE;
7504 } else if (!strcmp(msg->method, "INFO")) {
7505 process_incoming_info(sip, msg);
7506 found = TRUE;
7507 } else if (!strcmp(msg->method, "ACK")) {
7508 // ACK's don't need any response
7509 found = TRUE;
7510 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
7511 // LCS 2005 sends us these - just respond 200 OK
7512 found = TRUE;
7513 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7514 } else if (!strcmp(msg->method, "BYE")) {
7515 process_incoming_bye(sip, msg);
7516 found = TRUE;
7517 } else {
7518 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7520 } else { /* response */
7521 struct transaction *trans = transactions_find(sip, msg);
7522 if (trans) {
7523 if (msg->response == 407) {
7524 gchar *resend, *auth, *ptmp;
7526 if (sip->proxy.retries > 30) return;
7527 sip->proxy.retries++;
7528 /* do proxy authentication */
7530 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7532 fill_auth(ptmp, &sip->proxy);
7533 auth = auth_header(sip, &sip->proxy, trans->msg);
7534 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7535 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7536 g_free(auth);
7537 resend = sipmsg_to_string(trans->msg);
7538 /* resend request */
7539 sendout_pkt(sip->gc, resend);
7540 g_free(resend);
7541 } else {
7542 if (msg->response < 200) {
7543 /* ignore provisional response */
7544 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7545 } else {
7546 sip->proxy.retries = 0;
7547 if (!strcmp(trans->msg->method, "REGISTER")) {
7548 if (msg->response == 401)
7550 sip->registrar.retries++;
7552 else
7554 sip->registrar.retries = 0;
7556 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7557 } else {
7558 if (msg->response == 401) {
7559 gchar *resend, *auth, *ptmp;
7561 if (sip->registrar.retries > 4) return;
7562 sip->registrar.retries++;
7564 #ifdef USE_KERBEROS
7565 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
7566 #endif
7567 ptmp = sipmsg_find_auth_header(msg, "NTLM");
7568 #ifdef USE_KERBEROS
7569 } else {
7570 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
7572 #endif
7574 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp);
7576 fill_auth(ptmp, &sip->registrar);
7577 auth = auth_header(sip, &sip->registrar, trans->msg);
7578 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7579 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7581 //sipmsg_remove_header_now(trans->msg, "Authorization");
7582 //sipmsg_add_header(trans->msg, "Authorization", auth);
7583 g_free(auth);
7584 resend = sipmsg_to_string(trans->msg);
7585 /* resend request */
7586 sendout_pkt(sip->gc, resend);
7587 g_free(resend);
7591 if (trans->callback) {
7592 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7593 /* call the callback to process response*/
7594 (trans->callback)(sip, msg, trans);
7597 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7598 transactions_remove(sip, trans);
7602 found = TRUE;
7603 } else {
7604 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7607 if (!found) {
7608 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
7612 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7614 char *cur;
7615 char *dummy;
7616 char *tmp;
7617 struct sipmsg *msg;
7618 int restlen;
7619 cur = conn->inbuf;
7621 /* according to the RFC remove CRLF at the beginning */
7622 while (*cur == '\r' || *cur == '\n') {
7623 cur++;
7625 if (cur != conn->inbuf) {
7626 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7627 conn->inbufused = strlen(conn->inbuf);
7630 /* Received a full Header? */
7631 sip->processing_input = TRUE;
7632 while (sip->processing_input &&
7633 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7634 time_t currtime = time(NULL);
7635 cur += 2;
7636 cur[0] = '\0';
7637 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7638 g_free(tmp);
7639 msg = sipmsg_parse_header(conn->inbuf);
7640 cur[0] = '\r';
7641 cur += 2;
7642 restlen = conn->inbufused - (cur - conn->inbuf);
7643 if (msg && restlen >= msg->bodylen) {
7644 dummy = g_malloc(msg->bodylen + 1);
7645 memcpy(dummy, cur, msg->bodylen);
7646 dummy[msg->bodylen] = '\0';
7647 msg->body = dummy;
7648 cur += msg->bodylen;
7649 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7650 conn->inbufused = strlen(conn->inbuf);
7651 } else {
7652 if (msg){
7653 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7654 sipmsg_free(msg);
7656 return;
7659 /*if (msg->body) {
7660 purple_debug_info("sipe", "body:\n%s", msg->body);
7663 // Verify the signature before processing it
7664 if (sip->registrar.gssapi_context) {
7665 struct sipmsg_breakdown msgbd;
7666 gchar *signature_input_str;
7667 gchar *rspauth;
7668 msgbd.msg = msg;
7669 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7670 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
7672 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7674 if (rspauth != NULL) {
7675 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7676 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7677 process_input_message(sip, msg);
7678 } else {
7679 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7680 purple_connection_error(sip->gc, _("Invalid message signature received"));
7681 sip->gc->wants_to_die = TRUE;
7683 } else if (msg->response == 401) {
7684 purple_connection_error(sip->gc, _("Wrong password"));
7685 sip->gc->wants_to_die = TRUE;
7687 g_free(signature_input_str);
7689 g_free(rspauth);
7690 sipmsg_breakdown_free(&msgbd);
7691 } else {
7692 process_input_message(sip, msg);
7695 sipmsg_free(msg);
7699 static void sipe_udp_process(gpointer data, gint source,
7700 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
7702 PurpleConnection *gc = data;
7703 struct sipe_account_data *sip = gc->proto_data;
7704 int len;
7706 static char buffer[65536];
7707 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
7708 time_t currtime = time(NULL);
7709 struct sipmsg *msg;
7710 buffer[len] = '\0';
7711 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
7712 msg = sipmsg_parse_msg(buffer);
7713 if (msg) process_input_message(sip, msg);
7717 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
7719 struct sipe_account_data *sip = gc->proto_data;
7720 PurpleSslConnection *gsc = sip->gsc;
7722 purple_debug_error("sipe", "%s",debug);
7723 purple_connection_error(gc, msg);
7725 /* Invalidate this connection. Next send will open a new one */
7726 if (gsc) {
7727 connection_remove(sip, gsc->fd);
7728 purple_ssl_close(gsc);
7730 sip->gsc = NULL;
7731 sip->fd = -1;
7734 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7735 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7737 PurpleConnection *gc = data;
7738 struct sipe_account_data *sip;
7739 struct sip_connection *conn;
7740 int readlen, len;
7741 gboolean firstread = TRUE;
7743 /* NOTE: This check *IS* necessary */
7744 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
7745 purple_ssl_close(gsc);
7746 return;
7749 sip = gc->proto_data;
7750 conn = connection_find(sip, gsc->fd);
7751 if (conn == NULL) {
7752 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7753 gc->wants_to_die = TRUE;
7754 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
7755 return;
7758 /* Read all available data from the SSL connection */
7759 do {
7760 /* Increase input buffer size as needed */
7761 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7762 conn->inbuflen += SIMPLE_BUF_INC;
7763 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7764 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
7767 /* Try to read as much as there is space left in the buffer */
7768 readlen = conn->inbuflen - conn->inbufused - 1;
7769 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
7771 if (len < 0 && errno == EAGAIN) {
7772 /* Try again later */
7773 return;
7774 } else if (len < 0) {
7775 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
7776 return;
7777 } else if (firstread && (len == 0)) {
7778 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
7779 return;
7782 conn->inbufused += len;
7783 firstread = FALSE;
7785 /* Equivalence indicates that there is possibly more data to read */
7786 } while (len == readlen);
7788 conn->inbuf[conn->inbufused] = '\0';
7789 process_input(sip, conn);
7793 static void sipe_input_cb(gpointer data, gint source,
7794 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7796 PurpleConnection *gc = data;
7797 struct sipe_account_data *sip = gc->proto_data;
7798 int len;
7799 struct sip_connection *conn = connection_find(sip, source);
7800 if (!conn) {
7801 purple_debug_error("sipe", "Connection not found!\n");
7802 return;
7805 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7806 conn->inbuflen += SIMPLE_BUF_INC;
7807 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7810 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
7812 if (len < 0 && errno == EAGAIN)
7813 return;
7814 else if (len <= 0) {
7815 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7816 connection_remove(sip, source);
7817 if (sip->fd == source) sip->fd = -1;
7818 return;
7821 conn->inbufused += len;
7822 conn->inbuf[conn->inbufused] = '\0';
7824 process_input(sip, conn);
7827 /* Callback for new connections on incoming TCP port */
7828 static void sipe_newconn_cb(gpointer data, gint source,
7829 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7831 PurpleConnection *gc = data;
7832 struct sipe_account_data *sip = gc->proto_data;
7833 struct sip_connection *conn;
7835 int newfd = accept(source, NULL, NULL);
7837 conn = connection_create(sip, newfd);
7839 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7842 static void login_cb(gpointer data, gint source,
7843 SIPE_UNUSED_PARAMETER const gchar *error_message)
7845 PurpleConnection *gc = data;
7846 struct sipe_account_data *sip;
7847 struct sip_connection *conn;
7849 if (!PURPLE_CONNECTION_IS_VALID(gc))
7851 if (source >= 0)
7852 close(source);
7853 return;
7856 if (source < 0) {
7857 purple_connection_error(gc, _("Could not connect"));
7858 return;
7861 sip = gc->proto_data;
7862 sip->fd = source;
7863 sip->last_keepalive = time(NULL);
7865 conn = connection_create(sip, source);
7867 do_register(sip);
7869 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7872 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7873 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7875 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
7876 if (sip == NULL) return;
7878 do_register(sip);
7881 static guint sipe_ht_hash_nick(const char *nick)
7883 char *lc = g_utf8_strdown(nick, -1);
7884 guint bucket = g_str_hash(lc);
7885 g_free(lc);
7887 return bucket;
7890 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
7892 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
7895 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
7897 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7899 sip->listen_data = NULL;
7901 if (listenfd == -1) {
7902 purple_connection_error(sip->gc, _("Could not create listen socket"));
7903 return;
7906 sip->fd = listenfd;
7908 sip->listenport = purple_network_get_port_from_fd(sip->fd);
7909 sip->listenfd = sip->fd;
7911 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
7913 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
7914 do_register(sip);
7917 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
7918 SIPE_UNUSED_PARAMETER const char *error_message)
7920 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7922 sip->query_data = NULL;
7924 if (!hosts || !hosts->data) {
7925 purple_connection_error(sip->gc, _("Could not resolve hostname"));
7926 return;
7929 hosts = g_slist_remove(hosts, hosts->data);
7930 g_free(sip->serveraddr);
7931 sip->serveraddr = hosts->data;
7932 hosts = g_slist_remove(hosts, hosts->data);
7933 while (hosts) {
7934 hosts = g_slist_remove(hosts, hosts->data);
7935 g_free(hosts->data);
7936 hosts = g_slist_remove(hosts, hosts->data);
7939 /* create socket for incoming connections */
7940 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
7941 sipe_udp_host_resolved_listen_cb, sip);
7942 if (sip->listen_data == NULL) {
7943 purple_connection_error(sip->gc, _("Could not create listen socket"));
7944 return;
7948 static const struct sipe_service_data *current_service = NULL;
7950 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
7951 PurpleSslErrorType error,
7952 gpointer data)
7954 PurpleConnection *gc = data;
7955 struct sipe_account_data *sip;
7957 /* If the connection is already disconnected, we don't need to do anything else */
7958 if (!PURPLE_CONNECTION_IS_VALID(gc))
7959 return;
7961 sip = gc->proto_data;
7962 current_service = sip->service_data;
7963 if (current_service) {
7964 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
7965 current_service->transport ? current_service->transport : "NULL",
7966 current_service->service ? current_service->service : "NULL");
7969 sip->fd = -1;
7970 sip->gsc = NULL;
7972 switch(error) {
7973 case PURPLE_SSL_CONNECT_FAILED:
7974 purple_connection_error(gc, _("Connection failed"));
7975 break;
7976 case PURPLE_SSL_HANDSHAKE_FAILED:
7977 purple_connection_error(gc, _("SSL handshake failed"));
7978 break;
7979 case PURPLE_SSL_CERTIFICATE_INVALID:
7980 purple_connection_error(gc, _("SSL certificate invalid"));
7981 break;
7985 static void
7986 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
7988 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7989 PurpleProxyConnectData *connect_data;
7991 sip->listen_data = NULL;
7993 sip->listenfd = listenfd;
7994 if (sip->listenfd == -1) {
7995 purple_connection_error(sip->gc, _("Could not create listen socket"));
7996 return;
7999 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8000 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8001 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8002 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8003 sipe_newconn_cb, sip->gc);
8004 purple_debug_info("sipe", "connecting to %s port %d\n",
8005 sip->realhostname, sip->realport);
8006 /* open tcp connection to the server */
8007 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8008 sip->realport, login_cb, sip->gc);
8010 if (connect_data == NULL) {
8011 purple_connection_error(sip->gc, _("Could not create socket"));
8015 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8017 PurpleAccount *account = sip->account;
8018 PurpleConnection *gc = sip->gc;
8020 if (port == 0) {
8021 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8024 sip->realhostname = hostname;
8025 sip->realport = port;
8027 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8028 hostname, port);
8030 /* TODO: is there a good default grow size? */
8031 if (sip->transport != SIPE_TRANSPORT_UDP)
8032 sip->txbuf = purple_circ_buffer_new(0);
8034 if (sip->transport == SIPE_TRANSPORT_TLS) {
8035 /* SSL case */
8036 if (!purple_ssl_is_supported()) {
8037 gc->wants_to_die = TRUE;
8038 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8039 return;
8042 purple_debug_info("sipe", "using SSL\n");
8044 sip->gsc = purple_ssl_connect(account, hostname, port,
8045 login_cb_ssl, sipe_ssl_connect_failure, gc);
8046 if (sip->gsc == NULL) {
8047 purple_connection_error(gc, _("Could not create SSL context"));
8048 return;
8050 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8051 /* UDP case */
8052 purple_debug_info("sipe", "using UDP\n");
8054 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8055 if (sip->query_data == NULL) {
8056 purple_connection_error(gc, _("Could not resolve hostname"));
8058 } else {
8059 /* TCP case */
8060 purple_debug_info("sipe", "using TCP\n");
8061 /* create socket for incoming connections */
8062 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8063 sipe_tcp_connect_listen_cb, sip);
8064 if (sip->listen_data == NULL) {
8065 purple_connection_error(gc, _("Could not create listen socket"));
8066 return;
8071 /* Service list for autodection */
8072 static const struct sipe_service_data service_autodetect[] = {
8073 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8074 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8075 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8076 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8077 { NULL, NULL, 0 }
8080 /* Service list for SSL/TLS */
8081 static const struct sipe_service_data service_tls[] = {
8082 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8083 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8084 { NULL, NULL, 0 }
8087 /* Service list for TCP */
8088 static const struct sipe_service_data service_tcp[] = {
8089 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8090 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8091 { NULL, NULL, 0 }
8094 /* Service list for UDP */
8095 static const struct sipe_service_data service_udp[] = {
8096 { "sip", "udp", SIPE_TRANSPORT_UDP },
8097 { NULL, NULL, 0 }
8100 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8101 static void resolve_next_service(struct sipe_account_data *sip,
8102 const struct sipe_service_data *start)
8104 if (start) {
8105 sip->service_data = start;
8106 } else {
8107 sip->service_data++;
8108 if (sip->service_data->service == NULL) {
8109 gchar *hostname;
8110 /* Try connecting to the SIP hostname directly */
8111 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8112 if (sip->auto_transport) {
8113 // If SSL is supported, default to using it; OCS servers aren't configured
8114 // by default to accept TCP
8115 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8116 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8117 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8120 hostname = g_strdup(sip->sipdomain);
8121 create_connection(sip, hostname, 0);
8122 return;
8126 /* Try to resolve next service */
8127 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8128 sip->service_data->transport,
8129 sip->sipdomain,
8130 srvresolved, sip);
8133 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8135 struct sipe_account_data *sip = data;
8137 sip->srv_query_data = NULL;
8139 /* find the host to connect to */
8140 if (results) {
8141 gchar *hostname = g_strdup(resp->hostname);
8142 int port = resp->port;
8143 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8144 hostname, port);
8145 g_free(resp);
8147 sip->transport = sip->service_data->type;
8149 create_connection(sip, hostname, port);
8150 } else {
8151 resolve_next_service(sip, NULL);
8155 static void sipe_login(PurpleAccount *account)
8157 PurpleConnection *gc;
8158 struct sipe_account_data *sip;
8159 gchar **signinname_login, **userserver;
8160 const char *transport;
8161 const char *email;
8163 const char *username = purple_account_get_username(account);
8164 gc = purple_account_get_connection(account);
8166 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8168 if (strpbrk(username, "\t\v\r\n") != NULL) {
8169 gc->wants_to_die = TRUE;
8170 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8171 return;
8174 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8175 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8176 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8177 sip->gc = gc;
8178 sip->account = account;
8179 sip->reregister_set = FALSE;
8180 sip->reauthenticate_set = FALSE;
8181 sip->subscribed = FALSE;
8182 sip->subscribed_buddies = FALSE;
8183 sip->initial_state_published = FALSE;
8185 /* username format: <username>,[<optional login>] */
8186 signinname_login = g_strsplit(username, ",", 2);
8187 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8189 /* ensure that username format is name@domain */
8190 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8191 g_strfreev(signinname_login);
8192 gc->wants_to_die = TRUE;
8193 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8194 return;
8196 sip->username = g_strdup(signinname_login[0]);
8198 /* ensure that email format is name@domain if provided */
8199 email = purple_account_get_string(sip->account, "email", NULL);
8200 if (!is_empty(email) &&
8201 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8203 gc->wants_to_die = TRUE;
8204 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8205 return;
8207 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8209 /* login name specified? */
8210 if (signinname_login[1] && strlen(signinname_login[1])) {
8211 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8212 gboolean has_domain = domain_user[1] != NULL;
8213 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8214 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8215 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8216 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8217 sip->authdomain ? sip->authdomain : "", sip->authuser);
8218 g_strfreev(domain_user);
8221 userserver = g_strsplit(signinname_login[0], "@", 2);
8222 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8223 purple_connection_set_display_name(gc, userserver[0]);
8224 sip->sipdomain = g_strdup(userserver[1]);
8225 g_strfreev(userserver);
8226 g_strfreev(signinname_login);
8228 if (strchr(sip->username, ' ') != NULL) {
8229 gc->wants_to_die = TRUE;
8230 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8231 return;
8234 sip->password = g_strdup(purple_connection_get_password(gc));
8236 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8237 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8238 g_free, (GDestroyNotify)g_hash_table_destroy);
8239 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8240 g_free, (GDestroyNotify)sipe_subscription_free);
8242 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8244 g_free(sip->status);
8245 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8247 sip->auto_transport = FALSE;
8248 transport = purple_account_get_string(account, "transport", "auto");
8249 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8250 if (userserver[0]) {
8251 /* Use user specified server[:port] */
8252 int port = 0;
8254 if (userserver[1])
8255 port = atoi(userserver[1]);
8257 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8258 userserver[0], port);
8260 if (strcmp(transport, "auto") == 0) {
8261 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8262 } else if (strcmp(transport, "tls") == 0) {
8263 sip->transport = SIPE_TRANSPORT_TLS;
8264 } else if (strcmp(transport, "tcp") == 0) {
8265 sip->transport = SIPE_TRANSPORT_TCP;
8266 } else {
8267 sip->transport = SIPE_TRANSPORT_UDP;
8270 create_connection(sip, g_strdup(userserver[0]), port);
8271 } else {
8272 /* Server auto-discovery */
8273 if (strcmp(transport, "auto") == 0) {
8274 sip->auto_transport = TRUE;
8275 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8276 current_service++;
8277 resolve_next_service(sip, current_service);
8278 } else {
8279 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8281 } else if (strcmp(transport, "tls") == 0) {
8282 resolve_next_service(sip, service_tls);
8283 } else if (strcmp(transport, "tcp") == 0) {
8284 resolve_next_service(sip, service_tcp);
8285 } else {
8286 resolve_next_service(sip, service_udp);
8289 g_strfreev(userserver);
8292 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8294 connection_free_all(sip);
8296 g_free(sip->epid);
8297 sip->epid = NULL;
8299 if (sip->query_data != NULL)
8300 purple_dnsquery_destroy(sip->query_data);
8301 sip->query_data = NULL;
8303 if (sip->srv_query_data != NULL)
8304 purple_srv_cancel(sip->srv_query_data);
8305 sip->srv_query_data = NULL;
8307 if (sip->listen_data != NULL)
8308 purple_network_listen_cancel(sip->listen_data);
8309 sip->listen_data = NULL;
8311 if (sip->gsc != NULL)
8312 purple_ssl_close(sip->gsc);
8313 sip->gsc = NULL;
8315 sipe_auth_free(&sip->registrar);
8316 sipe_auth_free(&sip->proxy);
8318 if (sip->txbuf)
8319 purple_circ_buffer_destroy(sip->txbuf);
8320 sip->txbuf = NULL;
8322 g_free(sip->realhostname);
8323 sip->realhostname = NULL;
8325 g_free(sip->server_version);
8326 sip->server_version = NULL;
8328 if (sip->listenpa)
8329 purple_input_remove(sip->listenpa);
8330 sip->listenpa = 0;
8331 if (sip->tx_handler)
8332 purple_input_remove(sip->tx_handler);
8333 sip->tx_handler = 0;
8334 if (sip->resendtimeout)
8335 purple_timeout_remove(sip->resendtimeout);
8336 sip->resendtimeout = 0;
8337 if (sip->timeouts) {
8338 GSList *entry = sip->timeouts;
8339 while (entry) {
8340 struct scheduled_action *sched_action = entry->data;
8341 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8342 purple_timeout_remove(sched_action->timeout_handler);
8343 if (sched_action->destroy) {
8344 (*sched_action->destroy)(sched_action->payload);
8346 g_free(sched_action->name);
8347 g_free(sched_action);
8348 entry = entry->next;
8351 g_slist_free(sip->timeouts);
8353 if (sip->allow_events) {
8354 GSList *entry = sip->allow_events;
8355 while (entry) {
8356 g_free(entry->data);
8357 entry = entry->next;
8360 g_slist_free(sip->allow_events);
8362 if (sip->containers) {
8363 GSList *entry = sip->containers;
8364 while (entry) {
8365 free_container((struct sipe_container *)entry->data);
8366 entry = entry->next;
8369 g_slist_free(sip->containers);
8371 if (sip->contact)
8372 g_free(sip->contact);
8373 sip->contact = NULL;
8374 if (sip->regcallid)
8375 g_free(sip->regcallid);
8376 sip->regcallid = NULL;
8378 if (sip->serveraddr)
8379 g_free(sip->serveraddr);
8380 sip->serveraddr = NULL;
8382 if (sip->focus_factory_uri)
8383 g_free(sip->focus_factory_uri);
8384 sip->focus_factory_uri = NULL;
8386 sip->fd = -1;
8387 sip->processing_input = FALSE;
8389 if (sip->ews) {
8390 sipe_ews_free(sip->ews);
8392 sip->ews = NULL;
8396 * A callback for g_hash_table_foreach_remove
8398 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8399 SIPE_UNUSED_PARAMETER gpointer user_data)
8401 sipe_free_buddy((struct sipe_buddy *) buddy);
8403 /* We must return TRUE as the key/value have already been deleted */
8404 return(TRUE);
8407 static void sipe_close(PurpleConnection *gc)
8409 struct sipe_account_data *sip = gc->proto_data;
8411 if (sip) {
8412 /* leave all conversations */
8413 sipe_session_close_all(sip);
8414 sipe_session_remove_all(sip);
8416 if (sip->csta) {
8417 sip_csta_close(sip);
8420 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8421 /* unsubscribe all */
8422 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8424 /* unregister */
8425 do_register_exp(sip, 0);
8428 sipe_connection_cleanup(sip);
8429 g_free(sip->sipdomain);
8430 g_free(sip->username);
8431 g_free(sip->email);
8432 g_free(sip->password);
8433 g_free(sip->authdomain);
8434 g_free(sip->authuser);
8435 g_free(sip->status);
8436 g_free(sip->note);
8438 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8439 g_hash_table_destroy(sip->buddies);
8440 g_hash_table_destroy(sip->our_publications);
8441 g_hash_table_destroy(sip->user_state_publications);
8442 g_hash_table_destroy(sip->subscriptions);
8444 if (sip->groups) {
8445 GSList *entry = sip->groups;
8446 while (entry) {
8447 struct sipe_group *group = entry->data;
8448 g_free(group->name);
8449 g_free(group);
8450 entry = entry->next;
8453 g_slist_free(sip->groups);
8455 if (sip->our_publication_keys) {
8456 GSList *entry = sip->our_publication_keys;
8457 while (entry) {
8458 g_free(entry->data);
8459 entry = entry->next;
8462 g_slist_free(sip->our_publication_keys);
8464 while (sip->transactions)
8465 transactions_remove(sip, sip->transactions->data);
8467 g_free(gc->proto_data);
8468 gc->proto_data = NULL;
8471 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8472 SIPE_UNUSED_PARAMETER void *user_data)
8474 PurpleAccount *acct = purple_connection_get_account(gc);
8475 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8476 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8477 if (conv == NULL)
8478 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8479 purple_conversation_present(conv);
8480 g_free(id);
8483 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8484 SIPE_UNUSED_PARAMETER void *user_data)
8487 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8488 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8491 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8492 SIPE_UNUSED_PARAMETER struct transaction *trans)
8494 PurpleNotifySearchResults *results;
8495 PurpleNotifySearchColumn *column;
8496 xmlnode *searchResults;
8497 xmlnode *mrow;
8498 int match_count = 0;
8499 gboolean more = FALSE;
8500 gchar *secondary;
8502 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8504 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8505 if (!searchResults) {
8506 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8507 return FALSE;
8510 results = purple_notify_searchresults_new();
8512 if (results == NULL) {
8513 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8514 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8516 xmlnode_free(searchResults);
8517 return FALSE;
8520 column = purple_notify_searchresults_column_new(_("User name"));
8521 purple_notify_searchresults_column_add(results, column);
8523 column = purple_notify_searchresults_column_new(_("Name"));
8524 purple_notify_searchresults_column_add(results, column);
8526 column = purple_notify_searchresults_column_new(_("Company"));
8527 purple_notify_searchresults_column_add(results, column);
8529 column = purple_notify_searchresults_column_new(_("Country"));
8530 purple_notify_searchresults_column_add(results, column);
8532 column = purple_notify_searchresults_column_new(_("Email"));
8533 purple_notify_searchresults_column_add(results, column);
8535 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8536 GList *row = NULL;
8538 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8539 row = g_list_append(row, g_strdup(uri_parts[1]));
8540 g_strfreev(uri_parts);
8542 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8543 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8544 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8545 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8547 purple_notify_searchresults_row_add(results, row);
8548 match_count++;
8551 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8552 char *data = xmlnode_get_data_unescaped(mrow);
8553 more = (g_strcasecmp(data, "true") == 0);
8554 g_free(data);
8557 secondary = g_strdup_printf(
8558 dngettext(GETTEXT_PACKAGE,
8559 "Found %d contact%s:",
8560 "Found %d contacts%s:", match_count),
8561 match_count, more ? _(" (more matched your query)") : "");
8563 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8564 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8565 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8567 g_free(secondary);
8568 xmlnode_free(searchResults);
8569 return TRUE;
8572 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8574 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8575 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8576 unsigned i = 0;
8578 do {
8579 PurpleRequestField *field = entries->data;
8580 const char *id = purple_request_field_get_id(field);
8581 const char *value = purple_request_field_string_get_value(field);
8583 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8585 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8586 } while ((entries = g_list_next(entries)) != NULL);
8587 attrs[i] = NULL;
8589 if (i > 0) {
8590 struct sipe_account_data *sip = gc->proto_data;
8591 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8592 gchar *query = g_strjoinv(NULL, attrs);
8593 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8594 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8595 send_soap_request_with_cb(sip, domain_uri, body,
8596 (TransCallback) process_search_contact_response, NULL);
8597 g_free(domain_uri);
8598 g_free(body);
8599 g_free(query);
8602 g_strfreev(attrs);
8605 static void sipe_show_find_contact(PurplePluginAction *action)
8607 PurpleConnection *gc = (PurpleConnection *) action->context;
8608 PurpleRequestFields *fields;
8609 PurpleRequestFieldGroup *group;
8610 PurpleRequestField *field;
8612 fields = purple_request_fields_new();
8613 group = purple_request_field_group_new(NULL);
8614 purple_request_fields_add_group(fields, group);
8616 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8617 purple_request_field_group_add_field(group, field);
8618 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8619 purple_request_field_group_add_field(group, field);
8620 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8621 purple_request_field_group_add_field(group, field);
8622 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8623 purple_request_field_group_add_field(group, field);
8625 purple_request_fields(gc,
8626 _("Search"),
8627 _("Search for a contact"),
8628 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8629 fields,
8630 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8631 _("_Cancel"), NULL,
8632 purple_connection_get_account(gc), NULL, NULL, gc);
8635 static void sipe_show_about_plugin(PurplePluginAction *action)
8637 PurpleConnection *gc = (PurpleConnection *) action->context;
8638 const char *txt =
8639 "<b><font size=\"+1\">Sipe " SIPE_VERSION "</font></b><br/>"
8640 "<br/>"
8641 "A third-party plugin implementing extended version of SIP/SIMPLE used by various products:<br/>"
8642 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8643 "<li> - MS Office Communications Server 2007</li><br/>"
8644 "<li> - MS Live Communications Server 2005</li><br/>"
8645 "<li> - MS Live Communications Server 2003</li><br/>"
8646 "<li> - Reuters Messaging</li><br/>"
8647 "<br/>"
8648 "Home: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8649 "Support: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">Help Forum</a><br/>"
8650 "License: GPLv2+<br/>"
8651 "<br/>"
8652 "We support users in the following organizations to mention a few:<br/>"
8653 " - CERN<br/>"
8654 " - Reuters Messaging network<br/>"
8655 " - Deutsche Bank<br/>"
8656 " - Merrill Lynch<br/>"
8657 " - Wachovia<br/>"
8658 " - Intel<br/>"
8659 " - Nokia<br/>"
8660 " - HP<br/>"
8661 " - Siemens<br/>"
8662 " - Alcatel-Lucent<br/>"
8663 " - BT<br/>"
8664 "<br/>"
8665 "<b>Authors:</b><br/>"
8666 " - Anibal Avelar<br/>"
8667 " - Gabriel Burt<br/>"
8668 " - Stefan Becker<br/>"
8669 " - pier11<br/>";
8671 purple_notify_formatted(gc, NULL, " ", NULL, txt, NULL, NULL);
8674 static void sipe_republish_calendar(PurplePluginAction *action)
8676 PurpleConnection *gc = (PurpleConnection *) action->context;
8677 struct sipe_account_data *sip = gc->proto_data;
8679 sipe_update_calendar(sip);
8682 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
8683 gpointer value,
8684 GString* str)
8686 struct sipe_publication *publication = value;
8688 g_string_append_printf( str,
8689 SIPE_PUB_XML_PUBLICATION_CLEAR,
8690 publication->category,
8691 publication->instance,
8692 publication->container,
8693 publication->version,
8694 "static");
8697 static void sipe_reset_status(PurplePluginAction *action)
8699 PurpleConnection *gc = (PurpleConnection *) action->context;
8700 struct sipe_account_data *sip = gc->proto_data;
8702 if (sip->ocs2007) /* 2007+ */
8704 GString* str = g_string_new(NULL);
8705 gchar *publications;
8707 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
8708 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8709 return;
8712 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
8713 publications = g_string_free(str, FALSE);
8715 send_presence_publish(sip, publications);
8716 g_free(publications);
8718 else /* 2005 */
8720 send_presence_soap0(sip, FALSE, TRUE);
8724 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
8725 gpointer context)
8727 PurpleConnection *gc = (PurpleConnection *)context;
8728 struct sipe_account_data *sip = gc->proto_data;
8729 GList *menu = NULL;
8730 PurplePluginAction *act;
8731 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
8733 act = purple_plugin_action_new(_("About SIPE plugin"), sipe_show_about_plugin);
8734 menu = g_list_prepend(menu, act);
8736 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
8737 menu = g_list_prepend(menu, act);
8739 if (!strcmp(calendar, "EXCH")) {
8740 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
8741 menu = g_list_prepend(menu, act);
8744 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
8745 menu = g_list_prepend(menu, act);
8747 menu = g_list_reverse(menu);
8749 return menu;
8752 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
8756 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8758 return TRUE;
8762 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8764 return TRUE;
8768 static char *sipe_status_text(PurpleBuddy *buddy)
8770 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8771 const PurpleStatus *status = purple_presence_get_active_status(presence);
8772 const char *status_id = purple_status_get_id(status);
8773 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
8774 struct sipe_buddy *sbuddy;
8775 char *text = NULL;
8777 if (!sip) return NULL; /* happens on pidgin exit */
8779 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8780 if (sbuddy) {
8781 const char *activity_str = sbuddy->activity ?
8782 sbuddy->activity :
8783 !strcmp(status_id, SIPE_STATUS_ID_BUSY) || !strcmp(status_id, SIPE_STATUS_ID_BRB) ?
8784 purple_status_get_name(status) : NULL;
8786 if (activity_str && sbuddy->note)
8788 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
8790 else if (activity_str)
8792 text = g_strdup(activity_str);
8794 else if (sbuddy->note)
8796 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
8800 return text;
8803 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
8805 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8806 const PurpleStatus *status = purple_presence_get_active_status(presence);
8807 struct sipe_account_data *sip;
8808 struct sipe_buddy *sbuddy;
8809 char *note = NULL;
8810 gboolean is_oof_note = FALSE;
8811 char *activity = NULL;
8812 char *calendar = NULL;
8813 char *meeting_subject = NULL;
8814 char *meeting_location = NULL;
8816 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
8817 if (sip) //happens on pidgin exit
8819 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8820 if (sbuddy)
8822 note = sbuddy->note;
8823 is_oof_note = sbuddy->is_oof_note;
8824 activity = sbuddy->activity;
8825 calendar = sipe_cal_get_description(sbuddy);
8826 meeting_subject = sbuddy->meeting_subject;
8827 meeting_location = sbuddy->meeting_location;
8831 //Layout
8832 if (purple_presence_is_online(presence))
8834 const char *status_str = activity ? activity : purple_status_get_name(status);
8836 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
8838 if (purple_presence_is_online(presence) &&
8839 !is_empty(calendar))
8841 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
8843 g_free(calendar);
8844 if (!is_empty(meeting_location))
8846 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
8848 if (!is_empty(meeting_subject))
8850 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
8853 if (note)
8855 char *tmp = g_strdup_printf("<i>%s</i>", note);
8856 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
8858 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
8859 g_free(tmp);
8864 #if PURPLE_VERSION_CHECK(2,5,0)
8865 static GHashTable *
8866 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
8868 GHashTable *table;
8869 table = g_hash_table_new(g_str_hash, g_str_equal);
8870 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
8871 return table;
8873 #endif
8875 static PurpleBuddy *
8876 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
8878 PurpleBuddy *clone;
8879 const gchar *server_alias, *email;
8880 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
8882 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
8884 purple_blist_add_buddy(clone, NULL, group, NULL);
8886 server_alias = purple_buddy_get_server_alias(buddy);
8887 if (server_alias) {
8888 purple_blist_server_alias_buddy(clone, server_alias);
8891 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8892 if (email) {
8893 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
8896 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
8897 //for UI to update;
8898 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
8899 return clone;
8902 static void
8903 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
8905 PurpleBuddy *buddy, *b;
8906 PurpleConnection *gc;
8907 PurpleGroup * group = purple_find_group(group_name);
8909 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
8911 buddy = (PurpleBuddy *)node;
8913 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
8914 gc = purple_account_get_connection(buddy->account);
8916 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
8917 if (!b){
8918 purple_blist_add_buddy_clone(group, buddy);
8921 sipe_group_buddy(gc, buddy->name, NULL, group_name);
8924 static void
8925 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
8927 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8929 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
8931 /* 2007+ conference */
8932 if (sip->ocs2007)
8934 sipe_conf_add(sip, buddy->name);
8936 else /* 2005- multiparty chat */
8938 gchar *self = sip_uri_self(sip);
8939 struct sip_session *session;
8941 session = sipe_session_add_chat(sip);
8942 session->chat_title = sipe_chat_get_name(session->callid);
8943 session->roster_manager = g_strdup(self);
8945 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
8946 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
8947 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
8948 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
8950 g_free(self);
8954 static gboolean
8955 sipe_is_election_finished(struct sip_session *session)
8957 gboolean res = TRUE;
8959 SIPE_DIALOG_FOREACH {
8960 if (dialog->election_vote == 0) {
8961 res = FALSE;
8962 break;
8964 } SIPE_DIALOG_FOREACH_END;
8966 if (res) {
8967 session->is_voting_in_progress = FALSE;
8969 return res;
8972 static void
8973 sipe_election_start(struct sipe_account_data *sip,
8974 struct sip_session *session)
8976 int election_timeout;
8978 if (session->is_voting_in_progress) {
8979 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
8980 return;
8981 } else {
8982 session->is_voting_in_progress = TRUE;
8984 session->bid = rand();
8986 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
8988 SIPE_DIALOG_FOREACH {
8989 /* reset election_vote for each chat participant */
8990 dialog->election_vote = 0;
8992 /* send RequestRM to each chat participant*/
8993 sipe_send_election_request_rm(sip, dialog, session->bid);
8994 } SIPE_DIALOG_FOREACH_END;
8996 election_timeout = 15; /* sec */
8997 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9001 * @param who a URI to whom to invite to chat
9003 void
9004 sipe_invite_to_chat(struct sipe_account_data *sip,
9005 struct sip_session *session,
9006 const gchar *who)
9008 /* a conference */
9009 if (session->focus_uri)
9011 sipe_invite_conf(sip, session, who);
9013 else /* a multi-party chat */
9015 gchar *self = sip_uri_self(sip);
9016 if (session->roster_manager) {
9017 if (!strcmp(session->roster_manager, self)) {
9018 sipe_invite(sip, session, who, NULL, NULL, FALSE);
9019 } else {
9020 sipe_refer(sip, session, who);
9022 } else {
9023 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9025 session->pending_invite_queue = slist_insert_unique_sorted(
9026 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9028 sipe_election_start(sip, session);
9030 g_free(self);
9034 void
9035 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9036 struct sip_session *session)
9038 gchar *invitee;
9039 GSList *entry = session->pending_invite_queue;
9041 while (entry) {
9042 invitee = entry->data;
9043 sipe_invite_to_chat(sip, session, invitee);
9044 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9045 g_free(invitee);
9049 static void
9050 sipe_election_result(struct sipe_account_data *sip,
9051 void *sess)
9053 struct sip_session *session = (struct sip_session *)sess;
9054 gchar *rival;
9055 gboolean has_won = TRUE;
9057 if (session->roster_manager) {
9058 purple_debug_info("sipe",
9059 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9060 return;
9063 session->is_voting_in_progress = FALSE;
9065 SIPE_DIALOG_FOREACH {
9066 if (dialog->election_vote < 0) {
9067 has_won = FALSE;
9068 rival = dialog->with;
9069 break;
9071 } SIPE_DIALOG_FOREACH_END;
9073 if (has_won) {
9074 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9076 session->roster_manager = sip_uri_self(sip);
9078 SIPE_DIALOG_FOREACH {
9079 /* send SetRM to each chat participant*/
9080 sipe_send_election_set_rm(sip, dialog);
9081 } SIPE_DIALOG_FOREACH_END;
9082 } else {
9083 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9085 session->bid = 0;
9087 sipe_process_pending_invite_queue(sip, session);
9091 * For 2007+ conference only.
9093 static void
9094 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9096 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9097 struct sip_session *session;
9099 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9100 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9102 session = sipe_session_find_chat_by_title(sip, chat_title);
9104 sipe_conf_modify_user_role(sip, session, buddy->name);
9108 * For 2007+ conference only.
9110 static void
9111 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9113 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9114 struct sip_session *session;
9116 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9117 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9119 session = sipe_session_find_chat_by_title(sip, chat_title);
9121 sipe_conf_delete_user(sip, session, buddy->name);
9124 static void
9125 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9127 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9128 struct sip_session *session;
9130 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9131 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9133 session = sipe_session_find_chat_by_title(sip, chat_title);
9135 sipe_invite_to_chat(sip, session, buddy->name);
9138 static void
9139 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9141 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9143 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9144 if (phone) {
9145 char *tel_uri = sip_to_tel_uri(phone);
9147 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9148 sip_csta_make_call(sip, tel_uri);
9150 g_free(tel_uri);
9154 static void
9155 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9157 const gchar *email;
9158 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9160 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9161 if (email)
9163 char *mailto = g_strdup_printf("mailto:%s", email);
9164 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9165 #ifndef _WIN32
9167 pid_t pid;
9168 char *const parmList[] = {"xdg-email", mailto, NULL};
9169 if ((pid = fork()) == -1)
9171 purple_debug_info("sipe", "fork() error\n");
9173 else if (pid == 0)
9175 execvp(parmList[0], parmList);
9176 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9179 #else
9181 BOOL ret;
9182 _flushall();
9183 errno = 0;
9184 //@TODO resolve env variable %WINDIR% first
9185 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9186 if (errno)
9188 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9191 #endif
9193 g_free(mailto);
9195 else
9197 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9202 * A menu which appear when right-clicking on buddy in contact list.
9204 static GList *
9205 sipe_buddy_menu(PurpleBuddy *buddy)
9207 PurpleBlistNode *g_node;
9208 PurpleGroup *group, *gr_parent;
9209 PurpleMenuAction *act;
9210 GList *menu = NULL;
9211 GList *menu_groups = NULL;
9212 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9213 const char *email;
9214 const char *phone;
9215 const char *phone_disp_str;
9216 gchar *self = sip_uri_self(sip);
9218 SIPE_SESSION_FOREACH {
9219 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9221 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9223 PurpleConvChatBuddyFlags flags;
9224 PurpleConvChatBuddyFlags flags_us;
9226 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9227 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9228 if (session->focus_uri
9229 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9230 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9232 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9233 act = purple_menu_action_new(label,
9234 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9235 session->chat_title, NULL);
9236 g_free(label);
9237 menu = g_list_prepend(menu, act);
9240 if (session->focus_uri
9241 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9243 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9244 act = purple_menu_action_new(label,
9245 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9246 session->chat_title, NULL);
9247 g_free(label);
9248 menu = g_list_prepend(menu, act);
9251 else
9253 if (!session->focus_uri
9254 || (session->focus_uri && !session->locked))
9256 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9257 act = purple_menu_action_new(label,
9258 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9259 session->chat_title, NULL);
9260 g_free(label);
9261 menu = g_list_prepend(menu, act);
9265 } SIPE_SESSION_FOREACH_END;
9267 act = purple_menu_action_new(_("New chat"),
9268 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9269 NULL, NULL);
9270 menu = g_list_prepend(menu, act);
9272 if (sip->csta && !sip->csta->line_status) {
9273 gchar *tmp = NULL;
9274 /* work phone */
9275 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9276 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9277 if (phone) {
9278 gchar *label = g_strdup_printf(_("Work %s"),
9279 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9280 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9281 g_free(tmp);
9282 tmp = NULL;
9283 g_free(label);
9284 menu = g_list_prepend(menu, act);
9287 /* mobile phone */
9288 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9289 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9290 if (phone) {
9291 gchar *label = g_strdup_printf(_("Mobile %s"),
9292 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9293 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9294 g_free(tmp);
9295 tmp = NULL;
9296 g_free(label);
9297 menu = g_list_prepend(menu, act);
9300 /* home phone */
9301 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9302 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9303 if (phone) {
9304 gchar *label = g_strdup_printf(_("Home %s"),
9305 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9306 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9307 g_free(tmp);
9308 tmp = NULL;
9309 g_free(label);
9310 menu = g_list_prepend(menu, act);
9313 /* other phone */
9314 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9315 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9316 if (phone) {
9317 gchar *label = g_strdup_printf(_("Other %s"),
9318 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9319 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9320 g_free(tmp);
9321 tmp = NULL;
9322 g_free(label);
9323 menu = g_list_prepend(menu, act);
9326 /* custom1 phone */
9327 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9328 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9329 if (phone) {
9330 gchar *label = g_strdup_printf(_("Custom1 %s"),
9331 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9332 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9333 g_free(tmp);
9334 tmp = NULL;
9335 g_free(label);
9336 menu = g_list_prepend(menu, act);
9340 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9341 if (email) {
9342 act = purple_menu_action_new(_("Send email..."),
9343 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9344 NULL, NULL);
9345 menu = g_list_prepend(menu, act);
9348 gr_parent = purple_buddy_get_group(buddy);
9349 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9350 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9351 continue;
9353 group = (PurpleGroup *)g_node;
9354 if (group == gr_parent)
9355 continue;
9357 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9358 continue;
9360 act = purple_menu_action_new(purple_group_get_name(group),
9361 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9362 group->name, NULL);
9363 menu_groups = g_list_prepend(menu_groups, act);
9365 menu_groups = g_list_reverse(menu_groups);
9367 act = purple_menu_action_new(_("Copy to"),
9368 NULL,
9369 NULL, menu_groups);
9370 menu = g_list_prepend(menu, act);
9371 menu = g_list_reverse(menu);
9373 g_free(self);
9374 return menu;
9377 static void
9378 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9380 struct sipe_account_data *sip = chat->account->gc->proto_data;
9381 struct sip_session *session;
9383 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9384 sipe_conf_modify_conference_lock(sip, session, locked);
9387 static void
9388 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9390 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9391 sipe_conf_modify_lock(chat, FALSE);
9394 static void
9395 sipe_chat_menu_lock_cb(PurpleChat *chat)
9397 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9398 sipe_conf_modify_lock(chat, TRUE);
9401 static GList *
9402 sipe_chat_menu(PurpleChat *chat)
9404 PurpleMenuAction *act;
9405 PurpleConvChatBuddyFlags flags_us;
9406 GList *menu = NULL;
9407 struct sipe_account_data *sip = chat->account->gc->proto_data;
9408 struct sip_session *session;
9409 gchar *self;
9411 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9412 if (!session) return NULL;
9414 self = sip_uri_self(sip);
9415 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9417 if (session->focus_uri
9418 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9420 if (session->locked) {
9421 act = purple_menu_action_new(_("Unlock"),
9422 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9423 NULL, NULL);
9424 menu = g_list_prepend(menu, act);
9425 } else {
9426 act = purple_menu_action_new(_("Lock"),
9427 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9428 NULL, NULL);
9429 menu = g_list_prepend(menu, act);
9433 menu = g_list_reverse(menu);
9435 g_free(self);
9436 return menu;
9439 static GList *
9440 sipe_blist_node_menu(PurpleBlistNode *node)
9442 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9443 return sipe_buddy_menu((PurpleBuddy *) node);
9444 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9445 return sipe_chat_menu((PurpleChat *)node);
9446 } else {
9447 return NULL;
9451 static gboolean
9452 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9454 char *uri = trans->payload->data;
9456 PurpleNotifyUserInfo *info;
9457 PurpleBuddy *pbuddy = NULL;
9458 struct sipe_buddy *sbuddy;
9459 const char *alias = NULL;
9460 char *device_name = NULL;
9461 char *server_alias = NULL;
9462 char *phone_number = NULL;
9463 char *email = NULL;
9464 const char *site;
9466 if (!sip) return FALSE;
9468 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9470 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9471 alias = purple_buddy_get_local_alias(pbuddy);
9473 //will query buddy UA's capabilities and send answer to log
9474 sipe_options_request(sip, uri);
9476 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9477 if (sbuddy) {
9478 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9481 info = purple_notify_user_info_new();
9483 if (msg->response != 200) {
9484 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9485 } else {
9486 xmlnode *searchResults;
9487 xmlnode *mrow;
9489 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9490 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9491 if (!searchResults) {
9492 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9493 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9494 const char *value;
9495 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9496 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9497 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9499 /* For 2007 system we will take this from ContactCard -
9500 * it has cleaner tel: URIs at least
9502 if (!sip->ocs2007) {
9503 char *tel_uri = sip_to_tel_uri(phone_number);
9504 /* trims its parameters, so call first */
9505 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9506 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9507 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9508 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9509 g_free(tel_uri);
9512 if (server_alias && strlen(server_alias) > 0) {
9513 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9515 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9516 purple_notify_user_info_add_pair(info, _("Job title"), value);
9518 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9519 purple_notify_user_info_add_pair(info, _("Office"), value);
9521 if (phone_number && strlen(phone_number) > 0) {
9522 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9524 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9525 purple_notify_user_info_add_pair(info, _("Company"), value);
9527 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9528 purple_notify_user_info_add_pair(info, _("City"), value);
9530 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9531 purple_notify_user_info_add_pair(info, _("State"), value);
9533 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9534 purple_notify_user_info_add_pair(info, _("Country"), value);
9536 if (email && strlen(email) > 0) {
9537 purple_notify_user_info_add_pair(info, _("Email address"), email);
9541 xmlnode_free(searchResults);
9544 purple_notify_user_info_add_section_break(info);
9546 if (!server_alias || !strcmp("", server_alias)) {
9547 g_free(server_alias);
9548 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9549 if (server_alias) {
9550 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9554 /* present alias if it differs from server alias */
9555 if (alias && (!server_alias || strcmp(alias, server_alias)))
9557 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9560 if (!email || !strcmp("", email)) {
9561 g_free(email);
9562 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9563 if (email) {
9564 purple_notify_user_info_add_pair(info, _("Email address"), email);
9568 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9569 if (site) {
9570 purple_notify_user_info_add_pair(info, _("Site"), site);
9573 if (device_name) {
9574 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9577 /* show a buddy's user info in a nice dialog box */
9578 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9579 uri, /* buddy's URI */
9580 info, /* body */
9581 NULL, /* callback called when dialog closed */
9582 NULL); /* userdata for callback */
9584 g_free(phone_number);
9585 g_free(server_alias);
9586 g_free(email);
9587 g_free(device_name);
9589 return TRUE;
9593 * AD search first, LDAP based
9595 static void sipe_get_info(PurpleConnection *gc, const char *username)
9597 struct sipe_account_data *sip = gc->proto_data;
9598 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9599 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9600 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9601 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9603 payload->destroy = g_free;
9604 payload->data = g_strdup(username);
9606 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9607 send_soap_request_with_cb(sip, domain_uri, body,
9608 (TransCallback) process_get_info_response, payload);
9609 g_free(domain_uri);
9610 g_free(body);
9611 g_free(row);
9614 static PurplePlugin *my_protocol = NULL;
9616 static PurplePluginProtocolInfo prpl_info =
9618 OPT_PROTO_CHAT_TOPIC,
9619 NULL, /* user_splits */
9620 NULL, /* protocol_options */
9621 NO_BUDDY_ICONS, /* icon_spec */
9622 sipe_list_icon, /* list_icon */
9623 NULL, /* list_emblems */
9624 sipe_status_text, /* status_text */
9625 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9626 sipe_status_types, /* away_states */
9627 sipe_blist_node_menu, /* blist_node_menu */
9628 NULL, /* chat_info */
9629 NULL, /* chat_info_defaults */
9630 sipe_login, /* login */
9631 sipe_close, /* close */
9632 sipe_im_send, /* send_im */
9633 NULL, /* set_info */ // TODO maybe
9634 sipe_send_typing, /* send_typing */
9635 sipe_get_info, /* get_info */
9636 sipe_set_status, /* set_status */
9637 sipe_set_idle, /* set_idle */
9638 NULL, /* change_passwd */
9639 sipe_add_buddy, /* add_buddy */
9640 NULL, /* add_buddies */
9641 sipe_remove_buddy, /* remove_buddy */
9642 NULL, /* remove_buddies */
9643 sipe_add_permit, /* add_permit */
9644 sipe_add_deny, /* add_deny */
9645 sipe_add_deny, /* rem_permit */
9646 sipe_add_permit, /* rem_deny */
9647 dummy_permit_deny, /* set_permit_deny */
9648 NULL, /* join_chat */
9649 NULL, /* reject_chat */
9650 NULL, /* get_chat_name */
9651 sipe_chat_invite, /* chat_invite */
9652 sipe_chat_leave, /* chat_leave */
9653 NULL, /* chat_whisper */
9654 sipe_chat_send, /* chat_send */
9655 sipe_keep_alive, /* keepalive */
9656 NULL, /* register_user */
9657 NULL, /* get_cb_info */ // deprecated
9658 NULL, /* get_cb_away */ // deprecated
9659 sipe_alias_buddy, /* alias_buddy */
9660 sipe_group_buddy, /* group_buddy */
9661 sipe_rename_group, /* rename_group */
9662 NULL, /* buddy_free */
9663 sipe_convo_closed, /* convo_closed */
9664 purple_normalize_nocase, /* normalize */
9665 NULL, /* set_buddy_icon */
9666 sipe_remove_group, /* remove_group */
9667 NULL, /* get_cb_real_name */ // TODO?
9668 NULL, /* set_chat_topic */
9669 NULL, /* find_blist_chat */
9670 NULL, /* roomlist_get_list */
9671 NULL, /* roomlist_cancel */
9672 NULL, /* roomlist_expand_category */
9673 NULL, /* can_receive_file */
9674 NULL, /* send_file */
9675 NULL, /* new_xfer */
9676 NULL, /* offline_message */
9677 NULL, /* whiteboard_prpl_ops */
9678 sipe_send_raw, /* send_raw */
9679 NULL, /* roomlist_room_serialize */
9680 NULL, /* unregister_user */
9681 NULL, /* send_attention */
9682 NULL, /* get_attention_types */
9683 #if !PURPLE_VERSION_CHECK(2,5,0)
9684 /* Backward compatibility when compiling against 2.4.x API */
9685 (void (*)(void)) /* _purple_reserved4 */
9686 #endif
9687 sizeof(PurplePluginProtocolInfo), /* struct_size */
9688 #if PURPLE_VERSION_CHECK(2,5,0)
9689 sipe_get_account_text_table, /* get_account_text_table */
9690 #if PURPLE_VERSION_CHECK(2,6,0)
9691 NULL, /* initiate_media */
9692 NULL, /* get_media_caps */
9693 #endif
9694 #endif
9698 static PurplePluginInfo info = {
9699 PURPLE_PLUGIN_MAGIC,
9700 PURPLE_MAJOR_VERSION,
9701 PURPLE_MINOR_VERSION,
9702 PURPLE_PLUGIN_PROTOCOL, /**< type */
9703 NULL, /**< ui_requirement */
9704 0, /**< flags */
9705 NULL, /**< dependencies */
9706 PURPLE_PRIORITY_DEFAULT, /**< priority */
9707 "prpl-sipe", /**< id */
9708 "Office Communicator", /**< name */
9709 SIPE_VERSION, /**< version */
9710 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9711 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9712 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9713 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9714 "Gabriel Burt <gburt@novell.com>, " /**< author */
9715 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9716 "pier11 <pier11@operamail.com>", /**< author */
9717 "http://sipe.sourceforge.net/", /**< homepage */
9718 sipe_plugin_load, /**< load */
9719 sipe_plugin_unload, /**< unload */
9720 sipe_plugin_destroy, /**< destroy */
9721 NULL, /**< ui_info */
9722 &prpl_info, /**< extra_info */
9723 NULL,
9724 sipe_actions,
9725 NULL,
9726 NULL,
9727 NULL,
9728 NULL
9731 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9733 GList *entry;
9735 entry = prpl_info.protocol_options;
9736 while (entry) {
9737 purple_account_option_destroy(entry->data);
9738 entry = g_list_delete_link(entry, entry);
9740 prpl_info.protocol_options = NULL;
9742 entry = prpl_info.user_splits;
9743 while (entry) {
9744 purple_account_user_split_destroy(entry->data);
9745 entry = g_list_delete_link(entry, entry);
9747 prpl_info.user_splits = NULL;
9750 static void init_plugin(PurplePlugin *plugin)
9752 PurpleAccountUserSplit *split;
9753 PurpleAccountOption *option;
9755 srand(time(NULL));
9757 #ifdef ENABLE_NLS
9758 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
9759 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
9760 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
9761 textdomain(GETTEXT_PACKAGE);
9762 #endif
9764 purple_plugin_register(plugin);
9766 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
9767 purple_account_user_split_set_reverse(split, FALSE);
9768 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
9770 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
9771 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9773 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
9774 purple_account_option_add_list_item(option, _("Auto"), "auto");
9775 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
9776 purple_account_option_add_list_item(option, _("TCP"), "tcp");
9777 purple_account_option_add_list_item(option, _("UDP"), "udp");
9778 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9780 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
9781 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
9783 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
9784 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9786 #ifdef USE_KERBEROS
9787 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
9788 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9790 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
9791 * No login/password is taken into account if this option present,
9792 * instead used default credentials stored in OS.
9794 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
9795 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9796 #endif
9798 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
9799 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
9800 purple_account_option_add_list_item(option, _("None"), "NONE");
9801 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9803 /** Example: https://server.company.com/EWS/Exchange.asmx */
9804 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
9805 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9807 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
9808 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9810 /** Example: DOMAIN\user or user@company.com */
9811 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
9812 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9814 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
9815 purple_account_option_set_masked(option, TRUE);
9816 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9818 my_protocol = plugin;
9821 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
9824 Local Variables:
9825 mode: c
9826 c-file-style: "bsd"
9827 indent-tabs-mode: t
9828 tab-width: 8
9829 End: