presence.2005: choose note vs. oof note based on time
[siplcs.git] / src / core / sipe.c
blob7b4cf95cf167a0da782b91345af90f450e3f3e9c
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 pier11 <pier11@operamail.com>
7 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
10 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
13 * ***
14 * Thanks to Google's Summer of Code Program and the helpful mentors
15 * ***
17 * Session-based SIP MESSAGE documentation:
18 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 #ifndef _WIN32
36 #include <sys/socket.h>
37 #include <sys/ioctl.h>
38 #include <sys/types.h>
39 #include <netinet/in.h>
40 #include <net/if.h>
41 #else
42 #ifdef _DLL
43 #define _WS2TCPIP_H_
44 #define _WINSOCK2API_
45 #define _LIBC_INTERNAL_
46 #endif /* _DLL */
47 #include "internal.h"
48 #endif /* _WIN32 */
50 #include <time.h>
51 #include <stdio.h>
52 #include <errno.h>
53 #include <string.h>
54 #include <unistd.h>
55 #include <glib.h>
58 #include "accountopt.h"
59 #include "blist.h"
60 #include "conversation.h"
61 #include "dnsquery.h"
62 #include "debug.h"
63 #include "notify.h"
64 #include "savedstatuses.h"
65 #include "privacy.h"
66 #include "prpl.h"
67 #include "plugin.h"
68 #include "util.h"
69 #include "version.h"
70 #include "network.h"
71 #include "xmlnode.h"
72 #include "mime.h"
73 #include "core.h"
75 #include "sipe.h"
76 #include "sipe-cal.h"
77 #include "sipe-ews.h"
78 #include "sipe-chat.h"
79 #include "sipe-conf.h"
80 #include "sip-csta.h"
81 #include "sipe-dialog.h"
82 #include "sipe-nls.h"
83 #include "sipe-session.h"
84 #include "sipe-utils.h"
85 #include "sipmsg.h"
86 #include "sipe-sign.h"
87 #include "dnssrv.h"
88 #include "request.h"
90 /* Backward compatibility when compiling against 2.4.x API */
91 #if !PURPLE_VERSION_CHECK(2,5,0)
92 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
93 #endif
95 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
96 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
98 /* Keep in sync with sipe_transport_type! */
99 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
100 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
102 /* Status identifiers (see also: sipe_status_types()) */
103 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
104 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
105 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
106 /* PURPLE_STATUS_UNAVAILABLE: */
107 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
108 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
109 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
110 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
111 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
112 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
113 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
114 /* PURPLE_STATUS_AWAY: */
115 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
116 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
117 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
118 /** Reuters status (user settable) */
119 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
120 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
121 /* ??? PURPLE_STATUS_MOBILE */
122 /* ??? PURPLE_STATUS_TUNE */
124 /* Status attributes (see also sipe_status_types() */
125 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
127 static struct sipe_activity_map_struct
129 sipe_activity type;
130 const char *token;
131 const char *desc;
132 const char *status_id;
134 } const sipe_activity_map[] =
136 /* This has nothing to do with Availability numbers, like 3500 (online).
137 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
139 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
140 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
141 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , SIPE_STATUS_ID_IDLE },
142 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
143 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("BusyIdle") , SIPE_STATUS_ID_BUSYIDLE },
144 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
145 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
146 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
147 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , SIPE_STATUS_ID_LUNCH },
148 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
149 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , SIPE_STATUS_ID_ON_PHONE },
150 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , SIPE_STATUS_ID_IN_CONF },
151 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , SIPE_STATUS_ID_IN_MEETING },
152 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
153 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
155 /** @param x is sipe_activity */
156 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
159 /* Action name templates */
160 #define ACTION_NAME_PRESENCE "<presence><%s>"
162 static sipe_activity
163 sipe_get_activity_by_token(const char *token)
165 int i;
167 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
169 if (!strcmp(token, sipe_activity_map[i].token))
170 return sipe_activity_map[i].type;
173 return sipe_activity_map[0].type;
176 static const char *
177 sipe_get_activity_desc_by_token(const char *token)
179 if (!token) return NULL;
181 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
184 /** Allows to send typed messages from chat window again after account reinstantiation. */
185 static void
186 sipe_rejoin_chat(PurpleConversation *conv)
188 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
189 PURPLE_CONV_CHAT(conv)->left)
191 PURPLE_CONV_CHAT(conv)->left = FALSE;
192 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
196 static char *genbranch()
198 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
199 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
200 rand() & 0xFFFF, rand() & 0xFFFF);
204 static char *default_ua = NULL;
205 static const char*
206 sipe_get_useragent(struct sipe_account_data *sip)
208 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
209 if (is_empty(useragent)) {
210 if (!default_ua) {
211 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
212 /* ref: lzodefs.h */
213 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
214 #define SIPE_TARGET_PLATFORM "linux"
215 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
216 #define SIPE_TARGET_PLATFORM "bsd"
217 # elif defined(__APPLE__) || defined(__MACOS__)
218 #define SIPE_TARGET_PLATFORM "macosx"
219 #elif defined(__solaris__) || defined(__sun)
220 #define SIPE_TARGET_PLATFORM "sun"
221 #elif defined(_WIN32)
222 #define SIPE_TARGET_PLATFORM "win"
223 #else
224 #define SIPE_TARGET_PLATFORM "generic"
225 #endif
227 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
228 #define SIPE_TARGET_ARCH "x86_64"
229 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
230 #define SIPE_TARGET_ARCH "i386"
231 #elif defined(__ppc64__)
232 #define SIPE_TARGET_ARCH "ppc64"
233 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
234 #define SIPE_TARGET_ARCH "ppc"
235 #else
236 #define SIPE_TARGET_ARCH "other"
237 #endif
239 default_ua = g_strdup_printf("Purple/%s Sipe/%s (%s-%s; %s)",
240 purple_core_get_version(),
241 SIPE_VERSION,
242 SIPE_TARGET_PLATFORM,
243 SIPE_TARGET_ARCH,
244 sip->server_version ? sip->server_version : "");
246 useragent = default_ua;
248 return useragent;
251 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
252 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
254 return "sipe";
257 static void sipe_plugin_destroy(PurplePlugin *plugin);
259 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
261 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
262 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
263 gpointer data);
265 static void sipe_close(PurpleConnection *gc);
267 static void send_presence_status(struct sipe_account_data *sip);
269 static void sendout_pkt(PurpleConnection *gc, const char *buf);
271 static void sipe_keep_alive(PurpleConnection *gc)
273 struct sipe_account_data *sip = gc->proto_data;
274 if (sip->transport == SIPE_TRANSPORT_UDP) {
275 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
276 gchar buf[2] = {0, 0};
277 purple_debug_info("sipe", "sending keep alive\n");
278 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
279 } else {
280 time_t now = time(NULL);
281 if ((sip->keepalive_timeout > 0) &&
282 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
283 #if PURPLE_VERSION_CHECK(2,4,0)
284 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
285 #endif
287 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
288 sendout_pkt(gc, "\r\n\r\n");
289 sip->last_keepalive = now;
294 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
296 struct sip_connection *ret = NULL;
297 GSList *entry = sip->openconns;
298 while (entry) {
299 ret = entry->data;
300 if (ret->fd == fd) return ret;
301 entry = entry->next;
303 return NULL;
306 static void sipe_auth_free(struct sip_auth *auth)
308 g_free(auth->opaque);
309 auth->opaque = NULL;
310 g_free(auth->realm);
311 auth->realm = NULL;
312 g_free(auth->target);
313 auth->target = NULL;
314 auth->type = AUTH_TYPE_UNSET;
315 auth->retries = 0;
316 auth->expires = 0;
317 g_free(auth->gssapi_data);
318 auth->gssapi_data = NULL;
319 sip_sec_destroy_context(auth->gssapi_context);
320 auth->gssapi_context = NULL;
323 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
325 struct sip_connection *ret = g_new0(struct sip_connection, 1);
326 ret->fd = fd;
327 sip->openconns = g_slist_append(sip->openconns, ret);
328 return ret;
331 static void connection_remove(struct sipe_account_data *sip, int fd)
333 struct sip_connection *conn = connection_find(sip, fd);
334 if (conn) {
335 sip->openconns = g_slist_remove(sip->openconns, conn);
336 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
337 g_free(conn->inbuf);
338 g_free(conn);
342 static void connection_free_all(struct sipe_account_data *sip)
344 struct sip_connection *ret = NULL;
345 GSList *entry = sip->openconns;
346 while (entry) {
347 ret = entry->data;
348 connection_remove(sip, ret->fd);
349 entry = sip->openconns;
353 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
355 gchar noncecount[9];
356 const char *authuser = sip->authuser;
357 gchar *response;
358 gchar *ret;
360 if (!authuser || strlen(authuser) < 1) {
361 authuser = sip->username;
364 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
365 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
367 // If we have a signature for the message, include that
368 if (msg->signature) {
369 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);
372 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
373 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
374 gchar *gssapi_data;
375 gchar *opaque;
377 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
378 &(auth->expires),
379 auth->type,
380 purple_account_get_bool(sip->account, "sso", TRUE),
381 sip->authdomain ? sip->authdomain : "",
382 authuser,
383 sip->password,
384 auth->target,
385 auth->gssapi_data);
386 if (!gssapi_data || !auth->gssapi_context) {
387 sip->gc->wants_to_die = TRUE;
388 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
389 return NULL;
392 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
393 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
394 g_free(opaque);
395 g_free(gssapi_data);
396 return ret;
399 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
401 } else { /* Digest */
403 /* Calculate new session key */
404 if (!auth->opaque) {
405 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
406 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
407 authuser, auth->realm, sip->password,
408 auth->gssapi_data, NULL);
411 sprintf(noncecount, "%08d", auth->nc++);
412 response = purple_cipher_http_digest_calculate_response("md5",
413 msg->method, msg->target, NULL, NULL,
414 auth->gssapi_data, noncecount, NULL,
415 auth->opaque);
416 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
418 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);
419 g_free(response);
420 return ret;
424 static char *parse_attribute(const char *attrname, const char *source)
426 const char *tmp, *tmp2;
427 char *retval = NULL;
428 int len = strlen(attrname);
430 if (!strncmp(source, attrname, len)) {
431 tmp = source + len;
432 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
433 if (tmp2)
434 retval = g_strndup(tmp, tmp2 - tmp);
435 else
436 retval = g_strdup(tmp);
439 return retval;
442 static void fill_auth(gchar *hdr, struct sip_auth *auth)
444 int i;
445 gchar **parts;
447 if (!hdr) {
448 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
449 return;
452 if (!g_strncasecmp(hdr, "NTLM", 4)) {
453 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
454 auth->type = AUTH_TYPE_NTLM;
455 hdr += 5;
456 auth->nc = 1;
457 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
458 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
459 auth->type = AUTH_TYPE_KERBEROS;
460 hdr += 9;
461 auth->nc = 3;
462 } else {
463 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
464 auth->type = AUTH_TYPE_DIGEST;
465 hdr += 7;
468 parts = g_strsplit(hdr, "\", ", 0);
469 for (i = 0; parts[i]; i++) {
470 char *tmp;
472 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
474 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
475 g_free(auth->gssapi_data);
476 auth->gssapi_data = tmp;
478 if (auth->type == AUTH_TYPE_NTLM) {
479 /* NTLM module extracts nonce from gssapi-data */
480 auth->nc = 3;
483 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
484 /* Only used with AUTH_TYPE_DIGEST */
485 g_free(auth->gssapi_data);
486 auth->gssapi_data = tmp;
487 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
488 g_free(auth->opaque);
489 auth->opaque = tmp;
490 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
491 g_free(auth->realm);
492 auth->realm = tmp;
494 if (auth->type == AUTH_TYPE_DIGEST) {
495 /* Throw away old session key */
496 g_free(auth->opaque);
497 auth->opaque = NULL;
498 auth->nc = 1;
501 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
502 g_free(auth->target);
503 auth->target = tmp;
506 g_strfreev(parts);
508 return;
511 static void sipe_canwrite_cb(gpointer data,
512 SIPE_UNUSED_PARAMETER gint source,
513 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
515 PurpleConnection *gc = data;
516 struct sipe_account_data *sip = gc->proto_data;
517 gsize max_write;
518 gssize written;
520 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
522 if (max_write == 0) {
523 if (sip->tx_handler != 0){
524 purple_input_remove(sip->tx_handler);
525 sip->tx_handler = 0;
527 return;
530 written = write(sip->fd, sip->txbuf->outptr, max_write);
532 if (written < 0 && errno == EAGAIN)
533 written = 0;
534 else if (written <= 0) {
535 /*TODO: do we really want to disconnect on a failure to write?*/
536 purple_connection_error(gc, _("Could not write"));
537 return;
540 purple_circ_buffer_mark_read(sip->txbuf, written);
543 static void sipe_canwrite_cb_ssl(gpointer data,
544 SIPE_UNUSED_PARAMETER gint src,
545 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
547 PurpleConnection *gc = data;
548 struct sipe_account_data *sip = gc->proto_data;
549 gsize max_write;
550 gssize written;
552 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
554 if (max_write == 0) {
555 if (sip->tx_handler != 0) {
556 purple_input_remove(sip->tx_handler);
557 sip->tx_handler = 0;
558 return;
562 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
564 if (written < 0 && errno == EAGAIN)
565 written = 0;
566 else if (written <= 0) {
567 /*TODO: do we really want to disconnect on a failure to write?*/
568 purple_connection_error(gc, _("Could not write"));
569 return;
572 purple_circ_buffer_mark_read(sip->txbuf, written);
575 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
577 static void send_later_cb(gpointer data, gint source,
578 SIPE_UNUSED_PARAMETER const gchar *error)
580 PurpleConnection *gc = data;
581 struct sipe_account_data *sip;
582 struct sip_connection *conn;
584 if (!PURPLE_CONNECTION_IS_VALID(gc))
586 if (source >= 0)
587 close(source);
588 return;
591 if (source < 0) {
592 purple_connection_error(gc, _("Could not connect"));
593 return;
596 sip = gc->proto_data;
597 sip->fd = source;
598 sip->connecting = FALSE;
599 sip->last_keepalive = time(NULL);
601 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
603 /* If there is more to write now, we need to register a handler */
604 if (sip->txbuf->bufused > 0)
605 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
607 conn = connection_create(sip, source);
608 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
611 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
613 struct sipe_account_data *sip;
615 if (!PURPLE_CONNECTION_IS_VALID(gc))
617 if (gsc) purple_ssl_close(gsc);
618 return NULL;
621 sip = gc->proto_data;
622 sip->fd = gsc->fd;
623 sip->gsc = gsc;
624 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
625 sip->connecting = FALSE;
626 sip->last_keepalive = time(NULL);
628 connection_create(sip, gsc->fd);
630 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
632 return sip;
635 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
636 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
638 PurpleConnection *gc = data;
639 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
640 if (sip == NULL) return;
642 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
644 /* If there is more to write now */
645 if (sip->txbuf->bufused > 0) {
646 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
651 static void sendlater(PurpleConnection *gc, const char *buf)
653 struct sipe_account_data *sip = gc->proto_data;
655 if (!sip->connecting) {
656 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
657 if (sip->transport == SIPE_TRANSPORT_TLS){
658 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
659 } else {
660 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
661 purple_connection_error(gc, _("Could not create socket"));
664 sip->connecting = TRUE;
667 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
668 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
670 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
673 static void sendout_pkt(PurpleConnection *gc, const char *buf)
675 struct sipe_account_data *sip = gc->proto_data;
676 time_t currtime = time(NULL);
677 int writelen = strlen(buf);
678 char *tmp;
680 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
681 g_free(tmp);
682 if (sip->transport == SIPE_TRANSPORT_UDP) {
683 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
684 purple_debug_info("sipe", "could not send packet\n");
686 } else {
687 int ret;
688 if (sip->fd < 0) {
689 sendlater(gc, buf);
690 return;
693 if (sip->tx_handler) {
694 ret = -1;
695 errno = EAGAIN;
696 } else{
697 if (sip->gsc){
698 ret = purple_ssl_write(sip->gsc, buf, writelen);
699 }else{
700 ret = write(sip->fd, buf, writelen);
704 if (ret < 0 && errno == EAGAIN)
705 ret = 0;
706 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
707 sendlater(gc, buf);
708 return;
711 if (ret < writelen) {
712 if (!sip->tx_handler){
713 if (sip->gsc){
714 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
716 else{
717 sip->tx_handler = purple_input_add(sip->fd,
718 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
719 gc);
723 /* XXX: is it OK to do this? You might get part of a request sent
724 with part of another. */
725 if (sip->txbuf->bufused > 0)
726 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
728 purple_circ_buffer_append(sip->txbuf, buf + ret,
729 writelen - ret);
734 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
736 sendout_pkt(gc, buf);
737 return len;
740 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
742 GSList *tmp = msg->headers;
743 gchar *name;
744 gchar *value;
745 GString *outstr = g_string_new("");
746 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
747 while (tmp) {
748 name = ((struct siphdrelement*) (tmp->data))->name;
749 value = ((struct siphdrelement*) (tmp->data))->value;
750 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
751 tmp = g_slist_next(tmp);
753 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
754 sendout_pkt(sip->gc, outstr->str);
755 g_string_free(outstr, TRUE);
758 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
760 gchar * buf;
762 if (sip->registrar.type == AUTH_TYPE_UNSET) {
763 return;
766 if (sip->registrar.gssapi_context) {
767 struct sipmsg_breakdown msgbd;
768 gchar *signature_input_str;
769 msgbd.msg = msg;
770 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
771 msgbd.rand = g_strdup_printf("%08x", g_random_int());
772 sip->registrar.ntlm_num++;
773 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
774 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
775 if (signature_input_str != NULL) {
776 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
777 msg->signature = signature_hex;
778 msg->rand = g_strdup(msgbd.rand);
779 msg->num = g_strdup(msgbd.num);
780 g_free(signature_input_str);
782 sipmsg_breakdown_free(&msgbd);
785 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
786 buf = auth_header(sip, &sip->registrar, msg);
787 if (buf) {
788 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
790 g_free(buf);
791 } 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")) {
792 sip->registrar.nc = 3;
793 #ifdef USE_KERBEROS
794 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
795 #endif
796 sip->registrar.type = AUTH_TYPE_NTLM;
797 #ifdef USE_KERBEROS
798 } else {
799 sip->registrar.type = AUTH_TYPE_KERBEROS;
801 #endif
804 buf = auth_header(sip, &sip->registrar, msg);
805 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
806 g_free(buf);
807 } else {
808 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
812 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
813 const char *text, const char *body)
815 gchar *name;
816 gchar *value;
817 GString *outstr = g_string_new("");
818 struct sipe_account_data *sip = gc->proto_data;
819 gchar *contact;
820 GSList *tmp;
821 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
823 contact = get_contact(sip);
824 sipmsg_add_header(msg, "Contact", contact);
825 g_free(contact);
827 if (body) {
828 gchar len[12];
829 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
830 sipmsg_add_header(msg, "Content-Length", len);
831 } else {
832 sipmsg_add_header(msg, "Content-Length", "0");
835 msg->response = code;
837 sipmsg_strip_headers(msg, keepers);
838 sipmsg_merge_new_headers(msg);
839 sign_outgoing_message(msg, sip, msg->method);
841 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
842 tmp = msg->headers;
843 while (tmp) {
844 name = ((struct siphdrelement*) (tmp->data))->name;
845 value = ((struct siphdrelement*) (tmp->data))->value;
847 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
848 tmp = g_slist_next(tmp);
850 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
851 sendout_pkt(gc, outstr->str);
852 g_string_free(outstr, TRUE);
855 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
857 if (sip->transactions) {
858 sip->transactions = g_slist_remove(sip->transactions, trans);
859 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
861 if (trans->msg) sipmsg_free(trans->msg);
862 if (trans->payload) {
863 (*trans->payload->destroy)(trans->payload->data);
864 g_free(trans->payload);
866 g_free(trans->key);
867 g_free(trans);
871 static struct transaction *
872 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
874 gchar *call_id = NULL;
875 gchar *cseq = NULL;
876 struct transaction *trans = g_new0(struct transaction, 1);
878 trans->time = time(NULL);
879 trans->msg = (struct sipmsg *)msg;
880 call_id = sipmsg_find_header(trans->msg, "Call-ID");
881 cseq = sipmsg_find_header(trans->msg, "CSeq");
882 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
883 trans->callback = callback;
884 sip->transactions = g_slist_append(sip->transactions, trans);
885 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
886 return trans;
889 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
891 struct transaction *trans;
892 GSList *transactions = sip->transactions;
893 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
894 gchar *cseq = sipmsg_find_header(msg, "CSeq");
895 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
897 while (transactions) {
898 trans = transactions->data;
899 if (!g_strcasecmp(trans->key, key)) {
900 g_free(key);
901 return trans;
903 transactions = transactions->next;
906 g_free(key);
907 return NULL;
910 struct transaction *
911 send_sip_request(PurpleConnection *gc, const gchar *method,
912 const gchar *url, const gchar *to, const gchar *addheaders,
913 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
915 struct sipe_account_data *sip = gc->proto_data;
916 const char *addh = "";
917 char *buf;
918 struct sipmsg *msg;
919 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
920 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
921 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
922 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
923 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
924 gchar *route = g_strdup("");
925 gchar *epid = get_epid(sip);
926 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
927 struct transaction *trans = NULL;
929 if (dialog && dialog->routes)
931 GSList *iter = dialog->routes;
933 while(iter)
935 char *tmp = route;
936 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
937 g_free(tmp);
938 iter = g_slist_next(iter);
942 if (!ourtag && !dialog) {
943 ourtag = gentag();
946 if (!strcmp(method, "REGISTER")) {
947 if (sip->regcallid) {
948 g_free(callid);
949 callid = g_strdup(sip->regcallid);
950 } else {
951 sip->regcallid = g_strdup(callid);
953 cseq = ++sip->cseq;
956 if (addheaders) addh = addheaders;
958 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
959 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
960 "From: <sip:%s>%s%s;epid=%s\r\n"
961 "To: <%s>%s%s%s%s\r\n"
962 "Max-Forwards: 70\r\n"
963 "CSeq: %d %s\r\n"
964 "User-Agent: %s\r\n"
965 "Call-ID: %s\r\n"
966 "%s%s"
967 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
968 method,
969 dialog && dialog->request ? dialog->request : url,
970 TRANSPORT_DESCRIPTOR,
971 purple_network_get_my_ip(-1),
972 sip->listenport,
973 branch ? ";branch=" : "",
974 branch ? branch : "",
975 sip->username,
976 ourtag ? ";tag=" : "",
977 ourtag ? ourtag : "",
978 epid,
980 theirtag ? ";tag=" : "",
981 theirtag ? theirtag : "",
982 theirepid ? ";epid=" : "",
983 theirepid ? theirepid : "",
984 cseq,
985 method,
986 sipe_get_useragent(sip),
987 callid,
988 route,
989 addh,
990 body ? (gsize) strlen(body) : 0,
991 body ? body : "");
994 //printf ("parsing msg buf:\n%s\n\n", buf);
995 msg = sipmsg_parse_msg(buf);
997 g_free(buf);
998 g_free(ourtag);
999 g_free(theirtag);
1000 g_free(theirepid);
1001 g_free(branch);
1002 g_free(callid);
1003 g_free(route);
1004 g_free(epid);
1006 sign_outgoing_message (msg, sip, method);
1008 buf = sipmsg_to_string (msg);
1010 /* add to ongoing transactions */
1011 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1012 if (strcmp(method, "ACK")) {
1013 trans = transactions_add_buf(sip, msg, tc);
1014 } else {
1015 sipmsg_free(msg);
1017 sendout_pkt(gc, buf);
1018 g_free(buf);
1020 return trans;
1024 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1026 static void
1027 send_soap_request_with_cb(struct sipe_account_data *sip,
1028 gchar *from0,
1029 gchar *body,
1030 TransCallback callback,
1031 struct transaction_payload *payload)
1033 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1034 gchar *contact = get_contact(sip);
1035 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1036 "Content-Type: application/SOAP+xml\r\n",contact);
1038 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1039 trans->payload = payload;
1041 g_free(from);
1042 g_free(contact);
1043 g_free(hdr);
1046 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1048 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1051 static char *get_contact_register(struct sipe_account_data *sip)
1053 char *epid = get_epid(sip);
1054 char *uuid = generateUUIDfromEPID(epid);
1055 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);
1056 g_free(uuid);
1057 g_free(epid);
1058 return(buf);
1061 static void do_register_exp(struct sipe_account_data *sip, int expire)
1063 char *uri;
1064 char *expires;
1065 char *to;
1066 char *contact;
1067 char *hdr;
1069 if (!sip->sipdomain) return;
1071 uri = sip_uri_from_name(sip->sipdomain);
1072 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1073 to = sip_uri_self(sip);
1074 contact = get_contact_register(sip);
1075 hdr = g_strdup_printf("Contact: %s\r\n"
1076 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1077 "Event: registration\r\n"
1078 "Allow-Events: presence\r\n"
1079 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1080 "%s", contact, expires);
1081 g_free(contact);
1082 g_free(expires);
1084 sip->registerstatus = 1;
1086 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1087 process_register_response);
1089 g_free(hdr);
1090 g_free(uri);
1091 g_free(to);
1094 static void do_register_cb(struct sipe_account_data *sip,
1095 SIPE_UNUSED_PARAMETER void *unused)
1097 do_register_exp(sip, -1);
1098 sip->reregister_set = FALSE;
1101 static void do_register(struct sipe_account_data *sip)
1103 do_register_exp(sip, -1);
1106 static void
1107 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1109 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1110 send_soap_request(sip, body);
1111 g_free(body);
1114 static void
1115 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1117 if (allow) {
1118 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1119 } else {
1120 purple_debug_info("sipe", "Blocking contact %s\n", who);
1123 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1126 static
1127 void sipe_auth_user_cb(void * data)
1129 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1130 if (!job) return;
1132 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1133 g_free(job);
1136 static
1137 void sipe_deny_user_cb(void * data)
1139 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1140 if (!job) return;
1142 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1143 g_free(job);
1146 static void
1147 sipe_add_permit(PurpleConnection *gc, const char *name)
1149 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1150 sipe_contact_allow_deny(sip, name, TRUE);
1153 static void
1154 sipe_add_deny(PurpleConnection *gc, const char *name)
1156 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1157 sipe_contact_allow_deny(sip, name, FALSE);
1160 /*static void
1161 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1163 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1164 sipe_contact_set_acl(sip, name, "");
1167 static void
1168 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1170 xmlnode *watchers;
1171 xmlnode *watcher;
1172 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1173 if (msg->response != 0 && msg->response != 200) return;
1175 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1177 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1178 if (!watchers) return;
1180 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1181 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1182 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1183 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1185 // TODO pull out optional displayName to pass as alias
1186 if (remote_user) {
1187 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1188 job->who = remote_user;
1189 job->sip = sip;
1190 purple_account_request_authorization(
1191 sip->account,
1192 remote_user,
1193 _("you"), /* id */
1194 alias,
1195 NULL, /* message */
1196 on_list,
1197 sipe_auth_user_cb,
1198 sipe_deny_user_cb,
1199 (void *) job);
1204 xmlnode_free(watchers);
1205 return;
1208 static void
1209 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1211 PurpleGroup * purple_group = purple_find_group(group->name);
1212 if (!purple_group) {
1213 purple_group = purple_group_new(group->name);
1214 purple_blist_add_group(purple_group, NULL);
1217 if (purple_group) {
1218 group->purple_group = purple_group;
1219 sip->groups = g_slist_append(sip->groups, group);
1220 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1221 } else {
1222 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1226 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1228 struct sipe_group *group;
1229 GSList *entry;
1230 if (sip == NULL) {
1231 return NULL;
1234 entry = sip->groups;
1235 while (entry) {
1236 group = entry->data;
1237 if (group->id == id) {
1238 return group;
1240 entry = entry->next;
1242 return NULL;
1245 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1247 struct sipe_group *group;
1248 GSList *entry;
1249 if (!sip || !name) {
1250 return NULL;
1253 entry = sip->groups;
1254 while (entry) {
1255 group = entry->data;
1256 if (!strcmp(group->name, name)) {
1257 return group;
1259 entry = entry->next;
1261 return NULL;
1264 static void
1265 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1267 gchar *body;
1268 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1269 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1270 send_soap_request(sip, body);
1271 g_free(body);
1272 g_free(group->name);
1273 group->name = g_strdup(name);
1277 * Only appends if no such value already stored.
1278 * Like Set in Java.
1280 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1281 GSList * res = list;
1282 if (!g_slist_find_custom(list, data, func)) {
1283 res = g_slist_insert_sorted(list, data, func);
1285 return res;
1288 static int
1289 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1290 return group1->id - group2->id;
1294 * Returns string like "2 4 7 8" - group ids buddy belong to.
1296 static gchar *
1297 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1298 int i = 0;
1299 gchar *res;
1300 //creating array from GList, converting int to gchar*
1301 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1302 GSList *entry = buddy->groups;
1303 while (entry) {
1304 struct sipe_group * group = entry->data;
1305 ids_arr[i] = g_strdup_printf("%d", group->id);
1306 entry = entry->next;
1307 i++;
1309 ids_arr[i] = NULL;
1310 res = g_strjoinv(" ", ids_arr);
1311 g_strfreev(ids_arr);
1312 return res;
1316 * Sends buddy update to server
1318 static void
1319 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1321 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1322 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1324 if (buddy && purple_buddy) {
1325 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1326 gchar *body;
1327 gchar *groups = sipe_get_buddy_groups_string(buddy);
1328 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1330 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1331 alias, groups, "true", buddy->name, sip->contacts_delta++
1333 send_soap_request(sip, body);
1334 g_free(groups);
1335 g_free(body);
1339 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1341 if (msg->response == 200) {
1342 struct sipe_group *group;
1343 struct group_user_context *ctx = trans->payload->data;
1344 xmlnode *xml;
1345 xmlnode *node;
1346 char *group_id;
1347 struct sipe_buddy *buddy;
1349 xml = xmlnode_from_str(msg->body, msg->bodylen);
1350 if (!xml) {
1351 return FALSE;
1354 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1355 if (!node) {
1356 xmlnode_free(xml);
1357 return FALSE;
1360 group_id = xmlnode_get_data(node);
1361 if (!group_id) {
1362 xmlnode_free(xml);
1363 return FALSE;
1366 group = g_new0(struct sipe_group, 1);
1367 group->id = (int)g_ascii_strtod(group_id, NULL);
1368 g_free(group_id);
1369 group->name = g_strdup(ctx->group_name);
1371 sipe_group_add(sip, group);
1373 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1374 if (buddy) {
1375 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1378 sipe_group_set_user(sip, ctx->user_name);
1380 xmlnode_free(xml);
1381 return TRUE;
1383 return FALSE;
1386 static void sipe_group_context_destroy(gpointer data)
1388 struct group_user_context *ctx = data;
1389 g_free(ctx->group_name);
1390 g_free(ctx->user_name);
1391 g_free(ctx);
1394 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1396 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1397 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1398 gchar *body;
1399 ctx->group_name = g_strdup(name);
1400 ctx->user_name = g_strdup(who);
1401 payload->destroy = sipe_group_context_destroy;
1402 payload->data = ctx;
1404 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1405 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1406 g_free(body);
1410 * Data structure for scheduled actions
1413 struct scheduled_action {
1415 * Name of action.
1416 * Format is <Event>[<Data>...]
1417 * Example: <presence><sip:user@domain.com> or <registration>
1419 gchar *name;
1420 guint timeout_handler;
1421 gboolean repetitive;
1422 Action action;
1423 GDestroyNotify destroy;
1424 struct sipe_account_data *sip;
1425 void *payload;
1429 * A timer callback
1430 * Should return FALSE if repetitive action is not needed
1432 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1434 gboolean ret;
1435 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1436 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1437 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1438 (sched_action->action)(sched_action->sip, sched_action->payload);
1439 ret = sched_action->repetitive;
1440 if (sched_action->destroy) {
1441 (*sched_action->destroy)(sched_action->payload);
1443 g_free(sched_action->name);
1444 g_free(sched_action);
1445 return ret;
1449 * Kills action timer effectively cancelling
1450 * scheduled action
1452 * @param name of action
1454 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1456 GSList *entry;
1458 if (!sip->timeouts || !name) return;
1460 entry = sip->timeouts;
1461 while (entry) {
1462 struct scheduled_action *sched_action = entry->data;
1463 if(!strcmp(sched_action->name, name)) {
1464 GSList *to_delete = entry;
1465 entry = entry->next;
1466 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1467 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1468 purple_timeout_remove(sched_action->timeout_handler);
1469 if (sched_action->destroy) {
1470 (*sched_action->destroy)(sched_action->payload);
1472 g_free(sched_action->name);
1473 g_free(sched_action);
1474 } else {
1475 entry = entry->next;
1480 static void
1481 sipe_schedule_action0(const gchar *name,
1482 int timeout,
1483 gboolean isSeconds,
1484 Action action,
1485 GDestroyNotify destroy,
1486 struct sipe_account_data *sip,
1487 void *payload)
1489 struct scheduled_action *sched_action;
1491 /* Make sure each action only exists once */
1492 sipe_cancel_scheduled_action(sip, name);
1494 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1495 sched_action = g_new0(struct scheduled_action, 1);
1496 sched_action->repetitive = FALSE;
1497 sched_action->name = g_strdup(name);
1498 sched_action->action = action;
1499 sched_action->destroy = destroy;
1500 sched_action->sip = sip;
1501 sched_action->payload = payload;
1502 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1503 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1504 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1505 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1508 void
1509 sipe_schedule_action(const gchar *name,
1510 int timeout,
1511 Action action,
1512 GDestroyNotify destroy,
1513 struct sipe_account_data *sip,
1514 void *payload)
1516 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1520 * Same as sipe_schedule_action() but timeout is in milliseconds.
1522 static void
1523 sipe_schedule_action_msec(const gchar *name,
1524 int timeout,
1525 Action action,
1526 GDestroyNotify destroy,
1527 struct sipe_account_data *sip,
1528 void *payload)
1530 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1533 static void
1534 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1535 time_t calculate_from);
1537 static int
1538 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1540 static const char*
1541 sipe_get_status_by_availability(int avail,
1542 const char* activity);
1544 static void
1545 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1546 const char *status_id,
1547 const char *message,
1548 time_t do_not_publish[]);
1550 static void
1551 sipe_apply_calendar_status(struct sipe_account_data *sip,
1552 struct sipe_buddy *sbuddy,
1553 const char *status_id)
1555 time_t cal_avail_since;
1556 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1557 int avail;
1558 gchar *self_uri = sip_uri_self(sip);
1560 if (!sbuddy) return;
1562 if (cal_status < SIPE_CAL_NO_DATA) {
1563 purple_debug_info("sipe", "update_calendar_status_cb: cal_status : %d for %s\n", cal_status, sbuddy->name);
1564 purple_debug_info("sipe", "update_calendar_status_cb: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1567 /* scheduled Cal update call */
1568 if (!status_id) {
1569 status_id = sbuddy->last_non_cal_status_id;
1570 g_free(sbuddy->activity);
1571 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1574 /* adjust to calendar status */
1575 if (cal_status != SIPE_CAL_NO_DATA) {
1576 purple_debug_info("sipe", "update_calendar_status_cb: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1578 if (cal_status == SIPE_CAL_BUSY
1579 && cal_avail_since > sbuddy->user_avail_since
1580 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1582 status_id = SIPE_STATUS_ID_IN_MEETING;
1583 g_free(sbuddy->activity);
1584 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1586 avail = sipe_get_availability_by_status(status_id, NULL);
1588 purple_debug_info("sipe", "update_calendar_status_cb: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1589 if (cal_avail_since > sbuddy->activity_since) {
1590 if (cal_status == SIPE_CAL_OOF
1591 && avail >= 15000) /* 12000 in 2007 */
1593 g_free(sbuddy->activity);
1594 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1599 /* then set status_id actually */
1600 purple_debug_info("sipe", "sipe_got_user_status: to %s for %s\n", status_id, sbuddy->name);
1601 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1603 /* set our account state to the one in roaming (including calendar info) */
1604 if (sip->initial_state_published && !strcmp(sbuddy->name, self_uri)) {
1605 if (!strcmp(status_id, SIPE_STATUS_ID_OFFLINE)) {
1606 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1609 purple_debug_info("sipe", "sipe_got_user_status: to %s for the account\n", sip->status);
1610 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1612 g_free(self_uri);
1615 static void
1616 sipe_got_user_status(struct sipe_account_data *sip,
1617 const char* uri,
1618 const char *status_id)
1620 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1622 if (!sbuddy) return;
1624 /* Check if on 2005 system contact's calendar,
1625 * then set/preserve it.
1627 if (!sip->ocs2007) {
1628 sipe_apply_calendar_status(sip, sbuddy, status_id);
1629 } else {
1630 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1634 static void
1635 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1636 struct sipe_buddy *sbuddy,
1637 struct sipe_account_data *sip)
1639 sipe_apply_calendar_status(sip, sbuddy, NULL);
1643 * Updates contact's status
1644 * based on their calendar information.
1646 * Applicability: 2005 systems
1648 static void
1649 update_calendar_status(struct sipe_account_data *sip)
1651 purple_debug_info("sipe", "update_calendar_status() started.\n");
1652 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1654 /* repeat scheduling */
1655 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1659 * Schedules process of contacts' status update
1660 * based on their calendar information.
1661 * Should be scheduled to the beginning of every
1662 * 15 min interval, like:
1663 * 13:00, 13:15, 13:30, 13:45, etc.
1665 * Applicability: 2005 systems
1667 static void
1668 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1669 time_t calculate_from)
1671 int interval = 15*60;
1672 /** start of the beginning of closest 15 min interval. */
1673 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1675 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1676 asctime(localtime(&calculate_from)));
1677 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1678 asctime(localtime(&next_start)));
1680 sipe_schedule_action("<+2005-cal-status>",
1681 (int)(next_start - time(NULL)),
1682 (Action)update_calendar_status,
1683 NULL,
1684 sip,
1685 NULL);
1689 * Schedules process of self status publish
1690 * based on own calendar information.
1691 * Should be scheduled to the beginning of every
1692 * 15 min interval, like:
1693 * 13:00, 13:15, 13:30, 13:45, etc.
1695 * Applicability: 2007+ systems
1697 static void
1698 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1699 time_t calculate_from)
1701 int interval = 5*60;
1702 /** start of the beginning of closest 5 min interval. */
1703 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1705 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1706 asctime(localtime(&calculate_from)));
1707 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1708 asctime(localtime(&next_start)));
1710 sipe_schedule_action("<+2007-cal-status>",
1711 (int)(next_start - time(NULL)),
1712 (Action)publish_calendar_status_self,
1713 NULL,
1714 sip,
1715 NULL);
1718 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1720 /** Should be g_free()'d
1722 static gchar *
1723 sipe_get_subscription_key(gchar *event,
1724 gchar *with)
1726 gchar *key = NULL;
1728 if (is_empty(event)) return NULL;
1730 if (event && !g_ascii_strcasecmp(event, "presence")) {
1731 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1732 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1734 /* @TODO drop participated buddies' just_added flag */
1735 } else if (event) {
1736 /* Subscription is identified by <event> key */
1737 key = g_strdup_printf("<%s>", event);
1740 return key;
1743 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1744 SIPE_UNUSED_PARAMETER struct transaction *trans)
1746 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1747 gchar *event = sipmsg_find_header(msg, "Event");
1748 gchar *key;
1750 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1751 if (!event) {
1752 struct sipmsg *request_msg = trans->msg;
1753 event = sipmsg_find_header(request_msg, "Event");
1756 key = sipe_get_subscription_key(event, with);
1758 /* 200 OK; 481 Call Leg Does Not Exist */
1759 if (key && (msg->response == 200 || msg->response == 481)) {
1760 if (g_hash_table_lookup(sip->subscriptions, key)) {
1761 g_hash_table_remove(sip->subscriptions, key);
1762 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1766 /* create/store subscription dialog if not yet */
1767 if (msg->response == 200) {
1768 gchar *callid = sipmsg_find_header(msg, "Call-ID");
1769 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1771 if (key) {
1772 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1773 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1775 subscription->dialog.callid = g_strdup(callid);
1776 subscription->dialog.cseq = atoi(cseq);
1777 subscription->dialog.with = g_strdup(with);
1778 subscription->event = g_strdup(event);
1779 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1781 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1784 g_free(cseq);
1787 g_free(key);
1788 g_free(with);
1790 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1792 process_incoming_notify(sip, msg, FALSE, FALSE);
1794 return TRUE;
1797 static void sipe_subscribe_resource_uri(const char *name,
1798 SIPE_UNUSED_PARAMETER gpointer value,
1799 gchar **resources_uri)
1801 gchar *tmp = *resources_uri;
1802 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1803 g_free(tmp);
1806 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1808 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1809 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1810 gchar *tmp = *resources_uri;
1812 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1814 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1815 g_free(tmp);
1819 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1820 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1821 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1822 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1823 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1826 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1828 gchar *key;
1829 gchar *contact = get_contact(sip);
1830 gchar *request;
1831 gchar *content;
1832 gchar *require = "";
1833 gchar *accept = "";
1834 gchar *autoextend = "";
1835 gchar *content_type;
1836 struct sip_dialog *dialog;
1838 if (sip->ocs2007) {
1839 require = ", categoryList";
1840 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1841 content_type = "application/msrtc-adrl-categorylist+xml";
1842 content = g_strdup_printf(
1843 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1844 "<action name=\"subscribe\" id=\"63792024\">\n"
1845 "<adhocList>\n%s</adhocList>\n"
1846 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1847 "<category name=\"calendarData\"/>\n"
1848 "<category name=\"contactCard\"/>\n"
1849 "<category name=\"note\"/>\n"
1850 "<category name=\"state\"/>\n"
1851 "</categoryList>\n"
1852 "</action>\n"
1853 "</batchSub>", sip->username, resources_uri);
1854 } else {
1855 autoextend = "Supported: com.microsoft.autoextend\r\n";
1856 content_type = "application/adrl+xml";
1857 content = g_strdup_printf(
1858 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1859 "<create xmlns=\"\">\n%s</create>\n"
1860 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1862 g_free(resources_uri);
1864 request = g_strdup_printf(
1865 "Require: adhoclist%s\r\n"
1866 "Supported: eventlist\r\n"
1867 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1868 "Supported: ms-piggyback-first-notify\r\n"
1869 "%sSupported: ms-benotify\r\n"
1870 "Proxy-Require: ms-benotify\r\n"
1871 "Event: presence\r\n"
1872 "Content-Type: %s\r\n"
1873 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1874 g_free(contact);
1876 /* subscribe to buddy presence */
1877 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1878 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1879 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1880 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1882 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1884 g_free(content);
1885 g_free(to);
1886 g_free(request);
1887 g_free(key);
1890 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1891 SIPE_UNUSED_PARAMETER void *unused)
1893 gchar *to = sip_uri_self(sip);
1894 gchar *resources_uri = g_strdup("");
1895 if (sip->ocs2007) {
1896 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1897 } else {
1898 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1901 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1904 struct presence_batched_routed {
1905 gchar *host;
1906 GSList *buddies;
1909 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1911 struct presence_batched_routed *data = payload;
1912 GSList *buddies = data->buddies;
1913 while (buddies) {
1914 g_free(buddies->data);
1915 buddies = buddies->next;
1917 g_slist_free(data->buddies);
1918 g_free(data->host);
1919 g_free(payload);
1922 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1924 struct presence_batched_routed *data = payload;
1925 GSList *buddies = data->buddies;
1926 gchar *resources_uri = g_strdup("");
1927 while (buddies) {
1928 gchar *tmp = resources_uri;
1929 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1930 g_free(tmp);
1931 buddies = buddies->next;
1933 sipe_subscribe_presence_batched_to(sip, resources_uri,
1934 g_strdup(data->host));
1938 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1939 * The user sends a single SUBSCRIBE request to the subscribed contact.
1940 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1944 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1947 gchar *key;
1948 gchar *to = sip_uri((char *)buddy_name);
1949 gchar *tmp = get_contact(sip);
1950 gchar *request;
1951 gchar *content = NULL;
1952 gchar *autoextend = "";
1953 gchar *content_type = "";
1954 struct sip_dialog *dialog;
1955 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
1956 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1958 if (sbuddy) sbuddy->just_added = FALSE;
1960 if (sip->ocs2007) {
1961 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1962 } else {
1963 autoextend = "Supported: com.microsoft.autoextend\r\n";
1966 request = g_strdup_printf(
1967 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1968 "Supported: ms-piggyback-first-notify\r\n"
1969 "%s%sSupported: ms-benotify\r\n"
1970 "Proxy-Require: ms-benotify\r\n"
1971 "Event: presence\r\n"
1972 "Contact: %s\r\n", autoextend, content_type, tmp);
1974 if (sip->ocs2007) {
1975 content = g_strdup_printf(
1976 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1977 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1978 "<resource uri=\"%s\"%s\n"
1979 "</adhocList>\n"
1980 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1981 "<category name=\"calendarData\"/>\n"
1982 "<category name=\"contactCard\"/>\n"
1983 "<category name=\"note\"/>\n"
1984 "<category name=\"state\"/>\n"
1985 "</categoryList>\n"
1986 "</action>\n"
1987 "</batchSub>", sip->username, to, context);
1990 g_free(tmp);
1992 /* subscribe to buddy presence */
1993 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1994 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1995 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1996 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1998 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2000 g_free(content);
2001 g_free(to);
2002 g_free(request);
2003 g_free(key);
2006 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2008 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2010 if (!purple_status_is_active(status))
2011 return;
2013 if (account->gc) {
2014 struct sipe_account_data *sip = account->gc->proto_data;
2016 if (sip) {
2017 gchar *action_name;
2018 time_t now = time(NULL);
2019 const char *status_id = purple_status_get_id(status);
2020 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2021 sipe_activity activity = sipe_get_activity_by_token(status_id);
2022 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2025 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2026 status_id, (int)sip->do_not_publish[activity], (int)now);
2028 sip->do_not_publish[activity] = 0;
2029 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2030 status_id, (int)sip->do_not_publish[activity]);
2032 if (do_not_publish)
2034 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2035 return;
2038 g_free(sip->status);
2039 sip->status = g_strdup(status_id);
2041 /* this will preserve OOF flag as well */
2042 if (!(note && sip->note && !strcmp(note, sip->note))) {
2043 sip->is_oof_note = FALSE;
2044 g_free(sip->note);
2045 sip->note = g_strdup(note);
2046 sip->note_since = time(NULL);
2049 /* schedule 2 sec to capture idle flag */
2050 action_name = g_strdup_printf("<%s>", "+set-status");
2051 sipe_schedule_action(action_name, 2, (Action)send_presence_status, NULL, sip, NULL);
2052 g_free(action_name);
2056 static void
2057 sipe_set_idle(PurpleConnection * gc,
2058 int time)
2060 purple_debug_info("sipe", "sipe_set_idle: time=%d\n", time);
2062 if (gc) {
2063 struct sipe_account_data *sip = gc->proto_data;
2065 if (sip) {
2066 sip->was_idle = sip->is_idle;
2067 sip->is_idle = (time > 0);
2072 static void
2073 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2074 SIPE_UNUSED_PARAMETER const char *alias)
2076 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2077 sipe_group_set_user(sip, name);
2080 static void
2081 sipe_group_buddy(PurpleConnection *gc,
2082 const char *who,
2083 const char *old_group_name,
2084 const char *new_group_name)
2086 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2087 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2088 struct sipe_group * old_group = NULL;
2089 struct sipe_group * new_group;
2091 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2092 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2094 if(!buddy) { // buddy not in roaming list
2095 return;
2098 if (old_group_name) {
2099 old_group = sipe_group_find_by_name(sip, old_group_name);
2101 new_group = sipe_group_find_by_name(sip, new_group_name);
2103 if (old_group) {
2104 buddy->groups = g_slist_remove(buddy->groups, old_group);
2105 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2108 if (!new_group) {
2109 sipe_group_create(sip, new_group_name, who);
2110 } else {
2111 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2112 sipe_group_set_user(sip, who);
2116 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2118 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2120 /* libpurple can call us with undefined buddy or group */
2121 if (buddy && group) {
2122 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2124 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2125 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2126 purple_blist_rename_buddy(buddy, buddy_name);
2127 g_free(buddy_name);
2129 /* Prepend sip: if needed */
2130 if (strncmp("sip:", buddy->name, 4)) {
2131 gchar *buf = sip_uri_from_name(buddy->name);
2132 purple_blist_rename_buddy(buddy, buf);
2133 g_free(buf);
2136 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2137 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2138 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2139 b->name = g_strdup(buddy->name);
2140 b->just_added = TRUE;
2141 g_hash_table_insert(sip->buddies, b->name, b);
2142 sipe_group_buddy(gc, b->name, NULL, group->name);
2143 /* @TODO should go to callback */
2144 sipe_subscribe_presence_single(sip, b->name);
2145 } else {
2146 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2151 static void sipe_free_buddy(struct sipe_buddy *buddy)
2153 #ifndef _WIN32
2155 * We are calling g_hash_table_foreach_steal(). That means that no
2156 * key/value deallocation functions are called. Therefore the glib
2157 * hash code does not touch the key (buddy->name) or value (buddy)
2158 * of the to-be-deleted hash node at all. It follows that we
2160 * - MUST free the memory for the key ourselves and
2161 * - ARE allowed to do it in this function
2163 * Conclusion: glib must be broken on the Windows platform if sipe
2164 * crashes with SIGTRAP when closing. You'll have to live
2165 * with the memory leak until this is fixed.
2167 g_free(buddy->name);
2168 #endif
2169 g_free(buddy->activity);
2170 g_free(buddy->meeting_subject);
2171 g_free(buddy->meeting_location);
2172 g_free(buddy->annotation);
2174 g_free(buddy->cal_start_time);
2175 g_free(buddy->cal_free_busy_base64);
2176 g_free(buddy->cal_free_busy);
2177 g_free(buddy->last_non_cal_activity);
2179 sipe_cal_free_working_hours(buddy->cal_working_hours);
2181 g_free(buddy->device_name);
2182 g_slist_free(buddy->groups);
2183 g_free(buddy);
2187 * Unassociates buddy from group first.
2188 * Then see if no groups left, removes buddy completely.
2189 * Otherwise updates buddy groups on server.
2191 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2193 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2194 struct sipe_buddy *b;
2195 struct sipe_group *g = NULL;
2197 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2198 if (!buddy) return;
2200 b = g_hash_table_lookup(sip->buddies, buddy->name);
2201 if (!b) return;
2203 if (group) {
2204 g = sipe_group_find_by_name(sip, group->name);
2207 if (g) {
2208 b->groups = g_slist_remove(b->groups, g);
2209 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2212 if (g_slist_length(b->groups) < 1) {
2213 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2214 sipe_cancel_scheduled_action(sip, action_name);
2215 g_free(action_name);
2217 g_hash_table_remove(sip->buddies, buddy->name);
2219 if (b->name) {
2220 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2221 send_soap_request(sip, body);
2222 g_free(body);
2225 sipe_free_buddy(b);
2226 } else {
2227 //updates groups on server
2228 sipe_group_set_user(sip, b->name);
2233 static void
2234 sipe_rename_group(PurpleConnection *gc,
2235 const char *old_name,
2236 PurpleGroup *group,
2237 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2239 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2240 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2241 if (s_group) {
2242 sipe_group_rename(sip, s_group, group->name);
2243 } else {
2244 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2248 static void
2249 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2251 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2252 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2253 if (s_group) {
2254 gchar *body;
2255 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2256 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2257 send_soap_request(sip, body);
2258 g_free(body);
2260 sip->groups = g_slist_remove(sip->groups, s_group);
2261 g_free(s_group->name);
2262 g_free(s_group);
2263 } else {
2264 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2268 /** All statuses need message attribute to pass Note */
2269 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2271 PurpleStatusType *type;
2272 GList *types = NULL;
2274 /* Macros to reduce code repetition.
2275 Translators: noun */
2276 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2277 prim, id, name, \
2278 TRUE, user, FALSE, \
2279 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2280 NULL); \
2281 types = g_list_append(types, type);
2283 /* Online */
2284 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2285 NULL,
2286 NULL,
2287 TRUE);
2289 /* Busy */
2290 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2291 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2292 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2293 TRUE);
2295 /* BusyIdle (not user settable) */
2296 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2297 sipe_activity_map[SIPE_ACTIVITY_BUSYIDLE].status_id,
2298 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE),
2299 FALSE);
2301 /* Do Not Disturb */
2302 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2303 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2304 NULL,
2305 TRUE);
2307 /* In a meeting (not user settable) */
2308 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2309 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].status_id,
2310 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING),
2311 FALSE);
2313 /* In a conference (not user settable) */
2314 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2315 sipe_activity_map[SIPE_ACTIVITY_IN_CONF].status_id,
2316 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF),
2317 FALSE);
2319 /* Be Right Back */
2320 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2321 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2322 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2323 TRUE);
2325 /* Away */
2326 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2327 NULL,
2328 NULL,
2329 TRUE);
2331 /* On The Phone (not user settable) */
2332 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2333 sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].status_id,
2334 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE),
2335 FALSE);
2337 /* Out To Lunch (not user settable) */
2338 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2339 sipe_activity_map[SIPE_ACTIVITY_LUNCH].status_id,
2340 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH),
2341 FALSE);
2343 /* Idle/Inactive (not user settable) */
2344 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2345 sipe_activity_map[SIPE_ACTIVITY_INACTIVE].status_id,
2346 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE),
2347 FALSE);
2349 /* Appear Offline */
2350 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2351 NULL,
2352 NULL,
2353 TRUE);
2355 /* Offline (not user settable) */
2356 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE,
2357 NULL,
2358 NULL,
2359 FALSE);
2361 return types;
2365 * A callback for g_hash_table_foreach
2367 static void
2368 sipe_buddy_subscribe_cb(char *buddy_name,
2369 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2370 struct sipe_account_data *sip)
2372 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2373 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2374 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2375 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2377 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2378 g_free(action_name);
2382 * Removes entries from purple buddy list
2383 * that does not correspond ones in the roaming contact list.
2385 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2386 GSList *buddies = purple_find_buddies(sip->account, NULL);
2387 GSList *entry = buddies;
2388 struct sipe_buddy *buddy;
2389 PurpleBuddy *b;
2390 PurpleGroup *g;
2392 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2393 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2394 while (entry) {
2395 b = entry->data;
2396 g = purple_buddy_get_group(b);
2397 buddy = g_hash_table_lookup(sip->buddies, b->name);
2398 if(buddy) {
2399 gboolean in_sipe_groups = FALSE;
2400 GSList *entry2 = buddy->groups;
2401 while (entry2) {
2402 struct sipe_group *group = entry2->data;
2403 if (!strcmp(group->name, g->name)) {
2404 in_sipe_groups = TRUE;
2405 break;
2407 entry2 = entry2->next;
2409 if(!in_sipe_groups) {
2410 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2411 purple_blist_remove_buddy(b);
2413 } else {
2414 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2415 purple_blist_remove_buddy(b);
2417 entry = entry->next;
2419 g_slist_free(buddies);
2422 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2424 int len = msg->bodylen;
2426 gchar *tmp = sipmsg_find_header(msg, "Event");
2427 xmlnode *item;
2428 xmlnode *isc;
2429 const gchar *contacts_delta;
2430 xmlnode *group_node;
2431 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
2432 return FALSE;
2435 /* Convert the contact from XML to Purple Buddies */
2436 isc = xmlnode_from_str(msg->body, len);
2437 if (!isc) {
2438 return FALSE;
2441 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2442 if (contacts_delta) {
2443 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2446 if (!strcmp(isc->name, "contactList")) {
2448 /* Parse groups */
2449 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2450 struct sipe_group * group = g_new0(struct sipe_group, 1);
2451 const char *name = xmlnode_get_attrib(group_node, "name");
2453 if (!strncmp(name, "~", 1)) {
2454 name = _("Other Contacts");
2456 group->name = g_strdup(name);
2457 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2459 sipe_group_add(sip, group);
2462 // Make sure we have at least one group
2463 if (g_slist_length(sip->groups) == 0) {
2464 struct sipe_group * group = g_new0(struct sipe_group, 1);
2465 PurpleGroup *purple_group;
2466 group->name = g_strdup(_("Other Contacts"));
2467 group->id = 1;
2468 purple_group = purple_group_new(group->name);
2469 purple_blist_add_group(purple_group, NULL);
2470 sip->groups = g_slist_append(sip->groups, group);
2473 /* Parse contacts */
2474 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2475 const gchar *uri = xmlnode_get_attrib(item, "uri");
2476 const gchar *name = xmlnode_get_attrib(item, "name");
2477 gchar *buddy_name;
2478 struct sipe_buddy *buddy = NULL;
2479 gchar *tmp;
2480 gchar **item_groups;
2481 int i = 0;
2483 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2484 tmp = sip_uri_from_name(uri);
2485 buddy_name = g_ascii_strdown(tmp, -1);
2486 g_free(tmp);
2488 /* assign to group Other Contacts if nothing else received */
2489 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2490 if(!tmp || !strcmp("", tmp) ) {
2491 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2492 g_free(tmp);
2493 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2495 item_groups = g_strsplit(tmp, " ", 0);
2496 g_free(tmp);
2498 while (item_groups[i]) {
2499 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2501 // If couldn't find the right group for this contact, just put them in the first group we have
2502 if (group == NULL && g_slist_length(sip->groups) > 0) {
2503 group = sip->groups->data;
2506 if (group != NULL) {
2507 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2508 if (!b){
2509 b = purple_buddy_new(sip->account, buddy_name, uri);
2510 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2512 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2515 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2516 if (name != NULL && strlen(name) != 0) {
2517 purple_blist_alias_buddy(b, name);
2519 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2523 if (!buddy) {
2524 buddy = g_new0(struct sipe_buddy, 1);
2525 buddy->name = g_strdup(b->name);
2526 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2529 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2531 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2532 } else {
2533 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2534 name);
2537 i++;
2538 } // while, contact groups
2539 g_strfreev(item_groups);
2540 g_free(buddy_name);
2542 } // for, contacts
2544 sipe_cleanup_local_blist(sip);
2546 /* Add self-contact if not there yet. 2005 systems. */
2547 /* This will resemble subscription to roaming_self in 2007 systems */
2548 if (!sip->ocs2007) {
2549 gchar *self_uri = sip_uri_self(sip);
2550 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2552 if (!buddy) {
2553 buddy = g_new0(struct sipe_buddy, 1);
2554 buddy->name = g_strdup(self_uri);
2555 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2557 g_free(self_uri);
2560 xmlnode_free(isc);
2562 /* subscribe to buddies */
2563 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2564 if (sip->batched_support) {
2565 sipe_subscribe_presence_batched(sip, NULL);
2566 } else {
2567 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2569 sip->subscribed_buddies = TRUE;
2571 /* for 2005 systems schedule contacts' status update
2572 * based on their calendar information
2574 if (!sip->ocs2007) {
2575 sipe_sched_calendar_status_update(sip, time(NULL));
2578 return 0;
2582 * Subscribe roaming contacts
2584 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2586 gchar *to = sip_uri_self(sip);
2587 gchar *tmp = get_contact(sip);
2588 gchar *hdr = g_strdup_printf(
2589 "Event: vnd-microsoft-roaming-contacts\r\n"
2590 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2591 "Supported: com.microsoft.autoextend\r\n"
2592 "Supported: ms-benotify\r\n"
2593 "Proxy-Require: ms-benotify\r\n"
2594 "Supported: ms-piggyback-first-notify\r\n"
2595 "Contact: %s\r\n", tmp);
2596 g_free(tmp);
2598 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2599 g_free(to);
2600 g_free(hdr);
2603 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2604 SIPE_UNUSED_PARAMETER void *unused)
2606 gchar *key;
2607 struct sip_dialog *dialog;
2608 gchar *to = sip_uri_self(sip);
2609 gchar *tmp = get_contact(sip);
2610 gchar *hdr = g_strdup_printf(
2611 "Event: presence.wpending\r\n"
2612 "Accept: text/xml+msrtc.wpending\r\n"
2613 "Supported: com.microsoft.autoextend\r\n"
2614 "Supported: ms-benotify\r\n"
2615 "Proxy-Require: ms-benotify\r\n"
2616 "Supported: ms-piggyback-first-notify\r\n"
2617 "Contact: %s\r\n", tmp);
2618 g_free(tmp);
2620 /* Subscription is identified by <event> key */
2621 key = g_strdup_printf("<%s>", "presence.wpending");
2622 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2623 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2625 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2627 g_free(to);
2628 g_free(hdr);
2629 g_free(key);
2633 * Fires on deregistration event initiated by server.
2634 * [MS-SIPREGE] SIP extension.
2637 // 2007 Example
2639 // Content-Type: text/registration-event
2640 // subscription-state: terminated;expires=0
2641 // ms-diagnostics-public: 4141;reason="User disabled"
2643 // deregistered;event=rejected
2645 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2647 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2648 gchar *event = NULL;
2649 gchar *reason = NULL;
2650 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2652 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2653 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2655 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2656 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2657 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2658 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2659 } else {
2660 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2661 return;
2664 if (warning != NULL) {
2665 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2666 } else { // for LCS2005
2667 int error_id = 0;
2668 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2669 error_id = 4140; // [MS-SIPREGE]
2670 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2671 reason = g_strdup(_("you are already signed in at another location"));
2672 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2673 error_id = 4141;
2674 reason = g_strdup(_("user disabled")); // [MS-OCER]
2675 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2676 error_id = 4142;
2677 reason = g_strdup(_("user moved")); // [MS-OCER]
2680 g_free(event);
2681 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2682 g_free(reason);
2684 sip->gc->wants_to_die = TRUE;
2685 purple_connection_error(sip->gc, warning);
2686 g_free(warning);
2690 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2692 xmlnode *xn_provision_group_list;
2693 xmlnode *node;
2695 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2697 /* provisionGroup */
2698 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2699 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2700 g_free(sip->focus_factory_uri);
2701 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2702 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2703 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2704 break;
2707 xmlnode_free(xn_provision_group_list);
2710 /** for 2005 system */
2711 static void
2712 sipe_process_provisioning(struct sipe_account_data *sip,
2713 struct sipmsg *msg)
2715 xmlnode *xn_provision;
2716 xmlnode *node;
2718 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2719 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2720 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2721 if ((node = xmlnode_get_child(node, "line"))) {
2722 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2723 const gchar *server = xmlnode_get_attrib(node, "server");
2724 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2725 sip_csta_open(sip, line_uri, server);
2728 xmlnode_free(xn_provision);
2731 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2733 const gchar *contacts_delta;
2734 xmlnode *xml;
2736 xml = xmlnode_from_str(msg->body, msg->bodylen);
2737 if (!xml)
2739 return;
2742 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2743 if (contacts_delta)
2745 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2748 xmlnode_free(xml);
2751 static void
2752 free_container(struct sipe_container *container)
2754 GSList *entry;
2756 if (!container) return;
2758 entry = container->members;
2759 while (entry) {
2760 g_free(entry->data);
2761 entry = g_slist_remove(entry, entry->data);
2763 g_free(container);
2767 * Finds locally stored MS-PRES container member
2769 static struct sipe_container_member *
2770 sipe_find_container_member(struct sipe_container *container,
2771 const gchar *type,
2772 const gchar *value)
2774 struct sipe_container_member *member;
2775 GSList *entry;
2777 if (container == NULL || type == NULL) {
2778 return NULL;
2781 entry = container->members;
2782 while (entry) {
2783 member = entry->data;
2784 if (!g_strcasecmp(member->type, type)
2785 && ((!member->value && !value)
2786 || (value && member->value && !g_strcasecmp(member->value, value)))
2788 return member;
2790 entry = entry->next;
2792 return NULL;
2796 * Finds locally stored MS-PRES container by id
2798 static struct sipe_container *
2799 sipe_find_container(struct sipe_account_data *sip,
2800 guint id)
2802 struct sipe_container *container;
2803 GSList *entry;
2805 if (sip == NULL) {
2806 return NULL;
2809 entry = sip->containers;
2810 while (entry) {
2811 container = entry->data;
2812 if (id == container->id) {
2813 return container;
2815 entry = entry->next;
2817 return NULL;
2821 * Access Levels
2822 * 32000 - Blocked
2823 * 400 - Personal
2824 * 300 - Team
2825 * 200 - Company
2826 * 100 - Public
2828 static int
2829 sipe_find_access_level(struct sipe_account_data *sip,
2830 const gchar *type,
2831 const gchar *value)
2833 guint containers[] = {32000, 400, 300, 200, 100};
2834 int i = 0;
2836 for (i = 0; i < 5; i++) {
2837 struct sipe_container_member *member;
2838 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2839 if (!container) continue;
2841 member = sipe_find_container_member(container, type, value);
2842 if (member) {
2843 return containers[i];
2847 return -1;
2850 static void
2851 sipe_send_set_container_members(struct sipe_account_data *sip,
2852 guint container_id,
2853 guint container_version,
2854 const gchar* action,
2855 const gchar* type,
2856 const gchar* value)
2858 gchar *self = sip_uri_self(sip);
2859 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2860 gchar *contact;
2861 gchar *hdr;
2862 gchar *body = g_strdup_printf(
2863 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2864 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2865 "</setContainerMembers>",
2866 container_id,
2867 container_version,
2868 action,
2869 type,
2870 value_str);
2871 g_free(value_str);
2873 contact = get_contact(sip);
2874 hdr = g_strdup_printf("Contact: %s\r\n"
2875 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2876 g_free(contact);
2878 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2880 g_free(hdr);
2881 g_free(body);
2882 g_free(self);
2885 static void
2886 free_publication(struct sipe_publication *publication)
2888 g_free(publication->category);
2889 g_free(publication->cal_event_hash);
2890 g_free(publication->note);
2892 g_free(publication->working_hours_xml_str);
2893 g_free(publication->fb_start_str);
2894 g_free(publication->free_busy_base64);
2896 g_free(publication);
2899 /* key is <category><instance><container> */
2900 static gboolean
2901 sipe_is_our_publication(struct sipe_account_data *sip,
2902 const gchar *key)
2904 GSList *entry;
2906 /* filling keys for our publications if not yet cached */
2907 if (!sip->our_publication_keys) {
2908 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2909 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2910 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2911 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2912 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2913 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2914 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2916 /* device */
2917 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2918 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2920 /* state:machineState */
2921 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2922 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2923 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2924 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2926 /* state:userState */
2927 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2928 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2929 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2930 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2932 /* state:calendarState */
2933 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2934 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2935 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2936 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2938 /* state:calendarState OOF */
2939 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2940 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2941 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2942 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2944 /* note */
2945 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2946 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2947 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2948 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2949 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2950 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2952 /* note OOF */
2953 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2954 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2955 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2956 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2957 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2958 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2960 /* calendarData:WorkingHours */
2961 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2962 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2963 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2964 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2965 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2966 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2967 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2968 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2969 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2970 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2971 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2972 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2974 /* calendarData:FreeBusy */
2975 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2976 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2977 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2978 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2979 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2980 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2981 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2982 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2983 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2984 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2985 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2986 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2988 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2989 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2992 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
2994 entry = sip->our_publication_keys;
2995 while (entry) {
2996 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
2997 if (!strcmp(entry->data, key)) {
2998 return TRUE;
3000 entry = entry->next;
3002 return FALSE;
3005 /** Property names to store in blist.xml */
3006 #define ALIAS_PROP "alias"
3007 #define EMAIL_PROP "email"
3008 #define PHONE_PROP "phone"
3009 #define PHONE_DISPLAY_PROP "phone-display"
3010 #define PHONE_MOBILE_PROP "phone-mobile"
3011 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3012 #define PHONE_HOME_PROP "phone-home"
3013 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3014 #define PHONE_OTHER_PROP "phone-other"
3015 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3016 #define PHONE_CUSTOM1_PROP "phone-custom1"
3017 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3018 #define SITE_PROP "site"
3019 #define COMPANY_PROP "company"
3020 #define DEPARTMENT_PROP "department"
3021 #define TITLE_PROP "title"
3022 #define OFFICE_PROP "office"
3023 /** implies work address */
3024 #define ADDRESS_STREET_PROP "address-street"
3025 #define ADDRESS_CITY_PROP "address-city"
3026 #define ADDRESS_STATE_PROP "address-state"
3027 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3028 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3030 * Update user information
3032 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3033 * @param property_name
3034 * @param property_value may be modified to strip white space
3036 static void
3037 sipe_update_user_info(struct sipe_account_data *sip,
3038 const char *uri,
3039 const char *property_name,
3040 char *property_value)
3042 GSList *buddies, *entry;
3044 if (!property_name || strlen(property_name) == 0) return;
3046 if (property_value)
3047 property_value = g_strstrip(property_value);
3049 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3050 while (entry) {
3051 const char *prop_str;
3052 const char *server_alias;
3053 PurpleBuddy *p_buddy = entry->data;
3055 /* for Display Name */
3056 if (!strcmp(property_name, ALIAS_PROP)) {
3057 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3058 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3059 purple_blist_alias_buddy(p_buddy, property_value);
3062 server_alias = purple_buddy_get_server_alias(p_buddy);
3063 if (property_value && strlen(property_value) > 0 &&
3064 ( (server_alias && strcmp(property_value, server_alias))
3065 || !server_alias || strlen(server_alias) == 0 )
3067 purple_blist_server_alias_buddy(p_buddy, property_value);
3070 /* for other properties */
3071 else {
3072 if (property_value && strlen(property_value) > 0) {
3073 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3074 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3075 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3080 entry = entry->next;
3082 g_slist_free(buddies);
3086 * Update user phone
3087 * Suitable for both 2005 and 2007 systems.
3089 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3090 * @param phone_type
3091 * @param phone may be modified to strip white space
3092 * @param phone_display_string may be modified to strip white space
3094 static void
3095 sipe_update_user_phone(struct sipe_account_data *sip,
3096 const char *uri,
3097 const gchar *phone_type,
3098 gchar *phone,
3099 gchar *phone_display_string)
3101 const char *phone_node = PHONE_PROP; /* work phone by default */
3102 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3104 if(!phone || strlen(phone) == 0) return;
3106 if (phone_type && (!strcmp(phone_type, "mobile") || !strcmp(phone_type, "cell"))) {
3107 phone_node = PHONE_MOBILE_PROP;
3108 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3109 } else if (phone_type && !strcmp(phone_type, "home")) {
3110 phone_node = PHONE_HOME_PROP;
3111 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3112 } else if (phone_type && !strcmp(phone_type, "other")) {
3113 phone_node = PHONE_OTHER_PROP;
3114 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3115 } else if (phone_type && !strcmp(phone_type, "custom1")) {
3116 phone_node = PHONE_CUSTOM1_PROP;
3117 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3120 sipe_update_user_info(sip, uri, phone_node, phone);
3121 if (phone_display_string) {
3122 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3126 static void
3127 sipe_update_calendar(struct sipe_account_data *sip)
3129 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3131 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3133 if (!strcmp(calendar, "EXCH")) {
3134 sipe_ews_update_calendar(sip);
3137 /* schedule repeat */
3138 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3140 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3144 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3145 * by using standard Purple's means of signals and saved statuses.
3147 * Thus all UI elements get updated: Status Button with Note, docklet.
3148 * This is ablolutely important as both our status and note can come
3149 * inbound (roaming) or be updated programmatically (e.g. based on our
3150 * calendar data).
3152 static void
3153 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3154 const char *status_id,
3155 const char *message,
3156 time_t do_not_publish[])
3158 PurpleStatus *status = purple_account_get_active_status(account);
3159 gboolean changed = TRUE;
3161 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3162 purple_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3164 changed = FALSE;
3167 if (changed) {
3168 PurpleSavedStatus *saved_status;
3169 const PurpleStatusType *acct_status_type =
3170 purple_status_type_find_with_id(account->status_types, status_id);
3171 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3172 sipe_activity activity = sipe_get_activity_by_token(status_id);
3174 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3175 if (saved_status) {
3176 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3179 /* If this type+message is unique then create a new transient saved status
3180 * Ref: gtkstatusbox.c
3182 if (!saved_status) {
3183 GList *tmp;
3184 GList *active_accts = purple_accounts_get_all_active();
3186 saved_status = purple_savedstatus_new(NULL, primitive);
3187 purple_savedstatus_set_message(saved_status, message);
3189 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3190 purple_savedstatus_set_substatus(saved_status,
3191 (PurpleAccount *)tmp->data, acct_status_type, message);
3193 g_list_free(active_accts);
3196 do_not_publish[activity] = time(NULL);
3197 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3198 status_id, (int)do_not_publish[activity]);
3200 /* Set the status for each account */
3201 purple_savedstatus_activate(saved_status);
3205 static void
3206 send_publish_category_initial(struct sipe_account_data *sip);
3209 * When we receive some self (BE) NOTIFY with a new subscriber
3210 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3213 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3215 gchar *contact;
3216 gchar *to;
3217 xmlnode *xml;
3218 xmlnode *node;
3219 xmlnode *node2;
3220 char *display_name = NULL;
3221 char *uri;
3222 GSList *category_names = NULL;
3223 int aggreg_avail = 0;
3224 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3225 gboolean do_update_status = FALSE;
3227 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3229 xml = xmlnode_from_str(msg->body, msg->bodylen);
3230 if (!xml) return;
3232 contact = get_contact(sip);
3233 to = sip_uri_self(sip);
3236 /* categories */
3237 /* set list of categories participating in this XML */
3238 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3239 const gchar *name = xmlnode_get_attrib(node, "name");
3240 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3242 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3243 category_names ? (int) g_slist_length(category_names) : -1);
3244 /* drop category information */
3245 if (category_names) {
3246 GSList *entry = category_names;
3247 while (entry) {
3248 GHashTable *cat_publications;
3249 const gchar *category = entry->data;
3250 entry = entry->next;
3251 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3252 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3253 if (cat_publications) {
3254 g_hash_table_remove(sip->our_publications, category);
3255 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3259 g_slist_free(category_names);
3260 /* filling our categories reflected in roaming data */
3261 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3262 const gchar *name = xmlnode_get_attrib(node, "name");
3263 const gchar *container = xmlnode_get_attrib(node, "container");
3264 const gchar *instance = xmlnode_get_attrib(node, "instance");
3265 const gchar *version = xmlnode_get_attrib(node, "version");
3266 guint version_int = version ? atoi(version) : 0;
3267 gchar *key;
3269 if (!container || !instance) continue;
3271 /* key is <category><instance><container> */
3272 key = g_strdup_printf("<%s><%s><%s>", name, instance, container);
3273 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version_int);
3275 /* capture all userState publication for later clean up if required */
3276 if (!strcmp(name, "state") && (atoi(container) == 2 || atoi(container) == 3)) {
3277 xmlnode *xn_state = xmlnode_get_child(node, "state");
3279 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3280 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3281 publication->category = g_strdup(name);
3282 publication->instance = atoi(instance);
3283 publication->container = atoi(container);
3284 publication->version = version_int;
3286 if (!sip->user_state_publications) {
3287 sip->user_state_publications = g_hash_table_new_full(
3288 g_str_hash, g_str_equal,
3289 g_free, (GDestroyNotify)free_publication);
3291 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3292 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3293 key, version_int);
3297 if (sipe_is_our_publication(sip, key)) {
3298 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3300 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3301 publication->category = g_strdup(name);
3302 publication->instance = atoi(instance);
3303 publication->container = atoi(container);
3304 publication->version = version_int;
3305 /* filling publication->availability */
3306 if (!strcmp(name, "state")) {
3307 xmlnode *xn_state = xmlnode_get_child(node, "state");
3308 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3310 if (xn_avail) {
3311 gchar *avail_str = xmlnode_get_data(xn_avail);
3312 if (avail_str) {
3313 publication->availability = atoi(avail_str);
3315 g_free(avail_str);
3317 /* for calendarState */
3318 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3319 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3320 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3322 event->start_time = purple_str_to_time(xmlnode_get_attrib(xn_state, "startTime"),
3323 FALSE, NULL, NULL, NULL);
3324 if (xn_activity) {
3325 if (!strcmp(xmlnode_get_attrib(xn_activity, "token"),
3326 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3328 event->is_meeting = TRUE;
3331 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3332 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3334 publication->cal_event_hash = sipe_cal_event_hash(event);
3335 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3336 publication->cal_event_hash);
3337 sipe_cal_event_free(event);
3340 /* filling publication->note */
3341 if (!strcmp(name, "note")) {
3342 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3344 g_free(sip->note);
3345 if (xn_body) {
3346 publication->note = xmlnode_get_data(xn_body);
3347 sip->note = g_strdup(publication->note);
3349 do_update_status = TRUE;
3352 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3353 if (!strcmp(name, "calendarData") && (publication->container == 300)) {
3354 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3355 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3356 if (xn_free_busy) {
3357 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3358 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3360 if (xn_working_hours) {
3361 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3365 if (!cat_publications) {
3366 cat_publications = g_hash_table_new_full(
3367 g_str_hash, g_str_equal,
3368 g_free, (GDestroyNotify)free_publication);
3369 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3370 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3372 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3373 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version_int);
3375 g_free(key);
3377 /* aggregateState (not an our publication) from 2-nd container */
3378 if (!strcmp(name, "state") && atoi(container) == 2) {
3379 xmlnode *xn_state = xmlnode_get_child(node, "state");
3381 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3382 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3383 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3385 if (xn_avail) {
3386 gchar *avail_str = xmlnode_get_data(xn_avail);
3387 if (avail_str) {
3388 aggreg_avail = atoi(avail_str);
3390 g_free(avail_str);
3393 if (xn_activity) {
3394 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3396 aggreg_activity = sipe_get_activity_by_token(activity_token);
3399 do_update_status = TRUE;
3403 /* userProperties published by server from AD */
3404 if (!sip->csta && !strcmp(name, "userProperties")) {
3405 xmlnode *line;
3406 /* line, for Remote Call Control (RCC) */
3407 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3408 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3409 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3410 gchar *line_uri;
3412 if (!line_server || (strcmp(line_type, "Rcc") && strcmp(line_type, "Dual"))) continue;
3414 line_uri = xmlnode_get_data(line);
3415 if (line_uri) {
3416 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3417 sip_csta_open(sip, line_uri, line_server);
3419 g_free(line_uri);
3421 break;
3425 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3426 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3428 /* containers */
3429 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3430 guint id = atoi(xmlnode_get_attrib(node, "id"));
3431 struct sipe_container *container = sipe_find_container(sip, id);
3433 if (container) {
3434 sip->containers = g_slist_remove(sip->containers, container);
3435 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3436 free_container(container);
3438 container = g_new0(struct sipe_container, 1);
3439 container->id = id;
3440 container->version = atoi(xmlnode_get_attrib(node, "version"));
3441 sip->containers = g_slist_append(sip->containers, container);
3442 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3444 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3445 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3446 member->type = xmlnode_get_attrib(node2, "type");
3447 member->value = xmlnode_get_attrib(node2, "value");
3448 container->members = g_slist_append(container->members, member);
3449 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3450 member->type, member->value ? member->value : "");
3454 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3455 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3456 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3457 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3458 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3459 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3460 /* initial set-up to let counterparties see your status */
3461 if (sameEnterpriseAL < 0) {
3462 struct sipe_container *container = sipe_find_container(sip, 200);
3463 guint version = container ? container->version : 0;
3464 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3466 if (federatedAL < 0) {
3467 struct sipe_container *container = sipe_find_container(sip, 100);
3468 guint version = container ? container->version : 0;
3469 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3471 sip->access_level_set = TRUE;
3474 /* subscribers */
3475 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3476 const char *user;
3477 const char *acknowledged;
3478 gchar *hdr;
3479 gchar *body;
3481 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3482 if (!user) continue;
3483 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3484 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3485 uri = sip_uri_from_name(user);
3487 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3489 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3490 if(!g_ascii_strcasecmp(acknowledged,"false")){
3491 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3492 if (!purple_find_buddy(sip->account, uri)) {
3493 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3496 hdr = g_strdup_printf(
3497 "Contact: %s\r\n"
3498 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3500 body = g_strdup_printf(
3501 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3502 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3503 "</setSubscribers>", user);
3505 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3506 g_free(body);
3507 g_free(hdr);
3509 g_free(display_name);
3510 g_free(uri);
3513 g_free(contact);
3514 xmlnode_free(xml);
3516 /* Publish initial state if not yet.
3517 * Assuming this happens on initial responce to subscription to roaming-self
3518 * so we've already updated our roaming data in full.
3519 * Only for 2007+
3521 if (!sip->initial_state_published) {
3522 send_publish_category_initial(sip);
3523 sip->initial_state_published = TRUE;
3524 /* dalayed run */
3525 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3526 do_update_status = FALSE;
3527 } else if (aggreg_avail) {
3529 g_free(sip->status);
3530 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3531 if (aggreg_activity == SIPE_ACTIVITY_IN_MEETING ||
3532 aggreg_activity == SIPE_ACTIVITY_IN_CONF ||
3533 aggreg_activity == SIPE_ACTIVITY_ON_PHONE)
3535 sip->status = g_strdup(sipe_activity_map[aggreg_activity].status_id);
3537 else
3539 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3541 } else {
3542 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3546 if (do_update_status) {
3547 purple_debug_info("sipe", "sipe_process_roaming_self: to %s for the account\n", sip->status);
3548 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3551 g_free(to);
3554 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3556 gchar *to = sip_uri_self(sip);
3557 gchar *tmp = get_contact(sip);
3558 gchar *hdr = g_strdup_printf(
3559 "Event: vnd-microsoft-roaming-ACL\r\n"
3560 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3561 "Supported: com.microsoft.autoextend\r\n"
3562 "Supported: ms-benotify\r\n"
3563 "Proxy-Require: ms-benotify\r\n"
3564 "Supported: ms-piggyback-first-notify\r\n"
3565 "Contact: %s\r\n", tmp);
3566 g_free(tmp);
3568 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3569 g_free(to);
3570 g_free(hdr);
3574 * To request for presence information about the user, access level settings that have already been configured by the user
3575 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3576 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3579 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3581 gchar *to = sip_uri_self(sip);
3582 gchar *tmp = get_contact(sip);
3583 gchar *hdr = g_strdup_printf(
3584 "Event: vnd-microsoft-roaming-self\r\n"
3585 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3586 "Supported: ms-benotify\r\n"
3587 "Proxy-Require: ms-benotify\r\n"
3588 "Supported: ms-piggyback-first-notify\r\n"
3589 "Contact: %s\r\n"
3590 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3592 gchar *body=g_strdup(
3593 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3594 "<roaming type=\"categories\"/>"
3595 "<roaming type=\"containers\"/>"
3596 "<roaming type=\"subscribers\"/></roamingList>");
3598 g_free(tmp);
3599 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3600 g_free(body);
3601 g_free(to);
3602 g_free(hdr);
3606 * For 2005 version
3608 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3610 gchar *to = sip_uri_self(sip);
3611 gchar *tmp = get_contact(sip);
3612 gchar *hdr = g_strdup_printf(
3613 "Event: vnd-microsoft-provisioning\r\n"
3614 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3615 "Supported: com.microsoft.autoextend\r\n"
3616 "Supported: ms-benotify\r\n"
3617 "Proxy-Require: ms-benotify\r\n"
3618 "Supported: ms-piggyback-first-notify\r\n"
3619 "Expires: 0\r\n"
3620 "Contact: %s\r\n", tmp);
3622 g_free(tmp);
3623 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3624 g_free(to);
3625 g_free(hdr);
3628 /** Subscription for provisioning information to help with initial
3629 * configuration. This subscription is a one-time query (denoted by the Expires header,
3630 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3631 * configuration, meeting policies, and policy settings that Communicator must enforce.
3632 * TODO: for what we need this information.
3635 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3637 gchar *to = sip_uri_self(sip);
3638 gchar *tmp = get_contact(sip);
3639 gchar *hdr = g_strdup_printf(
3640 "Event: vnd-microsoft-provisioning-v2\r\n"
3641 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3642 "Supported: com.microsoft.autoextend\r\n"
3643 "Supported: ms-benotify\r\n"
3644 "Proxy-Require: ms-benotify\r\n"
3645 "Supported: ms-piggyback-first-notify\r\n"
3646 "Expires: 0\r\n"
3647 "Contact: %s\r\n"
3648 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3649 gchar *body = g_strdup(
3650 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3651 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3652 "<provisioningGroup name=\"ucPolicy\"/>"
3653 "</provisioningGroupList>");
3655 g_free(tmp);
3656 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3657 g_free(body);
3658 g_free(to);
3659 g_free(hdr);
3662 static void
3663 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3664 gpointer value, gpointer user_data)
3666 struct sip_subscription *subscription = value;
3667 struct sip_dialog *dialog = &subscription->dialog;
3668 struct sipe_account_data *sip = user_data;
3669 gchar *tmp = get_contact(sip);
3670 gchar *hdr = g_strdup_printf(
3671 "Event: %s\r\n"
3672 "Expires: 0\r\n"
3673 "Contact: %s\r\n", subscription->event, tmp);
3674 g_free(tmp);
3676 /* Rate limit to max. 25 requests per seconds */
3677 g_usleep(1000000 / 25);
3679 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3680 g_free(hdr);
3683 /* IM Session (INVITE and MESSAGE methods) */
3685 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3686 static gchar *
3687 get_end_points (struct sipe_account_data *sip,
3688 struct sip_session *session)
3690 gchar *res;
3692 if (session == NULL) {
3693 return NULL;
3696 res = g_strdup_printf("<sip:%s>", sip->username);
3698 SIPE_DIALOG_FOREACH {
3699 gchar *tmp = res;
3700 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3701 g_free(tmp);
3703 if (dialog->theirepid) {
3704 tmp = res;
3705 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3706 g_free(tmp);
3708 } SIPE_DIALOG_FOREACH_END;
3710 return res;
3713 static gboolean
3714 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3715 struct sipmsg *msg,
3716 SIPE_UNUSED_PARAMETER struct transaction *trans)
3718 gboolean ret = TRUE;
3720 if (msg->response != 200) {
3721 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3722 return FALSE;
3725 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3727 return ret;
3731 * Asks UA/proxy about its capabilities.
3733 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3735 gchar *to = sip_uri(who);
3736 gchar *contact = get_contact(sip);
3737 gchar *request = g_strdup_printf(
3738 "Accept: application/sdp\r\n"
3739 "Contact: %s\r\n", contact);
3740 g_free(contact);
3742 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3744 g_free(to);
3745 g_free(request);
3748 static void
3749 sipe_notify_user(struct sipe_account_data *sip,
3750 struct sip_session *session,
3751 PurpleMessageFlags flags,
3752 const gchar *message)
3754 PurpleConversation *conv;
3756 if (!session->conv) {
3757 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3758 } else {
3759 conv = session->conv;
3761 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3764 void
3765 sipe_present_info(struct sipe_account_data *sip,
3766 struct sip_session *session,
3767 const gchar *message)
3769 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3772 static void
3773 sipe_present_err(struct sipe_account_data *sip,
3774 struct sip_session *session,
3775 const gchar *message)
3777 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3780 void
3781 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3782 struct sip_session *session,
3783 int sip_error,
3784 const gchar *who,
3785 const gchar *message)
3787 char *msg, *msg_tmp, *msg_tmp2;
3788 const char *label;
3790 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
3791 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3792 g_free(msg_tmp);
3793 /* Service unavailable; Server Internal Error; Server Time-out */
3794 if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3795 label = _("This message was not delivered to %s because the service is not available");
3796 } else if (sip_error == 486) { /* Busy Here */
3797 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3798 } else {
3799 label = _("This message was not delivered to %s because one or more recipients are offline");
3802 msg_tmp = g_strdup_printf( "%s:\n%s" ,
3803 msg_tmp2 = g_strdup_printf(label, who ? who : ""), msg ? msg : "");
3804 sipe_present_err(sip, session, msg_tmp);
3805 g_free(msg_tmp2);
3806 g_free(msg_tmp);
3807 g_free(msg);
3811 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
3813 static gboolean
3814 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
3815 SIPE_UNUSED_PARAMETER struct transaction *trans)
3817 gboolean ret = TRUE;
3818 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3819 struct sip_session *session = sipe_session_find_im(sip, with);
3820 struct sip_dialog *dialog;
3821 gchar *cseq;
3822 char *key;
3823 gchar *message;
3825 if (!session) {
3826 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3827 g_free(with);
3828 return FALSE;
3831 dialog = sipe_dialog_find(session, with);
3832 if (!dialog) {
3833 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3834 g_free(with);
3835 return FALSE;
3838 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3839 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3840 g_free(cseq);
3841 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3843 if (msg->response >= 400) {
3844 PurpleBuddy *pbuddy;
3845 gchar *alias = with;
3847 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3849 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3850 alias = (gchar *)purple_buddy_get_alias(pbuddy);
3853 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
3854 ret = FALSE;
3855 } else {
3856 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3857 if (message_id) {
3858 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
3859 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
3860 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3863 g_hash_table_remove(session->unconfirmed_messages, key);
3864 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
3865 key, g_hash_table_size(session->unconfirmed_messages));
3868 g_free(key);
3869 g_free(with);
3871 if (ret) sipe_im_process_queue(sip, session);
3872 return ret;
3875 static gboolean
3876 sipe_is_election_finished(struct sip_session *session);
3878 static void
3879 sipe_election_result(struct sipe_account_data *sip,
3880 void *sess);
3882 static gboolean
3883 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
3884 SIPE_UNUSED_PARAMETER struct transaction *trans)
3886 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3887 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3888 struct sip_dialog *dialog;
3889 struct sip_session *session;
3891 session = sipe_session_find_chat_by_callid(sip, callid);
3892 if (!session) {
3893 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
3894 return FALSE;
3897 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
3898 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3899 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
3900 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
3902 if (xn_request_rm_response) {
3903 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
3904 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
3906 dialog = sipe_dialog_find(session, with);
3907 if (!dialog) {
3908 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
3909 return FALSE;
3912 if (allow && !g_strcasecmp(allow, "true")) {
3913 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
3914 dialog->election_vote = 1;
3915 } else if (allow && !g_strcasecmp(allow, "false")) {
3916 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
3917 dialog->election_vote = -1;
3920 if (sipe_is_election_finished(session)) {
3921 sipe_election_result(sip, session);
3924 } else if (xn_set_rm_response) {
3927 xmlnode_free(xn_action);
3931 return TRUE;
3934 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
3936 gchar *hdr;
3937 gchar *tmp;
3938 char *msgformat;
3939 char *msgtext;
3940 gchar *msgr_value;
3941 gchar *msgr;
3943 sipe_parse_html(msg, &msgformat, &msgtext);
3944 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
3946 msgr_value = sipmsg_get_msgr_string(msgformat);
3947 g_free(msgformat);
3948 if (msgr_value) {
3949 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3950 g_free(msgr_value);
3951 } else {
3952 msgr = g_strdup("");
3955 tmp = get_contact(sip);
3956 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3957 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3958 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3959 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
3960 g_free(tmp);
3961 g_free(msgr);
3963 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
3964 g_free(msgtext);
3965 g_free(hdr);
3969 static void
3970 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
3972 GSList *entry2 = session->outgoing_message_queue;
3973 while (entry2) {
3974 char *queued_msg = entry2->data;
3976 /* for multiparty chat or conference */
3977 if (session->is_multiparty || session->focus_uri) {
3978 gchar *who = sip_uri_self(sip);
3979 serv_got_chat_in(sip->gc, session->chat_id, who,
3980 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
3981 g_free(who);
3984 SIPE_DIALOG_FOREACH {
3985 char *key;
3987 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3989 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3990 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
3991 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
3992 key, g_hash_table_size(session->unconfirmed_messages));
3993 g_free(key);
3995 sipe_send_message(sip, dialog, queued_msg);
3996 } SIPE_DIALOG_FOREACH_END;
3998 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3999 g_free(queued_msg);
4003 static void
4004 sipe_refer_notify(struct sipe_account_data *sip,
4005 struct sip_session *session,
4006 const gchar *who,
4007 int status,
4008 const gchar *desc)
4010 gchar *hdr;
4011 gchar *body;
4012 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4014 hdr = g_strdup_printf(
4015 "Event: refer\r\n"
4016 "Subscription-State: %s\r\n"
4017 "Content-Type: message/sipfrag\r\n",
4018 status >= 200 ? "terminated" : "active");
4020 body = g_strdup_printf(
4021 "SIP/2.0 %d %s\r\n",
4022 status, desc);
4024 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4026 g_free(hdr);
4027 g_free(body);
4030 static gboolean
4031 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4033 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4034 struct sip_session *session;
4035 struct sip_dialog *dialog;
4036 char *cseq;
4037 char *key;
4038 gchar *message;
4039 struct sipmsg *request_msg = trans->msg;
4041 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4042 gchar *referred_by;
4044 session = sipe_session_find_chat_by_callid(sip, callid);
4045 if (!session) {
4046 session = sipe_session_find_im(sip, with);
4048 if (!session) {
4049 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4050 g_free(with);
4051 return FALSE;
4054 dialog = sipe_dialog_find(session, with);
4055 if (!dialog) {
4056 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4057 g_free(with);
4058 return FALSE;
4061 sipe_dialog_parse(dialog, msg, TRUE);
4063 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4064 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4065 g_free(cseq);
4066 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4068 if (msg->response != 200) {
4069 PurpleBuddy *pbuddy;
4070 gchar *alias = with;
4072 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4074 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4075 alias = (gchar *)purple_buddy_get_alias(pbuddy);
4078 if (message) {
4079 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
4080 } else {
4081 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4082 sipe_present_err(sip, session, tmp_msg);
4083 g_free(tmp_msg);
4086 sipe_dialog_remove(session, with);
4088 g_free(key);
4089 g_free(with);
4090 return FALSE;
4093 dialog->cseq = 0;
4094 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4095 dialog->outgoing_invite = NULL;
4096 dialog->is_established = TRUE;
4098 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4099 if (referred_by) {
4100 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4101 g_free(referred_by);
4104 /* add user to chat if it is a multiparty session */
4105 if (session->is_multiparty) {
4106 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4107 with, NULL,
4108 PURPLE_CBFLAGS_NONE, TRUE);
4111 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4112 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4113 if (session->outgoing_message_queue) {
4114 char *queued_msg = session->outgoing_message_queue->data;
4115 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
4116 g_free(queued_msg);
4120 sipe_im_process_queue(sip, session);
4122 g_hash_table_remove(session->unconfirmed_messages, key);
4123 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4124 key, g_hash_table_size(session->unconfirmed_messages));
4126 g_free(key);
4127 g_free(with);
4128 return TRUE;
4132 void
4133 sipe_invite(struct sipe_account_data *sip,
4134 struct sip_session *session,
4135 const gchar *who,
4136 const gchar *msg_body,
4137 const gchar *referred_by,
4138 const gboolean is_triggered)
4140 gchar *hdr;
4141 gchar *to;
4142 gchar *contact;
4143 gchar *body;
4144 gchar *self;
4145 char *ms_text_format = NULL;
4146 gchar *roster_manager;
4147 gchar *end_points;
4148 gchar *referred_by_str;
4149 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4151 if (dialog && dialog->is_established) {
4152 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4153 return;
4156 if (!dialog) {
4157 dialog = sipe_dialog_add(session);
4158 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4159 dialog->with = g_strdup(who);
4162 if (!(dialog->ourtag)) {
4163 dialog->ourtag = gentag();
4166 to = sip_uri(who);
4168 if (msg_body) {
4169 char *msgformat;
4170 char *msgtext;
4171 char *base64_msg;
4172 gchar *msgr_value;
4173 gchar *msgr;
4174 char *key;
4176 sipe_parse_html(msg_body, &msgformat, &msgtext);
4177 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4179 msgr_value = sipmsg_get_msgr_string(msgformat);
4180 g_free(msgformat);
4181 msgr = "";
4182 if (msgr_value) {
4183 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4184 g_free(msgr_value);
4187 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4188 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
4189 g_free(msgtext);
4190 g_free(msgr);
4191 g_free(base64_msg);
4193 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4194 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
4195 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4196 key, g_hash_table_size(session->unconfirmed_messages));
4197 g_free(key);
4200 contact = get_contact(sip);
4201 end_points = get_end_points(sip, session);
4202 self = sip_uri_self(sip);
4203 roster_manager = g_strdup_printf(
4204 "Roster-Manager: %s\r\n"
4205 "EndPoints: %s\r\n",
4206 self,
4207 end_points);
4208 referred_by_str = referred_by ?
4209 g_strdup_printf(
4210 "Referred-By: %s\r\n",
4211 referred_by)
4212 : g_strdup("");
4213 hdr = g_strdup_printf(
4214 "Supported: ms-sender\r\n"
4215 "%s"
4216 "%s"
4217 "%s"
4218 "%s"
4219 "Contact: %s\r\n%s"
4220 "Content-Type: application/sdp\r\n",
4221 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
4222 referred_by_str,
4223 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4224 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4225 contact,
4226 ms_text_format ? ms_text_format : "");
4227 g_free(ms_text_format);
4228 g_free(self);
4230 body = g_strdup_printf(
4231 "v=0\r\n"
4232 "o=- 0 0 IN IP4 %s\r\n"
4233 "s=session\r\n"
4234 "c=IN IP4 %s\r\n"
4235 "t=0 0\r\n"
4236 "m=%s %d sip null\r\n"
4237 "a=accept-types:text/plain text/html image/gif "
4238 "multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4239 purple_network_get_my_ip(-1),
4240 purple_network_get_my_ip(-1),
4241 sip->ocs2007 ? "message" : "x-ms-message",
4242 sip->realport);
4244 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4245 to, to, hdr, body, dialog, process_invite_response);
4247 g_free(to);
4248 g_free(roster_manager);
4249 g_free(end_points);
4250 g_free(referred_by_str);
4251 g_free(body);
4252 g_free(hdr);
4253 g_free(contact);
4256 static void
4257 sipe_refer(struct sipe_account_data *sip,
4258 struct sip_session *session,
4259 const gchar *who)
4261 gchar *hdr;
4262 gchar *contact;
4263 gchar *epid = get_epid(sip);
4264 struct sip_dialog *dialog = sipe_dialog_find(session,
4265 session->roster_manager);
4266 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4268 contact = get_contact(sip);
4269 hdr = g_strdup_printf(
4270 "Contact: %s\r\n"
4271 "Refer-to: <%s>\r\n"
4272 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4273 "Require: com.microsoft.rtc-multiparty\r\n",
4274 contact,
4275 who,
4276 sip->username,
4277 ourtag ? ";tag=" : "",
4278 ourtag ? ourtag : "",
4279 epid);
4280 g_free(epid);
4282 send_sip_request(sip->gc, "REFER",
4283 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4285 g_free(hdr);
4286 g_free(contact);
4289 static void
4290 sipe_send_election_request_rm(struct sipe_account_data *sip,
4291 struct sip_dialog *dialog,
4292 int bid)
4294 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4296 gchar *body = g_strdup_printf(
4297 "<?xml version=\"1.0\"?>\r\n"
4298 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4299 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4300 sip->username, bid);
4302 send_sip_request(sip->gc, "INFO",
4303 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4305 g_free(body);
4308 static void
4309 sipe_send_election_set_rm(struct sipe_account_data *sip,
4310 struct sip_dialog *dialog)
4312 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4314 gchar *body = g_strdup_printf(
4315 "<?xml version=\"1.0\"?>\r\n"
4316 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4317 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4318 sip->username);
4320 send_sip_request(sip->gc, "INFO",
4321 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4323 g_free(body);
4326 static void
4327 sipe_session_close(struct sipe_account_data *sip,
4328 struct sip_session * session)
4330 if (session && session->focus_uri) {
4331 sipe_conf_immcu_closed(sip, session);
4332 conf_session_close(sip, session);
4335 if (session) {
4336 SIPE_DIALOG_FOREACH {
4337 /* @TODO slow down BYE message sending rate */
4338 /* @see single subscription code */
4339 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4340 } SIPE_DIALOG_FOREACH_END;
4342 sipe_session_remove(sip, session);
4346 static void
4347 sipe_session_close_all(struct sipe_account_data *sip)
4349 GSList *entry;
4350 while ((entry = sip->sessions) != NULL) {
4351 sipe_session_close(sip, entry->data);
4355 static void
4356 sipe_convo_closed(PurpleConnection * gc, const char *who)
4358 struct sipe_account_data *sip = gc->proto_data;
4360 purple_debug_info("sipe", "conversation with %s closed\n", who);
4361 sipe_session_close(sip, sipe_session_find_im(sip, who));
4364 static void
4365 sipe_chat_leave (PurpleConnection *gc, int id)
4367 struct sipe_account_data *sip = gc->proto_data;
4368 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4370 sipe_session_close(sip, session);
4373 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4374 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4376 struct sipe_account_data *sip = gc->proto_data;
4377 struct sip_session *session;
4378 struct sip_dialog *dialog;
4379 gchar *uri = sip_uri(who);
4381 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4383 session = sipe_session_find_or_add_im(sip, uri);
4384 dialog = sipe_dialog_find(session, uri);
4386 // Queue the message
4387 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
4389 if (dialog && !dialog->outgoing_invite) {
4390 sipe_im_process_queue(sip, session);
4391 } else if (!dialog || !dialog->outgoing_invite) {
4392 // Need to send the INVITE to get the outgoing dialog setup
4393 sipe_invite(sip, session, uri, what, NULL, FALSE);
4396 g_free(uri);
4397 return 1;
4400 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4401 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4403 struct sipe_account_data *sip = gc->proto_data;
4404 struct sip_session *session;
4406 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4408 session = sipe_session_find_chat_by_id(sip, id);
4410 // Queue the message
4411 if (session && session->dialogs) {
4412 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4413 g_strdup(what));
4414 sipe_im_process_queue(sip, session);
4415 } else if (sip) {
4416 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4417 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4419 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4420 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4422 if (sip->ocs2007) {
4423 struct sip_session *session = sipe_session_add_chat(sip);
4425 session->is_multiparty = FALSE;
4426 session->focus_uri = g_strdup(proto_chat_id);
4427 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4428 g_strdup(what));
4429 sipe_invite_conf_focus(sip, session);
4433 return 1;
4436 /* End IM Session (INVITE and MESSAGE methods) */
4438 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4440 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4441 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4442 gchar *from;
4443 struct sip_session *session;
4445 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4447 /* Call Control protocol */
4448 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4450 process_incoming_info_csta(sip, msg);
4451 return;
4454 from = parse_from(sipmsg_find_header(msg, "From"));
4455 session = sipe_session_find_chat_by_callid(sip, callid);
4456 if (!session) {
4457 session = sipe_session_find_im(sip, from);
4459 if (!session) {
4460 g_free(from);
4461 return;
4464 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4466 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4467 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4468 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4470 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4472 if (xn_request_rm) {
4473 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4474 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
4475 gchar *body = g_strdup_printf(
4476 "<?xml version=\"1.0\"?>\r\n"
4477 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4478 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4479 sip->username,
4480 session->bid < bid ? "true" : "false");
4481 send_sip_response(sip->gc, msg, 200, "OK", body);
4482 g_free(body);
4483 } else if (xn_set_rm) {
4484 gchar *body;
4485 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4486 g_free(session->roster_manager);
4487 session->roster_manager = g_strdup(rm);
4489 body = g_strdup_printf(
4490 "<?xml version=\"1.0\"?>\r\n"
4491 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4492 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4493 sip->username);
4494 send_sip_response(sip->gc, msg, 200, "OK", body);
4495 g_free(body);
4497 xmlnode_free(xn_action);
4500 else
4502 /* looks like purple lacks typing notification for chat */
4503 if (!session->is_multiparty && !session->focus_uri) {
4504 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4505 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4506 "status");
4507 if (status && !strcmp(status, "type")) {
4508 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4509 } else if (status && !strcmp(status, "idle")) {
4510 serv_got_typing_stopped(sip->gc, from);
4512 xmlnode_free(xn_keyboard_activity);
4515 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4517 g_free(from);
4520 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4522 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4523 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4524 struct sip_session *session;
4525 struct sip_dialog *dialog;
4527 /* collect dialog identification
4528 * we need callid, ourtag and theirtag to unambiguously identify dialog
4530 /* take data before 'msg' will be modified by send_sip_response */
4531 dialog = g_new0(struct sip_dialog, 1);
4532 dialog->callid = g_strdup(callid);
4533 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4534 dialog->with = g_strdup(from);
4535 sipe_dialog_parse(dialog, msg, FALSE);
4537 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4539 session = sipe_session_find_chat_by_callid(sip, callid);
4540 if (!session) {
4541 session = sipe_session_find_im(sip, from);
4543 if (!session) {
4544 g_free(from);
4545 return;
4548 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4549 g_free(session->roster_manager);
4550 session->roster_manager = NULL;
4553 /* This what BYE is essentially for - terminating dialog */
4554 sipe_dialog_remove_3(session, dialog);
4555 sipe_dialog_free(dialog);
4556 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4557 sipe_conf_immcu_closed(sip, session);
4558 } else if (session->is_multiparty) {
4559 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4562 g_free(from);
4565 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4567 gchar *self = sip_uri_self(sip);
4568 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4569 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4570 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4571 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4572 struct sip_session *session;
4573 struct sip_dialog *dialog;
4575 session = sipe_session_find_chat_by_callid(sip, callid);
4576 dialog = sipe_dialog_find(session, from);
4578 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
4579 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4580 } else {
4581 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4583 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
4586 g_free(self);
4587 g_free(from);
4588 g_free(refer_to);
4589 g_free(referred_by);
4592 static unsigned int
4593 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4595 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4596 struct sip_session *session;
4597 struct sip_dialog *dialog;
4599 if (state == PURPLE_NOT_TYPING)
4600 return 0;
4602 session = sipe_session_find_im(sip, who);
4603 dialog = sipe_dialog_find(session, who);
4605 if (session && dialog && dialog->is_established) {
4606 send_sip_request(gc, "INFO", who, who,
4607 "Content-Type: application/xml\r\n",
4608 SIPE_SEND_TYPING, dialog, NULL);
4610 return SIPE_TYPING_SEND_TIMEOUT;
4613 static gboolean resend_timeout(struct sipe_account_data *sip)
4615 GSList *tmp = sip->transactions;
4616 time_t currtime = time(NULL);
4617 while (tmp) {
4618 struct transaction *trans = tmp->data;
4619 tmp = tmp->next;
4620 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4621 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4622 /* TODO 408 */
4623 } else {
4624 if ((currtime - trans->time > 2) && trans->retries == 0) {
4625 trans->retries++;
4626 sendout_sipmsg(sip, trans->msg);
4630 return TRUE;
4633 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4634 SIPE_UNUSED_PARAMETER void *unused)
4636 /* register again when security token expires */
4637 /* we have to start a new authentication as the security token
4638 * is almost expired by sending a not signed REGISTER message */
4639 purple_debug_info("sipe", "do a full reauthentication\n");
4640 sipe_auth_free(&sip->registrar);
4641 sipe_auth_free(&sip->proxy);
4642 sip->registerstatus = 0;
4643 do_register(sip);
4644 sip->reauthenticate_set = FALSE;
4647 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4649 gchar *from;
4650 gchar *contenttype;
4651 gboolean found = FALSE;
4653 from = parse_from(sipmsg_find_header(msg, "From"));
4655 if (!from) return;
4657 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4659 contenttype = sipmsg_find_header(msg, "Content-Type");
4660 if (!strncmp(contenttype, "text/plain", 10)
4661 || !strncmp(contenttype, "text/html", 9)
4662 || !strncmp(contenttype, "multipart/related", 17)
4663 || !strncmp(contenttype, "multipart/alternative", 21))
4665 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4666 gchar *html = get_html_message(contenttype, msg->body);
4668 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4669 if (!session) {
4670 session = sipe_session_find_im(sip, from);
4673 if (session && session->focus_uri) { /* a conference */
4674 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4675 gchar *sender = parse_from(tmp);
4676 g_free(tmp);
4677 serv_got_chat_in(sip->gc, session->chat_id, sender,
4678 PURPLE_MESSAGE_RECV, html, time(NULL));
4679 g_free(sender);
4680 } else if (session && session->is_multiparty) { /* a multiparty chat */
4681 serv_got_chat_in(sip->gc, session->chat_id, from,
4682 PURPLE_MESSAGE_RECV, html, time(NULL));
4683 } else {
4684 serv_got_im(sip->gc, from, html, 0, time(NULL));
4686 g_free(html);
4687 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4688 found = TRUE;
4690 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
4691 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4692 xmlnode *state;
4693 gchar *statedata;
4695 if (!isc) {
4696 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4697 return;
4700 state = xmlnode_get_child(isc, "state");
4702 if (!state) {
4703 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4704 xmlnode_free(isc);
4705 return;
4708 statedata = xmlnode_get_data(state);
4709 if (statedata) {
4710 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4711 else serv_got_typing_stopped(sip->gc, from);
4713 g_free(statedata);
4715 xmlnode_free(isc);
4716 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4717 found = TRUE;
4719 if (!found) {
4720 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4721 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4722 if (!session) {
4723 session = sipe_session_find_im(sip, from);
4725 if (session) {
4726 gchar *msg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4727 from);
4728 sipe_present_err(sip, session, msg);
4729 g_free(msg);
4732 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
4733 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
4735 g_free(from);
4738 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
4740 gchar *body;
4741 gchar *newTag;
4742 gchar *oldHeader;
4743 gchar *newHeader;
4744 gboolean is_multiparty = FALSE;
4745 gboolean is_triggered = FALSE;
4746 gboolean was_multiparty = TRUE;
4747 gboolean just_joined = FALSE;
4748 gchar *from;
4749 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4750 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4751 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4752 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4753 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4754 GSList *end_points = NULL;
4755 char *tmp = NULL;
4756 struct sip_session *session;
4758 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
4759 g_free(tmp);
4761 /* Invitation to join conference */
4762 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
4763 process_incoming_invite_conf(sip, msg);
4764 return;
4767 /* Only accept text invitations */
4768 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4769 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4770 return;
4773 // TODO There *must* be a better way to clean up the To header to add a tag...
4774 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4775 oldHeader = sipmsg_find_header(msg, "To");
4776 newTag = gentag();
4777 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4778 sipmsg_remove_header_now(msg, "To");
4779 sipmsg_add_header_now(msg, "To", newHeader);
4780 g_free(newHeader);
4782 if (end_points_hdr) {
4783 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4785 if (g_slist_length(end_points) > 2) {
4786 is_multiparty = TRUE;
4789 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4790 is_triggered = TRUE;
4791 is_multiparty = TRUE;
4794 session = sipe_session_find_chat_by_callid(sip, callid);
4795 /* Convert to multiparty */
4796 if (session && is_multiparty && !session->is_multiparty) {
4797 g_free(session->with);
4798 session->with = NULL;
4799 was_multiparty = FALSE;
4800 session->is_multiparty = TRUE;
4801 session->chat_id = rand();
4804 if (!session && is_multiparty) {
4805 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
4807 /* IM session */
4808 from = parse_from(sipmsg_find_header(msg, "From"));
4809 if (!session) {
4810 session = sipe_session_find_or_add_im(sip, from);
4813 if (session) {
4814 g_free(session->callid);
4815 session->callid = g_strdup(callid);
4817 session->is_multiparty = is_multiparty;
4818 if (roster_manager) {
4819 session->roster_manager = g_strdup(roster_manager);
4823 if (is_multiparty && end_points) {
4824 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
4825 GSList *entry = end_points;
4826 while (entry) {
4827 struct sip_dialog *dialog;
4828 struct sipendpoint *end_point = entry->data;
4829 entry = entry->next;
4831 if (!g_strcasecmp(from, end_point->contact) ||
4832 !g_strcasecmp(to, end_point->contact))
4833 continue;
4835 dialog = sipe_dialog_find(session, end_point->contact);
4836 if (dialog) {
4837 g_free(dialog->theirepid);
4838 dialog->theirepid = end_point->epid;
4839 end_point->epid = NULL;
4840 } else {
4841 dialog = sipe_dialog_add(session);
4843 dialog->callid = g_strdup(session->callid);
4844 dialog->with = end_point->contact;
4845 end_point->contact = NULL;
4846 dialog->theirepid = end_point->epid;
4847 end_point->epid = NULL;
4849 just_joined = TRUE;
4851 /* send triggered INVITE */
4852 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
4855 g_free(to);
4858 if (end_points) {
4859 GSList *entry = end_points;
4860 while (entry) {
4861 struct sipendpoint *end_point = entry->data;
4862 entry = entry->next;
4863 g_free(end_point->contact);
4864 g_free(end_point->epid);
4865 g_free(end_point);
4867 g_slist_free(end_points);
4870 if (session) {
4871 struct sip_dialog *dialog = sipe_dialog_find(session, from);
4872 if (dialog) {
4873 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
4874 } else {
4875 dialog = sipe_dialog_add(session);
4877 dialog->callid = g_strdup(session->callid);
4878 dialog->with = g_strdup(from);
4879 sipe_dialog_parse(dialog, msg, FALSE);
4881 if (!dialog->ourtag) {
4882 dialog->ourtag = newTag;
4883 newTag = NULL;
4886 just_joined = TRUE;
4888 } else {
4889 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
4891 g_free(newTag);
4893 if (is_multiparty && !session->conv) {
4894 gchar *chat_title = sipe_chat_get_name(callid);
4895 gchar *self = sip_uri_self(sip);
4896 /* create prpl chat */
4897 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
4898 session->chat_title = g_strdup(chat_title);
4899 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
4900 /* add self */
4901 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4902 self, NULL,
4903 PURPLE_CBFLAGS_NONE, FALSE);
4904 g_free(chat_title);
4905 g_free(self);
4908 if (is_multiparty && !was_multiparty) {
4909 /* add current IM counterparty to chat */
4910 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4911 sipe_dialog_first(session)->with, NULL,
4912 PURPLE_CBFLAGS_NONE, FALSE);
4915 /* add inviting party to chat */
4916 if (just_joined && session->conv) {
4917 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4918 from, NULL,
4919 PURPLE_CBFLAGS_NONE, TRUE);
4922 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4924 /* This used only in 2005 official client, not 2007 or Reuters.
4925 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4926 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4928 if (is_multiparty) {
4929 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4930 gchar *ms_text_format = sipmsg_find_header(msg, "ms-text-format");
4931 if (ms_text_format) {
4932 if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html")) {
4934 gchar *html = get_html_message(ms_text_format, NULL);
4935 if (html) {
4936 if (is_multiparty) {
4937 serv_got_chat_in(sip->gc, session->chat_id, from,
4938 PURPLE_MESSAGE_RECV, html, time(NULL));
4939 } else {
4940 serv_got_im(sip->gc, from, html, 0, time(NULL));
4942 g_free(html);
4943 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
4950 g_free(from);
4952 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
4953 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4954 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4956 body = g_strdup_printf(
4957 "v=0\r\n"
4958 "o=- 0 0 IN IP4 %s\r\n"
4959 "s=session\r\n"
4960 "c=IN IP4 %s\r\n"
4961 "t=0 0\r\n"
4962 "m=%s %d sip sip:%s\r\n"
4963 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4964 purple_network_get_my_ip(-1),
4965 purple_network_get_my_ip(-1),
4966 sip->ocs2007 ? "message" : "x-ms-message",
4967 sip->realport,
4968 sip->username);
4969 send_sip_response(sip->gc, msg, 200, "OK", body);
4970 g_free(body);
4973 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
4975 gchar *body;
4977 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
4978 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4979 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4981 body = g_strdup_printf(
4982 "v=0\r\n"
4983 "o=- 0 0 IN IP4 0.0.0.0\r\n"
4984 "s=session\r\n"
4985 "c=IN IP4 0.0.0.0\r\n"
4986 "t=0 0\r\n"
4987 "m=%s %d sip sip:%s\r\n"
4988 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4989 sip->ocs2007 ? "message" : "x-ms-message",
4990 sip->realport,
4991 sip->username);
4992 send_sip_response(sip->gc, msg, 200, "OK", body);
4993 g_free(body);
4996 static void sipe_connection_cleanup(struct sipe_account_data *);
4997 static void create_connection(struct sipe_account_data *, gchar *, int);
4999 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5000 SIPE_UNUSED_PARAMETER struct transaction *trans)
5002 gchar *tmp;
5003 const gchar *expires_header;
5004 int expires, i;
5005 GSList *hdr = msg->headers;
5006 struct siphdrelement *elem;
5008 expires_header = sipmsg_find_header(msg, "Expires");
5009 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5010 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5012 switch (msg->response) {
5013 case 200:
5014 if (expires == 0) {
5015 sip->registerstatus = 0;
5016 } else {
5017 gchar *contact_hdr = NULL;
5018 gchar *gruu = NULL;
5019 gchar *epid;
5020 gchar *uuid;
5021 gchar *timeout;
5022 gchar *server_hdr = sipmsg_find_header(msg, "Server");
5024 if (!sip->reregister_set) {
5025 gchar *action_name = g_strdup_printf("<%s>", "registration");
5026 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5027 g_free(action_name);
5028 sip->reregister_set = TRUE;
5031 sip->registerstatus = 3;
5033 if (server_hdr && !sip->server_version) {
5034 sip->server_version = g_strdup(server_hdr);
5035 g_free(default_ua);
5036 default_ua = NULL;
5039 #ifdef USE_KERBEROS
5040 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5041 #endif
5042 tmp = sipmsg_find_auth_header(msg, "NTLM");
5043 #ifdef USE_KERBEROS
5044 } else {
5045 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5047 #endif
5048 if (tmp) {
5049 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5050 fill_auth(tmp, &sip->registrar);
5053 if (!sip->reauthenticate_set) {
5054 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5055 guint reauth_timeout;
5056 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5057 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5058 reauth_timeout = sip->registrar.expires - 300;
5059 } else {
5060 /* NTLM: we have to reauthenticate as our security token expires
5061 after eight hours (be five minutes early) */
5062 reauth_timeout = (8 * 3600) - 300;
5064 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5065 g_free(action_name);
5066 sip->reauthenticate_set = TRUE;
5069 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5071 epid = get_epid(sip);
5072 uuid = generateUUIDfromEPID(epid);
5073 g_free(epid);
5075 // There can be multiple Contact headers (one per location where the user is logged in) so
5076 // make sure to only get the one for this uuid
5077 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5078 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5079 if (valid_contact) {
5080 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5081 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5082 g_free(valid_contact);
5083 break;
5084 } else {
5085 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5088 g_free(uuid);
5090 g_free(sip->contact);
5091 if(gruu) {
5092 sip->contact = g_strdup_printf("<%s>", gruu);
5093 g_free(gruu);
5094 } else {
5095 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5096 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);
5098 sip->ocs2007 = FALSE;
5099 sip->batched_support = FALSE;
5101 while(hdr)
5103 elem = hdr->data;
5104 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5105 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5106 /* We interpret this as OCS2007+ indicator */
5107 sip->ocs2007 = TRUE;
5108 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5110 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5111 sip->batched_support = TRUE;
5112 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5115 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5116 gchar **caps = g_strsplit(elem->value,",",0);
5117 i = 0;
5118 while (caps[i]) {
5119 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5120 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5121 i++;
5123 g_strfreev(caps);
5125 hdr = g_slist_next(hdr);
5128 /* rejoin open chats to be able to use them by continue to send messages */
5129 purple_conversation_foreach(sipe_rejoin_chat);
5131 /* subscriptions */
5132 if (!sip->subscribed) { //do it just once, not every re-register
5134 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5135 (GCompareFunc)g_ascii_strcasecmp)) {
5136 sipe_subscribe_roaming_contacts(sip);
5139 /* For 2007+ it does not make sence to subscribe to:
5140 * vnd-microsoft-roaming-ACL
5141 * vnd-microsoft-provisioning (not v2)
5142 * presence.wpending
5143 * These are for backward compatibility.
5145 if (sip->ocs2007)
5147 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5148 (GCompareFunc)g_ascii_strcasecmp)) {
5149 sipe_subscribe_roaming_self(sip);
5151 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5152 (GCompareFunc)g_ascii_strcasecmp)) {
5153 sipe_subscribe_roaming_provisioning_v2(sip);
5156 /* For 2005- servers */
5157 else
5159 //sipe_options_request(sip, sip->sipdomain);
5161 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5162 (GCompareFunc)g_ascii_strcasecmp)) {
5163 sipe_subscribe_roaming_acl(sip);
5165 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5166 (GCompareFunc)g_ascii_strcasecmp)) {
5167 sipe_subscribe_roaming_provisioning(sip);
5169 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5170 (GCompareFunc)g_ascii_strcasecmp)) {
5171 sipe_subscribe_presence_wpending(sip, msg);
5174 /* For 2007+ we publish our initial statuses and calendar data only after
5175 * received our existing publications in sipe_process_roaming_self()
5176 * Only in this case we know versions of current publications made
5177 * on our behalf.
5179 /* For 2005- we publish our initial statuses only after
5180 * received our existing UserInfo data in response to
5181 * self subscription.
5182 * Only in this case we won't override existing UserInfo data
5183 * set earlier or by other client on our behalf.
5187 sip->subscribed = TRUE;
5190 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5191 "timeout=", ";", NULL);
5192 if (timeout != NULL) {
5193 sscanf(timeout, "%u", &sip->keepalive_timeout);
5194 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5195 sip->keepalive_timeout);
5196 g_free(timeout);
5199 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5201 break;
5202 case 301:
5204 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5206 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5207 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5208 gchar **tmp;
5209 gchar *hostname;
5210 int port = 0;
5211 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5212 int i = 1;
5214 tmp = g_strsplit(parts[0], ":", 0);
5215 hostname = g_strdup(tmp[0]);
5216 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5217 g_strfreev(tmp);
5219 while (parts[i]) {
5220 tmp = g_strsplit(parts[i], "=", 0);
5221 if (tmp[1]) {
5222 if (g_strcasecmp("transport", tmp[0]) == 0) {
5223 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5224 transport = SIPE_TRANSPORT_TCP;
5225 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5226 transport = SIPE_TRANSPORT_UDP;
5230 g_strfreev(tmp);
5231 i++;
5233 g_strfreev(parts);
5235 /* Close old connection */
5236 sipe_connection_cleanup(sip);
5238 /* Create new connection */
5239 sip->transport = transport;
5240 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5241 hostname, port, TRANSPORT_DESCRIPTOR);
5242 create_connection(sip, hostname, port);
5244 g_free(redirect);
5246 break;
5247 case 401:
5248 if (sip->registerstatus != 2) {
5249 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5250 if (sip->registrar.retries > 3) {
5251 sip->gc->wants_to_die = TRUE;
5252 purple_connection_error(sip->gc, _("Wrong password"));
5253 return TRUE;
5255 #ifdef USE_KERBEROS
5256 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5257 #endif
5258 tmp = sipmsg_find_auth_header(msg, "NTLM");
5259 #ifdef USE_KERBEROS
5260 } else {
5261 tmp = sipmsg_find_auth_header(msg, "Kerberos");
5263 #endif
5264 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5265 fill_auth(tmp, &sip->registrar);
5266 sip->registerstatus = 2;
5267 if (sip->account->disconnecting) {
5268 do_register_exp(sip, 0);
5269 } else {
5270 do_register(sip);
5273 break;
5274 case 403:
5276 gchar *warning = sipmsg_find_header(msg, "Warning");
5277 gchar **reason = NULL;
5278 if (warning != NULL) {
5279 /* Example header:
5280 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5282 reason = g_strsplit(warning, "\"", 0);
5284 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5285 (reason && reason[1]) ? reason[1] : _("no reason given"));
5286 g_strfreev(reason);
5288 sip->gc->wants_to_die = TRUE;
5289 purple_connection_error(sip->gc, warning);
5290 g_free(warning);
5291 return TRUE;
5293 break;
5294 case 404:
5296 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5297 gchar *reason = NULL;
5298 if (warning != NULL) {
5299 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5301 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5302 warning ? (reason ? reason : _("no reason given")) :
5303 _("SIP is either not enabled for the destination URI or it does not exist"));
5304 g_free(reason);
5306 sip->gc->wants_to_die = TRUE;
5307 purple_connection_error(sip->gc, warning);
5308 g_free(warning);
5309 return TRUE;
5311 break;
5312 case 503:
5313 case 504: /* Server time-out */
5315 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5316 gchar *reason = NULL;
5317 if (warning != NULL) {
5318 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5320 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : "<a href=\"http://www.reuters.com\">http://www.reuters.com</a>"/*_("no reason given")*/);
5321 g_free(reason);
5323 sip->gc->wants_to_die = TRUE;
5324 purple_connection_error(sip->gc, warning);
5325 g_free(warning);
5326 return TRUE;
5328 break;
5330 return TRUE;
5334 * Returns 2005-style activity and Availability.
5336 * @param status Sipe statis id.
5338 static void
5339 sipe_get_act_avail_by_status_2005(const char *status, int *activity, int *availability)
5341 int avail = 300; /* online */
5342 int act = 400; /* Available */
5344 if (!strcmp(status, SIPE_STATUS_ID_AWAY)) {
5345 act = 100;
5346 } else if (!strcmp(status, SIPE_STATUS_ID_LUNCH)) {
5347 act = 150;
5348 } else if (!strcmp(status, SIPE_STATUS_ID_BRB)) {
5349 act = 300;
5350 } else if (!strcmp(status, SIPE_STATUS_ID_AVAILABLE)) {
5351 act = 400;
5352 } else if (!strcmp(status, SIPE_STATUS_ID_ON_PHONE)) {
5353 act = 500;
5354 } else if (!strcmp(status, SIPE_STATUS_ID_BUSY) ||
5355 !strcmp(status, SIPE_STATUS_ID_IN_MEETING) ||
5356 !strcmp(status, SIPE_STATUS_ID_IN_CONF) ||
5357 !strcmp(status, SIPE_STATUS_ID_DND)) {
5358 act = 600;
5359 } else if (!strcmp(status, SIPE_STATUS_ID_INVISIBLE) ||
5360 !strcmp(status, SIPE_STATUS_ID_OFFLINE)) {
5361 avail = 0; /* offline */
5362 act = 100;
5363 } else {
5364 act = 400; /* Available */
5367 if (activity) *activity = act;
5368 if (availability) *availability = avail;
5372 * [MS-SIP] 2.2.1
5374 * @param activity 2005 aggregated activity. Ex.: 600
5375 * @param availablity 2005 aggregated availablity. Ex.: 300
5377 static const char *
5378 sipe_get_status_by_act_avail_2005(const int activity,
5379 const int availablity)
5381 const char *status_id = NULL;
5383 if (activity < 150)
5384 status_id = SIPE_STATUS_ID_AWAY;
5385 else if (activity < 200)
5386 status_id = SIPE_STATUS_ID_LUNCH;
5387 else if (activity < 300)
5388 status_id = SIPE_STATUS_ID_IDLE;
5389 else if (activity < 400)
5390 status_id = SIPE_STATUS_ID_BRB;
5391 else if (activity < 500)
5392 status_id = SIPE_STATUS_ID_AVAILABLE;
5393 else if (activity < 600)
5394 status_id = SIPE_STATUS_ID_ON_PHONE;
5395 else if (activity < 700)
5396 status_id = SIPE_STATUS_ID_BUSY;
5397 else if (activity < 800)
5398 status_id = SIPE_STATUS_ID_AWAY;
5399 else
5400 status_id = SIPE_STATUS_ID_AVAILABLE;
5402 if (availablity < 100)
5403 status_id = SIPE_STATUS_ID_OFFLINE;
5405 return status_id;
5409 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5411 static const char*
5412 sipe_get_status_by_availability(int avail,
5413 const char* activity)
5415 const char *status;
5417 if (avail < 3000)
5418 status = SIPE_STATUS_ID_OFFLINE;
5419 else if (avail < 4500)
5420 status = SIPE_STATUS_ID_AVAILABLE;
5421 else if (avail < 6000)
5422 status = SIPE_STATUS_ID_IDLE;
5423 else if (avail < 7500)
5424 if (activity && !strcmp(activity, SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING))) {
5425 status = SIPE_STATUS_ID_IN_MEETING;
5426 } else if (activity && !strcmp(activity, SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF))) {
5427 status = SIPE_STATUS_ID_IN_CONF;
5428 } else if (activity && !strcmp(activity, SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE))) {
5429 status = SIPE_STATUS_ID_ON_PHONE;
5430 } else {
5431 status = SIPE_STATUS_ID_BUSY;
5433 else if (avail < 9000)
5434 status = SIPE_STATUS_ID_BUSYIDLE;
5435 else if (avail < 12000)
5436 status = SIPE_STATUS_ID_DND;
5437 else if (avail < 15000)
5438 status = SIPE_STATUS_ID_BRB;
5439 else if (avail < 18000)
5440 status = SIPE_STATUS_ID_AWAY;
5441 else
5442 status = SIPE_STATUS_ID_OFFLINE;
5444 return status;
5448 * Returns 2007-style availability value
5450 * @param sipe_status_id (in)
5451 * @param activity_token (out) Must be g_free()'d after use if consumed.
5453 static int
5454 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5456 int availability;
5457 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5459 if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AWAY) ||
5460 !strcmp(sipe_status_id, SIPE_STATUS_ID_LUNCH)) {
5461 availability = 15500;
5462 activity = SIPE_ACTIVITY_AWAY;
5463 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5464 availability = 12500;
5465 activity = SIPE_ACTIVITY_BRB;
5466 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_DND)) {
5467 availability = 9500;
5468 activity = SIPE_ACTIVITY_DND;
5469 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_BUSY) ||
5470 !strcmp(sipe_status_id, SIPE_STATUS_ID_IN_MEETING) ||
5471 !strcmp(sipe_status_id, SIPE_STATUS_ID_IN_CONF) ||
5472 !strcmp(sipe_status_id, SIPE_STATUS_ID_ON_PHONE)) {
5473 availability = 6500;
5474 activity = SIPE_ACTIVITY_BUSY;
5475 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5476 availability = 3500;
5477 activity = SIPE_ACTIVITY_ONLINE;
5478 } else if (!strcmp(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5479 availability = 0;
5480 } else {
5481 // Offline or invisible
5482 availability = 18500;
5483 activity = SIPE_ACTIVITY_OFFLINE;
5486 if (activity_token) {
5487 *activity_token = g_strdup(sipe_activity_map[activity].token);
5489 return availability;
5492 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5494 const char *uri;
5495 xmlnode *xn_categories;
5496 xmlnode *xn_category;
5497 xmlnode *xn_node;
5498 const char *status = NULL;
5499 gboolean do_update_status = FALSE;
5501 xn_categories = xmlnode_from_str(data, len);
5502 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5504 for (xn_category = xmlnode_get_child(xn_categories, "category");
5505 xn_category ;
5506 xn_category = xmlnode_get_next_twin(xn_category) )
5508 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5510 /* contactCard */
5511 if (!strcmp(attrVar, "contactCard"))
5513 xmlnode *node;
5514 /* identity - Display Name and email */
5515 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5516 if (node) {
5517 char* display_name = xmlnode_get_data(
5518 xmlnode_get_descendant(node, "name", "displayName", NULL));
5519 char* email = xmlnode_get_data(
5520 xmlnode_get_child(node, "email"));
5522 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5523 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5525 g_free(display_name);
5526 g_free(email);
5528 /* company */
5529 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5530 if (node) {
5531 char* company = xmlnode_get_data(node);
5532 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5533 g_free(company);
5535 /* department */
5536 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5537 if (node) {
5538 char* department = xmlnode_get_data(node);
5539 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5540 g_free(department);
5542 /* title */
5543 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5544 if (node) {
5545 char* title = xmlnode_get_data(node);
5546 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5547 g_free(title);
5549 /* office */
5550 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5551 if (node) {
5552 char* office = xmlnode_get_data(node);
5553 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5554 g_free(office);
5556 /* site (url) */
5557 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5558 if (node) {
5559 char* site = xmlnode_get_data(node);
5560 sipe_update_user_info(sip, uri, SITE_PROP, site);
5561 g_free(site);
5563 /* phone */
5564 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5565 node;
5566 node = xmlnode_get_next_twin(node))
5568 const char *phone_type = xmlnode_get_attrib(node, "type");
5569 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5570 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5572 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5574 g_free(phone);
5575 g_free(phone_display_string);
5577 /* address */
5578 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5579 node;
5580 node = xmlnode_get_next_twin(node))
5582 if (!strcmp(xmlnode_get_attrib(node, "type"), "work")) {
5583 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5584 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5585 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5586 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5587 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5589 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5590 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5591 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5592 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5593 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5595 g_free(street);
5596 g_free(city);
5597 g_free(state);
5598 g_free(zipcode);
5599 g_free(country_code);
5601 break;
5605 /* note */
5606 else if (!strcmp(attrVar, "note"))
5608 if (uri) {
5609 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5611 if (sbuddy) {
5612 /* clean up in case to 'note' element is supplied
5613 * which indicate note removal in client
5615 g_free(sbuddy->annotation);
5616 sbuddy->annotation = NULL;
5617 sbuddy->is_oof_note = FALSE;
5619 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
5620 if (xn_node) {
5621 sbuddy->annotation = xmlnode_get_data(xn_node);
5622 sbuddy->is_oof_note = !strcmp(xmlnode_get_attrib(xn_node, "type"), "OOF");
5624 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",
5625 uri, sbuddy->annotation ? sbuddy->annotation : "");
5628 /* to trigger UI refresh in case no status info is supplied in this update */
5629 do_update_status = TRUE;
5633 /* state */
5634 else if(!strcmp(attrVar, "state"))
5636 char *data;
5637 int availability;
5638 xmlnode *xn_availability;
5639 xmlnode *xn_activity;
5640 xmlnode *xn_meeting_subject;
5641 xmlnode *xn_meeting_location;
5642 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5644 xn_node = xmlnode_get_child(xn_category, "state");
5645 if (!xn_node) continue;
5646 xn_availability = xmlnode_get_child(xn_node, "availability");
5647 if (!xn_availability) continue;
5648 xn_activity = xmlnode_get_child(xn_node, "activity");
5649 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
5650 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
5652 data = xmlnode_get_data(xn_availability);
5653 availability = atoi(data);
5654 g_free(data);
5656 /* activity, meeting_subject, meeting_location */
5657 if (sbuddy) {
5658 /* activity */
5659 g_free(sbuddy->activity);
5660 sbuddy->activity = NULL;
5661 if (xn_activity) {
5662 const char *token = xmlnode_get_attrib(xn_activity, "token");
5663 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
5665 /* from token */
5666 if (!is_empty(token)) {
5667 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
5669 /* from custom element */
5670 if (xn_custom) {
5671 char *custom = xmlnode_get_data(xn_custom);
5673 if (!is_empty(custom)) {
5674 sbuddy->activity = custom;
5675 custom = NULL;
5677 g_free(custom);
5680 /* meeting_subject */
5681 g_free(sbuddy->meeting_subject);
5682 sbuddy->meeting_subject = NULL;
5683 if (xn_meeting_subject) {
5684 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
5686 if (!is_empty(meeting_subject)) {
5687 sbuddy->meeting_subject = meeting_subject;
5688 meeting_subject = NULL;
5690 g_free(meeting_subject);
5692 /* meeting_location */
5693 g_free(sbuddy->meeting_location);
5694 sbuddy->meeting_location = NULL;
5695 if (xn_meeting_location) {
5696 char *meeting_location = xmlnode_get_data(xn_meeting_location);
5698 if (!is_empty(meeting_location)) {
5699 sbuddy->meeting_location = meeting_location;
5700 meeting_location = NULL;
5702 g_free(meeting_location);
5705 status = sipe_get_status_by_availability(availability, sbuddy->activity);
5708 do_update_status = TRUE;
5710 /* calendarData */
5711 else if(!strcmp(attrVar, "calendarData"))
5713 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5714 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
5715 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
5717 if (sbuddy && xn_free_busy) {
5718 g_free(sbuddy->cal_start_time);
5719 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
5721 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
5722 15 : 0;
5724 g_free(sbuddy->cal_free_busy_base64);
5725 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
5727 g_free(sbuddy->cal_free_busy);
5728 sbuddy->cal_free_busy = NULL;
5730 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);
5733 if (sbuddy && xn_working_hours) {
5734 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
5739 if (do_update_status) {
5740 if (!status) { /* no status category in this update, using contact's current status */
5741 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
5742 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
5743 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
5744 status = purple_status_get_id(pstatus);
5747 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
5748 sipe_got_user_status(sip, uri, status);
5751 xmlnode_free(xn_categories);
5754 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
5756 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5757 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
5758 payload->host = g_strdup(host);
5759 payload->buddies = server;
5760 sipe_subscribe_presence_batched_routed(sip, payload);
5761 sipe_subscribe_presence_batched_routed_free(payload);
5764 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
5766 xmlnode *xn_list;
5767 xmlnode *xn_resource;
5768 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
5769 g_free, NULL);
5770 GSList *server;
5771 gchar *host;
5773 xn_list = xmlnode_from_str(data, len);
5775 for (xn_resource = xmlnode_get_child(xn_list, "resource");
5776 xn_resource;
5777 xn_resource = xmlnode_get_next_twin(xn_resource) )
5779 const char *uri, *state;
5780 xmlnode *xn_instance;
5782 xn_instance = xmlnode_get_child(xn_resource, "instance");
5783 if (!xn_instance) continue;
5785 uri = xmlnode_get_attrib(xn_resource, "uri");
5786 state = xmlnode_get_attrib(xn_instance, "state");
5787 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
5789 if (strstr(state, "resubscribe")) {
5790 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
5792 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
5793 gchar *user = g_strdup(uri);
5794 host = g_strdup(poolFqdn);
5795 server = g_hash_table_lookup(servers, host);
5796 server = g_slist_append(server, user);
5797 g_hash_table_insert(servers, host, server);
5798 } else {
5799 sipe_subscribe_presence_single(sip, (void *) uri);
5804 /* Send out any deferred poolFqdn subscriptions */
5805 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
5806 g_hash_table_destroy(servers);
5808 xmlnode_free(xn_list);
5811 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
5813 gchar *uri;
5814 gchar *getbasic;
5815 gchar *activity = NULL;
5816 xmlnode *pidf;
5817 xmlnode *basicstatus = NULL, *tuple, *status;
5818 gboolean isonline = FALSE;
5819 xmlnode *display_name_node;
5821 pidf = xmlnode_from_str(data, len);
5822 if (!pidf) {
5823 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
5824 return;
5827 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
5829 if ((tuple = xmlnode_get_child(pidf, "tuple")))
5831 if ((status = xmlnode_get_child(tuple, "status"))) {
5832 basicstatus = xmlnode_get_child(status, "basic");
5836 if (!basicstatus) {
5837 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
5838 xmlnode_free(pidf);
5839 return;
5842 getbasic = xmlnode_get_data(basicstatus);
5843 if (!getbasic) {
5844 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
5845 xmlnode_free(pidf);
5846 return;
5849 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
5850 if (strstr(getbasic, "open")) {
5851 isonline = TRUE;
5853 g_free(getbasic);
5855 display_name_node = xmlnode_get_child(pidf, "display-name");
5856 if (display_name_node) {
5857 char * display_name = xmlnode_get_data(display_name_node);
5859 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5860 g_free(display_name);
5863 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
5864 if ((status = xmlnode_get_child(tuple, "status"))) {
5865 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
5866 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
5867 activity = xmlnode_get_data(basicstatus);
5868 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
5874 if (isonline) {
5875 const gchar * status_id = NULL;
5876 if (activity) {
5877 if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
5878 status_id = SIPE_STATUS_ID_BUSY;
5879 } else if (!strcmp(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
5880 status_id = SIPE_STATUS_ID_AWAY;
5884 if (!status_id) {
5885 status_id = SIPE_STATUS_ID_AVAILABLE;
5888 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
5889 sipe_got_user_status(sip, uri, status_id);
5890 } else {
5891 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
5894 g_free(activity);
5895 g_free(uri);
5896 xmlnode_free(pidf);
5899 /** 2005 */
5900 static void
5901 sipe_user_info_has_updated(struct sipe_account_data *sip,
5902 xmlnode *xn_userinfo)
5904 if (sip->user_info) {
5905 xmlnode_free(sip->user_info);
5907 sip->user_info = xmlnode_copy(xn_userinfo);
5909 /* Publish initial state if not yet.
5910 * Assuming this happens on initial responce to self subscription
5911 * so we've already updated our UserInfo.
5913 if (!sip->initial_state_published) {
5914 send_presence_soap(sip, FALSE);
5915 /* dalayed run */
5916 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
5920 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
5922 const char *activity = NULL;
5923 const char *epid;
5924 const char *status_id = NULL;
5925 const char *name;
5926 char *uri;
5927 char *self_uri = sip_uri_self(sip);
5928 int avl;
5929 int act;
5930 const char *device_name = NULL;
5931 const char *cal_start_time = NULL;
5932 const char *cal_granularity = NULL;
5933 char *cal_free_busy_base64 = NULL;
5934 struct sipe_buddy *sbuddy;
5935 xmlnode *node;
5936 xmlnode *xn_presentity;
5937 xmlnode *xn_availability;
5938 xmlnode *xn_activity;
5939 xmlnode *xn_display_name;
5940 xmlnode *xn_email;
5941 xmlnode *xn_phone_number;
5942 xmlnode *xn_userinfo;
5943 xmlnode *xn_note;
5944 xmlnode *xn_oof;
5945 xmlnode *xn_state;
5946 xmlnode *xn_contact;
5947 char *note;
5948 char *free_activity;
5949 int user_avail;
5950 const char *user_avail_nil;
5951 int res_avail;
5952 time_t user_avail_since = 0;
5953 time_t activity_since = 0;
5955 /* fix for Reuters environment on Linux */
5956 if (data && strstr(data, "encoding=\"utf-16\"")) {
5957 char *tmp_data;
5958 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
5959 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
5960 g_free(tmp_data);
5961 } else {
5962 xn_presentity = xmlnode_from_str(data, len);
5965 xn_availability = xmlnode_get_child(xn_presentity, "availability");
5966 xn_activity = xmlnode_get_child(xn_presentity, "activity");
5967 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
5968 xn_email = xmlnode_get_child(xn_presentity, "email");
5969 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
5970 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
5971 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
5972 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
5973 user_avail = xn_state ? atoi(xmlnode_get_attrib(xn_state, "avail")) : 0;
5974 user_avail_since = xn_state ? purple_str_to_time(xmlnode_get_attrib(xn_state, "since"), FALSE, NULL, NULL, NULL) : 0;
5975 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
5976 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
5977 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
5978 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
5980 if (user_avail_nil && !strcmp(user_avail_nil, "true")) { /* null-ed */
5981 user_avail = 0;
5982 user_avail_since = 0;
5985 free_activity = NULL;
5987 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
5988 uri = sip_uri_from_name(name);
5989 avl = atoi(xmlnode_get_attrib(xn_availability, "aggregate"));
5990 epid = xmlnode_get_attrib(xn_availability, "epid");
5991 act = atoi(xmlnode_get_attrib(xn_activity, "aggregate"));
5993 status_id = sipe_get_status_by_act_avail_2005(act, avl);
5994 res_avail = sipe_get_availability_by_status(status_id, NULL);
5995 if (user_avail > res_avail) {
5996 res_avail = user_avail;
5997 status_id = sipe_get_status_by_availability(user_avail, NULL);
6000 if (xn_display_name) {
6001 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6002 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6003 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6004 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6005 char *tel_uri = sip_to_tel_uri(phone_number);
6007 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6008 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6009 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6010 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6012 g_free(tel_uri);
6013 g_free(phone_label);
6014 g_free(phone_number);
6015 g_free(email);
6016 g_free(display_name);
6019 if (xn_contact) {
6020 /* tel */
6021 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6023 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6024 const char *phone_type = xmlnode_get_attrib(node, "type");
6025 char* phone = xmlnode_get_data(node);
6027 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6029 g_free(phone);
6033 /* devicePresence */
6034 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6035 xmlnode *xn_device_name;
6036 xmlnode *xn_calendar_info;
6037 xmlnode *xn_state;
6038 char *state;
6040 /* deviceName */
6041 if (!strcmp(xmlnode_get_attrib(node, "epid"), epid)) {
6042 xn_device_name = xmlnode_get_child(node, "deviceName");
6043 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6046 /* calendarInfo */
6047 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6048 if (xn_calendar_info) {
6049 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6051 if (cal_start_time) {
6052 time_t cal_start_time_t = purple_str_to_time(cal_start_time, FALSE, NULL, NULL, NULL);
6053 time_t cal_start_time_t_tmp = purple_str_to_time(cal_start_time_tmp, FALSE, NULL, NULL, NULL);
6055 if (cal_start_time_t_tmp > cal_start_time_t) {
6056 cal_start_time = cal_start_time_tmp;
6057 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6058 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6060 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);
6062 } else {
6063 cal_start_time = cal_start_time_tmp;
6064 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6065 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6067 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);
6071 /* state */
6072 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6073 if (xn_state) {
6074 int dev_avail = atoi(xmlnode_get_attrib(xn_state, "avail"));
6075 time_t dev_avail_since = purple_str_to_time(xmlnode_get_attrib(xn_state, "since"), FALSE, NULL, NULL, NULL);
6077 state = xmlnode_get_data(xn_state);
6078 if (dev_avail_since > user_avail_since &&
6079 dev_avail >= res_avail)
6081 res_avail = dev_avail;
6082 if (!is_empty(state))
6084 if (!strcmp(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6085 activity = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
6086 } else if (!strcmp(state, "presenting")) {
6087 activity = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF);
6088 } else {
6089 activity = free_activity = state;
6090 state = NULL;
6092 activity_since = dev_avail_since;
6094 status_id = sipe_get_status_by_availability(res_avail, activity);
6096 g_free(state);
6100 /* oof */
6101 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6102 activity = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF);
6103 activity_since = 0;
6106 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6107 if (sbuddy)
6109 g_free(sbuddy->activity);
6110 sbuddy->activity = NULL;
6111 if (!is_empty(activity)) { sbuddy->activity = g_strdup(activity); }
6113 sbuddy->activity_since = activity_since;
6115 sbuddy->user_avail = user_avail;
6116 sbuddy->user_avail_since = user_avail_since;
6118 g_free(sbuddy->annotation);
6119 sbuddy->annotation = NULL;
6120 if (!is_empty(note)) { sbuddy->annotation = g_strdup(note); }
6122 sbuddy->is_oof_note = (xn_oof != NULL);
6124 g_free(sbuddy->device_name);
6125 sbuddy->device_name = NULL;
6126 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6128 if (!is_empty(cal_free_busy_base64)) {
6129 g_free(sbuddy->cal_start_time);
6130 sbuddy->cal_start_time = g_strdup(cal_start_time);
6132 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6134 g_free(sbuddy->cal_free_busy_base64);
6135 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6137 g_free(sbuddy->cal_free_busy);
6138 sbuddy->cal_free_busy = NULL;
6141 sbuddy->last_non_cal_status_id = status_id;
6142 g_free(sbuddy->last_non_cal_activity);
6143 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6145 if (!strcmp(sbuddy->name, self_uri)) {
6146 if (!(sbuddy->annotation && sip->note && !strcmp(sbuddy->annotation, sip->note))) /* not same */
6148 sip->is_oof_note = sbuddy->is_oof_note;
6150 g_free(sip->note);
6151 sip->note = g_strdup(sbuddy->annotation);
6152 sip->note_since = time(NULL);
6155 g_free(sip->status);
6156 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6160 if (free_activity) g_free(free_activity);
6162 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6163 sipe_got_user_status(sip, uri, status_id);
6165 if (!sip->ocs2007 && !strcmp(self_uri, uri)) {
6166 sipe_user_info_has_updated(sip, xn_userinfo);
6169 g_free(note);
6170 xmlnode_free(xn_presentity);
6171 g_free(uri);
6172 g_free(self_uri);
6175 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6177 char *ctype = sipmsg_find_header(msg, "Content-Type");
6179 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6181 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6182 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6184 const char *content = msg->body;
6185 unsigned length = msg->bodylen;
6186 PurpleMimeDocument *mime = NULL;
6188 if (strstr(ctype, "multipart"))
6190 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6191 const char *content_type;
6192 GList* parts;
6193 mime = purple_mime_document_parse(doc);
6194 parts = purple_mime_document_get_parts(mime);
6195 while(parts) {
6196 content = purple_mime_part_get_data(parts->data);
6197 length = purple_mime_part_get_length(parts->data);
6198 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6199 if(content_type && strstr(content_type,"application/rlmi+xml"))
6201 process_incoming_notify_rlmi_resub(sip, content, length);
6203 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6205 process_incoming_notify_msrtc(sip, content, length);
6207 else
6209 process_incoming_notify_rlmi(sip, content, length);
6211 parts = parts->next;
6213 g_free(doc);
6215 if (mime)
6217 purple_mime_document_free(mime);
6220 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6222 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6224 else if(strstr(ctype, "application/rlmi+xml"))
6226 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6229 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6231 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6233 else
6235 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6239 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6241 char *ctype = sipmsg_find_header(msg, "Content-Type");
6242 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6244 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6246 if (ctype &&
6247 strstr(ctype, "multipart") &&
6248 (strstr(ctype, "application/rlmi+xml") ||
6249 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6250 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6251 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6252 GList *parts = purple_mime_document_get_parts(mime);
6253 GSList *buddies = NULL;
6254 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6256 while (parts) {
6257 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6258 purple_mime_part_get_length(parts->data));
6260 if (strcmp(xml->name, "list")) {
6261 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6263 buddies = g_slist_append(buddies, uri);
6265 xmlnode_free(xml);
6267 parts = parts->next;
6269 g_free(doc);
6270 if (mime) purple_mime_document_free(mime);
6272 payload->host = g_strdup(who);
6273 payload->buddies = buddies;
6274 sipe_schedule_action(action_name, timeout,
6275 sipe_subscribe_presence_batched_routed,
6276 sipe_subscribe_presence_batched_routed_free,
6277 sip, payload);
6278 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6280 } else {
6281 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6282 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6284 g_free(action_name);
6288 * Dispatcher for all incoming subscription information
6289 * whether it comes from NOTIFY, BENOTIFY requests or
6290 * piggy-backed to subscription's OK responce.
6292 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6293 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6295 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6297 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6298 gchar *event = sipmsg_find_header(msg, "Event");
6299 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6300 char *tmp;
6301 int timeout = 0;
6303 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6304 event ? event : "",
6305 tmp = fix_newlines(msg->body));
6306 g_free(tmp);
6307 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6309 /* implicit subscriptions */
6310 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6311 sipe_process_imdn(sip, msg);
6314 if (!request)
6316 const gchar *expires_header;
6317 expires_header = sipmsg_find_header(msg, "Expires");
6318 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6319 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6320 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout; // 2 min ahead of expiration
6323 /* for one off subscriptions (send with Expire: 0) */
6324 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6326 sipe_process_provisioning_v2(sip, msg);
6328 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6330 sipe_process_provisioning(sip, msg);
6333 if (!subscription_state || strstr(subscription_state, "active"))
6335 if (event && !g_ascii_strcasecmp(event, "presence"))
6337 sipe_process_presence(sip, msg);
6339 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6341 sipe_process_roaming_contacts(sip, msg);
6343 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6345 sipe_process_roaming_self(sip, msg);
6347 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6349 sipe_process_roaming_acl(sip, msg);
6351 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
6353 sipe_process_presence_wpending(sip, msg);
6355 else if (event && !g_ascii_strcasecmp(event, "conference"))
6357 sipe_process_conference(sip, msg);
6361 /* The server sends status 'terminated' */
6362 if (subscription_state && strstr(subscription_state, "terminated") ) {
6363 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6364 gchar *key = sipe_get_subscription_key(event, who);
6366 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6367 g_free(who);
6369 if (g_hash_table_lookup(sip->subscriptions, key)) {
6370 g_hash_table_remove(sip->subscriptions, key);
6371 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6374 g_free(key);
6377 if (timeout && event) {// For LSC 2005 and OCS 2007
6378 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
6379 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
6381 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
6382 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
6383 g_free(action_name);
6385 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
6386 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
6388 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
6389 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
6390 g_free(action_name);
6392 else*/
6393 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6394 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6396 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6397 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6398 g_free(action_name);
6400 else if (!g_ascii_strcasecmp(event, "presence") &&
6401 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6403 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6404 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6405 if (sip->batched_support) {
6406 sipe_process_presence_timeout(sip, msg, who, timeout);
6408 else {
6409 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6410 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6412 g_free(action_name);
6413 g_free(who);
6417 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
6419 sipe_process_registration_notify(sip, msg);
6422 /* The client responses on received a NOTIFY message */
6423 if (request && !benotify)
6425 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6430 * Whether user manually changed status or
6431 * it was changed automatically due to user
6432 * became inactive/active again
6434 static gboolean
6435 sipe_is_user_state(struct sipe_account_data *sip)
6437 gboolean res = (sip->was_idle == sip->is_idle);
6438 return res;
6441 static void
6442 send_presence_soap0(struct sipe_account_data *sip,
6443 gboolean do_publish_calendar,
6444 gboolean do_reset_status)
6446 struct sipe_ews* ews = sip->ews;
6447 int availability = 0;
6448 int activity = 0;
6449 gchar *body;
6450 gchar *tmp;
6451 gchar *res_note = NULL;
6452 gchar *res_oof = NULL;
6453 const gchar *note_pub = NULL;
6454 gchar *states = NULL;
6455 gchar *calendar_data = NULL;
6456 gchar *epid = get_epid(sip);
6457 time_t now = time(NULL);
6458 gchar *since_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&now)));
6459 const gchar *oof_note = sipe_ews_get_oof_note(ews);
6460 const char *user_input;
6461 gboolean pub_oof = oof_note && (!sip->note || sip->note_since < ews->oof_start);
6463 if (oof_note && sip->note) {
6464 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6465 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6468 if (!sip->initial_state_published ||
6469 do_reset_status)
6471 g_free(sip->status);
6472 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6475 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6477 /* Note */
6478 if (pub_oof) {
6479 note_pub = oof_note;
6480 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6481 } else if (sip->note) {
6482 if (sip->is_oof_note) { /* stale OOF note, as it's not present in ews already (oof_note == NULL) */
6483 g_free(sip->note);
6484 sip->note = NULL;
6485 sip->is_oof_note = FALSE;
6486 } else {
6487 note_pub = sip->note;
6491 if (note_pub)
6493 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, note_pub);
6496 /* User State */
6497 if (!do_reset_status) {
6498 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6500 gchar *activity_token = NULL;
6501 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6503 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6504 avail_2007,
6505 since_time_str,
6506 epid,
6507 activity_token);
6508 g_free(activity_token);
6510 else /* preserve existing publication */
6512 xmlnode *xn_states;
6513 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6514 states = xmlnode_to_str(xn_states, NULL);
6515 /* this is a hack-around to remove added newline after inner element,
6516 * state in this case, where it shouldn't be.
6517 * After several use of xmlnode_to_str, amount of added newlines
6518 * grows significantly.
6520 purple_str_strip_char(states, '\n');
6521 //purple_str_strip_char(states, '\r');
6524 } else {
6525 /* do nothing - then User state will be erased */
6527 sip->initial_state_published = TRUE;
6529 /* CalendarInfo */
6530 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6532 char *fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
6533 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6534 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6535 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6536 fb_start_str,
6537 free_busy_base64);
6538 g_free(fb_start_str);
6539 g_free(free_busy_base64);
6542 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6544 /* forming resulting XML */
6545 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6546 sip->username,
6547 availability,
6548 activity,
6549 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6550 res_note ? res_note : "",
6551 res_oof ? res_oof : "",
6552 states ? states : "",
6553 calendar_data ? calendar_data : "",
6554 epid,
6555 since_time_str,
6556 since_time_str,
6557 user_input);
6558 g_free(tmp);
6559 g_free(res_note);
6560 g_free(states);
6561 g_free(calendar_data);
6563 send_soap_request(sip, body);
6565 g_free(body);
6566 g_free(since_time_str);
6567 g_free(epid);
6570 void
6571 send_presence_soap(struct sipe_account_data *sip,
6572 gboolean do_publish_calendar)
6574 return send_presence_soap0(sip, do_publish_calendar, FALSE);
6578 static gboolean
6579 process_send_presence_category_publish_response(struct sipe_account_data *sip,
6580 struct sipmsg *msg,
6581 struct transaction *trans)
6583 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
6585 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
6586 xmlnode *xml;
6587 xmlnode *node;
6588 gchar *fault_code;
6589 GHashTable *faults;
6590 int index_our;
6591 gboolean has_device_publication = FALSE;
6593 xml = xmlnode_from_str(msg->body, msg->bodylen);
6595 /* test if version mismatch fault */
6596 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
6597 if (strcmp(fault_code, "Client.BadCall.WrongDelta")) {
6598 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
6599 g_free(fault_code);
6600 xmlnode_free(xml);
6601 return TRUE;
6603 g_free(fault_code);
6605 /* accumulating information about faulty versions */
6606 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
6607 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
6608 node;
6609 node = xmlnode_get_next_twin(node))
6611 const gchar *index = xmlnode_get_attrib(node, "index");
6612 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
6614 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
6615 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
6617 xmlnode_free(xml);
6619 /* here we are parsing own request to figure out what publication
6620 * referensed here only by index went wrong
6622 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
6624 /* publication */
6625 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
6626 index_our = 1; /* starts with 1 - our first publication */
6627 node;
6628 node = xmlnode_get_next_twin(node), index_our++)
6630 gchar *idx = g_strdup_printf("%d", index_our);
6631 const gchar *curVersion = g_hash_table_lookup(faults, idx);
6632 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
6633 g_free(idx);
6635 if (!strcmp("device", categoryName)) {
6636 has_device_publication = TRUE;
6639 if (curVersion) { /* fault exist on this index */
6640 const gchar *container = xmlnode_get_attrib(node, "container");
6641 const gchar *instance = xmlnode_get_attrib(node, "instance");
6642 /* key is <category><instance><container> */
6643 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
6644 struct sipe_publication *publication =
6645 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
6647 purple_debug_info("sipe", "key is %s\n", key);
6649 if (publication) {
6650 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6651 key, curVersion, publication->version);
6652 /* updating publication's version to the correct one */
6653 publication->version = atoi(curVersion);
6655 g_free(key);
6658 xmlnode_free(xml);
6659 g_hash_table_destroy(faults);
6661 /* rebublishing with right versions */
6662 if (has_device_publication) {
6663 send_publish_category_initial(sip);
6664 } else {
6665 send_presence_status(sip);
6668 return TRUE;
6672 * Returns 'device' XML part for publication.
6673 * Must be g_free'd after use.
6675 static gchar *
6676 sipe_publish_get_category_device(struct sipe_account_data *sip)
6678 gchar *uri;
6679 gchar *doc;
6680 gchar *epid = get_epid(sip);
6681 gchar *uuid = generateUUIDfromEPID(epid);
6682 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
6683 /* key is <category><instance><container> */
6684 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
6685 struct sipe_publication *publication =
6686 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
6688 g_free(key);
6689 g_free(epid);
6691 uri = sip_uri_self(sip);
6692 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
6693 device_instance,
6694 publication ? publication->version : 0,
6695 uuid,
6696 uri,
6697 "00:00:00+01:00", /* @TODO make timezone real*/
6698 sipe_get_host_name()
6701 g_free(uri);
6702 g_free(uuid);
6704 return doc;
6708 * A service method - use
6709 * - send_publish_get_category_state_machine and
6710 * - send_publish_get_category_state_user instead.
6711 * Must be g_free'd after use.
6713 static gchar *
6714 sipe_publish_get_category_state(struct sipe_account_data *sip,
6715 gboolean is_user_state)
6717 int availability = sipe_get_availability_by_status(sip->status, NULL);
6718 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
6719 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
6720 /* key is <category><instance><container> */
6721 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6722 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6723 struct sipe_publication *publication_2 =
6724 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6725 struct sipe_publication *publication_3 =
6726 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6728 g_free(key_2);
6729 g_free(key_3);
6731 if (publication_2 && (publication_2->availability == availability))
6733 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6734 return NULL; /* nothing to update */
6737 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
6738 instance,
6739 publication_2 ? publication_2->version : 0,
6740 availability,
6741 instance,
6742 publication_3 ? publication_3->version : 0,
6743 availability);
6747 * Only Busy and OOF calendar event are published.
6748 * Different instances are used for that.
6750 * Must be g_free'd after use.
6752 static gchar *
6753 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
6754 struct sipe_cal_event *event,
6755 const char *uri,
6756 int cal_satus)
6758 gchar *start_time_str;
6759 int availability = 0;
6760 gchar *res;
6761 guint instance = (cal_satus == SIPE_CAL_OOF) ?
6762 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
6763 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
6765 /* key is <category><instance><container> */
6766 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6767 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6768 struct sipe_publication *publication_2 =
6769 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6770 struct sipe_publication *publication_3 =
6771 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6773 g_free(key_2);
6774 g_free(key_3);
6776 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
6777 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6778 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
6779 return NULL;
6782 if (event &&
6783 publication_3 &&
6784 (publication_3->availability == availability) &&
6785 !strcmp(publication_3->cal_event_hash, sipe_cal_event_hash(event)))
6787 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6788 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
6789 return NULL; /* nothing to update */
6792 if (event &&
6793 (event->cal_status == SIPE_CAL_BUSY ||
6794 event->cal_status == SIPE_CAL_OOF))
6796 gchar *availability_xml_str = NULL;
6797 gchar *activity_xml_str = NULL;
6799 if (event->cal_status == SIPE_CAL_BUSY) {
6800 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
6803 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
6804 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6805 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
6806 "minAvailability=\"6500\"",
6807 "maxAvailability=\"8999\"");
6808 } else if (event->cal_status == SIPE_CAL_OOF) {
6809 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6810 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
6811 "minAvailability=\"12000\"",
6812 "");
6814 start_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&event->start_time)));
6816 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
6817 instance,
6818 publication_2 ? publication_2->version : 0,
6819 uri,
6820 start_time_str,
6821 availability_xml_str ? availability_xml_str : "",
6822 activity_xml_str ? activity_xml_str : "",
6823 event->subject ? event->subject : "",
6824 event->location ? event->location : "",
6826 instance,
6827 publication_3 ? publication_3->version : 0,
6828 uri,
6829 start_time_str,
6830 availability_xml_str ? availability_xml_str : "",
6831 activity_xml_str ? activity_xml_str : "",
6832 event->subject ? event->subject : "",
6833 event->location ? event->location : ""
6835 g_free(start_time_str);
6836 g_free(availability_xml_str);
6837 g_free(activity_xml_str);
6840 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
6842 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
6843 instance,
6844 publication_2 ? publication_2->version : 0,
6846 instance,
6847 publication_3 ? publication_3->version : 0
6851 return res;
6855 * Returns 'machineState' XML part for publication.
6856 * Must be g_free'd after use.
6858 static gchar *
6859 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
6861 return sipe_publish_get_category_state(sip, FALSE);
6865 * Returns 'userState' XML part for publication.
6866 * Must be g_free'd after use.
6868 static gchar *
6869 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
6871 return sipe_publish_get_category_state(sip, TRUE);
6875 * Compares two strings even in case both are NULL/empty
6877 static gboolean
6878 sipe_is_equal(const char* n1, const char* n2) {
6879 return ((!n1 || !strlen(n1)) && (!n2 || !strlen(n2))) /* both empty */
6880 || (n1 && n2 && !strcmp(n1, n2)); /* or not empty and equal */
6884 * Returns 'note' XML part for publication.
6885 * Must be g_free'd after use.
6887 * @param note_type either personal or OOF
6889 static gchar *
6890 sipe_publish_get_category_note(struct sipe_account_data *sip,
6891 const char *note,
6892 const char *note_type)
6894 guint instance = !strcmp("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
6895 /* key is <category><instance><container> */
6896 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
6897 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
6898 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
6900 struct sipe_publication *publication_note_200 =
6901 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
6902 struct sipe_publication *publication_note_300 =
6903 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
6904 struct sipe_publication *publication_note_400 =
6905 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
6907 const char *n1 = note;
6908 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
6910 g_free(key_note_200);
6911 g_free(key_note_300);
6912 g_free(key_note_400);
6914 if (sipe_is_equal(n1, n2))
6916 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
6917 return NULL; /* nothing to update */
6920 return g_markup_printf_escaped(SIPE_PUB_XML_NOTE,
6921 instance,
6922 publication_note_200 ? publication_note_200->version : 0,
6923 note_type,
6924 note ? note : "",
6926 instance,
6927 publication_note_300 ? publication_note_300->version : 0,
6928 note_type,
6929 note ? note : "",
6931 instance,
6932 publication_note_400 ? publication_note_400->version : 0,
6933 note_type,
6934 note ? note : "");
6938 * Returns 'calendarData' XML part with WorkingHours for publication.
6939 * Must be g_free'd after use.
6941 static gchar *
6942 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
6944 struct sipe_ews* ews = sip->ews;
6946 /* key is <category><instance><container> */
6947 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
6948 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
6949 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
6950 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
6951 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
6952 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
6954 struct sipe_publication *publication_cal_1 =
6955 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
6956 struct sipe_publication *publication_cal_100 =
6957 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
6958 struct sipe_publication *publication_cal_200 =
6959 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
6960 struct sipe_publication *publication_cal_300 =
6961 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
6962 struct sipe_publication *publication_cal_400 =
6963 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
6964 struct sipe_publication *publication_cal_32000 =
6965 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
6967 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
6968 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
6970 g_free(key_cal_1);
6971 g_free(key_cal_100);
6972 g_free(key_cal_200);
6973 g_free(key_cal_300);
6974 g_free(key_cal_400);
6975 g_free(key_cal_32000);
6977 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
6978 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
6979 return NULL;
6982 if (sipe_is_equal(n1, n2))
6984 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
6985 return NULL; /* nothing to update */
6988 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
6989 /* 1 */
6990 publication_cal_1 ? publication_cal_1->version : 0,
6991 ews->email,
6992 ews->working_hours_xml_str,
6993 /* 100 - Public */
6994 publication_cal_100 ? publication_cal_100->version : 0,
6995 /* 200 - Company */
6996 publication_cal_200 ? publication_cal_200->version : 0,
6997 ews->email,
6998 ews->working_hours_xml_str,
6999 /* 300 - Team */
7000 publication_cal_300 ? publication_cal_300->version : 0,
7001 ews->email,
7002 ews->working_hours_xml_str,
7003 /* 400 - Personal */
7004 publication_cal_400 ? publication_cal_400->version : 0,
7005 ews->email,
7006 ews->working_hours_xml_str,
7007 /* 32000 - Blocked */
7008 publication_cal_32000 ? publication_cal_32000->version : 0
7013 * Returns 'calendarData' XML part with FreeBusy for publication.
7014 * Must be g_free'd after use.
7016 static gchar *
7017 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7019 struct sipe_ews* ews = sip->ews;
7020 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7021 char *fb_start_str;
7022 char *free_busy_base64;
7023 const char *st;
7024 const char *fb;
7025 char *res;
7027 /* key is <category><instance><container> */
7028 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7029 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7030 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7031 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7032 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7033 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7035 struct sipe_publication *publication_cal_1 =
7036 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7037 struct sipe_publication *publication_cal_100 =
7038 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7039 struct sipe_publication *publication_cal_200 =
7040 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7041 struct sipe_publication *publication_cal_300 =
7042 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7043 struct sipe_publication *publication_cal_400 =
7044 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7045 struct sipe_publication *publication_cal_32000 =
7046 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7048 g_free(key_cal_1);
7049 g_free(key_cal_100);
7050 g_free(key_cal_200);
7051 g_free(key_cal_300);
7052 g_free(key_cal_400);
7053 g_free(key_cal_32000);
7055 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7056 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7057 return NULL;
7060 fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
7061 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7063 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7064 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7066 if (sipe_is_equal(st, fb_start_str) && sipe_is_equal(fb, free_busy_base64))
7068 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7069 g_free(fb_start_str);
7070 g_free(free_busy_base64);
7071 return NULL; /* nothing to update */
7074 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7075 /* 1 */
7076 cal_data_instance,
7077 publication_cal_1 ? publication_cal_1->version : 0,
7078 /* 100 - Public */
7079 cal_data_instance,
7080 publication_cal_100 ? publication_cal_100->version : 0,
7081 /* 200 - Company */
7082 cal_data_instance,
7083 publication_cal_200 ? publication_cal_200->version : 0,
7084 ews->email,
7085 fb_start_str,
7086 free_busy_base64,
7087 /* 300 - Team */
7088 cal_data_instance,
7089 publication_cal_300 ? publication_cal_300->version : 0,
7090 ews->email,
7091 fb_start_str,
7092 free_busy_base64,
7093 /* 400 - Personal */
7094 cal_data_instance,
7095 publication_cal_400 ? publication_cal_400->version : 0,
7096 ews->email,
7097 fb_start_str,
7098 free_busy_base64,
7099 /* 32000 - Blocked */
7100 cal_data_instance,
7101 publication_cal_32000 ? publication_cal_32000->version : 0
7104 g_free(fb_start_str);
7105 g_free(free_busy_base64);
7106 return res;
7109 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7111 gchar *uri;
7112 gchar *doc;
7113 gchar *tmp;
7114 gchar *hdr;
7116 uri = sip_uri_self(sip);
7117 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7118 uri,
7119 publications);
7121 tmp = get_contact(sip);
7122 hdr = g_strdup_printf("Contact: %s\r\n"
7123 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7125 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7127 g_free(tmp);
7128 g_free(hdr);
7129 g_free(uri);
7130 g_free(doc);
7133 static void
7134 send_publish_category_initial(struct sipe_account_data *sip)
7136 gchar *pub_device = sipe_publish_get_category_device(sip);
7137 gchar *pub_machine;
7138 gchar *publications;
7140 g_free(sip->status);
7141 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7143 pub_machine = sipe_publish_get_category_state_machine(sip);
7144 publications = g_strdup_printf("%s%s",
7145 pub_device,
7146 pub_machine ? pub_machine : "");
7147 g_free(pub_device);
7148 g_free(pub_machine);
7150 send_presence_publish(sip, publications);
7151 g_free(publications);
7154 static void
7155 send_presence_category_publish(struct sipe_account_data *sip,
7156 const char *note)
7158 gchar *pub_state = sipe_is_user_state(sip) ?
7159 sipe_publish_get_category_state_user(sip) :
7160 sipe_publish_get_category_state_machine(sip);
7161 gchar *pub_note = sipe_publish_get_category_note(sip, note, "personal");
7162 gchar *publications;
7164 if (!pub_state && !pub_note) {
7165 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7166 return;
7169 publications = g_strdup_printf("%s%s",
7170 pub_state ? pub_state : "",
7171 pub_note ? pub_note : "");
7173 purple_debug_info("sipe", "send_presence_category_publish: sip->status: %s sip->is_idle:%s sip->was_idle:%s\n",
7174 sip->status, sip->is_idle ? "Y" : "N", sip->was_idle ? "Y" : "N");
7176 g_free(pub_state);
7177 g_free(pub_note);
7179 send_presence_publish(sip, publications);
7180 g_free(publications);
7184 * Publishes self status
7185 * based on own calendar information.
7187 * For 2007+
7189 void
7190 publish_calendar_status_self(struct sipe_account_data *sip)
7192 struct sipe_cal_event* event = NULL;
7193 gchar *pub_cal_working_hours = NULL;
7194 gchar *pub_cal_free_busy = NULL;
7195 gchar *pub_calendar = NULL;
7196 gchar *pub_calendar2 = NULL;
7197 gchar *pub_oof_note = NULL;
7198 const gchar *oof_note;
7200 if (!sip->ews) {
7201 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7202 return;
7205 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7206 if (sip->ews->cal_events) {
7207 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7210 if (!event) {
7211 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7212 } else {
7213 char *desc = sipe_cal_event_describe(event);
7214 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7215 g_free(desc);
7218 /* Logic
7219 if OOF
7220 OOF publish, Busy clean
7221 ilse if Busy
7222 OOF clean, Busy publish
7223 else
7224 OOF clean, Busy clean
7226 if (event && event->cal_status == SIPE_CAL_OOF) {
7227 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7228 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7229 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7230 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7231 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7232 } else {
7233 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7234 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7237 if ((oof_note = sipe_ews_get_oof_note(sip->ews))) {
7238 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF");
7241 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7242 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7244 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7245 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7246 } else {
7247 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7248 pub_cal_working_hours ? pub_cal_working_hours : "",
7249 pub_cal_free_busy ? pub_cal_free_busy : "",
7250 pub_calendar ? pub_calendar : "",
7251 pub_calendar2 ? pub_calendar2 : "",
7252 pub_oof_note ? pub_oof_note : "");
7254 send_presence_publish(sip, publications);
7255 g_free(publications);
7258 g_free(pub_cal_working_hours);
7259 g_free(pub_cal_free_busy);
7260 g_free(pub_calendar);
7261 g_free(pub_calendar2);
7262 g_free(pub_oof_note);
7264 /* repeat scheduling */
7265 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7268 static void send_presence_status(struct sipe_account_data *sip)
7270 PurpleStatus * status = purple_account_get_active_status(sip->account);
7271 const gchar *note;
7272 if (!status) return;
7274 note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
7275 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7276 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7277 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7278 purple_debug_info("sipe", "send_presence_status: note: '%s'\n", note ? note : "");
7280 if (sip->ocs2007) {
7281 send_presence_category_publish(sip, note);
7282 } else {
7283 send_presence_soap(sip, FALSE);
7287 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7289 gboolean found = FALSE;
7290 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
7291 if (msg->response == 0) { /* request */
7292 if (!strcmp(msg->method, "MESSAGE")) {
7293 process_incoming_message(sip, msg);
7294 found = TRUE;
7295 } else if (!strcmp(msg->method, "NOTIFY")) {
7296 purple_debug_info("sipe","send->process_incoming_notify\n");
7297 process_incoming_notify(sip, msg, TRUE, FALSE);
7298 found = TRUE;
7299 } else if (!strcmp(msg->method, "BENOTIFY")) {
7300 purple_debug_info("sipe","send->process_incoming_benotify\n");
7301 process_incoming_notify(sip, msg, TRUE, TRUE);
7302 found = TRUE;
7303 } else if (!strcmp(msg->method, "INVITE")) {
7304 process_incoming_invite(sip, msg);
7305 found = TRUE;
7306 } else if (!strcmp(msg->method, "REFER")) {
7307 process_incoming_refer(sip, msg);
7308 found = TRUE;
7309 } else if (!strcmp(msg->method, "OPTIONS")) {
7310 process_incoming_options(sip, msg);
7311 found = TRUE;
7312 } else if (!strcmp(msg->method, "INFO")) {
7313 process_incoming_info(sip, msg);
7314 found = TRUE;
7315 } else if (!strcmp(msg->method, "ACK")) {
7316 // ACK's don't need any response
7317 found = TRUE;
7318 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
7319 // LCS 2005 sends us these - just respond 200 OK
7320 found = TRUE;
7321 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7322 } else if (!strcmp(msg->method, "BYE")) {
7323 process_incoming_bye(sip, msg);
7324 found = TRUE;
7325 } else {
7326 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7328 } else { /* response */
7329 struct transaction *trans = transactions_find(sip, msg);
7330 if (trans) {
7331 if (msg->response == 407) {
7332 gchar *resend, *auth, *ptmp;
7334 if (sip->proxy.retries > 30) return;
7335 sip->proxy.retries++;
7336 /* do proxy authentication */
7338 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7340 fill_auth(ptmp, &sip->proxy);
7341 auth = auth_header(sip, &sip->proxy, trans->msg);
7342 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7343 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7344 g_free(auth);
7345 resend = sipmsg_to_string(trans->msg);
7346 /* resend request */
7347 sendout_pkt(sip->gc, resend);
7348 g_free(resend);
7349 } else {
7350 if (msg->response < 200) {
7351 /* ignore provisional response */
7352 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7353 } else {
7354 sip->proxy.retries = 0;
7355 if (!strcmp(trans->msg->method, "REGISTER")) {
7356 if (msg->response == 401)
7358 sip->registrar.retries++;
7360 else
7362 sip->registrar.retries = 0;
7364 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7365 } else {
7366 if (msg->response == 401) {
7367 gchar *resend, *auth, *ptmp;
7369 if (sip->registrar.retries > 4) return;
7370 sip->registrar.retries++;
7372 #ifdef USE_KERBEROS
7373 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
7374 #endif
7375 ptmp = sipmsg_find_auth_header(msg, "NTLM");
7376 #ifdef USE_KERBEROS
7377 } else {
7378 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
7380 #endif
7382 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp);
7384 fill_auth(ptmp, &sip->registrar);
7385 auth = auth_header(sip, &sip->registrar, trans->msg);
7386 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7387 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7389 //sipmsg_remove_header_now(trans->msg, "Authorization");
7390 //sipmsg_add_header(trans->msg, "Authorization", auth);
7391 g_free(auth);
7392 resend = sipmsg_to_string(trans->msg);
7393 /* resend request */
7394 sendout_pkt(sip->gc, resend);
7395 g_free(resend);
7399 if (trans->callback) {
7400 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7401 /* call the callback to process response*/
7402 (trans->callback)(sip, msg, trans);
7405 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7406 transactions_remove(sip, trans);
7410 found = TRUE;
7411 } else {
7412 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7415 if (!found) {
7416 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
7420 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7422 char *cur;
7423 char *dummy;
7424 char *tmp;
7425 struct sipmsg *msg;
7426 int restlen;
7427 cur = conn->inbuf;
7429 /* according to the RFC remove CRLF at the beginning */
7430 while (*cur == '\r' || *cur == '\n') {
7431 cur++;
7433 if (cur != conn->inbuf) {
7434 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7435 conn->inbufused = strlen(conn->inbuf);
7438 /* Received a full Header? */
7439 sip->processing_input = TRUE;
7440 while (sip->processing_input &&
7441 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7442 time_t currtime = time(NULL);
7443 cur += 2;
7444 cur[0] = '\0';
7445 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7446 g_free(tmp);
7447 msg = sipmsg_parse_header(conn->inbuf);
7448 cur[0] = '\r';
7449 cur += 2;
7450 restlen = conn->inbufused - (cur - conn->inbuf);
7451 if (msg && restlen >= msg->bodylen) {
7452 dummy = g_malloc(msg->bodylen + 1);
7453 memcpy(dummy, cur, msg->bodylen);
7454 dummy[msg->bodylen] = '\0';
7455 msg->body = dummy;
7456 cur += msg->bodylen;
7457 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7458 conn->inbufused = strlen(conn->inbuf);
7459 } else {
7460 if (msg){
7461 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7462 sipmsg_free(msg);
7464 return;
7467 /*if (msg->body) {
7468 purple_debug_info("sipe", "body:\n%s", msg->body);
7471 // Verify the signature before processing it
7472 if (sip->registrar.gssapi_context) {
7473 struct sipmsg_breakdown msgbd;
7474 gchar *signature_input_str;
7475 gchar *rspauth;
7476 msgbd.msg = msg;
7477 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7478 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
7480 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7482 if (rspauth != NULL) {
7483 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7484 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7485 process_input_message(sip, msg);
7486 } else {
7487 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7488 purple_connection_error(sip->gc, _("Invalid message signature received"));
7489 sip->gc->wants_to_die = TRUE;
7491 } else if (msg->response == 401) {
7492 purple_connection_error(sip->gc, _("Wrong password"));
7493 sip->gc->wants_to_die = TRUE;
7495 g_free(signature_input_str);
7497 g_free(rspauth);
7498 sipmsg_breakdown_free(&msgbd);
7499 } else {
7500 process_input_message(sip, msg);
7503 sipmsg_free(msg);
7507 static void sipe_udp_process(gpointer data, gint source,
7508 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
7510 PurpleConnection *gc = data;
7511 struct sipe_account_data *sip = gc->proto_data;
7512 int len;
7514 static char buffer[65536];
7515 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
7516 time_t currtime = time(NULL);
7517 struct sipmsg *msg;
7518 buffer[len] = '\0';
7519 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
7520 msg = sipmsg_parse_msg(buffer);
7521 if (msg) process_input_message(sip, msg);
7525 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
7527 struct sipe_account_data *sip = gc->proto_data;
7528 PurpleSslConnection *gsc = sip->gsc;
7530 purple_debug_error("sipe", "%s",debug);
7531 purple_connection_error(gc, msg);
7533 /* Invalidate this connection. Next send will open a new one */
7534 if (gsc) {
7535 connection_remove(sip, gsc->fd);
7536 purple_ssl_close(gsc);
7538 sip->gsc = NULL;
7539 sip->fd = -1;
7542 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7543 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7545 PurpleConnection *gc = data;
7546 struct sipe_account_data *sip;
7547 struct sip_connection *conn;
7548 int readlen, len;
7549 gboolean firstread = TRUE;
7551 /* NOTE: This check *IS* necessary */
7552 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
7553 purple_ssl_close(gsc);
7554 return;
7557 sip = gc->proto_data;
7558 conn = connection_find(sip, gsc->fd);
7559 if (conn == NULL) {
7560 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7561 gc->wants_to_die = TRUE;
7562 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
7563 return;
7566 /* Read all available data from the SSL connection */
7567 do {
7568 /* Increase input buffer size as needed */
7569 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7570 conn->inbuflen += SIMPLE_BUF_INC;
7571 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7572 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
7575 /* Try to read as much as there is space left in the buffer */
7576 readlen = conn->inbuflen - conn->inbufused - 1;
7577 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
7579 if (len < 0 && errno == EAGAIN) {
7580 /* Try again later */
7581 return;
7582 } else if (len < 0) {
7583 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
7584 return;
7585 } else if (firstread && (len == 0)) {
7586 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
7587 return;
7590 conn->inbufused += len;
7591 firstread = FALSE;
7593 /* Equivalence indicates that there is possibly more data to read */
7594 } while (len == readlen);
7596 conn->inbuf[conn->inbufused] = '\0';
7597 process_input(sip, conn);
7601 static void sipe_input_cb(gpointer data, gint source,
7602 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7604 PurpleConnection *gc = data;
7605 struct sipe_account_data *sip = gc->proto_data;
7606 int len;
7607 struct sip_connection *conn = connection_find(sip, source);
7608 if (!conn) {
7609 purple_debug_error("sipe", "Connection not found!\n");
7610 return;
7613 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7614 conn->inbuflen += SIMPLE_BUF_INC;
7615 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7618 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
7620 if (len < 0 && errno == EAGAIN)
7621 return;
7622 else if (len <= 0) {
7623 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7624 connection_remove(sip, source);
7625 if (sip->fd == source) sip->fd = -1;
7626 return;
7629 conn->inbufused += len;
7630 conn->inbuf[conn->inbufused] = '\0';
7632 process_input(sip, conn);
7635 /* Callback for new connections on incoming TCP port */
7636 static void sipe_newconn_cb(gpointer data, gint source,
7637 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7639 PurpleConnection *gc = data;
7640 struct sipe_account_data *sip = gc->proto_data;
7641 struct sip_connection *conn;
7643 int newfd = accept(source, NULL, NULL);
7645 conn = connection_create(sip, newfd);
7647 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7650 static void login_cb(gpointer data, gint source,
7651 SIPE_UNUSED_PARAMETER const gchar *error_message)
7653 PurpleConnection *gc = data;
7654 struct sipe_account_data *sip;
7655 struct sip_connection *conn;
7657 if (!PURPLE_CONNECTION_IS_VALID(gc))
7659 if (source >= 0)
7660 close(source);
7661 return;
7664 if (source < 0) {
7665 purple_connection_error(gc, _("Could not connect"));
7666 return;
7669 sip = gc->proto_data;
7670 sip->fd = source;
7671 sip->last_keepalive = time(NULL);
7673 conn = connection_create(sip, source);
7675 do_register(sip);
7677 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7680 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7681 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7683 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
7684 if (sip == NULL) return;
7686 do_register(sip);
7689 static guint sipe_ht_hash_nick(const char *nick)
7691 char *lc = g_utf8_strdown(nick, -1);
7692 guint bucket = g_str_hash(lc);
7693 g_free(lc);
7695 return bucket;
7698 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
7700 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
7703 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
7705 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7707 sip->listen_data = NULL;
7709 if (listenfd == -1) {
7710 purple_connection_error(sip->gc, _("Could not create listen socket"));
7711 return;
7714 sip->fd = listenfd;
7716 sip->listenport = purple_network_get_port_from_fd(sip->fd);
7717 sip->listenfd = sip->fd;
7719 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
7721 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
7722 do_register(sip);
7725 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
7726 SIPE_UNUSED_PARAMETER const char *error_message)
7728 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7730 sip->query_data = NULL;
7732 if (!hosts || !hosts->data) {
7733 purple_connection_error(sip->gc, _("Could not resolve hostname"));
7734 return;
7737 hosts = g_slist_remove(hosts, hosts->data);
7738 g_free(sip->serveraddr);
7739 sip->serveraddr = hosts->data;
7740 hosts = g_slist_remove(hosts, hosts->data);
7741 while (hosts) {
7742 hosts = g_slist_remove(hosts, hosts->data);
7743 g_free(hosts->data);
7744 hosts = g_slist_remove(hosts, hosts->data);
7747 /* create socket for incoming connections */
7748 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
7749 sipe_udp_host_resolved_listen_cb, sip);
7750 if (sip->listen_data == NULL) {
7751 purple_connection_error(sip->gc, _("Could not create listen socket"));
7752 return;
7756 static const struct sipe_service_data *current_service = NULL;
7758 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
7759 PurpleSslErrorType error,
7760 gpointer data)
7762 PurpleConnection *gc = data;
7763 struct sipe_account_data *sip;
7765 /* If the connection is already disconnected, we don't need to do anything else */
7766 if (!PURPLE_CONNECTION_IS_VALID(gc))
7767 return;
7769 sip = gc->proto_data;
7770 current_service = sip->service_data;
7771 if (current_service) {
7772 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
7773 current_service->transport ? current_service->transport : "NULL",
7774 current_service->service ? current_service->service : "NULL");
7777 sip->fd = -1;
7778 sip->gsc = NULL;
7780 switch(error) {
7781 case PURPLE_SSL_CONNECT_FAILED:
7782 purple_connection_error(gc, _("Connection failed"));
7783 break;
7784 case PURPLE_SSL_HANDSHAKE_FAILED:
7785 purple_connection_error(gc, _("SSL handshake failed"));
7786 break;
7787 case PURPLE_SSL_CERTIFICATE_INVALID:
7788 purple_connection_error(gc, _("SSL certificate invalid"));
7789 break;
7793 static void
7794 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
7796 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7797 PurpleProxyConnectData *connect_data;
7799 sip->listen_data = NULL;
7801 sip->listenfd = listenfd;
7802 if (sip->listenfd == -1) {
7803 purple_connection_error(sip->gc, _("Could not create listen socket"));
7804 return;
7807 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
7808 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7809 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7810 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
7811 sipe_newconn_cb, sip->gc);
7812 purple_debug_info("sipe", "connecting to %s port %d\n",
7813 sip->realhostname, sip->realport);
7814 /* open tcp connection to the server */
7815 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
7816 sip->realport, login_cb, sip->gc);
7818 if (connect_data == NULL) {
7819 purple_connection_error(sip->gc, _("Could not create socket"));
7823 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
7825 PurpleAccount *account = sip->account;
7826 PurpleConnection *gc = sip->gc;
7828 if (port == 0) {
7829 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
7832 sip->realhostname = hostname;
7833 sip->realport = port;
7835 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
7836 hostname, port);
7838 /* TODO: is there a good default grow size? */
7839 if (sip->transport != SIPE_TRANSPORT_UDP)
7840 sip->txbuf = purple_circ_buffer_new(0);
7842 if (sip->transport == SIPE_TRANSPORT_TLS) {
7843 /* SSL case */
7844 if (!purple_ssl_is_supported()) {
7845 gc->wants_to_die = TRUE;
7846 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
7847 return;
7850 purple_debug_info("sipe", "using SSL\n");
7852 sip->gsc = purple_ssl_connect(account, hostname, port,
7853 login_cb_ssl, sipe_ssl_connect_failure, gc);
7854 if (sip->gsc == NULL) {
7855 purple_connection_error(gc, _("Could not create SSL context"));
7856 return;
7858 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
7859 /* UDP case */
7860 purple_debug_info("sipe", "using UDP\n");
7862 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
7863 if (sip->query_data == NULL) {
7864 purple_connection_error(gc, _("Could not resolve hostname"));
7866 } else {
7867 /* TCP case */
7868 purple_debug_info("sipe", "using TCP\n");
7869 /* create socket for incoming connections */
7870 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
7871 sipe_tcp_connect_listen_cb, sip);
7872 if (sip->listen_data == NULL) {
7873 purple_connection_error(gc, _("Could not create listen socket"));
7874 return;
7879 /* Service list for autodection */
7880 static const struct sipe_service_data service_autodetect[] = {
7881 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
7882 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
7883 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
7884 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
7885 { NULL, NULL, 0 }
7888 /* Service list for SSL/TLS */
7889 static const struct sipe_service_data service_tls[] = {
7890 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
7891 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
7892 { NULL, NULL, 0 }
7895 /* Service list for TCP */
7896 static const struct sipe_service_data service_tcp[] = {
7897 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
7898 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
7899 { NULL, NULL, 0 }
7902 /* Service list for UDP */
7903 static const struct sipe_service_data service_udp[] = {
7904 { "sip", "udp", SIPE_TRANSPORT_UDP },
7905 { NULL, NULL, 0 }
7908 static void srvresolved(PurpleSrvResponse *, int, gpointer);
7909 static void resolve_next_service(struct sipe_account_data *sip,
7910 const struct sipe_service_data *start)
7912 if (start) {
7913 sip->service_data = start;
7914 } else {
7915 sip->service_data++;
7916 if (sip->service_data->service == NULL) {
7917 gchar *hostname;
7918 /* Try connecting to the SIP hostname directly */
7919 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
7920 if (sip->auto_transport) {
7921 // If SSL is supported, default to using it; OCS servers aren't configured
7922 // by default to accept TCP
7923 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
7924 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
7925 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
7928 hostname = g_strdup(sip->sipdomain);
7929 create_connection(sip, hostname, 0);
7930 return;
7934 /* Try to resolve next service */
7935 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
7936 sip->service_data->transport,
7937 sip->sipdomain,
7938 srvresolved, sip);
7941 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
7943 struct sipe_account_data *sip = data;
7945 sip->srv_query_data = NULL;
7947 /* find the host to connect to */
7948 if (results) {
7949 gchar *hostname = g_strdup(resp->hostname);
7950 int port = resp->port;
7951 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
7952 hostname, port);
7953 g_free(resp);
7955 sip->transport = sip->service_data->type;
7957 create_connection(sip, hostname, port);
7958 } else {
7959 resolve_next_service(sip, NULL);
7963 static void sipe_login(PurpleAccount *account)
7965 PurpleConnection *gc;
7966 struct sipe_account_data *sip;
7967 gchar **signinname_login, **userserver;
7968 const char *transport;
7969 const char *email;
7971 const char *username = purple_account_get_username(account);
7972 gc = purple_account_get_connection(account);
7974 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
7976 if (strpbrk(username, "\t\v\r\n") != NULL) {
7977 gc->wants_to_die = TRUE;
7978 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
7979 return;
7982 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
7983 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
7984 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
7985 sip->gc = gc;
7986 sip->account = account;
7987 sip->reregister_set = FALSE;
7988 sip->reauthenticate_set = FALSE;
7989 sip->subscribed = FALSE;
7990 sip->subscribed_buddies = FALSE;
7991 sip->initial_state_published = FALSE;
7993 /* username format: <username>,[<optional login>] */
7994 signinname_login = g_strsplit(username, ",", 2);
7995 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
7997 /* ensure that username format is name@domain */
7998 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
7999 g_strfreev(signinname_login);
8000 gc->wants_to_die = TRUE;
8001 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8002 return;
8004 sip->username = g_strdup(signinname_login[0]);
8006 /* ensure that email format is name@domain if provided */
8007 email = purple_account_get_string(sip->account, "email", NULL);
8008 if (!is_empty(email) &&
8009 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8011 gc->wants_to_die = TRUE;
8012 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8013 return;
8015 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8017 /* login name specified? */
8018 if (signinname_login[1] && strlen(signinname_login[1])) {
8019 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8020 gboolean has_domain = domain_user[1] != NULL;
8021 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8022 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8023 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8024 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8025 sip->authdomain ? sip->authdomain : "", sip->authuser);
8026 g_strfreev(domain_user);
8029 userserver = g_strsplit(signinname_login[0], "@", 2);
8030 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8031 purple_connection_set_display_name(gc, userserver[0]);
8032 sip->sipdomain = g_strdup(userserver[1]);
8033 g_strfreev(userserver);
8034 g_strfreev(signinname_login);
8036 if (strchr(sip->username, ' ') != NULL) {
8037 gc->wants_to_die = TRUE;
8038 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8039 return;
8042 sip->password = g_strdup(purple_connection_get_password(gc));
8044 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8045 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8046 g_free, (GDestroyNotify)g_hash_table_destroy);
8047 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8048 g_free, (GDestroyNotify)sipe_subscription_free);
8050 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8052 g_free(sip->status);
8053 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8055 sip->auto_transport = FALSE;
8056 transport = purple_account_get_string(account, "transport", "auto");
8057 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8058 if (userserver[0]) {
8059 /* Use user specified server[:port] */
8060 int port = 0;
8062 if (userserver[1])
8063 port = atoi(userserver[1]);
8065 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8066 userserver[0], port);
8068 if (strcmp(transport, "auto") == 0) {
8069 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8070 } else if (strcmp(transport, "tls") == 0) {
8071 sip->transport = SIPE_TRANSPORT_TLS;
8072 } else if (strcmp(transport, "tcp") == 0) {
8073 sip->transport = SIPE_TRANSPORT_TCP;
8074 } else {
8075 sip->transport = SIPE_TRANSPORT_UDP;
8078 create_connection(sip, g_strdup(userserver[0]), port);
8079 } else {
8080 /* Server auto-discovery */
8081 if (strcmp(transport, "auto") == 0) {
8082 sip->auto_transport = TRUE;
8083 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8084 current_service++;
8085 resolve_next_service(sip, current_service);
8086 } else {
8087 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8089 } else if (strcmp(transport, "tls") == 0) {
8090 resolve_next_service(sip, service_tls);
8091 } else if (strcmp(transport, "tcp") == 0) {
8092 resolve_next_service(sip, service_tcp);
8093 } else {
8094 resolve_next_service(sip, service_udp);
8097 g_strfreev(userserver);
8100 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8102 connection_free_all(sip);
8104 g_free(sip->epid);
8105 sip->epid = NULL;
8107 if (sip->query_data != NULL)
8108 purple_dnsquery_destroy(sip->query_data);
8109 sip->query_data = NULL;
8111 if (sip->srv_query_data != NULL)
8112 purple_srv_cancel(sip->srv_query_data);
8113 sip->srv_query_data = NULL;
8115 if (sip->listen_data != NULL)
8116 purple_network_listen_cancel(sip->listen_data);
8117 sip->listen_data = NULL;
8119 if (sip->gsc != NULL)
8120 purple_ssl_close(sip->gsc);
8121 sip->gsc = NULL;
8123 sipe_auth_free(&sip->registrar);
8124 sipe_auth_free(&sip->proxy);
8126 if (sip->txbuf)
8127 purple_circ_buffer_destroy(sip->txbuf);
8128 sip->txbuf = NULL;
8130 g_free(sip->realhostname);
8131 sip->realhostname = NULL;
8133 g_free(sip->server_version);
8134 sip->server_version = NULL;
8136 if (sip->listenpa)
8137 purple_input_remove(sip->listenpa);
8138 sip->listenpa = 0;
8139 if (sip->tx_handler)
8140 purple_input_remove(sip->tx_handler);
8141 sip->tx_handler = 0;
8142 if (sip->resendtimeout)
8143 purple_timeout_remove(sip->resendtimeout);
8144 sip->resendtimeout = 0;
8145 if (sip->timeouts) {
8146 GSList *entry = sip->timeouts;
8147 while (entry) {
8148 struct scheduled_action *sched_action = entry->data;
8149 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8150 purple_timeout_remove(sched_action->timeout_handler);
8151 if (sched_action->destroy) {
8152 (*sched_action->destroy)(sched_action->payload);
8154 g_free(sched_action->name);
8155 g_free(sched_action);
8156 entry = entry->next;
8159 g_slist_free(sip->timeouts);
8161 if (sip->allow_events) {
8162 GSList *entry = sip->allow_events;
8163 while (entry) {
8164 g_free(entry->data);
8165 entry = entry->next;
8168 g_slist_free(sip->allow_events);
8170 if (sip->containers) {
8171 GSList *entry = sip->containers;
8172 while (entry) {
8173 free_container((struct sipe_container *)entry->data);
8174 entry = entry->next;
8177 g_slist_free(sip->containers);
8179 if (sip->contact)
8180 g_free(sip->contact);
8181 sip->contact = NULL;
8182 if (sip->regcallid)
8183 g_free(sip->regcallid);
8184 sip->regcallid = NULL;
8186 if (sip->serveraddr)
8187 g_free(sip->serveraddr);
8188 sip->serveraddr = NULL;
8190 if (sip->focus_factory_uri)
8191 g_free(sip->focus_factory_uri);
8192 sip->focus_factory_uri = NULL;
8194 sip->fd = -1;
8195 sip->processing_input = FALSE;
8197 if (sip->ews) {
8198 sipe_ews_free(sip->ews);
8200 sip->ews = NULL;
8204 * A callback for g_hash_table_foreach_remove
8206 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8207 SIPE_UNUSED_PARAMETER gpointer user_data)
8209 sipe_free_buddy((struct sipe_buddy *) buddy);
8211 /* We must return TRUE as the key/value have already been deleted */
8212 return(TRUE);
8215 static void sipe_close(PurpleConnection *gc)
8217 struct sipe_account_data *sip = gc->proto_data;
8219 if (sip) {
8220 /* leave all conversations */
8221 sipe_session_close_all(sip);
8222 sipe_session_remove_all(sip);
8224 if (sip->csta) {
8225 sip_csta_close(sip);
8228 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8229 /* unsubscribe all */
8230 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8232 /* unregister */
8233 do_register_exp(sip, 0);
8236 sipe_connection_cleanup(sip);
8237 g_free(sip->sipdomain);
8238 g_free(sip->username);
8239 g_free(sip->email);
8240 g_free(sip->password);
8241 g_free(sip->authdomain);
8242 g_free(sip->authuser);
8243 g_free(sip->status);
8244 g_free(sip->note);
8246 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8247 g_hash_table_destroy(sip->buddies);
8248 g_hash_table_destroy(sip->our_publications);
8249 g_hash_table_destroy(sip->user_state_publications);
8250 g_hash_table_destroy(sip->subscriptions);
8252 if (sip->groups) {
8253 GSList *entry = sip->groups;
8254 while (entry) {
8255 struct sipe_group *group = entry->data;
8256 g_free(group->name);
8257 g_free(group);
8258 entry = entry->next;
8261 g_slist_free(sip->groups);
8263 if (sip->our_publication_keys) {
8264 GSList *entry = sip->our_publication_keys;
8265 while (entry) {
8266 g_free(entry->data);
8267 entry = entry->next;
8270 g_slist_free(sip->our_publication_keys);
8272 while (sip->transactions)
8273 transactions_remove(sip, sip->transactions->data);
8275 g_free(gc->proto_data);
8276 gc->proto_data = NULL;
8279 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8280 SIPE_UNUSED_PARAMETER void *user_data)
8282 PurpleAccount *acct = purple_connection_get_account(gc);
8283 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8284 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8285 if (conv == NULL)
8286 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8287 purple_conversation_present(conv);
8288 g_free(id);
8291 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8292 SIPE_UNUSED_PARAMETER void *user_data)
8295 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8296 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8299 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8300 SIPE_UNUSED_PARAMETER struct transaction *trans)
8302 PurpleNotifySearchResults *results;
8303 PurpleNotifySearchColumn *column;
8304 xmlnode *searchResults;
8305 xmlnode *mrow;
8306 int match_count = 0;
8307 gboolean more = FALSE;
8308 gchar *secondary;
8310 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8312 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8313 if (!searchResults) {
8314 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8315 return FALSE;
8318 results = purple_notify_searchresults_new();
8320 if (results == NULL) {
8321 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8322 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8324 xmlnode_free(searchResults);
8325 return FALSE;
8328 column = purple_notify_searchresults_column_new(_("User name"));
8329 purple_notify_searchresults_column_add(results, column);
8331 column = purple_notify_searchresults_column_new(_("Name"));
8332 purple_notify_searchresults_column_add(results, column);
8334 column = purple_notify_searchresults_column_new(_("Company"));
8335 purple_notify_searchresults_column_add(results, column);
8337 column = purple_notify_searchresults_column_new(_("Country"));
8338 purple_notify_searchresults_column_add(results, column);
8340 column = purple_notify_searchresults_column_new(_("Email"));
8341 purple_notify_searchresults_column_add(results, column);
8343 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8344 GList *row = NULL;
8346 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8347 row = g_list_append(row, g_strdup(uri_parts[1]));
8348 g_strfreev(uri_parts);
8350 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8351 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8352 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8353 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8355 purple_notify_searchresults_row_add(results, row);
8356 match_count++;
8359 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8360 char *data = xmlnode_get_data_unescaped(mrow);
8361 more = (g_strcasecmp(data, "true") == 0);
8362 g_free(data);
8365 secondary = g_strdup_printf(
8366 dngettext(GETTEXT_PACKAGE,
8367 "Found %d contact%s:",
8368 "Found %d contacts%s:", match_count),
8369 match_count, more ? _(" (more matched your query)") : "");
8371 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8372 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8373 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8375 g_free(secondary);
8376 xmlnode_free(searchResults);
8377 return TRUE;
8380 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8382 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8383 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8384 unsigned i = 0;
8386 do {
8387 PurpleRequestField *field = entries->data;
8388 const char *id = purple_request_field_get_id(field);
8389 const char *value = purple_request_field_string_get_value(field);
8391 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8393 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8394 } while ((entries = g_list_next(entries)) != NULL);
8395 attrs[i] = NULL;
8397 if (i > 0) {
8398 struct sipe_account_data *sip = gc->proto_data;
8399 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8400 gchar *query = g_strjoinv(NULL, attrs);
8401 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8402 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8403 send_soap_request_with_cb(sip, domain_uri, body,
8404 (TransCallback) process_search_contact_response, NULL);
8405 g_free(domain_uri);
8406 g_free(body);
8407 g_free(query);
8410 g_strfreev(attrs);
8413 static void sipe_show_find_contact(PurplePluginAction *action)
8415 PurpleConnection *gc = (PurpleConnection *) action->context;
8416 PurpleRequestFields *fields;
8417 PurpleRequestFieldGroup *group;
8418 PurpleRequestField *field;
8420 fields = purple_request_fields_new();
8421 group = purple_request_field_group_new(NULL);
8422 purple_request_fields_add_group(fields, group);
8424 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8425 purple_request_field_group_add_field(group, field);
8426 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8427 purple_request_field_group_add_field(group, field);
8428 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8429 purple_request_field_group_add_field(group, field);
8430 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8431 purple_request_field_group_add_field(group, field);
8433 purple_request_fields(gc,
8434 _("Search"),
8435 _("Search for a contact"),
8436 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8437 fields,
8438 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8439 _("_Cancel"), NULL,
8440 purple_connection_get_account(gc), NULL, NULL, gc);
8443 static void sipe_show_about_plugin(PurplePluginAction *action)
8445 PurpleConnection *gc = (PurpleConnection *) action->context;
8446 const char *txt =
8447 "<b><font size=\"+1\">Sipe " SIPE_VERSION "</font></b><br/>"
8448 "<br/>"
8449 "A third-party plugin implementing extended version of SIP/SIMPLE used by various products:<br/>"
8450 "<li> - MS Office Communications Server 2007 (R2)</li><br/>"
8451 "<li> - MS Live Communications Server 2005/2003</li><br/>"
8452 "<li> - Reuters Messaging</li><br/>"
8453 "<br/>"
8454 "Home: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8455 "Support: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">Help Forum</a><br/>"
8456 "License: GPLv2<br/>"
8457 "<br/>"
8458 "We support users in the following organizations to mention a few:<br/>"
8459 " - CERN<br/>"
8460 " - Reuters Messaging network<br/>"
8461 " - Deutsche Bank<br/>"
8462 " - Merrill Lynch<br/>"
8463 " - Wachovia<br/>"
8464 " - Siemens<br/>"
8465 " - Alcatel-Lucent<br/>"
8466 " - BT<br/>"
8467 " - Nokia<br/>"
8468 " - HP<br/>"
8469 "<br/>"
8470 "<b>Authors:</b><br/>"
8471 " - Anibal Avelar<br/>"
8472 " - Gabriel Burt<br/>"
8473 " - Stefan Becker<br/>"
8474 " - pier11<br/>";
8476 purple_notify_formatted(gc, NULL, " ", NULL, txt, NULL, NULL);
8479 static void sipe_republish_calendar(PurplePluginAction *action)
8481 PurpleConnection *gc = (PurpleConnection *) action->context;
8482 struct sipe_account_data *sip = gc->proto_data;
8484 sipe_update_calendar(sip);
8487 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
8488 gpointer value,
8489 GString* str)
8491 struct sipe_publication *publication = value;
8493 g_string_append_printf( str,
8494 SIPE_PUB_XML_PUBLICATION_CLEAR,
8495 publication->category,
8496 publication->instance,
8497 publication->container,
8498 publication->version,
8499 "static");
8502 static void sipe_reset_status(PurplePluginAction *action)
8504 PurpleConnection *gc = (PurpleConnection *) action->context;
8505 struct sipe_account_data *sip = gc->proto_data;
8507 if (sip->ocs2007) /* 2007+ */
8509 GString* str = g_string_new(NULL);
8510 gchar *publications;
8512 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
8513 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8514 return;
8517 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
8518 publications = g_string_free(str, FALSE);
8520 send_presence_publish(sip, publications);
8521 g_free(publications);
8523 else /* 2005 */
8525 send_presence_soap0(sip, FALSE, TRUE);
8529 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
8530 gpointer context)
8532 PurpleConnection *gc = (PurpleConnection *)context;
8533 struct sipe_account_data *sip = gc->proto_data;
8534 GList *menu = NULL;
8535 PurplePluginAction *act;
8536 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
8538 act = purple_plugin_action_new(_("About SIPE plugin"), sipe_show_about_plugin);
8539 menu = g_list_prepend(menu, act);
8541 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
8542 menu = g_list_prepend(menu, act);
8544 if (!strcmp(calendar, "EXCH")) {
8545 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
8546 menu = g_list_prepend(menu, act);
8549 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
8550 menu = g_list_prepend(menu, act);
8552 menu = g_list_reverse(menu);
8554 return menu;
8557 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
8561 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8563 return TRUE;
8567 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8569 return TRUE;
8573 static char *sipe_status_text(PurpleBuddy *buddy)
8575 struct sipe_account_data *sip;
8576 struct sipe_buddy *sbuddy;
8577 char *text = NULL;
8579 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
8580 if (sip) //happens on pidgin exit
8582 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8583 if (sbuddy) {
8584 if (!is_empty(sbuddy->activity) && !is_empty(sbuddy->annotation))
8586 text = g_strdup_printf("%s - %s", sbuddy->activity, sbuddy->annotation);
8588 else if (!is_empty(sbuddy->activity))
8590 text = g_strdup(sbuddy->activity);
8592 else
8594 text = g_strdup(sbuddy->annotation);
8599 return text;
8602 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
8604 const PurplePresence *presence = purple_buddy_get_presence(buddy);
8605 const PurpleStatus *status = purple_presence_get_active_status(presence);
8606 struct sipe_account_data *sip;
8607 struct sipe_buddy *sbuddy;
8608 char *annotation = NULL;
8609 gboolean is_oof_note = FALSE;
8610 char *activity = NULL;
8611 char *calendar = NULL;
8612 char *meeting_subject = NULL;
8613 char *meeting_location = NULL;
8615 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
8616 if (sip) //happens on pidgin exit
8618 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
8619 if (sbuddy)
8621 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
8622 is_oof_note = sbuddy->is_oof_note;
8623 activity = sbuddy->activity;
8624 calendar = sipe_cal_get_description(sbuddy);
8625 meeting_subject = sbuddy->meeting_subject;
8626 meeting_location = sbuddy->meeting_location;
8630 //Layout
8631 if (purple_presence_is_online(presence))
8633 const char *status_str = activity && status && strcmp(purple_status_get_id(status), SIPE_STATUS_ID_ON_PHONE) ?
8634 activity :
8635 purple_status_get_name(status);
8637 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
8639 if (purple_presence_is_online(presence) &&
8640 !is_empty(calendar))
8642 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
8644 g_free(calendar);
8645 if (!is_empty(meeting_location))
8647 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
8649 if (!is_empty(meeting_subject))
8651 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
8654 if (annotation)
8656 /* Tooltip does not know how to handle markup like <br> */
8657 gchar *s = annotation;
8658 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, annotation);
8659 while ((s = strchr(s, '<')) != NULL) {
8660 if (!g_ascii_strncasecmp(s, "<br>", 4)) {
8661 *s = '\n';
8662 strcpy(s + 1, s + 4);
8664 s++;
8666 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, annotation);
8668 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), annotation);
8669 g_free(annotation);
8674 #if PURPLE_VERSION_CHECK(2,5,0)
8675 static GHashTable *
8676 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
8678 GHashTable *table;
8679 table = g_hash_table_new(g_str_hash, g_str_equal);
8680 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
8681 return table;
8683 #endif
8685 static PurpleBuddy *
8686 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
8688 PurpleBuddy *clone;
8689 const gchar *server_alias, *email;
8690 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
8692 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
8694 purple_blist_add_buddy(clone, NULL, group, NULL);
8696 server_alias = purple_buddy_get_server_alias(buddy);
8697 if (server_alias) {
8698 purple_blist_server_alias_buddy(clone, server_alias);
8701 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8702 if (email) {
8703 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
8706 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
8707 //for UI to update;
8708 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
8709 return clone;
8712 static void
8713 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
8715 PurpleBuddy *buddy, *b;
8716 PurpleConnection *gc;
8717 PurpleGroup * group = purple_find_group(group_name);
8719 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
8721 buddy = (PurpleBuddy *)node;
8723 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
8724 gc = purple_account_get_connection(buddy->account);
8726 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
8727 if (!b){
8728 purple_blist_add_buddy_clone(group, buddy);
8731 sipe_group_buddy(gc, buddy->name, NULL, group_name);
8734 static void
8735 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
8737 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8739 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
8741 /* 2007+ conference */
8742 if (sip->ocs2007)
8744 sipe_conf_add(sip, buddy->name);
8746 else /* 2005- multiparty chat */
8748 gchar *self = sip_uri_self(sip);
8749 struct sip_session *session;
8751 session = sipe_session_add_chat(sip);
8752 session->chat_title = sipe_chat_get_name(session->callid);
8753 session->roster_manager = g_strdup(self);
8755 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
8756 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
8757 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
8758 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
8760 g_free(self);
8764 static gboolean
8765 sipe_is_election_finished(struct sip_session *session)
8767 gboolean res = TRUE;
8769 SIPE_DIALOG_FOREACH {
8770 if (dialog->election_vote == 0) {
8771 res = FALSE;
8772 break;
8774 } SIPE_DIALOG_FOREACH_END;
8776 if (res) {
8777 session->is_voting_in_progress = FALSE;
8779 return res;
8782 static void
8783 sipe_election_start(struct sipe_account_data *sip,
8784 struct sip_session *session)
8786 int election_timeout;
8788 if (session->is_voting_in_progress) {
8789 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
8790 return;
8791 } else {
8792 session->is_voting_in_progress = TRUE;
8794 session->bid = rand();
8796 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
8798 SIPE_DIALOG_FOREACH {
8799 /* reset election_vote for each chat participant */
8800 dialog->election_vote = 0;
8802 /* send RequestRM to each chat participant*/
8803 sipe_send_election_request_rm(sip, dialog, session->bid);
8804 } SIPE_DIALOG_FOREACH_END;
8806 election_timeout = 15; /* sec */
8807 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
8811 * @param who a URI to whom to invite to chat
8813 void
8814 sipe_invite_to_chat(struct sipe_account_data *sip,
8815 struct sip_session *session,
8816 const gchar *who)
8818 /* a conference */
8819 if (session->focus_uri)
8821 sipe_invite_conf(sip, session, who);
8823 else /* a multi-party chat */
8825 gchar *self = sip_uri_self(sip);
8826 if (session->roster_manager) {
8827 if (!strcmp(session->roster_manager, self)) {
8828 sipe_invite(sip, session, who, NULL, NULL, FALSE);
8829 } else {
8830 sipe_refer(sip, session, who);
8832 } else {
8833 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
8835 session->pending_invite_queue = slist_insert_unique_sorted(
8836 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
8838 sipe_election_start(sip, session);
8840 g_free(self);
8844 void
8845 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
8846 struct sip_session *session)
8848 gchar *invitee;
8849 GSList *entry = session->pending_invite_queue;
8851 while (entry) {
8852 invitee = entry->data;
8853 sipe_invite_to_chat(sip, session, invitee);
8854 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
8855 g_free(invitee);
8859 static void
8860 sipe_election_result(struct sipe_account_data *sip,
8861 void *sess)
8863 struct sip_session *session = (struct sip_session *)sess;
8864 gchar *rival;
8865 gboolean has_won = TRUE;
8867 if (session->roster_manager) {
8868 purple_debug_info("sipe",
8869 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
8870 return;
8873 session->is_voting_in_progress = FALSE;
8875 SIPE_DIALOG_FOREACH {
8876 if (dialog->election_vote < 0) {
8877 has_won = FALSE;
8878 rival = dialog->with;
8879 break;
8881 } SIPE_DIALOG_FOREACH_END;
8883 if (has_won) {
8884 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
8886 session->roster_manager = sip_uri_self(sip);
8888 SIPE_DIALOG_FOREACH {
8889 /* send SetRM to each chat participant*/
8890 sipe_send_election_set_rm(sip, dialog);
8891 } SIPE_DIALOG_FOREACH_END;
8892 } else {
8893 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
8895 session->bid = 0;
8897 sipe_process_pending_invite_queue(sip, session);
8901 * For 2007+ conference only.
8903 static void
8904 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
8906 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8907 struct sip_session *session;
8909 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
8910 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
8912 session = sipe_session_find_chat_by_title(sip, chat_title);
8914 sipe_conf_modify_user_role(sip, session, buddy->name);
8918 * For 2007+ conference only.
8920 static void
8921 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
8923 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8924 struct sip_session *session;
8926 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
8927 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
8929 session = sipe_session_find_chat_by_title(sip, chat_title);
8931 sipe_conf_delete_user(sip, session, buddy->name);
8934 static void
8935 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
8937 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8938 struct sip_session *session;
8940 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
8941 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
8943 session = sipe_session_find_chat_by_title(sip, chat_title);
8945 sipe_invite_to_chat(sip, session, buddy->name);
8948 static void
8949 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
8951 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8953 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
8954 if (phone) {
8955 char *tel_uri = sip_to_tel_uri(phone);
8957 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
8958 sip_csta_make_call(sip, tel_uri);
8960 g_free(tel_uri);
8964 static void
8965 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
8967 const gchar *email;
8968 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
8970 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8971 if (email)
8973 char *mailto = g_strdup_printf("mailto:%s", email);
8974 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
8975 #ifndef _WIN32
8977 pid_t pid;
8978 char *const parmList[] = {"xdg-email", mailto, NULL};
8979 if ((pid = fork()) == -1)
8981 purple_debug_info("sipe", "fork() error\n");
8983 else if (pid == 0)
8985 execvp(parmList[0], parmList);
8986 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
8989 #else
8991 BOOL ret;
8992 _flushall();
8993 errno = 0;
8994 //@TODO resolve env variable %WINDIR% first
8995 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
8996 if (errno)
8998 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9001 #endif
9003 g_free(mailto);
9005 else
9007 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9012 * A menu which appear when right-clicking on buddy in contact list.
9014 static GList *
9015 sipe_buddy_menu(PurpleBuddy *buddy)
9017 PurpleBlistNode *g_node;
9018 PurpleGroup *group, *gr_parent;
9019 PurpleMenuAction *act;
9020 GList *menu = NULL;
9021 GList *menu_groups = NULL;
9022 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9023 const char *email;
9024 const char *phone;
9025 const char *phone_disp_str;
9026 gchar *self = sip_uri_self(sip);
9028 SIPE_SESSION_FOREACH {
9029 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9031 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9033 PurpleConvChatBuddyFlags flags;
9034 PurpleConvChatBuddyFlags flags_us;
9036 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9037 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9038 if (session->focus_uri
9039 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9040 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9042 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9043 act = purple_menu_action_new(label,
9044 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9045 session->chat_title, NULL);
9046 g_free(label);
9047 menu = g_list_prepend(menu, act);
9050 if (session->focus_uri
9051 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9053 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9054 act = purple_menu_action_new(label,
9055 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9056 session->chat_title, NULL);
9057 g_free(label);
9058 menu = g_list_prepend(menu, act);
9061 else
9063 if (!session->focus_uri
9064 || (session->focus_uri && !session->locked))
9066 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9067 act = purple_menu_action_new(label,
9068 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9069 session->chat_title, NULL);
9070 g_free(label);
9071 menu = g_list_prepend(menu, act);
9075 } SIPE_SESSION_FOREACH_END;
9077 act = purple_menu_action_new(_("New chat"),
9078 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9079 NULL, NULL);
9080 menu = g_list_prepend(menu, act);
9082 if (sip->csta && !sip->csta->line_status) {
9083 gchar *tmp = NULL;
9084 /* work phone */
9085 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9086 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9087 if (phone) {
9088 gchar *label = g_strdup_printf(_("Work %s"),
9089 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9090 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9091 g_free(tmp);
9092 tmp = NULL;
9093 g_free(label);
9094 menu = g_list_prepend(menu, act);
9097 /* mobile phone */
9098 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9099 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9100 if (phone) {
9101 gchar *label = g_strdup_printf(_("Mobile %s"),
9102 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9103 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9104 g_free(tmp);
9105 tmp = NULL;
9106 g_free(label);
9107 menu = g_list_prepend(menu, act);
9110 /* home phone */
9111 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9112 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9113 if (phone) {
9114 gchar *label = g_strdup_printf(_("Home %s"),
9115 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9116 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9117 g_free(tmp);
9118 tmp = NULL;
9119 g_free(label);
9120 menu = g_list_prepend(menu, act);
9123 /* other phone */
9124 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9125 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9126 if (phone) {
9127 gchar *label = g_strdup_printf(_("Other %s"),
9128 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9129 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9130 g_free(tmp);
9131 tmp = NULL;
9132 g_free(label);
9133 menu = g_list_prepend(menu, act);
9136 /* custom1 phone */
9137 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9138 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9139 if (phone) {
9140 gchar *label = g_strdup_printf(_("Custom1 %s"),
9141 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9142 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9143 g_free(tmp);
9144 tmp = NULL;
9145 g_free(label);
9146 menu = g_list_prepend(menu, act);
9150 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9151 if (email) {
9152 act = purple_menu_action_new(_("Send email..."),
9153 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9154 NULL, NULL);
9155 menu = g_list_prepend(menu, act);
9158 gr_parent = purple_buddy_get_group(buddy);
9159 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9160 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9161 continue;
9163 group = (PurpleGroup *)g_node;
9164 if (group == gr_parent)
9165 continue;
9167 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9168 continue;
9170 act = purple_menu_action_new(purple_group_get_name(group),
9171 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9172 group->name, NULL);
9173 menu_groups = g_list_prepend(menu_groups, act);
9175 menu_groups = g_list_reverse(menu_groups);
9177 act = purple_menu_action_new(_("Copy to"),
9178 NULL,
9179 NULL, menu_groups);
9180 menu = g_list_prepend(menu, act);
9181 menu = g_list_reverse(menu);
9183 g_free(self);
9184 return menu;
9187 static void
9188 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9190 struct sipe_account_data *sip = chat->account->gc->proto_data;
9191 struct sip_session *session;
9193 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9194 sipe_conf_modify_conference_lock(sip, session, locked);
9197 static void
9198 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9200 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9201 sipe_conf_modify_lock(chat, FALSE);
9204 static void
9205 sipe_chat_menu_lock_cb(PurpleChat *chat)
9207 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9208 sipe_conf_modify_lock(chat, TRUE);
9211 static GList *
9212 sipe_chat_menu(PurpleChat *chat)
9214 PurpleMenuAction *act;
9215 PurpleConvChatBuddyFlags flags_us;
9216 GList *menu = NULL;
9217 struct sipe_account_data *sip = chat->account->gc->proto_data;
9218 struct sip_session *session;
9219 gchar *self;
9221 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9222 if (!session) return NULL;
9224 self = sip_uri_self(sip);
9225 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9227 if (session->focus_uri
9228 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9230 if (session->locked) {
9231 act = purple_menu_action_new(_("Unlock"),
9232 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9233 NULL, NULL);
9234 menu = g_list_prepend(menu, act);
9235 } else {
9236 act = purple_menu_action_new(_("Lock"),
9237 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9238 NULL, NULL);
9239 menu = g_list_prepend(menu, act);
9243 menu = g_list_reverse(menu);
9245 g_free(self);
9246 return menu;
9249 static GList *
9250 sipe_blist_node_menu(PurpleBlistNode *node)
9252 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9253 return sipe_buddy_menu((PurpleBuddy *) node);
9254 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9255 return sipe_chat_menu((PurpleChat *)node);
9256 } else {
9257 return NULL;
9261 static gboolean
9262 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9264 char *uri = trans->payload->data;
9266 PurpleNotifyUserInfo *info;
9267 PurpleBuddy *pbuddy = NULL;
9268 struct sipe_buddy *sbuddy;
9269 const char *alias = NULL;
9270 char *device_name = NULL;
9271 char *server_alias = NULL;
9272 char *phone_number = NULL;
9273 char *email = NULL;
9274 const char *site;
9276 if (!sip) return FALSE;
9278 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9280 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9281 alias = purple_buddy_get_local_alias(pbuddy);
9283 //will query buddy UA's capabilities and send answer to log
9284 sipe_options_request(sip, uri);
9286 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9287 if (sbuddy) {
9288 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9291 info = purple_notify_user_info_new();
9293 if (msg->response != 200) {
9294 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9295 } else {
9296 xmlnode *searchResults;
9297 xmlnode *mrow;
9299 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9300 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9301 if (!searchResults) {
9302 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9303 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9304 const char *value;
9305 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9306 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9307 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9309 /* For 2007 system we will take this from ContactCard -
9310 * it has cleaner tel: URIs at least
9312 if (!sip->ocs2007) {
9313 char *tel_uri = sip_to_tel_uri(phone_number);
9314 /* trims its parameters, so call first */
9315 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9316 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9317 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9318 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9319 g_free(tel_uri);
9322 if (server_alias && strlen(server_alias) > 0) {
9323 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9325 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9326 purple_notify_user_info_add_pair(info, _("Job title"), value);
9328 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9329 purple_notify_user_info_add_pair(info, _("Office"), value);
9331 if (phone_number && strlen(phone_number) > 0) {
9332 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9334 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9335 purple_notify_user_info_add_pair(info, _("Company"), value);
9337 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9338 purple_notify_user_info_add_pair(info, _("City"), value);
9340 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9341 purple_notify_user_info_add_pair(info, _("State"), value);
9343 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9344 purple_notify_user_info_add_pair(info, _("Country"), value);
9346 if (email && strlen(email) > 0) {
9347 purple_notify_user_info_add_pair(info, _("Email address"), email);
9351 xmlnode_free(searchResults);
9354 purple_notify_user_info_add_section_break(info);
9356 if (!server_alias || !strcmp("", server_alias)) {
9357 g_free(server_alias);
9358 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9359 if (server_alias) {
9360 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9364 /* present alias if it differs from server alias */
9365 if (alias && (!server_alias || strcmp(alias, server_alias)))
9367 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9370 if (!email || !strcmp("", email)) {
9371 g_free(email);
9372 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9373 if (email) {
9374 purple_notify_user_info_add_pair(info, _("Email address"), email);
9378 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9379 if (site) {
9380 purple_notify_user_info_add_pair(info, _("Site"), site);
9383 if (device_name) {
9384 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9387 /* show a buddy's user info in a nice dialog box */
9388 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9389 uri, /* buddy's URI */
9390 info, /* body */
9391 NULL, /* callback called when dialog closed */
9392 NULL); /* userdata for callback */
9394 g_free(phone_number);
9395 g_free(server_alias);
9396 g_free(email);
9397 g_free(device_name);
9399 return TRUE;
9403 * AD search first, LDAP based
9405 static void sipe_get_info(PurpleConnection *gc, const char *username)
9407 struct sipe_account_data *sip = gc->proto_data;
9408 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9409 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9410 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9411 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9413 payload->destroy = g_free;
9414 payload->data = g_strdup(username);
9416 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9417 send_soap_request_with_cb(sip, domain_uri, body,
9418 (TransCallback) process_get_info_response, payload);
9419 g_free(domain_uri);
9420 g_free(body);
9421 g_free(row);
9424 static PurplePlugin *my_protocol = NULL;
9426 static PurplePluginProtocolInfo prpl_info =
9428 OPT_PROTO_CHAT_TOPIC,
9429 NULL, /* user_splits */
9430 NULL, /* protocol_options */
9431 NO_BUDDY_ICONS, /* icon_spec */
9432 sipe_list_icon, /* list_icon */
9433 NULL, /* list_emblems */
9434 sipe_status_text, /* status_text */
9435 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9436 sipe_status_types, /* away_states */
9437 sipe_blist_node_menu, /* blist_node_menu */
9438 NULL, /* chat_info */
9439 NULL, /* chat_info_defaults */
9440 sipe_login, /* login */
9441 sipe_close, /* close */
9442 sipe_im_send, /* send_im */
9443 NULL, /* set_info */ // TODO maybe
9444 sipe_send_typing, /* send_typing */
9445 sipe_get_info, /* get_info */
9446 sipe_set_status, /* set_status */
9447 sipe_set_idle, /* set_idle */
9448 NULL, /* change_passwd */
9449 sipe_add_buddy, /* add_buddy */
9450 NULL, /* add_buddies */
9451 sipe_remove_buddy, /* remove_buddy */
9452 NULL, /* remove_buddies */
9453 sipe_add_permit, /* add_permit */
9454 sipe_add_deny, /* add_deny */
9455 sipe_add_deny, /* rem_permit */
9456 sipe_add_permit, /* rem_deny */
9457 dummy_permit_deny, /* set_permit_deny */
9458 NULL, /* join_chat */
9459 NULL, /* reject_chat */
9460 NULL, /* get_chat_name */
9461 sipe_chat_invite, /* chat_invite */
9462 sipe_chat_leave, /* chat_leave */
9463 NULL, /* chat_whisper */
9464 sipe_chat_send, /* chat_send */
9465 sipe_keep_alive, /* keepalive */
9466 NULL, /* register_user */
9467 NULL, /* get_cb_info */ // deprecated
9468 NULL, /* get_cb_away */ // deprecated
9469 sipe_alias_buddy, /* alias_buddy */
9470 sipe_group_buddy, /* group_buddy */
9471 sipe_rename_group, /* rename_group */
9472 NULL, /* buddy_free */
9473 sipe_convo_closed, /* convo_closed */
9474 purple_normalize_nocase, /* normalize */
9475 NULL, /* set_buddy_icon */
9476 sipe_remove_group, /* remove_group */
9477 NULL, /* get_cb_real_name */ // TODO?
9478 NULL, /* set_chat_topic */
9479 NULL, /* find_blist_chat */
9480 NULL, /* roomlist_get_list */
9481 NULL, /* roomlist_cancel */
9482 NULL, /* roomlist_expand_category */
9483 NULL, /* can_receive_file */
9484 NULL, /* send_file */
9485 NULL, /* new_xfer */
9486 NULL, /* offline_message */
9487 NULL, /* whiteboard_prpl_ops */
9488 sipe_send_raw, /* send_raw */
9489 NULL, /* roomlist_room_serialize */
9490 NULL, /* unregister_user */
9491 NULL, /* send_attention */
9492 NULL, /* get_attention_types */
9493 #if !PURPLE_VERSION_CHECK(2,5,0)
9494 /* Backward compatibility when compiling against 2.4.x API */
9495 (void (*)(void)) /* _purple_reserved4 */
9496 #endif
9497 sizeof(PurplePluginProtocolInfo), /* struct_size */
9498 #if PURPLE_VERSION_CHECK(2,5,0)
9499 sipe_get_account_text_table, /* get_account_text_table */
9500 #if PURPLE_VERSION_CHECK(2,6,0)
9501 NULL, /* initiate_media */
9502 NULL, /* get_media_caps */
9503 #endif
9504 #endif
9508 static PurplePluginInfo info = {
9509 PURPLE_PLUGIN_MAGIC,
9510 PURPLE_MAJOR_VERSION,
9511 PURPLE_MINOR_VERSION,
9512 PURPLE_PLUGIN_PROTOCOL, /**< type */
9513 NULL, /**< ui_requirement */
9514 0, /**< flags */
9515 NULL, /**< dependencies */
9516 PURPLE_PRIORITY_DEFAULT, /**< priority */
9517 "prpl-sipe", /**< id */
9518 "Office Communicator", /**< name */
9519 SIPE_VERSION, /**< version */
9520 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9521 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9522 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9523 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9524 "Gabriel Burt <gburt@novell.com>, " /**< author */
9525 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9526 "pier11 <pier11@operamail.com>", /**< author */
9527 "http://sipe.sourceforge.net/", /**< homepage */
9528 sipe_plugin_load, /**< load */
9529 sipe_plugin_unload, /**< unload */
9530 sipe_plugin_destroy, /**< destroy */
9531 NULL, /**< ui_info */
9532 &prpl_info, /**< extra_info */
9533 NULL,
9534 sipe_actions,
9535 NULL,
9536 NULL,
9537 NULL,
9538 NULL
9541 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9543 GList *entry;
9545 entry = prpl_info.protocol_options;
9546 while (entry) {
9547 purple_account_option_destroy(entry->data);
9548 entry = g_list_delete_link(entry, entry);
9550 prpl_info.protocol_options = NULL;
9552 entry = prpl_info.user_splits;
9553 while (entry) {
9554 purple_account_user_split_destroy(entry->data);
9555 entry = g_list_delete_link(entry, entry);
9557 prpl_info.user_splits = NULL;
9560 static void init_plugin(PurplePlugin *plugin)
9562 PurpleAccountUserSplit *split;
9563 PurpleAccountOption *option;
9565 srand(time(NULL));
9567 #ifdef ENABLE_NLS
9568 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
9569 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
9570 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
9571 textdomain(GETTEXT_PACKAGE);
9572 #endif
9574 purple_plugin_register(plugin);
9576 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
9577 purple_account_user_split_set_reverse(split, FALSE);
9578 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
9580 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
9581 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9583 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
9584 purple_account_option_add_list_item(option, _("Auto"), "auto");
9585 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
9586 purple_account_option_add_list_item(option, _("TCP"), "tcp");
9587 purple_account_option_add_list_item(option, _("UDP"), "udp");
9588 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9590 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
9591 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
9593 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
9594 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9596 #ifdef USE_KERBEROS
9597 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
9598 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9600 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
9601 * No login/password is taken into account if this option present,
9602 * instead used default credentials stored in OS.
9604 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
9605 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9606 #endif
9608 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
9609 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
9610 purple_account_option_add_list_item(option, _("None"), "NONE");
9611 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9613 /** Example: https://server.company.com/EWS/Exchange.asmx */
9614 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
9615 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9617 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
9618 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9620 /** Example: DOMAIN\user or user@company.com */
9621 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
9622 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9624 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
9625 purple_account_option_set_masked(option, TRUE);
9626 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
9628 my_protocol = plugin;
9631 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
9634 Local Variables:
9635 mode: c
9636 c-file-style: "bsd"
9637 indent-tabs-mode: t
9638 tab-width: 8
9639 End: