filetransfer: re-enabled embedded INVITE processing for OC 2007
[siplcs.git] / src / core / sipe.c
blobe7957159781587f7e8c3d804fb60a7b2947fb120
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 "sipe-ft.h"
86 #include "sipmsg.h"
87 #include "sipe-sign.h"
88 #include "dnssrv.h"
89 #include "request.h"
91 /* Backward compatibility when compiling against 2.4.x API */
92 #if !PURPLE_VERSION_CHECK(2,5,0)
93 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
94 #endif
96 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
98 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
99 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
101 /* Keep in sync with sipe_transport_type! */
102 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
103 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
105 /* Status identifiers (see also: sipe_status_types()) */
106 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
107 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
108 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
109 /* PURPLE_STATUS_UNAVAILABLE: */
110 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
111 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
112 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
113 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
114 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
115 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
116 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
117 /* PURPLE_STATUS_AWAY: */
118 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
119 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
120 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
121 /** Reuters status (user settable) */
122 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
123 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
124 /* ??? PURPLE_STATUS_MOBILE */
125 /* ??? PURPLE_STATUS_TUNE */
127 /* Status attributes (see also sipe_status_types() */
128 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
130 static struct sipe_activity_map_struct
132 sipe_activity type;
133 const char *token;
134 const char *desc;
135 const char *status_id;
137 } const sipe_activity_map[] =
139 /* This has nothing to do with Availability numbers, like 3500 (online).
140 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
142 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
143 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
144 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
145 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
146 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
147 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
148 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
149 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
150 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
151 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
152 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
153 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
154 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
155 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
156 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
158 /** @param x is sipe_activity */
159 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
162 /* Action name templates */
163 #define ACTION_NAME_PRESENCE "<presence><%s>"
165 static sipe_activity
166 sipe_get_activity_by_token(const char *token)
168 int i;
170 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
172 if (sipe_strequal(token, sipe_activity_map[i].token))
173 return sipe_activity_map[i].type;
176 return sipe_activity_map[0].type;
179 static const char *
180 sipe_get_activity_desc_by_token(const char *token)
182 if (!token) return NULL;
184 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
187 /** Allows to send typed messages from chat window again after account reinstantiation. */
188 static void
189 sipe_rejoin_chat(PurpleConversation *conv)
191 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
192 PURPLE_CONV_CHAT(conv)->left)
194 PURPLE_CONV_CHAT(conv)->left = FALSE;
195 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
199 static char *genbranch()
201 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
202 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
203 rand() & 0xFFFF, rand() & 0xFFFF);
207 static char *default_ua = NULL;
208 static const char*
209 sipe_get_useragent(struct sipe_account_data *sip)
211 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
212 if (is_empty(useragent)) {
213 if (!default_ua) {
214 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
215 /* ref: lzodefs.h */
216 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
217 #define SIPE_TARGET_PLATFORM "linux"
218 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
219 #define SIPE_TARGET_PLATFORM "bsd"
220 # elif defined(__APPLE__) || defined(__MACOS__)
221 #define SIPE_TARGET_PLATFORM "macosx"
222 #elif defined(__solaris__) || defined(__sun)
223 #define SIPE_TARGET_PLATFORM "sun"
224 #elif defined(_WIN32)
225 #define SIPE_TARGET_PLATFORM "win"
226 #else
227 #define SIPE_TARGET_PLATFORM "generic"
228 #endif
230 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
231 #define SIPE_TARGET_ARCH "x86_64"
232 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
233 #define SIPE_TARGET_ARCH "i386"
234 #elif defined(__ppc64__)
235 #define SIPE_TARGET_ARCH "ppc64"
236 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
237 #define SIPE_TARGET_ARCH "ppc"
238 #else
239 #define SIPE_TARGET_ARCH "other"
240 #endif
242 default_ua = g_strdup_printf("Purple/%s Sipe/%s (%s-%s; %s)",
243 purple_core_get_version(),
244 SIPE_VERSION,
245 SIPE_TARGET_PLATFORM,
246 SIPE_TARGET_ARCH,
247 sip->server_version ? sip->server_version : "");
249 useragent = default_ua;
251 return useragent;
254 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
255 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
257 return "sipe";
260 static void sipe_plugin_destroy(PurplePlugin *plugin);
262 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
264 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
265 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
266 gpointer data);
268 static void sipe_close(PurpleConnection *gc);
270 static void send_presence_status(struct sipe_account_data *sip);
272 static void sendout_pkt(PurpleConnection *gc, const char *buf);
274 static void sipe_keep_alive(PurpleConnection *gc)
276 struct sipe_account_data *sip = gc->proto_data;
277 if (sip->transport == SIPE_TRANSPORT_UDP) {
278 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
279 gchar buf[2] = {0, 0};
280 purple_debug_info("sipe", "sending keep alive\n");
281 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
282 } else {
283 time_t now = time(NULL);
284 if ((sip->keepalive_timeout > 0) &&
285 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
286 #if PURPLE_VERSION_CHECK(2,4,0)
287 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
288 #endif
290 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
291 sendout_pkt(gc, "\r\n\r\n");
292 sip->last_keepalive = now;
297 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
299 struct sip_connection *ret = NULL;
300 GSList *entry = sip->openconns;
301 while (entry) {
302 ret = entry->data;
303 if (ret->fd == fd) return ret;
304 entry = entry->next;
306 return NULL;
309 static void sipe_auth_free(struct sip_auth *auth)
311 g_free(auth->opaque);
312 auth->opaque = NULL;
313 g_free(auth->realm);
314 auth->realm = NULL;
315 g_free(auth->target);
316 auth->target = NULL;
317 auth->type = AUTH_TYPE_UNSET;
318 auth->retries = 0;
319 auth->expires = 0;
320 g_free(auth->gssapi_data);
321 auth->gssapi_data = NULL;
322 sip_sec_destroy_context(auth->gssapi_context);
323 auth->gssapi_context = NULL;
326 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
328 struct sip_connection *ret = g_new0(struct sip_connection, 1);
329 ret->fd = fd;
330 sip->openconns = g_slist_append(sip->openconns, ret);
331 return ret;
334 static void connection_remove(struct sipe_account_data *sip, int fd)
336 struct sip_connection *conn = connection_find(sip, fd);
337 if (conn) {
338 sip->openconns = g_slist_remove(sip->openconns, conn);
339 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
340 g_free(conn->inbuf);
341 g_free(conn);
345 static void connection_free_all(struct sipe_account_data *sip)
347 struct sip_connection *ret = NULL;
348 GSList *entry = sip->openconns;
349 while (entry) {
350 ret = entry->data;
351 connection_remove(sip, ret->fd);
352 entry = sip->openconns;
356 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
358 gchar noncecount[9];
359 const char *authuser = sip->authuser;
360 gchar *response;
361 gchar *ret;
363 if (!authuser || strlen(authuser) < 1) {
364 authuser = sip->username;
367 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
368 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
370 // If we have a signature for the message, include that
371 if (msg->signature) {
372 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);
375 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
376 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
377 gchar *gssapi_data;
378 gchar *opaque;
380 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
381 &(auth->expires),
382 auth->type,
383 purple_account_get_bool(sip->account, "sso", TRUE),
384 sip->authdomain ? sip->authdomain : "",
385 authuser,
386 sip->password,
387 auth->target,
388 auth->gssapi_data);
389 if (!gssapi_data || !auth->gssapi_context) {
390 sip->gc->wants_to_die = TRUE;
391 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
392 return NULL;
395 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
396 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
397 g_free(opaque);
398 g_free(gssapi_data);
399 return ret;
402 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
404 } else { /* Digest */
406 /* Calculate new session key */
407 if (!auth->opaque) {
408 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
409 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
410 authuser, auth->realm, sip->password,
411 auth->gssapi_data, NULL);
414 sprintf(noncecount, "%08d", auth->nc++);
415 response = purple_cipher_http_digest_calculate_response("md5",
416 msg->method, msg->target, NULL, NULL,
417 auth->gssapi_data, noncecount, NULL,
418 auth->opaque);
419 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
421 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);
422 g_free(response);
423 return ret;
427 static char *parse_attribute(const char *attrname, const char *source)
429 const char *tmp, *tmp2;
430 char *retval = NULL;
431 int len = strlen(attrname);
433 if (g_str_has_prefix(source, attrname)) {
434 tmp = source + len;
435 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
436 if (tmp2)
437 retval = g_strndup(tmp, tmp2 - tmp);
438 else
439 retval = g_strdup(tmp);
442 return retval;
445 static void fill_auth(gchar *hdr, struct sip_auth *auth)
447 int i;
448 gchar **parts;
450 if (!hdr) {
451 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
452 return;
455 if (!g_strncasecmp(hdr, "NTLM", 4)) {
456 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
457 auth->type = AUTH_TYPE_NTLM;
458 hdr += 5;
459 auth->nc = 1;
460 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
461 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
462 auth->type = AUTH_TYPE_KERBEROS;
463 hdr += 9;
464 auth->nc = 3;
465 } else {
466 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
467 auth->type = AUTH_TYPE_DIGEST;
468 hdr += 7;
471 parts = g_strsplit(hdr, "\", ", 0);
472 for (i = 0; parts[i]; i++) {
473 char *tmp;
475 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
477 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
478 g_free(auth->gssapi_data);
479 auth->gssapi_data = tmp;
481 if (auth->type == AUTH_TYPE_NTLM) {
482 /* NTLM module extracts nonce from gssapi-data */
483 auth->nc = 3;
486 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
487 /* Only used with AUTH_TYPE_DIGEST */
488 g_free(auth->gssapi_data);
489 auth->gssapi_data = tmp;
490 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
491 g_free(auth->opaque);
492 auth->opaque = tmp;
493 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
494 g_free(auth->realm);
495 auth->realm = tmp;
497 if (auth->type == AUTH_TYPE_DIGEST) {
498 /* Throw away old session key */
499 g_free(auth->opaque);
500 auth->opaque = NULL;
501 auth->nc = 1;
504 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
505 g_free(auth->target);
506 auth->target = tmp;
509 g_strfreev(parts);
511 return;
514 static void sipe_canwrite_cb(gpointer data,
515 SIPE_UNUSED_PARAMETER gint source,
516 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
518 PurpleConnection *gc = data;
519 struct sipe_account_data *sip = gc->proto_data;
520 gsize max_write;
521 gssize written;
523 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
525 if (max_write == 0) {
526 if (sip->tx_handler != 0){
527 purple_input_remove(sip->tx_handler);
528 sip->tx_handler = 0;
530 return;
533 written = write(sip->fd, sip->txbuf->outptr, max_write);
535 if (written < 0 && errno == EAGAIN)
536 written = 0;
537 else if (written <= 0) {
538 /*TODO: do we really want to disconnect on a failure to write?*/
539 purple_connection_error(gc, _("Could not write"));
540 return;
543 purple_circ_buffer_mark_read(sip->txbuf, written);
546 static void sipe_canwrite_cb_ssl(gpointer data,
547 SIPE_UNUSED_PARAMETER gint src,
548 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
550 PurpleConnection *gc = data;
551 struct sipe_account_data *sip = gc->proto_data;
552 gsize max_write;
553 gssize written;
555 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
557 if (max_write == 0) {
558 if (sip->tx_handler != 0) {
559 purple_input_remove(sip->tx_handler);
560 sip->tx_handler = 0;
561 return;
565 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
567 if (written < 0 && errno == EAGAIN)
568 written = 0;
569 else if (written <= 0) {
570 /*TODO: do we really want to disconnect on a failure to write?*/
571 purple_connection_error(gc, _("Could not write"));
572 return;
575 purple_circ_buffer_mark_read(sip->txbuf, written);
578 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
580 static void send_later_cb(gpointer data, gint source,
581 SIPE_UNUSED_PARAMETER const gchar *error)
583 PurpleConnection *gc = data;
584 struct sipe_account_data *sip;
585 struct sip_connection *conn;
587 if (!PURPLE_CONNECTION_IS_VALID(gc))
589 if (source >= 0)
590 close(source);
591 return;
594 if (source < 0) {
595 purple_connection_error(gc, _("Could not connect"));
596 return;
599 sip = gc->proto_data;
600 sip->fd = source;
601 sip->connecting = FALSE;
602 sip->last_keepalive = time(NULL);
604 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
606 /* If there is more to write now, we need to register a handler */
607 if (sip->txbuf->bufused > 0)
608 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
610 conn = connection_create(sip, source);
611 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
614 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
616 struct sipe_account_data *sip;
618 if (!PURPLE_CONNECTION_IS_VALID(gc))
620 if (gsc) purple_ssl_close(gsc);
621 return NULL;
624 sip = gc->proto_data;
625 sip->fd = gsc->fd;
626 sip->gsc = gsc;
627 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
628 sip->connecting = FALSE;
629 sip->last_keepalive = time(NULL);
631 connection_create(sip, gsc->fd);
633 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
635 return sip;
638 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
639 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
641 PurpleConnection *gc = data;
642 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
643 if (sip == NULL) return;
645 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
647 /* If there is more to write now */
648 if (sip->txbuf->bufused > 0) {
649 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
654 static void sendlater(PurpleConnection *gc, const char *buf)
656 struct sipe_account_data *sip = gc->proto_data;
658 if (!sip->connecting) {
659 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
660 if (sip->transport == SIPE_TRANSPORT_TLS){
661 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
662 } else {
663 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
664 purple_connection_error(gc, _("Could not create socket"));
667 sip->connecting = TRUE;
670 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
671 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
673 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
676 static void sendout_pkt(PurpleConnection *gc, const char *buf)
678 struct sipe_account_data *sip = gc->proto_data;
679 time_t currtime = time(NULL);
680 int writelen = strlen(buf);
681 char *tmp;
683 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
684 g_free(tmp);
685 if (sip->transport == SIPE_TRANSPORT_UDP) {
686 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
687 purple_debug_info("sipe", "could not send packet\n");
689 } else {
690 int ret;
691 if (sip->fd < 0) {
692 sendlater(gc, buf);
693 return;
696 if (sip->tx_handler) {
697 ret = -1;
698 errno = EAGAIN;
699 } else{
700 if (sip->gsc){
701 ret = purple_ssl_write(sip->gsc, buf, writelen);
702 }else{
703 ret = write(sip->fd, buf, writelen);
707 if (ret < 0 && errno == EAGAIN)
708 ret = 0;
709 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
710 sendlater(gc, buf);
711 return;
714 if (ret < writelen) {
715 if (!sip->tx_handler){
716 if (sip->gsc){
717 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
719 else{
720 sip->tx_handler = purple_input_add(sip->fd,
721 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
722 gc);
726 /* XXX: is it OK to do this? You might get part of a request sent
727 with part of another. */
728 if (sip->txbuf->bufused > 0)
729 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
731 purple_circ_buffer_append(sip->txbuf, buf + ret,
732 writelen - ret);
737 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
739 sendout_pkt(gc, buf);
740 return len;
743 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
745 GSList *tmp = msg->headers;
746 gchar *name;
747 gchar *value;
748 GString *outstr = g_string_new("");
749 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
750 while (tmp) {
751 name = ((struct siphdrelement*) (tmp->data))->name;
752 value = ((struct siphdrelement*) (tmp->data))->value;
753 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
754 tmp = g_slist_next(tmp);
756 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
757 sendout_pkt(sip->gc, outstr->str);
758 g_string_free(outstr, TRUE);
761 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
763 gchar * buf;
765 if (sip->registrar.type == AUTH_TYPE_UNSET) {
766 return;
769 if (sip->registrar.gssapi_context) {
770 struct sipmsg_breakdown msgbd;
771 gchar *signature_input_str;
772 msgbd.msg = msg;
773 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
774 msgbd.rand = g_strdup_printf("%08x", g_random_int());
775 sip->registrar.ntlm_num++;
776 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
777 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
778 if (signature_input_str != NULL) {
779 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
780 msg->signature = signature_hex;
781 msg->rand = g_strdup(msgbd.rand);
782 msg->num = g_strdup(msgbd.num);
783 g_free(signature_input_str);
785 sipmsg_breakdown_free(&msgbd);
788 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
789 buf = auth_header(sip, &sip->registrar, msg);
790 if (buf) {
791 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
793 g_free(buf);
794 } else if (sipe_strequal(method,"SUBSCRIBE") || sipe_strequal(method,"SERVICE") || sipe_strequal(method,"MESSAGE") || sipe_strequal(method,"INVITE") || sipe_strequal(method, "ACK") || sipe_strequal(method, "NOTIFY") || sipe_strequal(method, "BYE") || sipe_strequal(method, "INFO") || sipe_strequal(method, "OPTIONS") || sipe_strequal(method, "REFER")) {
795 sip->registrar.nc = 3;
796 sip->registrar.type = AUTH_TYPE_NTLM;
797 #ifdef USE_KERBEROS
798 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
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 /* Can return NULL! */
824 contact = get_contact(sip);
825 if (contact) {
826 sipmsg_add_header(msg, "Contact", contact);
827 g_free(contact);
830 if (body) {
831 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
832 sipmsg_add_header(msg, "Content-Length", len);
833 g_free(len);
834 } else {
835 sipmsg_add_header(msg, "Content-Length", "0");
838 msg->response = code;
840 sipmsg_strip_headers(msg, keepers);
841 sipmsg_merge_new_headers(msg);
842 sign_outgoing_message(msg, sip, msg->method);
844 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
845 tmp = msg->headers;
846 while (tmp) {
847 name = ((struct siphdrelement*) (tmp->data))->name;
848 value = ((struct siphdrelement*) (tmp->data))->value;
850 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
851 tmp = g_slist_next(tmp);
853 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
854 sendout_pkt(gc, outstr->str);
855 g_string_free(outstr, TRUE);
858 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
860 if (sip->transactions) {
861 sip->transactions = g_slist_remove(sip->transactions, trans);
862 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
864 if (trans->msg) sipmsg_free(trans->msg);
865 if (trans->payload) {
866 (*trans->payload->destroy)(trans->payload->data);
867 g_free(trans->payload);
869 g_free(trans->key);
870 g_free(trans);
874 static struct transaction *
875 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
877 gchar *call_id = NULL;
878 gchar *cseq = NULL;
879 struct transaction *trans = g_new0(struct transaction, 1);
881 trans->time = time(NULL);
882 trans->msg = (struct sipmsg *)msg;
883 call_id = sipmsg_find_header(trans->msg, "Call-ID");
884 cseq = sipmsg_find_header(trans->msg, "CSeq");
885 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
886 trans->callback = callback;
887 sip->transactions = g_slist_append(sip->transactions, trans);
888 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
889 return trans;
892 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
894 struct transaction *trans;
895 GSList *transactions = sip->transactions;
896 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
897 gchar *cseq = sipmsg_find_header(msg, "CSeq");
898 gchar *key;
900 if (!call_id || !cseq) {
901 purple_debug(PURPLE_DEBUG_ERROR, "sipe", "transaction_find: no Call-ID or CSeq!\n");
902 return NULL;
905 key = g_strdup_printf("<%s><%s>", call_id, cseq);
906 while (transactions) {
907 trans = transactions->data;
908 if (!g_strcasecmp(trans->key, key)) {
909 g_free(key);
910 return trans;
912 transactions = transactions->next;
915 g_free(key);
916 return NULL;
919 struct transaction *
920 send_sip_request(PurpleConnection *gc, const gchar *method,
921 const gchar *url, const gchar *to, const gchar *addheaders,
922 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
924 struct sipe_account_data *sip = gc->proto_data;
925 const char *addh = "";
926 char *buf;
927 struct sipmsg *msg;
928 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
929 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
930 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
931 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
932 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
933 gchar *route = g_strdup("");
934 gchar *epid = get_epid(sip);
935 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
936 struct transaction *trans = NULL;
938 if (dialog && dialog->routes)
940 GSList *iter = dialog->routes;
942 while(iter)
944 char *tmp = route;
945 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
946 g_free(tmp);
947 iter = g_slist_next(iter);
951 if (!ourtag && !dialog) {
952 ourtag = gentag();
955 if (sipe_strequal(method, "REGISTER")) {
956 if (sip->regcallid) {
957 g_free(callid);
958 callid = g_strdup(sip->regcallid);
959 } else {
960 sip->regcallid = g_strdup(callid);
962 cseq = ++sip->cseq;
965 if (addheaders) addh = addheaders;
967 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
968 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
969 "From: <sip:%s>%s%s;epid=%s\r\n"
970 "To: <%s>%s%s%s%s\r\n"
971 "Max-Forwards: 70\r\n"
972 "CSeq: %d %s\r\n"
973 "User-Agent: %s\r\n"
974 "Call-ID: %s\r\n"
975 "%s%s"
976 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
977 method,
978 dialog && dialog->request ? dialog->request : url,
979 TRANSPORT_DESCRIPTOR,
980 purple_network_get_my_ip(-1),
981 sip->listenport,
982 branch ? ";branch=" : "",
983 branch ? branch : "",
984 sip->username,
985 ourtag ? ";tag=" : "",
986 ourtag ? ourtag : "",
987 epid,
989 theirtag ? ";tag=" : "",
990 theirtag ? theirtag : "",
991 theirepid ? ";epid=" : "",
992 theirepid ? theirepid : "",
993 cseq,
994 method,
995 sipe_get_useragent(sip),
996 callid,
997 route,
998 addh,
999 body ? (gsize) strlen(body) : 0,
1000 body ? body : "");
1003 //printf ("parsing msg buf:\n%s\n\n", buf);
1004 msg = sipmsg_parse_msg(buf);
1006 g_free(buf);
1007 g_free(ourtag);
1008 g_free(theirtag);
1009 g_free(theirepid);
1010 g_free(branch);
1011 g_free(callid);
1012 g_free(route);
1013 g_free(epid);
1015 sign_outgoing_message (msg, sip, method);
1017 buf = sipmsg_to_string (msg);
1019 /* add to ongoing transactions */
1020 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1021 if (!sipe_strequal(method, "ACK")) {
1022 trans = transactions_add_buf(sip, msg, tc);
1023 } else {
1024 sipmsg_free(msg);
1026 sendout_pkt(gc, buf);
1027 g_free(buf);
1029 return trans;
1033 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1035 static void
1036 send_soap_request_with_cb(struct sipe_account_data *sip,
1037 gchar *from0,
1038 gchar *body,
1039 TransCallback callback,
1040 struct transaction_payload *payload)
1042 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1043 gchar *contact = get_contact(sip);
1044 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1045 "Content-Type: application/SOAP+xml\r\n",contact);
1047 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1048 trans->payload = payload;
1050 g_free(from);
1051 g_free(contact);
1052 g_free(hdr);
1055 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1057 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1060 static char *get_contact_register(struct sipe_account_data *sip)
1062 char *epid = get_epid(sip);
1063 char *uuid = generateUUIDfromEPID(epid);
1064 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);
1065 g_free(uuid);
1066 g_free(epid);
1067 return(buf);
1070 static void do_register_exp(struct sipe_account_data *sip, int expire)
1072 char *uri;
1073 char *expires;
1074 char *to;
1075 char *contact;
1076 char *hdr;
1078 if (!sip->sipdomain) return;
1080 uri = sip_uri_from_name(sip->sipdomain);
1081 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1082 to = sip_uri_self(sip);
1083 contact = get_contact_register(sip);
1084 hdr = g_strdup_printf("Contact: %s\r\n"
1085 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1086 "Event: registration\r\n"
1087 "Allow-Events: presence\r\n"
1088 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1089 "%s", contact, expires);
1090 g_free(contact);
1091 g_free(expires);
1093 sip->registerstatus = 1;
1095 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1096 process_register_response);
1098 g_free(hdr);
1099 g_free(uri);
1100 g_free(to);
1103 static void do_register_cb(struct sipe_account_data *sip,
1104 SIPE_UNUSED_PARAMETER void *unused)
1106 do_register_exp(sip, -1);
1107 sip->reregister_set = FALSE;
1110 static void do_register(struct sipe_account_data *sip)
1112 do_register_exp(sip, -1);
1115 static void
1116 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1118 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1119 send_soap_request(sip, body);
1120 g_free(body);
1123 static void
1124 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1126 if (allow) {
1127 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1128 } else {
1129 purple_debug_info("sipe", "Blocking contact %s\n", who);
1132 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1135 static
1136 void sipe_auth_user_cb(void * data)
1138 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1139 if (!job) return;
1141 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1142 g_free(job);
1145 static
1146 void sipe_deny_user_cb(void * data)
1148 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1149 if (!job) return;
1151 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1152 g_free(job);
1155 static void
1156 sipe_add_permit(PurpleConnection *gc, const char *name)
1158 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1159 sipe_contact_allow_deny(sip, name, TRUE);
1162 static void
1163 sipe_add_deny(PurpleConnection *gc, const char *name)
1165 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1166 sipe_contact_allow_deny(sip, name, FALSE);
1169 /*static void
1170 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1172 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1173 sipe_contact_set_acl(sip, name, "");
1176 static void
1177 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1179 xmlnode *watchers;
1180 xmlnode *watcher;
1181 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1182 if (msg->response != 0 && msg->response != 200) return;
1184 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1186 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1187 if (!watchers) return;
1189 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1190 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1191 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1192 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1194 // TODO pull out optional displayName to pass as alias
1195 if (remote_user) {
1196 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1197 job->who = remote_user;
1198 job->sip = sip;
1199 purple_account_request_authorization(
1200 sip->account,
1201 remote_user,
1202 _("you"), /* id */
1203 alias,
1204 NULL, /* message */
1205 on_list,
1206 sipe_auth_user_cb,
1207 sipe_deny_user_cb,
1208 (void *) job);
1213 xmlnode_free(watchers);
1214 return;
1217 static void
1218 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1220 PurpleGroup * purple_group = purple_find_group(group->name);
1221 if (!purple_group) {
1222 purple_group = purple_group_new(group->name);
1223 purple_blist_add_group(purple_group, NULL);
1226 if (purple_group) {
1227 group->purple_group = purple_group;
1228 sip->groups = g_slist_append(sip->groups, group);
1229 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1230 } else {
1231 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1235 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1237 struct sipe_group *group;
1238 GSList *entry;
1239 if (sip == NULL) {
1240 return NULL;
1243 entry = sip->groups;
1244 while (entry) {
1245 group = entry->data;
1246 if (group->id == id) {
1247 return group;
1249 entry = entry->next;
1251 return NULL;
1254 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1256 struct sipe_group *group;
1257 GSList *entry;
1258 if (!sip || !name) {
1259 return NULL;
1262 entry = sip->groups;
1263 while (entry) {
1264 group = entry->data;
1265 if (sipe_strequal(group->name, name)) {
1266 return group;
1268 entry = entry->next;
1270 return NULL;
1273 static void
1274 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1276 gchar *body;
1277 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1278 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1279 send_soap_request(sip, body);
1280 g_free(body);
1281 g_free(group->name);
1282 group->name = g_strdup(name);
1286 * Only appends if no such value already stored.
1287 * Like Set in Java.
1289 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1290 GSList * res = list;
1291 if (!g_slist_find_custom(list, data, func)) {
1292 res = g_slist_insert_sorted(list, data, func);
1294 return res;
1297 static int
1298 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1299 return group1->id - group2->id;
1303 * Returns string like "2 4 7 8" - group ids buddy belong to.
1305 static gchar *
1306 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1307 int i = 0;
1308 gchar *res;
1309 //creating array from GList, converting int to gchar*
1310 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1311 GSList *entry = buddy->groups;
1313 if (!ids_arr) return NULL;
1315 while (entry) {
1316 struct sipe_group * group = entry->data;
1317 ids_arr[i] = g_strdup_printf("%d", group->id);
1318 entry = entry->next;
1319 i++;
1321 ids_arr[i] = NULL;
1322 res = g_strjoinv(" ", ids_arr);
1323 g_strfreev(ids_arr);
1324 return res;
1328 * Sends buddy update to server
1330 static void
1331 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1333 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1334 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1336 if (buddy && purple_buddy) {
1337 const char *alias = purple_buddy_get_alias(purple_buddy);
1338 gchar *groups = sipe_get_buddy_groups_string(buddy);
1339 if (groups) {
1340 gchar *body;
1341 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1343 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1344 alias, groups, "true", buddy->name, sip->contacts_delta++
1346 send_soap_request(sip, body);
1347 g_free(groups);
1348 g_free(body);
1353 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1355 if (msg->response == 200) {
1356 struct sipe_group *group;
1357 struct group_user_context *ctx = trans->payload->data;
1358 xmlnode *xml;
1359 xmlnode *node;
1360 char *group_id;
1361 struct sipe_buddy *buddy;
1363 xml = xmlnode_from_str(msg->body, msg->bodylen);
1364 if (!xml) {
1365 return FALSE;
1368 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1369 if (!node) {
1370 xmlnode_free(xml);
1371 return FALSE;
1374 group_id = xmlnode_get_data(node);
1375 if (!group_id) {
1376 xmlnode_free(xml);
1377 return FALSE;
1380 group = g_new0(struct sipe_group, 1);
1381 group->id = (int)g_ascii_strtod(group_id, NULL);
1382 g_free(group_id);
1383 group->name = g_strdup(ctx->group_name);
1385 sipe_group_add(sip, group);
1387 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1388 if (buddy) {
1389 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1392 sipe_group_set_user(sip, ctx->user_name);
1394 xmlnode_free(xml);
1395 return TRUE;
1397 return FALSE;
1400 static void sipe_group_context_destroy(gpointer data)
1402 struct group_user_context *ctx = data;
1403 g_free(ctx->group_name);
1404 g_free(ctx->user_name);
1405 g_free(ctx);
1408 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1410 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1411 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1412 gchar *body;
1413 ctx->group_name = g_strdup(name);
1414 ctx->user_name = g_strdup(who);
1415 payload->destroy = sipe_group_context_destroy;
1416 payload->data = ctx;
1418 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1419 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1420 g_free(body);
1424 * Data structure for scheduled actions
1427 struct scheduled_action {
1429 * Name of action.
1430 * Format is <Event>[<Data>...]
1431 * Example: <presence><sip:user@domain.com> or <registration>
1433 gchar *name;
1434 guint timeout_handler;
1435 gboolean repetitive;
1436 Action action;
1437 GDestroyNotify destroy;
1438 struct sipe_account_data *sip;
1439 void *payload;
1443 * A timer callback
1444 * Should return FALSE if repetitive action is not needed
1446 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1448 gboolean ret;
1449 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1450 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1451 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1452 (sched_action->action)(sched_action->sip, sched_action->payload);
1453 ret = sched_action->repetitive;
1454 if (sched_action->destroy) {
1455 (*sched_action->destroy)(sched_action->payload);
1457 g_free(sched_action->name);
1458 g_free(sched_action);
1459 return ret;
1463 * Kills action timer effectively cancelling
1464 * scheduled action
1466 * @param name of action
1468 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1470 GSList *entry;
1472 if (!sip->timeouts || !name) return;
1474 entry = sip->timeouts;
1475 while (entry) {
1476 struct scheduled_action *sched_action = entry->data;
1477 if(sipe_strequal(sched_action->name, name)) {
1478 GSList *to_delete = entry;
1479 entry = entry->next;
1480 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1481 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1482 purple_timeout_remove(sched_action->timeout_handler);
1483 if (sched_action->destroy) {
1484 (*sched_action->destroy)(sched_action->payload);
1486 g_free(sched_action->name);
1487 g_free(sched_action);
1488 } else {
1489 entry = entry->next;
1494 static void
1495 sipe_schedule_action0(const gchar *name,
1496 int timeout,
1497 gboolean isSeconds,
1498 Action action,
1499 GDestroyNotify destroy,
1500 struct sipe_account_data *sip,
1501 void *payload)
1503 struct scheduled_action *sched_action;
1505 /* Make sure each action only exists once */
1506 sipe_cancel_scheduled_action(sip, name);
1508 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1509 sched_action = g_new0(struct scheduled_action, 1);
1510 sched_action->repetitive = FALSE;
1511 sched_action->name = g_strdup(name);
1512 sched_action->action = action;
1513 sched_action->destroy = destroy;
1514 sched_action->sip = sip;
1515 sched_action->payload = payload;
1516 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1517 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1518 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1519 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1522 void
1523 sipe_schedule_action(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, TRUE, action, destroy, sip, payload);
1534 * Same as sipe_schedule_action() but timeout is in milliseconds.
1536 static void
1537 sipe_schedule_action_msec(const gchar *name,
1538 int timeout,
1539 Action action,
1540 GDestroyNotify destroy,
1541 struct sipe_account_data *sip,
1542 void *payload)
1544 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1547 static void
1548 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1549 time_t calculate_from);
1551 static int
1552 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1554 static const char*
1555 sipe_get_status_by_availability(int avail,
1556 char** activity);
1558 static void
1559 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1560 const char *status_id,
1561 const char *message,
1562 time_t do_not_publish[]);
1564 static void
1565 sipe_apply_calendar_status(struct sipe_account_data *sip,
1566 struct sipe_buddy *sbuddy,
1567 const char *status_id)
1569 time_t cal_avail_since;
1570 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1571 int avail;
1572 gchar *self_uri;
1574 if (!sbuddy) return;
1576 if (cal_status < SIPE_CAL_NO_DATA) {
1577 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_status : %d for %s\n", cal_status, sbuddy->name);
1578 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1581 /* scheduled Cal update call */
1582 if (!status_id) {
1583 status_id = sbuddy->last_non_cal_status_id;
1584 g_free(sbuddy->activity);
1585 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1588 if (!status_id) {
1589 purple_debug_info("sipe", "sipe_apply_calendar_status: status_id is NULL for %s, exiting.\n",
1590 sbuddy->name ? sbuddy->name : "" );
1591 return;
1594 /* adjust to calendar status */
1595 if (cal_status != SIPE_CAL_NO_DATA) {
1596 purple_debug_info("sipe", "sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1598 if (cal_status == SIPE_CAL_BUSY
1599 && cal_avail_since > sbuddy->user_avail_since
1600 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1602 status_id = SIPE_STATUS_ID_BUSY;
1603 g_free(sbuddy->activity);
1604 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1606 avail = sipe_get_availability_by_status(status_id, NULL);
1608 purple_debug_info("sipe", "sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1609 if (cal_avail_since > sbuddy->activity_since) {
1610 if (cal_status == SIPE_CAL_OOF
1611 && avail >= 15000) /* 12000 in 2007 */
1613 g_free(sbuddy->activity);
1614 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1619 /* then set status_id actually */
1620 purple_debug_info("sipe", "sipe_apply_calendar_status: to %s for %s\n", status_id, sbuddy->name ? sbuddy->name : "" );
1621 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1623 /* set our account state to the one in roaming (including calendar info) */
1624 self_uri = sip_uri_self(sip);
1625 if (sip->initial_state_published && sipe_strequal(sbuddy->name, self_uri)) {
1626 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1627 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1630 purple_debug_info("sipe", "sipe_apply_calendar_status: switch to '%s' for the account\n", sip->status);
1631 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1633 g_free(self_uri);
1636 static void
1637 sipe_got_user_status(struct sipe_account_data *sip,
1638 const char* uri,
1639 const char *status_id)
1641 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1643 if (!sbuddy) return;
1645 /* Check if on 2005 system contact's calendar,
1646 * then set/preserve it.
1648 if (!sip->ocs2007) {
1649 sipe_apply_calendar_status(sip, sbuddy, status_id);
1650 } else {
1651 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1655 static void
1656 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1657 struct sipe_buddy *sbuddy,
1658 struct sipe_account_data *sip)
1660 sipe_apply_calendar_status(sip, sbuddy, NULL);
1664 * Updates contact's status
1665 * based on their calendar information.
1667 * Applicability: 2005 systems
1669 static void
1670 update_calendar_status(struct sipe_account_data *sip)
1672 purple_debug_info("sipe", "update_calendar_status() started.\n");
1673 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1675 /* repeat scheduling */
1676 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1680 * Schedules process of contacts' status update
1681 * based on their calendar information.
1682 * Should be scheduled to the beginning of every
1683 * 15 min interval, like:
1684 * 13:00, 13:15, 13:30, 13:45, etc.
1686 * Applicability: 2005 systems
1688 static void
1689 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1690 time_t calculate_from)
1692 int interval = 15*60;
1693 /** start of the beginning of closest 15 min interval. */
1694 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1696 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1697 asctime(localtime(&calculate_from)));
1698 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1699 asctime(localtime(&next_start)));
1701 sipe_schedule_action("<+2005-cal-status>",
1702 (int)(next_start - time(NULL)),
1703 (Action)update_calendar_status,
1704 NULL,
1705 sip,
1706 NULL);
1710 * Schedules process of self status publish
1711 * based on own calendar information.
1712 * Should be scheduled to the beginning of every
1713 * 15 min interval, like:
1714 * 13:00, 13:15, 13:30, 13:45, etc.
1716 * Applicability: 2007+ systems
1718 static void
1719 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1720 time_t calculate_from)
1722 int interval = 5*60;
1723 /** start of the beginning of closest 5 min interval. */
1724 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1726 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1727 asctime(localtime(&calculate_from)));
1728 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1729 asctime(localtime(&next_start)));
1731 sipe_schedule_action("<+2007-cal-status>",
1732 (int)(next_start - time(NULL)),
1733 (Action)publish_calendar_status_self,
1734 NULL,
1735 sip,
1736 NULL);
1739 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1741 /** Should be g_free()'d
1743 static gchar *
1744 sipe_get_subscription_key(const gchar *event,
1745 const gchar *with)
1747 gchar *key = NULL;
1749 if (is_empty(event)) return NULL;
1751 if (event && !g_ascii_strcasecmp(event, "presence")) {
1752 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1753 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1755 /* @TODO drop participated buddies' just_added flag */
1756 } else if (event) {
1757 /* Subscription is identified by <event> key */
1758 key = g_strdup_printf("<%s>", event);
1761 return key;
1764 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1765 SIPE_UNUSED_PARAMETER struct transaction *trans)
1767 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1768 gchar *event = sipmsg_find_header(msg, "Event");
1769 gchar *key;
1771 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1772 if (!event) {
1773 struct sipmsg *request_msg = trans->msg;
1774 event = sipmsg_find_header(request_msg, "Event");
1777 key = sipe_get_subscription_key(event, with);
1779 /* 200 OK; 481 Call Leg Does Not Exist */
1780 if (key && (msg->response == 200 || msg->response == 481)) {
1781 if (g_hash_table_lookup(sip->subscriptions, key)) {
1782 g_hash_table_remove(sip->subscriptions, key);
1783 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1787 /* create/store subscription dialog if not yet */
1788 if (msg->response == 200) {
1789 gchar *callid = sipmsg_find_header(msg, "Call-ID");
1790 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1792 if (key) {
1793 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1794 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1796 subscription->dialog.callid = g_strdup(callid);
1797 subscription->dialog.cseq = atoi(cseq);
1798 subscription->dialog.with = g_strdup(with);
1799 subscription->event = g_strdup(event);
1800 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1802 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1805 g_free(cseq);
1808 g_free(key);
1809 g_free(with);
1811 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1813 process_incoming_notify(sip, msg, FALSE, FALSE);
1815 return TRUE;
1818 static void sipe_subscribe_resource_uri(const char *name,
1819 SIPE_UNUSED_PARAMETER gpointer value,
1820 gchar **resources_uri)
1822 gchar *tmp = *resources_uri;
1823 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1824 g_free(tmp);
1827 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1829 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1830 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1831 gchar *tmp = *resources_uri;
1833 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1835 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1836 g_free(tmp);
1840 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1841 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1842 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1843 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1844 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1847 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1849 gchar *key;
1850 gchar *contact = get_contact(sip);
1851 gchar *request;
1852 gchar *content;
1853 gchar *require = "";
1854 gchar *accept = "";
1855 gchar *autoextend = "";
1856 gchar *content_type;
1857 struct sip_dialog *dialog;
1859 if (sip->ocs2007) {
1860 require = ", categoryList";
1861 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1862 content_type = "application/msrtc-adrl-categorylist+xml";
1863 content = g_strdup_printf(
1864 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1865 "<action name=\"subscribe\" id=\"63792024\">\n"
1866 "<adhocList>\n%s</adhocList>\n"
1867 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1868 "<category name=\"calendarData\"/>\n"
1869 "<category name=\"contactCard\"/>\n"
1870 "<category name=\"note\"/>\n"
1871 "<category name=\"state\"/>\n"
1872 "</categoryList>\n"
1873 "</action>\n"
1874 "</batchSub>", sip->username, resources_uri);
1875 } else {
1876 autoextend = "Supported: com.microsoft.autoextend\r\n";
1877 content_type = "application/adrl+xml";
1878 content = g_strdup_printf(
1879 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1880 "<create xmlns=\"\">\n%s</create>\n"
1881 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1883 g_free(resources_uri);
1885 request = g_strdup_printf(
1886 "Require: adhoclist%s\r\n"
1887 "Supported: eventlist\r\n"
1888 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1889 "Supported: ms-piggyback-first-notify\r\n"
1890 "%sSupported: ms-benotify\r\n"
1891 "Proxy-Require: ms-benotify\r\n"
1892 "Event: presence\r\n"
1893 "Content-Type: %s\r\n"
1894 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1895 g_free(contact);
1897 /* subscribe to buddy presence */
1898 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1899 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1900 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1901 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1903 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1905 g_free(content);
1906 g_free(to);
1907 g_free(request);
1908 g_free(key);
1911 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1912 SIPE_UNUSED_PARAMETER void *unused)
1914 gchar *to = sip_uri_self(sip);
1915 gchar *resources_uri = g_strdup("");
1916 if (sip->ocs2007) {
1917 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1918 } else {
1919 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1922 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1925 struct presence_batched_routed {
1926 gchar *host;
1927 GSList *buddies;
1930 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1932 struct presence_batched_routed *data = payload;
1933 GSList *buddies = data->buddies;
1934 while (buddies) {
1935 g_free(buddies->data);
1936 buddies = buddies->next;
1938 g_slist_free(data->buddies);
1939 g_free(data->host);
1940 g_free(payload);
1943 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1945 struct presence_batched_routed *data = payload;
1946 GSList *buddies = data->buddies;
1947 gchar *resources_uri = g_strdup("");
1948 while (buddies) {
1949 gchar *tmp = resources_uri;
1950 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1951 g_free(tmp);
1952 buddies = buddies->next;
1954 sipe_subscribe_presence_batched_to(sip, resources_uri,
1955 g_strdup(data->host));
1959 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1960 * The user sends a single SUBSCRIBE request to the subscribed contact.
1961 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1965 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1968 gchar *key;
1969 gchar *to = sip_uri((char *)buddy_name);
1970 gchar *tmp = get_contact(sip);
1971 gchar *request;
1972 gchar *content = NULL;
1973 gchar *autoextend = "";
1974 gchar *content_type = "";
1975 struct sip_dialog *dialog;
1976 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
1977 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1979 if (sbuddy) sbuddy->just_added = FALSE;
1981 if (sip->ocs2007) {
1982 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1983 } else {
1984 autoextend = "Supported: com.microsoft.autoextend\r\n";
1987 request = g_strdup_printf(
1988 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1989 "Supported: ms-piggyback-first-notify\r\n"
1990 "%s%sSupported: ms-benotify\r\n"
1991 "Proxy-Require: ms-benotify\r\n"
1992 "Event: presence\r\n"
1993 "Contact: %s\r\n", autoextend, content_type, tmp);
1995 if (sip->ocs2007) {
1996 content = g_strdup_printf(
1997 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1998 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1999 "<resource uri=\"%s\"%s\n"
2000 "</adhocList>\n"
2001 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2002 "<category name=\"calendarData\"/>\n"
2003 "<category name=\"contactCard\"/>\n"
2004 "<category name=\"note\"/>\n"
2005 "<category name=\"state\"/>\n"
2006 "</categoryList>\n"
2007 "</action>\n"
2008 "</batchSub>", sip->username, to, context);
2011 g_free(tmp);
2013 /* subscribe to buddy presence */
2014 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2015 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2016 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2017 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2019 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2021 g_free(content);
2022 g_free(to);
2023 g_free(request);
2024 g_free(key);
2027 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2029 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2031 if (!purple_status_is_active(status))
2032 return;
2034 if (account->gc) {
2035 struct sipe_account_data *sip = account->gc->proto_data;
2037 if (sip) {
2038 gchar *action_name;
2039 gchar *tmp;
2040 time_t now = time(NULL);
2041 const char *status_id = purple_status_get_id(status);
2042 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2043 sipe_activity activity = sipe_get_activity_by_token(status_id);
2044 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2046 /* when other point of presence clears note, but we are keeping
2047 * state if OOF note.
2049 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2050 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2051 do_not_publish = FALSE;
2054 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2055 status_id, (int)sip->do_not_publish[activity], (int)now);
2057 sip->do_not_publish[activity] = 0;
2058 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2059 status_id, (int)sip->do_not_publish[activity]);
2061 if (do_not_publish)
2063 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2064 return;
2067 g_free(sip->status);
2068 sip->status = g_strdup(status_id);
2070 /* hack to escape apostrof before comparison */
2071 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2073 /* this will preserve OOF flag as well */
2074 if (!sipe_strequal(tmp, sip->note)) {
2075 sip->is_oof_note = FALSE;
2076 g_free(sip->note);
2077 sip->note = g_strdup(note);
2078 sip->note_since = time(NULL);
2080 g_free(tmp);
2082 /* schedule 2 sec to capture idle flag */
2083 action_name = g_strdup_printf("<%s>", "+set-status");
2084 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2085 g_free(action_name);
2089 static void
2090 sipe_set_idle(PurpleConnection * gc,
2091 int interval)
2093 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2095 if (gc) {
2096 struct sipe_account_data *sip = gc->proto_data;
2098 if (sip) {
2099 sip->idle_switch = time(NULL);
2100 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2105 static void
2106 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2107 SIPE_UNUSED_PARAMETER const char *alias)
2109 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2110 sipe_group_set_user(sip, name);
2113 static void
2114 sipe_group_buddy(PurpleConnection *gc,
2115 const char *who,
2116 const char *old_group_name,
2117 const char *new_group_name)
2119 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2120 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2121 struct sipe_group * old_group = NULL;
2122 struct sipe_group * new_group;
2124 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2125 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2127 if(!buddy) { // buddy not in roaming list
2128 return;
2131 if (old_group_name) {
2132 old_group = sipe_group_find_by_name(sip, old_group_name);
2134 new_group = sipe_group_find_by_name(sip, new_group_name);
2136 if (old_group) {
2137 buddy->groups = g_slist_remove(buddy->groups, old_group);
2138 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2141 if (!new_group) {
2142 sipe_group_create(sip, new_group_name, who);
2143 } else {
2144 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2145 sipe_group_set_user(sip, who);
2149 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2151 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2153 /* libpurple can call us with undefined buddy or group */
2154 if (buddy && group) {
2155 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2157 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2158 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2159 purple_blist_rename_buddy(buddy, buddy_name);
2160 g_free(buddy_name);
2162 /* Prepend sip: if needed */
2163 if (!g_str_has_prefix(buddy->name, "sip:")) {
2164 gchar *buf = sip_uri_from_name(buddy->name);
2165 purple_blist_rename_buddy(buddy, buf);
2166 g_free(buf);
2169 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2170 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2171 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2172 b->name = g_strdup(buddy->name);
2173 b->just_added = TRUE;
2174 g_hash_table_insert(sip->buddies, b->name, b);
2175 sipe_group_buddy(gc, b->name, NULL, group->name);
2176 /* @TODO should go to callback */
2177 sipe_subscribe_presence_single(sip, b->name);
2178 } else {
2179 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2184 static void sipe_free_buddy(struct sipe_buddy *buddy)
2186 #ifndef _WIN32
2188 * We are calling g_hash_table_foreach_steal(). That means that no
2189 * key/value deallocation functions are called. Therefore the glib
2190 * hash code does not touch the key (buddy->name) or value (buddy)
2191 * of the to-be-deleted hash node at all. It follows that we
2193 * - MUST free the memory for the key ourselves and
2194 * - ARE allowed to do it in this function
2196 * Conclusion: glib must be broken on the Windows platform if sipe
2197 * crashes with SIGTRAP when closing. You'll have to live
2198 * with the memory leak until this is fixed.
2200 g_free(buddy->name);
2201 #endif
2202 g_free(buddy->activity);
2203 g_free(buddy->meeting_subject);
2204 g_free(buddy->meeting_location);
2205 g_free(buddy->note);
2207 g_free(buddy->cal_start_time);
2208 g_free(buddy->cal_free_busy_base64);
2209 g_free(buddy->cal_free_busy);
2210 g_free(buddy->last_non_cal_activity);
2212 sipe_cal_free_working_hours(buddy->cal_working_hours);
2214 g_free(buddy->device_name);
2215 g_slist_free(buddy->groups);
2216 g_free(buddy);
2220 * Unassociates buddy from group first.
2221 * Then see if no groups left, removes buddy completely.
2222 * Otherwise updates buddy groups on server.
2224 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2226 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2227 struct sipe_buddy *b;
2228 struct sipe_group *g = NULL;
2230 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2231 if (!buddy) return;
2233 b = g_hash_table_lookup(sip->buddies, buddy->name);
2234 if (!b) return;
2236 if (group) {
2237 g = sipe_group_find_by_name(sip, group->name);
2240 if (g) {
2241 b->groups = g_slist_remove(b->groups, g);
2242 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2245 if (g_slist_length(b->groups) < 1) {
2246 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2247 sipe_cancel_scheduled_action(sip, action_name);
2248 g_free(action_name);
2250 g_hash_table_remove(sip->buddies, buddy->name);
2252 if (b->name) {
2253 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2254 send_soap_request(sip, body);
2255 g_free(body);
2258 sipe_free_buddy(b);
2259 } else {
2260 //updates groups on server
2261 sipe_group_set_user(sip, b->name);
2266 static void
2267 sipe_rename_group(PurpleConnection *gc,
2268 const char *old_name,
2269 PurpleGroup *group,
2270 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2272 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2273 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2274 if (s_group) {
2275 sipe_group_rename(sip, s_group, group->name);
2276 } else {
2277 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2281 static void
2282 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2284 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2285 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2286 if (s_group) {
2287 gchar *body;
2288 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2289 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2290 send_soap_request(sip, body);
2291 g_free(body);
2293 sip->groups = g_slist_remove(sip->groups, s_group);
2294 g_free(s_group->name);
2295 g_free(s_group);
2296 } else {
2297 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2301 /** All statuses need message attribute to pass Note */
2302 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2304 PurpleStatusType *type;
2305 GList *types = NULL;
2307 /* Macros to reduce code repetition.
2308 Translators: noun */
2309 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2310 prim, id, name, \
2311 TRUE, user, FALSE, \
2312 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2313 NULL); \
2314 types = g_list_append(types, type);
2316 /* Online */
2317 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2318 NULL,
2319 NULL,
2320 TRUE);
2322 /* Busy */
2323 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2324 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2325 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2326 TRUE);
2328 /* Do Not Disturb */
2329 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2330 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2331 NULL,
2332 TRUE);
2334 /* Away */
2335 /* Goes first in the list as
2336 * purple picks the first status with the AWAY type
2337 * for idle.
2339 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2340 NULL,
2341 NULL,
2342 TRUE);
2344 /* Be Right Back */
2345 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2346 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2347 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2348 TRUE);
2350 /* Appear Offline */
2351 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2352 NULL,
2353 NULL,
2354 TRUE);
2356 /* Offline (not user settable) */
2357 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE,
2358 NULL,
2359 NULL,
2360 FALSE);
2362 return types;
2366 * A callback for g_hash_table_foreach
2368 static void
2369 sipe_buddy_subscribe_cb(char *buddy_name,
2370 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2371 struct sipe_account_data *sip)
2373 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2374 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2375 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2376 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2378 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2379 g_free(action_name);
2383 * Removes entries from purple buddy list
2384 * that does not correspond ones in the roaming contact list.
2386 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2387 GSList *buddies = purple_find_buddies(sip->account, NULL);
2388 GSList *entry = buddies;
2389 struct sipe_buddy *buddy;
2390 PurpleBuddy *b;
2391 PurpleGroup *g;
2393 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2394 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2395 while (entry) {
2396 b = entry->data;
2397 g = purple_buddy_get_group(b);
2398 buddy = g_hash_table_lookup(sip->buddies, b->name);
2399 if(buddy) {
2400 gboolean in_sipe_groups = FALSE;
2401 GSList *entry2 = buddy->groups;
2402 while (entry2) {
2403 struct sipe_group *group = entry2->data;
2404 if (sipe_strequal(group->name, g->name)) {
2405 in_sipe_groups = TRUE;
2406 break;
2408 entry2 = entry2->next;
2410 if(!in_sipe_groups) {
2411 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2412 purple_blist_remove_buddy(b);
2414 } else {
2415 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2416 purple_blist_remove_buddy(b);
2418 entry = entry->next;
2420 g_slist_free(buddies);
2423 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2425 int len = msg->bodylen;
2427 gchar *tmp = sipmsg_find_header(msg, "Event");
2428 xmlnode *item;
2429 xmlnode *isc;
2430 const gchar *contacts_delta;
2431 xmlnode *group_node;
2432 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2433 return FALSE;
2436 /* Convert the contact from XML to Purple Buddies */
2437 isc = xmlnode_from_str(msg->body, len);
2438 if (!isc) {
2439 return FALSE;
2442 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2443 if (contacts_delta) {
2444 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2447 if (sipe_strequal(isc->name, "contactList")) {
2449 /* Parse groups */
2450 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2451 struct sipe_group * group = g_new0(struct sipe_group, 1);
2452 const char *name = xmlnode_get_attrib(group_node, "name");
2454 if (g_str_has_prefix(name, "~")) {
2455 name = _("Other Contacts");
2457 group->name = g_strdup(name);
2458 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2460 sipe_group_add(sip, group);
2463 // Make sure we have at least one group
2464 if (g_slist_length(sip->groups) == 0) {
2465 struct sipe_group * group = g_new0(struct sipe_group, 1);
2466 PurpleGroup *purple_group;
2467 group->name = g_strdup(_("Other Contacts"));
2468 group->id = 1;
2469 purple_group = purple_group_new(group->name);
2470 purple_blist_add_group(purple_group, NULL);
2471 sip->groups = g_slist_append(sip->groups, group);
2474 /* Parse contacts */
2475 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2476 const gchar *uri = xmlnode_get_attrib(item, "uri");
2477 const gchar *name = xmlnode_get_attrib(item, "name");
2478 gchar *buddy_name;
2479 struct sipe_buddy *buddy = NULL;
2480 gchar *tmp;
2481 gchar **item_groups;
2482 int i = 0;
2484 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2485 tmp = sip_uri_from_name(uri);
2486 buddy_name = g_ascii_strdown(tmp, -1);
2487 g_free(tmp);
2489 /* assign to group Other Contacts if nothing else received */
2490 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2491 if(is_empty(tmp)) {
2492 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2493 g_free(tmp);
2494 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2496 item_groups = g_strsplit(tmp, " ", 0);
2497 g_free(tmp);
2499 while (item_groups[i]) {
2500 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2502 // If couldn't find the right group for this contact, just put them in the first group we have
2503 if (group == NULL && g_slist_length(sip->groups) > 0) {
2504 group = sip->groups->data;
2507 if (group != NULL) {
2508 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2509 if (!b){
2510 b = purple_buddy_new(sip->account, buddy_name, uri);
2511 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2513 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2516 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2517 if (name != NULL && strlen(name) != 0) {
2518 purple_blist_alias_buddy(b, name);
2520 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2524 if (!buddy) {
2525 buddy = g_new0(struct sipe_buddy, 1);
2526 buddy->name = g_strdup(b->name);
2527 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2530 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2532 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2533 } else {
2534 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2535 name);
2538 i++;
2539 } // while, contact groups
2540 g_strfreev(item_groups);
2541 g_free(buddy_name);
2543 } // for, contacts
2545 sipe_cleanup_local_blist(sip);
2547 /* Add self-contact if not there yet. 2005 systems. */
2548 /* This will resemble subscription to roaming_self in 2007 systems */
2549 if (!sip->ocs2007) {
2550 gchar *self_uri = sip_uri_self(sip);
2551 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2553 if (!buddy) {
2554 buddy = g_new0(struct sipe_buddy, 1);
2555 buddy->name = g_strdup(self_uri);
2556 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2558 g_free(self_uri);
2561 xmlnode_free(isc);
2563 /* subscribe to buddies */
2564 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2565 if (sip->batched_support) {
2566 sipe_subscribe_presence_batched(sip, NULL);
2567 } else {
2568 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2570 sip->subscribed_buddies = TRUE;
2572 /* for 2005 systems schedule contacts' status update
2573 * based on their calendar information
2575 if (!sip->ocs2007) {
2576 sipe_sched_calendar_status_update(sip, time(NULL));
2579 return 0;
2583 * Subscribe roaming contacts
2585 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2587 gchar *to = sip_uri_self(sip);
2588 gchar *tmp = get_contact(sip);
2589 gchar *hdr = g_strdup_printf(
2590 "Event: vnd-microsoft-roaming-contacts\r\n"
2591 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2592 "Supported: com.microsoft.autoextend\r\n"
2593 "Supported: ms-benotify\r\n"
2594 "Proxy-Require: ms-benotify\r\n"
2595 "Supported: ms-piggyback-first-notify\r\n"
2596 "Contact: %s\r\n", tmp);
2597 g_free(tmp);
2599 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2600 g_free(to);
2601 g_free(hdr);
2604 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2605 SIPE_UNUSED_PARAMETER void *unused)
2607 gchar *key;
2608 struct sip_dialog *dialog;
2609 gchar *to = sip_uri_self(sip);
2610 gchar *tmp = get_contact(sip);
2611 gchar *hdr = g_strdup_printf(
2612 "Event: presence.wpending\r\n"
2613 "Accept: text/xml+msrtc.wpending\r\n"
2614 "Supported: com.microsoft.autoextend\r\n"
2615 "Supported: ms-benotify\r\n"
2616 "Proxy-Require: ms-benotify\r\n"
2617 "Supported: ms-piggyback-first-notify\r\n"
2618 "Contact: %s\r\n", tmp);
2619 g_free(tmp);
2621 /* Subscription is identified by <event> key */
2622 key = g_strdup_printf("<%s>", "presence.wpending");
2623 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2624 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2626 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2628 g_free(to);
2629 g_free(hdr);
2630 g_free(key);
2634 * Fires on deregistration event initiated by server.
2635 * [MS-SIPREGE] SIP extension.
2638 // 2007 Example
2640 // Content-Type: text/registration-event
2641 // subscription-state: terminated;expires=0
2642 // ms-diagnostics-public: 4141;reason="User disabled"
2644 // deregistered;event=rejected
2646 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2648 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2649 gchar *event = NULL;
2650 gchar *reason = NULL;
2651 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2653 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2654 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2656 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2657 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2658 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2659 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2660 } else {
2661 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2662 return;
2665 if (warning != NULL) {
2666 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2667 } else { // for LCS2005
2668 int error_id = 0;
2669 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2670 error_id = 4140; // [MS-SIPREGE]
2671 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2672 reason = g_strdup(_("you are already signed in at another location"));
2673 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2674 error_id = 4141;
2675 reason = g_strdup(_("user disabled")); // [MS-OCER]
2676 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2677 error_id = 4142;
2678 reason = g_strdup(_("user moved")); // [MS-OCER]
2681 g_free(event);
2682 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2683 g_free(reason);
2685 sip->gc->wants_to_die = TRUE;
2686 purple_connection_error(sip->gc, warning);
2687 g_free(warning);
2691 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2693 xmlnode *xn_provision_group_list;
2694 xmlnode *node;
2696 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2698 /* provisionGroup */
2699 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2700 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2701 g_free(sip->focus_factory_uri);
2702 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2703 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2704 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2705 break;
2708 xmlnode_free(xn_provision_group_list);
2711 /** for 2005 system */
2712 static void
2713 sipe_process_provisioning(struct sipe_account_data *sip,
2714 struct sipmsg *msg)
2716 xmlnode *xn_provision;
2717 xmlnode *node;
2719 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2720 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2721 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2722 if ((node = xmlnode_get_child(node, "line"))) {
2723 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2724 const gchar *server = xmlnode_get_attrib(node, "server");
2725 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2726 sip_csta_open(sip, line_uri, server);
2729 xmlnode_free(xn_provision);
2732 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2734 const gchar *contacts_delta;
2735 xmlnode *xml;
2737 xml = xmlnode_from_str(msg->body, msg->bodylen);
2738 if (!xml)
2740 return;
2743 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2744 if (contacts_delta)
2746 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2749 xmlnode_free(xml);
2752 static void
2753 free_container(struct sipe_container *container)
2755 GSList *entry;
2757 if (!container) return;
2759 entry = container->members;
2760 while (entry) {
2761 void *data = entry->data;
2762 entry = g_slist_remove(entry, data);
2763 g_free(data);
2765 g_free(container);
2769 * Finds locally stored MS-PRES container member
2771 static struct sipe_container_member *
2772 sipe_find_container_member(struct sipe_container *container,
2773 const gchar *type,
2774 const gchar *value)
2776 struct sipe_container_member *member;
2777 GSList *entry;
2779 if (container == NULL || type == NULL) {
2780 return NULL;
2783 entry = container->members;
2784 while (entry) {
2785 member = entry->data;
2786 if (!g_strcasecmp(member->type, type)
2787 && ((!member->value && !value)
2788 || (value && member->value && !g_strcasecmp(member->value, value)))
2790 return member;
2792 entry = entry->next;
2794 return NULL;
2798 * Finds locally stored MS-PRES container by id
2800 static struct sipe_container *
2801 sipe_find_container(struct sipe_account_data *sip,
2802 guint id)
2804 struct sipe_container *container;
2805 GSList *entry;
2807 if (sip == NULL) {
2808 return NULL;
2811 entry = sip->containers;
2812 while (entry) {
2813 container = entry->data;
2814 if (id == container->id) {
2815 return container;
2817 entry = entry->next;
2819 return NULL;
2823 * Access Levels
2824 * 32000 - Blocked
2825 * 400 - Personal
2826 * 300 - Team
2827 * 200 - Company
2828 * 100 - Public
2830 static int
2831 sipe_find_access_level(struct sipe_account_data *sip,
2832 const gchar *type,
2833 const gchar *value)
2835 guint containers[] = {32000, 400, 300, 200, 100};
2836 int i = 0;
2838 for (i = 0; i < 5; i++) {
2839 struct sipe_container_member *member;
2840 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2841 if (!container) continue;
2843 member = sipe_find_container_member(container, type, value);
2844 if (member) {
2845 return containers[i];
2849 return -1;
2852 static void
2853 sipe_send_set_container_members(struct sipe_account_data *sip,
2854 guint container_id,
2855 guint container_version,
2856 const gchar* action,
2857 const gchar* type,
2858 const gchar* value)
2860 gchar *self = sip_uri_self(sip);
2861 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2862 gchar *contact;
2863 gchar *hdr;
2864 gchar *body = g_strdup_printf(
2865 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2866 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2867 "</setContainerMembers>",
2868 container_id,
2869 container_version,
2870 action,
2871 type,
2872 value_str);
2873 g_free(value_str);
2875 contact = get_contact(sip);
2876 hdr = g_strdup_printf("Contact: %s\r\n"
2877 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2878 g_free(contact);
2880 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2882 g_free(hdr);
2883 g_free(body);
2884 g_free(self);
2887 static void
2888 free_publication(struct sipe_publication *publication)
2890 g_free(publication->category);
2891 g_free(publication->cal_event_hash);
2892 g_free(publication->note);
2894 g_free(publication->working_hours_xml_str);
2895 g_free(publication->fb_start_str);
2896 g_free(publication->free_busy_base64);
2898 g_free(publication);
2901 /* key is <category><instance><container> */
2902 static gboolean
2903 sipe_is_our_publication(struct sipe_account_data *sip,
2904 const gchar *key)
2906 GSList *entry;
2908 /* filling keys for our publications if not yet cached */
2909 if (!sip->our_publication_keys) {
2910 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2911 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2912 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2913 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2914 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2915 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2916 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2918 purple_debug_info("sipe", "* Our Publication Instances *\n");
2919 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2920 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2921 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2922 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2923 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2924 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2925 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2926 purple_debug_info("sipe", "\tNote : %u\n", 0);
2927 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2929 /* device */
2930 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2931 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2933 /* state:machineState */
2934 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2935 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2936 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2937 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2939 /* state:userState */
2940 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2941 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2942 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2943 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2945 /* state:calendarState */
2946 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2947 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2948 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2949 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2951 /* state:calendarState OOF */
2952 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2953 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2954 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2955 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2957 /* note */
2958 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2959 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2960 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2961 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2962 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2963 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2965 /* note OOF */
2966 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2967 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2968 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2969 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2970 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2971 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2973 /* calendarData:WorkingHours */
2974 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2975 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2976 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2977 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2978 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2979 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2980 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2981 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2982 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2983 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2984 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2985 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2987 /* calendarData:FreeBusy */
2988 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2989 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2990 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2991 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2992 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2993 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2994 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2995 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2996 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2997 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2998 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2999 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3001 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
3002 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3005 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3007 entry = sip->our_publication_keys;
3008 while (entry) {
3009 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3010 if (sipe_strequal(entry->data, key)) {
3011 return TRUE;
3013 entry = entry->next;
3015 return FALSE;
3018 /** Property names to store in blist.xml */
3019 #define ALIAS_PROP "alias"
3020 #define EMAIL_PROP "email"
3021 #define PHONE_PROP "phone"
3022 #define PHONE_DISPLAY_PROP "phone-display"
3023 #define PHONE_MOBILE_PROP "phone-mobile"
3024 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3025 #define PHONE_HOME_PROP "phone-home"
3026 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3027 #define PHONE_OTHER_PROP "phone-other"
3028 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3029 #define PHONE_CUSTOM1_PROP "phone-custom1"
3030 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3031 #define SITE_PROP "site"
3032 #define COMPANY_PROP "company"
3033 #define DEPARTMENT_PROP "department"
3034 #define TITLE_PROP "title"
3035 #define OFFICE_PROP "office"
3036 /** implies work address */
3037 #define ADDRESS_STREET_PROP "address-street"
3038 #define ADDRESS_CITY_PROP "address-city"
3039 #define ADDRESS_STATE_PROP "address-state"
3040 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3041 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3044 * Tries to figure out user first and last name
3045 * based on Display Name and email properties.
3047 * Allocates memory - must be g_free()'d
3049 * Examples to parse:
3050 * First Last
3051 * First Last - Company Name
3052 * Last, First
3053 * Last, First M.
3054 * Last, First (C)(STP) (Company)
3055 * first.last@company.com (preprocessed as "first last")
3056 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3058 * Unusable examples:
3059 * user@company.com (preprocessed as "user")
3060 * first.m.last@company.com (preprocessed as "first m last")
3061 * user.company.com@reuters.net (preprocessed as "user company com")
3063 static void
3064 sipe_get_first_last_names(struct sipe_account_data *sip,
3065 const char *uri,
3066 char **first_name,
3067 char **last_name)
3069 PurpleBuddy *p_buddy;
3070 char *display_name;
3071 const char *email;
3072 const char *first, *last;
3073 char *tmp;
3074 char **parts;
3075 gboolean has_comma = FALSE;
3077 if (!sip || !uri) return;
3079 p_buddy = purple_find_buddy(sip->account, uri);
3081 if (!p_buddy) return;
3083 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3084 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3086 if (!display_name && !email) return;
3088 /* if no display name, make "first last anything_else" out of email */
3089 if (email && !display_name) {
3090 display_name = g_strndup(email, strstr(email, "@") - email);
3091 display_name = purple_strreplace((tmp = display_name), ".", " ");
3092 g_free(tmp);
3095 if (display_name) {
3096 has_comma = (strstr(display_name, ",") != NULL);
3097 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3098 g_free(tmp);
3099 display_name = purple_strreplace((tmp = display_name), ",", " ");
3100 g_free(tmp);
3103 parts = g_strsplit(display_name, " ", 0);
3105 if (!parts[0] || !parts[1]) {
3106 g_free(display_name);
3107 g_strfreev(parts);
3108 return;
3111 if (has_comma) {
3112 last = parts[0];
3113 first = parts[1];
3114 } else {
3115 first = parts[0];
3116 last = parts[1];
3119 if (first_name) {
3120 *first_name = g_strstrip(g_strdup(first));
3123 if (last_name) {
3124 *last_name = g_strstrip(g_strdup(last));
3127 g_free(display_name);
3128 g_strfreev(parts);
3132 * Update user information
3134 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3135 * @param property_name
3136 * @param property_value may be modified to strip white space
3138 static void
3139 sipe_update_user_info(struct sipe_account_data *sip,
3140 const char *uri,
3141 const char *property_name,
3142 char *property_value)
3144 GSList *buddies, *entry;
3146 if (!property_name || strlen(property_name) == 0) return;
3148 if (property_value)
3149 property_value = g_strstrip(property_value);
3151 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3152 while (entry) {
3153 const char *prop_str;
3154 const char *server_alias;
3155 PurpleBuddy *p_buddy = entry->data;
3157 /* for Display Name */
3158 if (sipe_strequal(property_name, ALIAS_PROP)) {
3159 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3160 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3161 purple_blist_alias_buddy(p_buddy, property_value);
3164 server_alias = purple_buddy_get_server_alias(p_buddy);
3165 if (!is_empty(property_value) &&
3166 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3168 purple_blist_server_alias_buddy(p_buddy, property_value);
3171 /* for other properties */
3172 else {
3173 if (!is_empty(property_value)) {
3174 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3175 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3176 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3181 entry = entry->next;
3183 g_slist_free(buddies);
3187 * Update user phone
3188 * Suitable for both 2005 and 2007 systems.
3190 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3191 * @param phone_type
3192 * @param phone may be modified to strip white space
3193 * @param phone_display_string may be modified to strip white space
3195 static void
3196 sipe_update_user_phone(struct sipe_account_data *sip,
3197 const char *uri,
3198 const gchar *phone_type,
3199 gchar *phone,
3200 gchar *phone_display_string)
3202 const char *phone_node = PHONE_PROP; /* work phone by default */
3203 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3205 if(!phone || strlen(phone) == 0) return;
3207 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3208 phone_node = PHONE_MOBILE_PROP;
3209 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3210 } else if (sipe_strequal(phone_type, "home")) {
3211 phone_node = PHONE_HOME_PROP;
3212 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3213 } else if (sipe_strequal(phone_type, "other")) {
3214 phone_node = PHONE_OTHER_PROP;
3215 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3216 } else if (sipe_strequal(phone_type, "custom1")) {
3217 phone_node = PHONE_CUSTOM1_PROP;
3218 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3221 sipe_update_user_info(sip, uri, phone_node, phone);
3222 if (phone_display_string) {
3223 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3227 static void
3228 sipe_update_calendar(struct sipe_account_data *sip)
3230 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3232 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3234 if (sipe_strequal(calendar, "EXCH")) {
3235 sipe_ews_update_calendar(sip);
3238 /* schedule repeat */
3239 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3241 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3245 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3246 * by using standard Purple's means of signals and saved statuses.
3248 * Thus all UI elements get updated: Status Button with Note, docklet.
3249 * This is ablolutely important as both our status and note can come
3250 * inbound (roaming) or be updated programmatically (e.g. based on our
3251 * calendar data).
3253 static void
3254 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3255 const char *status_id,
3256 const char *message,
3257 time_t do_not_publish[])
3259 PurpleStatus *status = purple_account_get_active_status(account);
3260 gboolean changed = TRUE;
3262 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3263 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3265 changed = FALSE;
3268 if (purple_savedstatus_is_idleaway()) {
3269 changed = FALSE;
3272 if (changed) {
3273 PurpleSavedStatus *saved_status;
3274 const PurpleStatusType *acct_status_type =
3275 purple_status_type_find_with_id(account->status_types, status_id);
3276 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3277 sipe_activity activity = sipe_get_activity_by_token(status_id);
3279 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3280 if (saved_status) {
3281 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3284 /* If this type+message is unique then create a new transient saved status
3285 * Ref: gtkstatusbox.c
3287 if (!saved_status) {
3288 GList *tmp;
3289 GList *active_accts = purple_accounts_get_all_active();
3291 saved_status = purple_savedstatus_new(NULL, primitive);
3292 purple_savedstatus_set_message(saved_status, message);
3294 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3295 purple_savedstatus_set_substatus(saved_status,
3296 (PurpleAccount *)tmp->data, acct_status_type, message);
3298 g_list_free(active_accts);
3301 do_not_publish[activity] = time(NULL);
3302 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3303 status_id, (int)do_not_publish[activity]);
3305 /* Set the status for each account */
3306 purple_savedstatus_activate(saved_status);
3310 struct hash_table_delete_payload {
3311 GHashTable *hash_table;
3312 guint container;
3315 static void
3316 sipe_remove_category_container_publications_cb(const char *name,
3317 struct sipe_publication *publication,
3318 struct hash_table_delete_payload *payload)
3320 if (publication->container == payload->container) {
3321 g_hash_table_remove(payload->hash_table, name);
3324 static void
3325 sipe_remove_category_container_publications(GHashTable *our_publications,
3326 const char *category,
3327 guint container)
3329 struct hash_table_delete_payload payload;
3330 payload.hash_table = g_hash_table_lookup(our_publications, category);
3332 if (!payload.hash_table) return;
3334 payload.container = container;
3335 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3338 static void
3339 send_publish_category_initial(struct sipe_account_data *sip);
3342 * When we receive some self (BE) NOTIFY with a new subscriber
3343 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3346 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3348 gchar *contact;
3349 gchar *to;
3350 xmlnode *xml;
3351 xmlnode *node;
3352 xmlnode *node2;
3353 char *display_name = NULL;
3354 char *uri;
3355 GSList *category_names = NULL;
3356 int aggreg_avail = 0;
3357 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3358 gboolean do_update_status = FALSE;
3359 gboolean has_note_cleaned = FALSE;
3361 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3363 xml = xmlnode_from_str(msg->body, msg->bodylen);
3364 if (!xml) return;
3366 contact = get_contact(sip);
3367 to = sip_uri_self(sip);
3370 /* categories */
3371 /* set list of categories participating in this XML */
3372 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3373 const gchar *name = xmlnode_get_attrib(node, "name");
3374 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3376 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3377 category_names ? (int) g_slist_length(category_names) : -1);
3378 /* drop category information */
3379 if (category_names) {
3380 GSList *entry = category_names;
3381 while (entry) {
3382 GHashTable *cat_publications;
3383 const gchar *category = entry->data;
3384 entry = entry->next;
3385 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3386 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3387 if (cat_publications) {
3388 g_hash_table_remove(sip->our_publications, category);
3389 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3393 g_slist_free(category_names);
3394 /* filling our categories reflected in roaming data */
3395 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3396 const char *tmp;
3397 const gchar *name = xmlnode_get_attrib(node, "name");
3398 guint container = xmlnode_get_int_attrib(node, "container", -1);
3399 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3400 guint version = xmlnode_get_int_attrib(node, "version", 0);
3401 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3402 sipe_utils_str_to_time(tmp) : 0;
3403 gchar *key;
3404 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3406 /* Ex. clear note: <category name="note"/> */
3407 if (container == (guint)-1) {
3408 g_free(sip->note);
3409 sip->note = NULL;
3410 do_update_status = TRUE;
3411 continue;
3414 /* Ex. clear note: <category name="note" container="200"/> */
3415 if (instance == (guint)-1) {
3416 if (container == 200) {
3417 g_free(sip->note);
3418 sip->note = NULL;
3419 do_update_status = TRUE;
3421 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3422 sipe_remove_category_container_publications(
3423 sip->our_publications, name, container);
3424 continue;
3427 /* key is <category><instance><container> */
3428 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3429 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3431 /* capture all userState publication for later clean up if required */
3432 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3433 xmlnode *xn_state = xmlnode_get_child(node, "state");
3435 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3436 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3437 publication->category = g_strdup(name);
3438 publication->instance = instance;
3439 publication->container = container;
3440 publication->version = version;
3442 if (!sip->user_state_publications) {
3443 sip->user_state_publications = g_hash_table_new_full(
3444 g_str_hash, g_str_equal,
3445 g_free, (GDestroyNotify)free_publication);
3447 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3448 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3449 key, version);
3453 if (sipe_is_our_publication(sip, key)) {
3454 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3456 publication->category = g_strdup(name);
3457 publication->instance = instance;
3458 publication->container = container;
3459 publication->version = version;
3461 /* filling publication->availability */
3462 if (sipe_strequal(name, "state")) {
3463 xmlnode *xn_state = xmlnode_get_child(node, "state");
3464 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3466 if (xn_avail) {
3467 gchar *avail_str = xmlnode_get_data(xn_avail);
3468 if (avail_str) {
3469 publication->availability = atoi(avail_str);
3471 g_free(avail_str);
3473 /* for calendarState */
3474 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3475 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3476 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3478 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3479 if (xn_activity) {
3480 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3481 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3483 event->is_meeting = TRUE;
3486 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3487 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3489 publication->cal_event_hash = sipe_cal_event_hash(event);
3490 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3491 publication->cal_event_hash);
3492 sipe_cal_event_free(event);
3495 /* filling publication->note */
3496 if (sipe_strequal(name, "note")) {
3497 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3499 if (!has_note_cleaned) {
3500 has_note_cleaned = TRUE;
3502 g_free(sip->note);
3503 sip->note = NULL;
3504 sip->note_since = publish_time;
3506 do_update_status = TRUE;
3509 g_free(publication->note);
3510 publication->note = NULL;
3511 if (xn_body) {
3512 char *tmp;
3514 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3515 g_free(tmp);
3516 if (publish_time >= sip->note_since) {
3517 g_free(sip->note);
3518 sip->note = g_strdup(publication->note);
3519 sip->note_since = publish_time;
3520 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3522 do_update_status = TRUE;
3527 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3528 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3529 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3530 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3531 if (xn_free_busy) {
3532 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3533 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3535 if (xn_working_hours) {
3536 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3540 if (!cat_publications) {
3541 cat_publications = g_hash_table_new_full(
3542 g_str_hash, g_str_equal,
3543 g_free, (GDestroyNotify)free_publication);
3544 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3545 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3547 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3548 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3550 g_free(key);
3552 /* aggregateState (not an our publication) from 2-nd container */
3553 if (sipe_strequal(name, "state") && container == 2) {
3554 xmlnode *xn_state = xmlnode_get_child(node, "state");
3556 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3557 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3558 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3560 if (xn_avail) {
3561 gchar *avail_str = xmlnode_get_data(xn_avail);
3562 if (avail_str) {
3563 aggreg_avail = atoi(avail_str);
3565 g_free(avail_str);
3568 if (xn_activity) {
3569 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3571 aggreg_activity = sipe_get_activity_by_token(activity_token);
3574 do_update_status = TRUE;
3578 /* userProperties published by server from AD */
3579 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3580 xmlnode *line;
3581 /* line, for Remote Call Control (RCC) */
3582 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3583 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3584 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3585 gchar *line_uri;
3587 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3589 line_uri = xmlnode_get_data(line);
3590 if (line_uri) {
3591 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3592 sip_csta_open(sip, line_uri, line_server);
3594 g_free(line_uri);
3596 break;
3600 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3601 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3603 /* containers */
3604 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3605 guint id = xmlnode_get_int_attrib(node, "id", 0);
3606 struct sipe_container *container = sipe_find_container(sip, id);
3608 if (container) {
3609 sip->containers = g_slist_remove(sip->containers, container);
3610 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3611 free_container(container);
3613 container = g_new0(struct sipe_container, 1);
3614 container->id = id;
3615 container->version = xmlnode_get_int_attrib(node, "version", 0);
3616 sip->containers = g_slist_append(sip->containers, container);
3617 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3619 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3620 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3621 member->type = xmlnode_get_attrib(node2, "type");
3622 member->value = xmlnode_get_attrib(node2, "value");
3623 container->members = g_slist_append(container->members, member);
3624 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3625 member->type, member->value ? member->value : "");
3629 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3630 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3631 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3632 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3633 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3634 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3635 /* initial set-up to let counterparties see your status */
3636 if (sameEnterpriseAL < 0) {
3637 struct sipe_container *container = sipe_find_container(sip, 200);
3638 guint version = container ? container->version : 0;
3639 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3641 if (federatedAL < 0) {
3642 struct sipe_container *container = sipe_find_container(sip, 100);
3643 guint version = container ? container->version : 0;
3644 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3646 sip->access_level_set = TRUE;
3649 /* subscribers */
3650 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3651 const char *user;
3652 const char *acknowledged;
3653 gchar *hdr;
3654 gchar *body;
3656 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3657 if (!user) continue;
3658 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3659 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3660 uri = sip_uri_from_name(user);
3662 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3664 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3665 if(!g_ascii_strcasecmp(acknowledged,"false")){
3666 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3667 if (!purple_find_buddy(sip->account, uri)) {
3668 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3671 hdr = g_strdup_printf(
3672 "Contact: %s\r\n"
3673 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3675 body = g_strdup_printf(
3676 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3677 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3678 "</setSubscribers>", user);
3680 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3681 g_free(body);
3682 g_free(hdr);
3684 g_free(display_name);
3685 g_free(uri);
3688 g_free(contact);
3689 xmlnode_free(xml);
3691 /* Publish initial state if not yet.
3692 * Assuming this happens on initial responce to subscription to roaming-self
3693 * so we've already updated our roaming data in full.
3694 * Only for 2007+
3696 if (!sip->initial_state_published) {
3697 send_publish_category_initial(sip);
3698 sip->initial_state_published = TRUE;
3699 /* dalayed run */
3700 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3701 do_update_status = FALSE;
3702 } else if (aggreg_avail) {
3704 g_free(sip->status);
3705 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3706 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3707 } else {
3708 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3712 if (do_update_status) {
3713 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3714 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3717 g_free(to);
3720 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3722 gchar *to = sip_uri_self(sip);
3723 gchar *tmp = get_contact(sip);
3724 gchar *hdr = g_strdup_printf(
3725 "Event: vnd-microsoft-roaming-ACL\r\n"
3726 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3727 "Supported: com.microsoft.autoextend\r\n"
3728 "Supported: ms-benotify\r\n"
3729 "Proxy-Require: ms-benotify\r\n"
3730 "Supported: ms-piggyback-first-notify\r\n"
3731 "Contact: %s\r\n", tmp);
3732 g_free(tmp);
3734 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3735 g_free(to);
3736 g_free(hdr);
3740 * To request for presence information about the user, access level settings that have already been configured by the user
3741 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3742 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3745 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3747 gchar *to = sip_uri_self(sip);
3748 gchar *tmp = get_contact(sip);
3749 gchar *hdr = g_strdup_printf(
3750 "Event: vnd-microsoft-roaming-self\r\n"
3751 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3752 "Supported: ms-benotify\r\n"
3753 "Proxy-Require: ms-benotify\r\n"
3754 "Supported: ms-piggyback-first-notify\r\n"
3755 "Contact: %s\r\n"
3756 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3758 gchar *body=g_strdup(
3759 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3760 "<roaming type=\"categories\"/>"
3761 "<roaming type=\"containers\"/>"
3762 "<roaming type=\"subscribers\"/></roamingList>");
3764 g_free(tmp);
3765 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3766 g_free(body);
3767 g_free(to);
3768 g_free(hdr);
3772 * For 2005 version
3774 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3776 gchar *to = sip_uri_self(sip);
3777 gchar *tmp = get_contact(sip);
3778 gchar *hdr = g_strdup_printf(
3779 "Event: vnd-microsoft-provisioning\r\n"
3780 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3781 "Supported: com.microsoft.autoextend\r\n"
3782 "Supported: ms-benotify\r\n"
3783 "Proxy-Require: ms-benotify\r\n"
3784 "Supported: ms-piggyback-first-notify\r\n"
3785 "Expires: 0\r\n"
3786 "Contact: %s\r\n", tmp);
3788 g_free(tmp);
3789 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3790 g_free(to);
3791 g_free(hdr);
3794 /** Subscription for provisioning information to help with initial
3795 * configuration. This subscription is a one-time query (denoted by the Expires header,
3796 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3797 * configuration, meeting policies, and policy settings that Communicator must enforce.
3798 * TODO: for what we need this information.
3801 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3803 gchar *to = sip_uri_self(sip);
3804 gchar *tmp = get_contact(sip);
3805 gchar *hdr = g_strdup_printf(
3806 "Event: vnd-microsoft-provisioning-v2\r\n"
3807 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3808 "Supported: com.microsoft.autoextend\r\n"
3809 "Supported: ms-benotify\r\n"
3810 "Proxy-Require: ms-benotify\r\n"
3811 "Supported: ms-piggyback-first-notify\r\n"
3812 "Expires: 0\r\n"
3813 "Contact: %s\r\n"
3814 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3815 gchar *body = g_strdup(
3816 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3817 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3818 "<provisioningGroup name=\"ucPolicy\"/>"
3819 "</provisioningGroupList>");
3821 g_free(tmp);
3822 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3823 g_free(body);
3824 g_free(to);
3825 g_free(hdr);
3828 static void
3829 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3830 gpointer value, gpointer user_data)
3832 struct sip_subscription *subscription = value;
3833 struct sip_dialog *dialog = &subscription->dialog;
3834 struct sipe_account_data *sip = user_data;
3835 gchar *tmp = get_contact(sip);
3836 gchar *hdr = g_strdup_printf(
3837 "Event: %s\r\n"
3838 "Expires: 0\r\n"
3839 "Contact: %s\r\n", subscription->event, tmp);
3840 g_free(tmp);
3842 /* Rate limit to max. 25 requests per seconds */
3843 g_usleep(1000000 / 25);
3845 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3846 g_free(hdr);
3849 /* IM Session (INVITE and MESSAGE methods) */
3851 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3852 static gchar *
3853 get_end_points (struct sipe_account_data *sip,
3854 struct sip_session *session)
3856 gchar *res;
3858 if (session == NULL) {
3859 return NULL;
3862 res = g_strdup_printf("<sip:%s>", sip->username);
3864 SIPE_DIALOG_FOREACH {
3865 gchar *tmp = res;
3866 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3867 g_free(tmp);
3869 if (dialog->theirepid) {
3870 tmp = res;
3871 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3872 g_free(tmp);
3874 } SIPE_DIALOG_FOREACH_END;
3876 return res;
3879 static gboolean
3880 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3881 struct sipmsg *msg,
3882 SIPE_UNUSED_PARAMETER struct transaction *trans)
3884 gboolean ret = TRUE;
3886 if (msg->response != 200) {
3887 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3888 return FALSE;
3891 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3893 return ret;
3897 * Asks UA/proxy about its capabilities.
3899 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3901 gchar *to = sip_uri(who);
3902 gchar *contact = get_contact(sip);
3903 gchar *request = g_strdup_printf(
3904 "Accept: application/sdp\r\n"
3905 "Contact: %s\r\n", contact);
3906 g_free(contact);
3908 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3910 g_free(to);
3911 g_free(request);
3914 static void
3915 sipe_notify_user(struct sipe_account_data *sip,
3916 struct sip_session *session,
3917 PurpleMessageFlags flags,
3918 const gchar *message)
3920 PurpleConversation *conv;
3922 if (!session->conv) {
3923 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3924 } else {
3925 conv = session->conv;
3927 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3930 void
3931 sipe_present_info(struct sipe_account_data *sip,
3932 struct sip_session *session,
3933 const gchar *message)
3935 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3938 static void
3939 sipe_present_err(struct sipe_account_data *sip,
3940 struct sip_session *session,
3941 const gchar *message)
3943 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3946 void
3947 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3948 struct sip_session *session,
3949 int sip_error,
3950 const gchar *who,
3951 const gchar *message)
3953 char *msg, *msg_tmp, *msg_tmp2;
3954 const char *label;
3956 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
3957 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3958 g_free(msg_tmp);
3959 /* Service unavailable; Server Internal Error; Server Time-out */
3960 if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3961 label = _("This message was not delivered to %s because the service is not available");
3962 } else if (sip_error == 486) { /* Busy Here */
3963 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3964 } else {
3965 label = _("This message was not delivered to %s because one or more recipients are offline");
3968 msg_tmp = g_strdup_printf( "%s:\n%s" ,
3969 msg_tmp2 = g_strdup_printf(label, who ? who : ""), msg ? msg : "");
3970 sipe_present_err(sip, session, msg_tmp);
3971 g_free(msg_tmp2);
3972 g_free(msg_tmp);
3973 g_free(msg);
3977 static gboolean
3978 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
3979 SIPE_UNUSED_PARAMETER struct transaction *trans)
3981 gboolean ret = TRUE;
3982 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3983 struct sip_session *session = sipe_session_find_im(sip, with);
3984 struct sip_dialog *dialog;
3985 gchar *cseq;
3986 char *key;
3987 gchar *message;
3989 if (!session) {
3990 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3991 g_free(with);
3992 return FALSE;
3995 dialog = sipe_dialog_find(session, with);
3996 if (!dialog) {
3997 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3998 g_free(with);
3999 return FALSE;
4002 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4003 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4004 g_free(cseq);
4005 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4007 if (msg->response >= 400) {
4008 PurpleBuddy *pbuddy;
4009 const char *alias = with;
4011 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
4013 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4014 alias = purple_buddy_get_alias(pbuddy);
4017 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
4018 ret = FALSE;
4019 } else {
4020 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4021 if (message_id) {
4022 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
4023 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4024 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4027 g_hash_table_remove(session->unconfirmed_messages, key);
4028 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4029 key, g_hash_table_size(session->unconfirmed_messages));
4032 g_free(key);
4033 g_free(with);
4035 if (ret) sipe_im_process_queue(sip, session);
4036 return ret;
4039 static gboolean
4040 sipe_is_election_finished(struct sip_session *session);
4042 static void
4043 sipe_election_result(struct sipe_account_data *sip,
4044 void *sess);
4046 static gboolean
4047 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4048 SIPE_UNUSED_PARAMETER struct transaction *trans)
4050 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4051 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4052 struct sip_dialog *dialog;
4053 struct sip_session *session;
4055 session = sipe_session_find_chat_by_callid(sip, callid);
4056 if (!session) {
4057 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4058 return FALSE;
4061 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4062 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4063 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4064 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4066 if (xn_request_rm_response) {
4067 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4068 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4070 dialog = sipe_dialog_find(session, with);
4071 if (!dialog) {
4072 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4073 xmlnode_free(xn_action);
4074 return FALSE;
4077 if (allow && !g_strcasecmp(allow, "true")) {
4078 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4079 dialog->election_vote = 1;
4080 } else if (allow && !g_strcasecmp(allow, "false")) {
4081 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4082 dialog->election_vote = -1;
4085 if (sipe_is_election_finished(session)) {
4086 sipe_election_result(sip, session);
4089 } else if (xn_set_rm_response) {
4092 xmlnode_free(xn_action);
4096 return TRUE;
4099 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4101 gchar *hdr;
4102 gchar *tmp;
4103 char *msgformat;
4104 char *msgtext;
4105 gchar *msgr_value;
4106 gchar *msgr;
4108 sipe_parse_html(msg, &msgformat, &msgtext);
4109 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4111 msgr_value = sipmsg_get_msgr_string(msgformat);
4112 g_free(msgformat);
4113 if (msgr_value) {
4114 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4115 g_free(msgr_value);
4116 } else {
4117 msgr = g_strdup("");
4120 tmp = get_contact(sip);
4121 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4122 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4123 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4124 if (content_type == NULL)
4125 content_type = "text/plain";
4127 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4128 g_free(tmp);
4129 g_free(msgr);
4131 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4132 g_free(msgtext);
4133 g_free(hdr);
4137 void
4138 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4140 GSList *entry2 = session->outgoing_message_queue;
4141 while (entry2) {
4142 struct queued_message *msg = entry2->data;
4144 /* for multiparty chat or conference */
4145 if (session->is_multiparty || session->focus_uri) {
4146 gchar *who = sip_uri_self(sip);
4147 serv_got_chat_in(sip->gc, session->chat_id, who,
4148 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4149 g_free(who);
4152 SIPE_DIALOG_FOREACH {
4153 char *key;
4155 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4157 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4158 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg->body));
4159 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4160 key, g_hash_table_size(session->unconfirmed_messages));
4161 g_free(key);
4163 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4164 } SIPE_DIALOG_FOREACH_END;
4166 entry2 = sipe_session_dequeue_message(session);
4170 static void
4171 sipe_refer_notify(struct sipe_account_data *sip,
4172 struct sip_session *session,
4173 const gchar *who,
4174 int status,
4175 const gchar *desc)
4177 gchar *hdr;
4178 gchar *body;
4179 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4181 hdr = g_strdup_printf(
4182 "Event: refer\r\n"
4183 "Subscription-State: %s\r\n"
4184 "Content-Type: message/sipfrag\r\n",
4185 status >= 200 ? "terminated" : "active");
4187 body = g_strdup_printf(
4188 "SIP/2.0 %d %s\r\n",
4189 status, desc);
4191 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4193 g_free(hdr);
4194 g_free(body);
4197 static gboolean
4198 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4200 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4201 struct sip_session *session;
4202 struct sip_dialog *dialog;
4203 char *cseq;
4204 char *key;
4205 gchar *message;
4206 struct sipmsg *request_msg = trans->msg;
4208 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4209 gchar *referred_by;
4211 session = sipe_session_find_chat_by_callid(sip, callid);
4212 if (!session) {
4213 session = sipe_session_find_im(sip, with);
4215 if (!session) {
4216 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4217 g_free(with);
4218 return FALSE;
4221 dialog = sipe_dialog_find(session, with);
4222 if (!dialog) {
4223 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4224 g_free(with);
4225 return FALSE;
4228 sipe_dialog_parse(dialog, msg, TRUE);
4230 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4231 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4232 g_free(cseq);
4233 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4235 if (msg->response != 200) {
4236 PurpleBuddy *pbuddy;
4237 const char *alias = with;
4239 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4241 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4242 alias = purple_buddy_get_alias(pbuddy);
4245 if (message) {
4246 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
4247 } else {
4248 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4249 sipe_present_err(sip, session, tmp_msg);
4250 g_free(tmp_msg);
4253 sipe_dialog_remove(session, with);
4255 g_free(key);
4256 g_free(with);
4257 return FALSE;
4260 dialog->cseq = 0;
4261 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4262 dialog->outgoing_invite = NULL;
4263 dialog->is_established = TRUE;
4265 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4266 if (referred_by) {
4267 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4268 g_free(referred_by);
4271 /* add user to chat if it is a multiparty session */
4272 if (session->is_multiparty) {
4273 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4274 with, NULL,
4275 PURPLE_CBFLAGS_NONE, TRUE);
4278 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4279 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4280 sipe_session_dequeue_message(session);
4283 sipe_im_process_queue(sip, session);
4285 g_hash_table_remove(session->unconfirmed_messages, key);
4286 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4287 key, g_hash_table_size(session->unconfirmed_messages));
4289 g_free(key);
4290 g_free(with);
4291 return TRUE;
4295 void
4296 sipe_invite(struct sipe_account_data *sip,
4297 struct sip_session *session,
4298 const gchar *who,
4299 const gchar *msg_body,
4300 const gchar *msg_content_type,
4301 const gchar *referred_by,
4302 const gboolean is_triggered)
4304 gchar *hdr;
4305 gchar *to;
4306 gchar *contact;
4307 gchar *body;
4308 gchar *self;
4309 char *ms_text_format = NULL;
4310 gchar *roster_manager;
4311 gchar *end_points;
4312 gchar *referred_by_str;
4313 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4315 if (dialog && dialog->is_established) {
4316 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4317 return;
4320 if (!dialog) {
4321 dialog = sipe_dialog_add(session);
4322 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4323 dialog->with = g_strdup(who);
4326 if (!(dialog->ourtag)) {
4327 dialog->ourtag = gentag();
4330 to = sip_uri(who);
4332 if (msg_body) {
4333 char *msgformat;
4334 char *msgtext;
4335 char *base64_msg;
4336 gchar *msgr_value;
4337 gchar *msgr;
4338 char *key;
4340 sipe_parse_html(msg_body, &msgformat, &msgtext);
4341 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4343 msgr_value = sipmsg_get_msgr_string(msgformat);
4344 g_free(msgformat);
4345 if (msgr_value) {
4346 msgr = g_strdup_printf(";msgr=%s", msgr_value);
4347 g_free(msgr_value);
4348 } else {
4349 msgr = g_strdup("");
4352 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4353 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4354 msg_content_type ? msg_content_type : "text/plain",
4355 msgr, base64_msg);
4356 g_free(msgtext);
4357 g_free(msgr);
4358 g_free(base64_msg);
4360 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4361 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
4362 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4363 key, g_hash_table_size(session->unconfirmed_messages));
4364 g_free(key);
4367 contact = get_contact(sip);
4368 end_points = get_end_points(sip, session);
4369 self = sip_uri_self(sip);
4370 roster_manager = g_strdup_printf(
4371 "Roster-Manager: %s\r\n"
4372 "EndPoints: %s\r\n",
4373 self,
4374 end_points);
4375 referred_by_str = referred_by ?
4376 g_strdup_printf(
4377 "Referred-By: %s\r\n",
4378 referred_by)
4379 : g_strdup("");
4380 hdr = g_strdup_printf(
4381 "Supported: ms-sender\r\n"
4382 "%s"
4383 "%s"
4384 "%s"
4385 "%s"
4386 "Contact: %s\r\n%s"
4387 "Content-Type: application/sdp\r\n",
4388 sipe_strequal(session->roster_manager, self) ? roster_manager : "",
4389 referred_by_str,
4390 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4391 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4392 contact,
4393 ms_text_format ? ms_text_format : "");
4394 g_free(ms_text_format);
4395 g_free(self);
4397 body = g_strdup_printf(
4398 "v=0\r\n"
4399 "o=- 0 0 IN IP4 %s\r\n"
4400 "s=session\r\n"
4401 "c=IN IP4 %s\r\n"
4402 "t=0 0\r\n"
4403 "m=%s %d sip null\r\n"
4404 "a=accept-types:text/plain text/html image/gif "
4405 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4406 purple_network_get_my_ip(-1),
4407 purple_network_get_my_ip(-1),
4408 sip->ocs2007 ? "message" : "x-ms-message",
4409 sip->realport);
4411 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4412 to, to, hdr, body, dialog, process_invite_response);
4414 g_free(to);
4415 g_free(roster_manager);
4416 g_free(end_points);
4417 g_free(referred_by_str);
4418 g_free(body);
4419 g_free(hdr);
4420 g_free(contact);
4423 static void
4424 sipe_refer(struct sipe_account_data *sip,
4425 struct sip_session *session,
4426 const gchar *who)
4428 gchar *hdr;
4429 gchar *contact;
4430 gchar *epid = get_epid(sip);
4431 struct sip_dialog *dialog = sipe_dialog_find(session,
4432 session->roster_manager);
4433 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4435 contact = get_contact(sip);
4436 hdr = g_strdup_printf(
4437 "Contact: %s\r\n"
4438 "Refer-to: <%s>\r\n"
4439 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4440 "Require: com.microsoft.rtc-multiparty\r\n",
4441 contact,
4442 who,
4443 sip->username,
4444 ourtag ? ";tag=" : "",
4445 ourtag ? ourtag : "",
4446 epid);
4447 g_free(epid);
4449 send_sip_request(sip->gc, "REFER",
4450 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4452 g_free(hdr);
4453 g_free(contact);
4456 static void
4457 sipe_send_election_request_rm(struct sipe_account_data *sip,
4458 struct sip_dialog *dialog,
4459 int bid)
4461 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4463 gchar *body = g_strdup_printf(
4464 "<?xml version=\"1.0\"?>\r\n"
4465 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4466 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4467 sip->username, bid);
4469 send_sip_request(sip->gc, "INFO",
4470 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4472 g_free(body);
4475 static void
4476 sipe_send_election_set_rm(struct sipe_account_data *sip,
4477 struct sip_dialog *dialog)
4479 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4481 gchar *body = g_strdup_printf(
4482 "<?xml version=\"1.0\"?>\r\n"
4483 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4484 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4485 sip->username);
4487 send_sip_request(sip->gc, "INFO",
4488 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4490 g_free(body);
4493 static void
4494 sipe_session_close(struct sipe_account_data *sip,
4495 struct sip_session * session)
4497 if (session && session->focus_uri) {
4498 sipe_conf_immcu_closed(sip, session);
4499 conf_session_close(sip, session);
4502 if (session) {
4503 SIPE_DIALOG_FOREACH {
4504 /* @TODO slow down BYE message sending rate */
4505 /* @see single subscription code */
4506 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4507 } SIPE_DIALOG_FOREACH_END;
4509 sipe_session_remove(sip, session);
4513 static void
4514 sipe_session_close_all(struct sipe_account_data *sip)
4516 GSList *entry;
4517 while ((entry = sip->sessions) != NULL) {
4518 sipe_session_close(sip, entry->data);
4522 static void
4523 sipe_convo_closed(PurpleConnection * gc, const char *who)
4525 struct sipe_account_data *sip = gc->proto_data;
4527 purple_debug_info("sipe", "conversation with %s closed\n", who);
4528 sipe_session_close(sip, sipe_session_find_im(sip, who));
4531 static void
4532 sipe_chat_leave (PurpleConnection *gc, int id)
4534 struct sipe_account_data *sip = gc->proto_data;
4535 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4537 sipe_session_close(sip, session);
4540 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4541 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4543 struct sipe_account_data *sip = gc->proto_data;
4544 struct sip_session *session;
4545 struct sip_dialog *dialog;
4546 gchar *uri = sip_uri(who);
4548 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4550 session = sipe_session_find_or_add_im(sip, uri);
4551 dialog = sipe_dialog_find(session, uri);
4553 // Queue the message
4554 sipe_session_enqueue_message(session, what, NULL);
4556 if (dialog && !dialog->outgoing_invite) {
4557 sipe_im_process_queue(sip, session);
4558 } else if (!dialog || !dialog->outgoing_invite) {
4559 // Need to send the INVITE to get the outgoing dialog setup
4560 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4563 g_free(uri);
4564 return 1;
4567 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4568 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4570 struct sipe_account_data *sip = gc->proto_data;
4571 struct sip_session *session;
4573 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4575 session = sipe_session_find_chat_by_id(sip, id);
4577 // Queue the message
4578 if (session && session->dialogs) {
4579 sipe_session_enqueue_message(session,what,NULL);
4580 sipe_im_process_queue(sip, session);
4581 } else if (sip) {
4582 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4583 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4585 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4586 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4588 if (sip->ocs2007) {
4589 struct sip_session *session = sipe_session_add_chat(sip);
4591 session->is_multiparty = FALSE;
4592 session->focus_uri = g_strdup(proto_chat_id);
4593 sipe_session_enqueue_message(session, what, NULL);
4594 sipe_invite_conf_focus(sip, session);
4598 return 1;
4601 /* End IM Session (INVITE and MESSAGE methods) */
4603 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4605 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4606 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4607 gchar *from;
4608 struct sip_session *session;
4610 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4612 /* Call Control protocol */
4613 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4615 process_incoming_info_csta(sip, msg);
4616 return;
4619 from = parse_from(sipmsg_find_header(msg, "From"));
4620 session = sipe_session_find_chat_by_callid(sip, callid);
4621 if (!session) {
4622 session = sipe_session_find_im(sip, from);
4624 if (!session) {
4625 g_free(from);
4626 return;
4629 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4631 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4632 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4633 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4635 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4637 if (xn_request_rm) {
4638 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4639 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4640 gchar *body = g_strdup_printf(
4641 "<?xml version=\"1.0\"?>\r\n"
4642 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4643 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4644 sip->username,
4645 session->bid < bid ? "true" : "false");
4646 send_sip_response(sip->gc, msg, 200, "OK", body);
4647 g_free(body);
4648 } else if (xn_set_rm) {
4649 gchar *body;
4650 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4651 g_free(session->roster_manager);
4652 session->roster_manager = g_strdup(rm);
4654 body = g_strdup_printf(
4655 "<?xml version=\"1.0\"?>\r\n"
4656 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4657 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4658 sip->username);
4659 send_sip_response(sip->gc, msg, 200, "OK", body);
4660 g_free(body);
4662 xmlnode_free(xn_action);
4665 else
4667 /* looks like purple lacks typing notification for chat */
4668 if (!session->is_multiparty && !session->focus_uri) {
4669 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4670 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4671 "status");
4672 if (sipe_strequal(status, "type")) {
4673 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4674 } else if (sipe_strequal(status, "idle")) {
4675 serv_got_typing_stopped(sip->gc, from);
4677 xmlnode_free(xn_keyboard_activity);
4680 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4682 g_free(from);
4685 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4687 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4688 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4689 struct sip_session *session;
4690 struct sip_dialog *dialog;
4692 /* collect dialog identification
4693 * we need callid, ourtag and theirtag to unambiguously identify dialog
4695 /* take data before 'msg' will be modified by send_sip_response */
4696 dialog = g_new0(struct sip_dialog, 1);
4697 dialog->callid = g_strdup(callid);
4698 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4699 dialog->with = g_strdup(from);
4700 sipe_dialog_parse(dialog, msg, FALSE);
4702 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4704 session = sipe_session_find_chat_by_callid(sip, callid);
4705 if (!session) {
4706 session = sipe_session_find_im(sip, from);
4708 if (!session) {
4709 sipe_dialog_free(dialog);
4710 g_free(from);
4711 return;
4714 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4715 g_free(session->roster_manager);
4716 session->roster_manager = NULL;
4719 /* This what BYE is essentially for - terminating dialog */
4720 sipe_dialog_remove_3(session, dialog);
4721 sipe_dialog_free(dialog);
4722 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4723 sipe_conf_immcu_closed(sip, session);
4724 } else if (session->is_multiparty) {
4725 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4728 g_free(from);
4731 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4733 gchar *self = sip_uri_self(sip);
4734 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4735 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4736 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4737 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4738 struct sip_session *session;
4739 struct sip_dialog *dialog;
4741 session = sipe_session_find_chat_by_callid(sip, callid);
4742 dialog = sipe_dialog_find(session, from);
4744 if (!session || !dialog || !session->roster_manager || !sipe_strequal(session->roster_manager, self)) {
4745 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4746 } else {
4747 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4749 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4752 g_free(self);
4753 g_free(from);
4754 g_free(refer_to);
4755 g_free(referred_by);
4758 static unsigned int
4759 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4761 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4762 struct sip_session *session;
4763 struct sip_dialog *dialog;
4765 if (state == PURPLE_NOT_TYPING)
4766 return 0;
4768 session = sipe_session_find_im(sip, who);
4769 dialog = sipe_dialog_find(session, who);
4771 if (session && dialog && dialog->is_established) {
4772 send_sip_request(gc, "INFO", who, who,
4773 "Content-Type: application/xml\r\n",
4774 SIPE_SEND_TYPING, dialog, NULL);
4776 return SIPE_TYPING_SEND_TIMEOUT;
4779 static gboolean resend_timeout(struct sipe_account_data *sip)
4781 GSList *tmp = sip->transactions;
4782 time_t currtime = time(NULL);
4783 while (tmp) {
4784 struct transaction *trans = tmp->data;
4785 tmp = tmp->next;
4786 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4787 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4788 /* TODO 408 */
4789 } else {
4790 if ((currtime - trans->time > 2) && trans->retries == 0) {
4791 trans->retries++;
4792 sendout_sipmsg(sip, trans->msg);
4796 return TRUE;
4799 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4800 SIPE_UNUSED_PARAMETER void *unused)
4802 /* register again when security token expires */
4803 /* we have to start a new authentication as the security token
4804 * is almost expired by sending a not signed REGISTER message */
4805 purple_debug_info("sipe", "do a full reauthentication\n");
4806 sipe_auth_free(&sip->registrar);
4807 sipe_auth_free(&sip->proxy);
4808 sip->registerstatus = 0;
4809 do_register(sip);
4810 sip->reauthenticate_set = FALSE;
4813 static gboolean
4814 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4815 struct sipmsg *msg,
4816 int result)
4818 gboolean found = FALSE;
4820 if (result == TRUE) {
4821 gchar *invitation_command = sipmsg_find_header(msg, "Invitation-Command");
4823 if (sipe_strequal(invitation_command, "INVITE")) {
4824 sipe_ft_incoming_transfer(sip->gc->account,msg);
4825 found = TRUE;
4826 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4827 sipe_ft_incoming_cancel(sip->gc->account, msg);
4828 found = TRUE;
4829 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4830 sipe_ft_incoming_accept(sip->gc->account, msg);
4831 found = TRUE;
4834 return found;
4837 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4839 gchar *from;
4840 gchar *contenttype;
4841 gboolean found = FALSE;
4843 from = parse_from(sipmsg_find_header(msg, "From"));
4845 if (!from) return;
4847 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4849 contenttype = sipmsg_find_header(msg, "Content-Type");
4850 if (g_str_has_prefix(contenttype, "text/plain")
4851 || g_str_has_prefix(contenttype, "text/html")
4852 || g_str_has_prefix(contenttype, "multipart/related")
4853 || g_str_has_prefix(contenttype, "multipart/alternative"))
4855 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4856 gchar *html = get_html_message(contenttype, msg->body);
4858 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4859 if (!session) {
4860 session = sipe_session_find_im(sip, from);
4863 if (session && session->focus_uri) { /* a conference */
4864 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4865 gchar *sender = parse_from(tmp);
4866 g_free(tmp);
4867 serv_got_chat_in(sip->gc, session->chat_id, sender,
4868 PURPLE_MESSAGE_RECV, html, time(NULL));
4869 g_free(sender);
4870 } else if (session && session->is_multiparty) { /* a multiparty chat */
4871 serv_got_chat_in(sip->gc, session->chat_id, from,
4872 PURPLE_MESSAGE_RECV, html, time(NULL));
4873 } else {
4874 serv_got_im(sip->gc, from, html, 0, time(NULL));
4876 g_free(html);
4877 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4878 found = TRUE;
4880 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
4881 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4882 xmlnode *state;
4883 gchar *statedata;
4885 if (!isc) {
4886 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4887 g_free(from);
4888 return;
4891 state = xmlnode_get_child(isc, "state");
4893 if (!state) {
4894 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4895 xmlnode_free(isc);
4896 g_free(from);
4897 return;
4900 statedata = xmlnode_get_data(state);
4901 if (statedata) {
4902 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4903 else serv_got_typing_stopped(sip->gc, from);
4905 g_free(statedata);
4907 xmlnode_free(isc);
4908 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4909 found = TRUE;
4910 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
4911 gchar **lines = g_strsplit(msg->body,"\r\n",0);
4912 /* pier11: "I don't like it - to add body to headers." */
4913 int result = sipmsg_parse_and_append_header(msg, lines);
4915 g_strfreev(lines);
4916 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, result);
4917 if (found) {
4918 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4921 if (!found) {
4922 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4923 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4924 if (!session) {
4925 session = sipe_session_find_im(sip, from);
4927 if (session) {
4928 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4929 from);
4930 sipe_present_err(sip, session, errmsg);
4931 g_free(errmsg);
4934 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
4935 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
4937 g_free(from);
4940 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
4942 gchar *body;
4943 gchar *newTag;
4944 gchar *oldHeader;
4945 gchar *newHeader;
4946 gboolean is_multiparty = FALSE;
4947 gboolean is_triggered = FALSE;
4948 gboolean was_multiparty = TRUE;
4949 gboolean just_joined = FALSE;
4950 gchar *from;
4951 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4952 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4953 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4954 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4955 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4956 GSList *end_points = NULL;
4957 char *tmp = NULL;
4958 struct sip_session *session;
4959 const gchar *ms_text_format;
4961 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
4962 g_free(tmp);
4964 /* Invitation to join conference */
4965 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
4966 process_incoming_invite_conf(sip, msg);
4967 return;
4970 /* Only accept text invitations */
4971 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4972 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4973 return;
4976 // TODO There *must* be a better way to clean up the To header to add a tag...
4977 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4978 oldHeader = sipmsg_find_header(msg, "To");
4979 newTag = gentag();
4980 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4981 sipmsg_remove_header_now(msg, "To");
4982 sipmsg_add_header_now(msg, "To", newHeader);
4983 g_free(newHeader);
4985 if (end_points_hdr) {
4986 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4988 if (g_slist_length(end_points) > 2) {
4989 is_multiparty = TRUE;
4992 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4993 is_triggered = TRUE;
4994 is_multiparty = TRUE;
4997 session = sipe_session_find_chat_by_callid(sip, callid);
4998 /* Convert to multiparty */
4999 if (session && is_multiparty && !session->is_multiparty) {
5000 g_free(session->with);
5001 session->with = NULL;
5002 was_multiparty = FALSE;
5003 session->is_multiparty = TRUE;
5004 session->chat_id = rand();
5007 if (!session && is_multiparty) {
5008 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5010 /* IM session */
5011 from = parse_from(sipmsg_find_header(msg, "From"));
5012 if (!session) {
5013 session = sipe_session_find_or_add_im(sip, from);
5016 if (session) {
5017 g_free(session->callid);
5018 session->callid = g_strdup(callid);
5020 session->is_multiparty = is_multiparty;
5021 if (roster_manager) {
5022 session->roster_manager = g_strdup(roster_manager);
5026 if (is_multiparty && end_points) {
5027 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5028 GSList *entry = end_points;
5029 while (entry) {
5030 struct sip_dialog *dialog;
5031 struct sipendpoint *end_point = entry->data;
5032 entry = entry->next;
5034 if (!g_strcasecmp(from, end_point->contact) ||
5035 !g_strcasecmp(to, end_point->contact))
5036 continue;
5038 dialog = sipe_dialog_find(session, end_point->contact);
5039 if (dialog) {
5040 g_free(dialog->theirepid);
5041 dialog->theirepid = end_point->epid;
5042 end_point->epid = NULL;
5043 } else {
5044 dialog = sipe_dialog_add(session);
5046 dialog->callid = g_strdup(session->callid);
5047 dialog->with = end_point->contact;
5048 end_point->contact = NULL;
5049 dialog->theirepid = end_point->epid;
5050 end_point->epid = NULL;
5052 just_joined = TRUE;
5054 /* send triggered INVITE */
5055 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5058 g_free(to);
5061 if (end_points) {
5062 GSList *entry = end_points;
5063 while (entry) {
5064 struct sipendpoint *end_point = entry->data;
5065 entry = entry->next;
5066 g_free(end_point->contact);
5067 g_free(end_point->epid);
5068 g_free(end_point);
5070 g_slist_free(end_points);
5073 if (session) {
5074 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5075 if (dialog) {
5076 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5077 } else {
5078 dialog = sipe_dialog_add(session);
5080 dialog->callid = g_strdup(session->callid);
5081 dialog->with = g_strdup(from);
5082 sipe_dialog_parse(dialog, msg, FALSE);
5084 if (!dialog->ourtag) {
5085 dialog->ourtag = newTag;
5086 newTag = NULL;
5089 just_joined = TRUE;
5091 } else {
5092 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5094 g_free(newTag);
5096 if (is_multiparty && !session->conv) {
5097 gchar *chat_title = sipe_chat_get_name(callid);
5098 gchar *self = sip_uri_self(sip);
5099 /* create prpl chat */
5100 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5101 session->chat_title = g_strdup(chat_title);
5102 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5103 /* add self */
5104 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5105 self, NULL,
5106 PURPLE_CBFLAGS_NONE, FALSE);
5107 g_free(chat_title);
5108 g_free(self);
5111 if (is_multiparty && !was_multiparty) {
5112 /* add current IM counterparty to chat */
5113 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5114 sipe_dialog_first(session)->with, NULL,
5115 PURPLE_CBFLAGS_NONE, FALSE);
5118 /* add inviting party to chat */
5119 if (just_joined && session->conv) {
5120 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5121 from, NULL,
5122 PURPLE_CBFLAGS_NONE, TRUE);
5125 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5127 /* This used only in 2005 official client, not 2007 or Reuters.
5128 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5129 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5131 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5132 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5133 if (is_multiparty ||
5134 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5136 if (ms_text_format) {
5137 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5139 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5140 if (tmp) {
5141 gchar *body = (gchar *) purple_base64_decode(tmp, NULL);
5142 gchar **lines = g_strsplit(body,"\r\n",0);
5143 int result;
5145 g_free(body);
5146 /* pier11: "I don't like it - to add body to headers." */
5147 result = sipmsg_parse_and_append_header(msg, lines);
5148 g_strfreev(lines);
5150 sipe_process_incoming_x_msmsgsinvite(sip, msg, result);
5151 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5153 g_free(tmp);
5155 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5157 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5158 gchar *html = get_html_message(ms_text_format, NULL);
5159 if (html) {
5160 if (is_multiparty) {
5161 serv_got_chat_in(sip->gc, session->chat_id, from,
5162 PURPLE_MESSAGE_RECV, html, time(NULL));
5163 } else {
5164 serv_got_im(sip->gc, from, html, 0, time(NULL));
5166 g_free(html);
5167 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5173 g_free(from);
5175 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5176 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5177 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5179 body = g_strdup_printf(
5180 "v=0\r\n"
5181 "o=- 0 0 IN IP4 %s\r\n"
5182 "s=session\r\n"
5183 "c=IN IP4 %s\r\n"
5184 "t=0 0\r\n"
5185 "m=%s %d sip sip:%s\r\n"
5186 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5187 purple_network_get_my_ip(-1),
5188 purple_network_get_my_ip(-1),
5189 sip->ocs2007 ? "message" : "x-ms-message",
5190 sip->realport,
5191 sip->username);
5192 send_sip_response(sip->gc, msg, 200, "OK", body);
5193 g_free(body);
5196 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5198 gchar *body;
5200 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5201 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5202 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5204 body = g_strdup_printf(
5205 "v=0\r\n"
5206 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5207 "s=session\r\n"
5208 "c=IN IP4 0.0.0.0\r\n"
5209 "t=0 0\r\n"
5210 "m=%s %d sip sip:%s\r\n"
5211 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5212 sip->ocs2007 ? "message" : "x-ms-message",
5213 sip->realport,
5214 sip->username);
5215 send_sip_response(sip->gc, msg, 200, "OK", body);
5216 g_free(body);
5219 static const char*
5220 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5222 const char *res = "NTLM";
5223 #ifdef USE_KERBEROS
5224 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5225 res = "Kerberos";
5227 #else
5228 (void) sip; /* make compiler happy */
5229 #endif
5230 return res;
5233 static void sipe_connection_cleanup(struct sipe_account_data *);
5234 static void create_connection(struct sipe_account_data *, gchar *, int);
5236 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5237 SIPE_UNUSED_PARAMETER struct transaction *trans)
5239 gchar *tmp;
5240 const gchar *expires_header;
5241 int expires, i;
5242 GSList *hdr = msg->headers;
5243 struct siphdrelement *elem;
5245 expires_header = sipmsg_find_header(msg, "Expires");
5246 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5247 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5249 switch (msg->response) {
5250 case 200:
5251 if (expires == 0) {
5252 sip->registerstatus = 0;
5253 } else {
5254 gchar *contact_hdr = NULL;
5255 gchar *gruu = NULL;
5256 gchar *epid;
5257 gchar *uuid;
5258 gchar *timeout;
5259 gchar *server_hdr = sipmsg_find_header(msg, "Server");
5260 const char *auth_scheme;
5262 if (!sip->reregister_set) {
5263 gchar *action_name = g_strdup_printf("<%s>", "registration");
5264 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5265 g_free(action_name);
5266 sip->reregister_set = TRUE;
5269 sip->registerstatus = 3;
5271 if (server_hdr && !sip->server_version) {
5272 sip->server_version = g_strdup(server_hdr);
5273 g_free(default_ua);
5274 default_ua = NULL;
5277 auth_scheme = sipe_get_auth_scheme_name(sip);
5278 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5280 if (tmp) {
5281 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5282 fill_auth(tmp, &sip->registrar);
5285 if (!sip->reauthenticate_set) {
5286 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5287 guint reauth_timeout;
5288 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5289 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5290 reauth_timeout = sip->registrar.expires - 300;
5291 } else {
5292 /* NTLM: we have to reauthenticate as our security token expires
5293 after eight hours (be five minutes early) */
5294 reauth_timeout = (8 * 3600) - 300;
5296 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5297 g_free(action_name);
5298 sip->reauthenticate_set = TRUE;
5301 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5303 epid = get_epid(sip);
5304 uuid = generateUUIDfromEPID(epid);
5305 g_free(epid);
5307 // There can be multiple Contact headers (one per location where the user is logged in) so
5308 // make sure to only get the one for this uuid
5309 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5310 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5311 if (valid_contact) {
5312 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5313 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5314 g_free(valid_contact);
5315 break;
5316 } else {
5317 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5320 g_free(uuid);
5322 g_free(sip->contact);
5323 if(gruu) {
5324 sip->contact = g_strdup_printf("<%s>", gruu);
5325 g_free(gruu);
5326 } else {
5327 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5328 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);
5330 sip->ocs2007 = FALSE;
5331 sip->batched_support = FALSE;
5333 while(hdr)
5335 elem = hdr->data;
5336 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5337 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5338 /* We interpret this as OCS2007+ indicator */
5339 sip->ocs2007 = TRUE;
5340 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5342 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5343 sip->batched_support = TRUE;
5344 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5347 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5348 gchar **caps = g_strsplit(elem->value,",",0);
5349 i = 0;
5350 while (caps[i]) {
5351 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5352 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5353 i++;
5355 g_strfreev(caps);
5357 hdr = g_slist_next(hdr);
5360 /* rejoin open chats to be able to use them by continue to send messages */
5361 purple_conversation_foreach(sipe_rejoin_chat);
5363 /* subscriptions */
5364 if (!sip->subscribed) { //do it just once, not every re-register
5366 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5367 (GCompareFunc)g_ascii_strcasecmp)) {
5368 sipe_subscribe_roaming_contacts(sip);
5371 /* For 2007+ it does not make sence to subscribe to:
5372 * vnd-microsoft-roaming-ACL
5373 * vnd-microsoft-provisioning (not v2)
5374 * presence.wpending
5375 * These are for backward compatibility.
5377 if (sip->ocs2007)
5379 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5380 (GCompareFunc)g_ascii_strcasecmp)) {
5381 sipe_subscribe_roaming_self(sip);
5383 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5384 (GCompareFunc)g_ascii_strcasecmp)) {
5385 sipe_subscribe_roaming_provisioning_v2(sip);
5388 /* For 2005- servers */
5389 else
5391 //sipe_options_request(sip, sip->sipdomain);
5393 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5394 (GCompareFunc)g_ascii_strcasecmp)) {
5395 sipe_subscribe_roaming_acl(sip);
5397 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5398 (GCompareFunc)g_ascii_strcasecmp)) {
5399 sipe_subscribe_roaming_provisioning(sip);
5401 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5402 (GCompareFunc)g_ascii_strcasecmp)) {
5403 sipe_subscribe_presence_wpending(sip, msg);
5406 /* For 2007+ we publish our initial statuses and calendar data only after
5407 * received our existing publications in sipe_process_roaming_self()
5408 * Only in this case we know versions of current publications made
5409 * on our behalf.
5411 /* For 2005- we publish our initial statuses only after
5412 * received our existing UserInfo data in response to
5413 * self subscription.
5414 * Only in this case we won't override existing UserInfo data
5415 * set earlier or by other client on our behalf.
5419 sip->subscribed = TRUE;
5422 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5423 "timeout=", ";", NULL);
5424 if (timeout != NULL) {
5425 sscanf(timeout, "%u", &sip->keepalive_timeout);
5426 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5427 sip->keepalive_timeout);
5428 g_free(timeout);
5431 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5433 break;
5434 case 301:
5436 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5438 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5439 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5440 gchar **tmp;
5441 gchar *hostname;
5442 int port = 0;
5443 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5444 int i = 1;
5446 tmp = g_strsplit(parts[0], ":", 0);
5447 hostname = g_strdup(tmp[0]);
5448 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5449 g_strfreev(tmp);
5451 while (parts[i]) {
5452 tmp = g_strsplit(parts[i], "=", 0);
5453 if (tmp[1]) {
5454 if (g_strcasecmp("transport", tmp[0]) == 0) {
5455 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5456 transport = SIPE_TRANSPORT_TCP;
5457 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5458 transport = SIPE_TRANSPORT_UDP;
5462 g_strfreev(tmp);
5463 i++;
5465 g_strfreev(parts);
5467 /* Close old connection */
5468 sipe_connection_cleanup(sip);
5470 /* Create new connection */
5471 sip->transport = transport;
5472 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5473 hostname, port, TRANSPORT_DESCRIPTOR);
5474 create_connection(sip, hostname, port);
5476 g_free(redirect);
5478 break;
5479 case 401:
5480 if (sip->registerstatus != 2) {
5481 const char *auth_scheme;
5482 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5483 if (sip->registrar.retries > 3) {
5484 sip->gc->wants_to_die = TRUE;
5485 purple_connection_error(sip->gc, _("Wrong password"));
5486 return TRUE;
5489 auth_scheme = sipe_get_auth_scheme_name(sip);
5490 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5492 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5493 if (!tmp) {
5494 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5495 sip->gc->wants_to_die = TRUE;
5496 purple_connection_error(sip->gc, tmp2);
5497 g_free(tmp2);
5498 return TRUE;
5500 fill_auth(tmp, &sip->registrar);
5501 sip->registerstatus = 2;
5502 if (sip->account->disconnecting) {
5503 do_register_exp(sip, 0);
5504 } else {
5505 do_register(sip);
5508 break;
5509 case 403:
5511 gchar *warning = sipmsg_find_header(msg, "Warning");
5512 gchar **reason = NULL;
5513 if (warning != NULL) {
5514 /* Example header:
5515 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5517 reason = g_strsplit(warning, "\"", 0);
5519 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5520 (reason && reason[1]) ? reason[1] : _("no reason given"));
5521 g_strfreev(reason);
5523 sip->gc->wants_to_die = TRUE;
5524 purple_connection_error(sip->gc, warning);
5525 g_free(warning);
5526 return TRUE;
5528 break;
5529 case 404:
5531 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5532 gchar *reason = NULL;
5533 if (warning != NULL) {
5534 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5536 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5537 warning ? (reason ? reason : _("no reason given")) :
5538 _("SIP is either not enabled for the destination URI or it does not exist"));
5539 g_free(reason);
5541 sip->gc->wants_to_die = TRUE;
5542 purple_connection_error(sip->gc, warning);
5543 g_free(warning);
5544 return TRUE;
5546 break;
5547 case 503:
5548 case 504: /* Server time-out */
5550 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5551 gchar *reason = NULL;
5552 if (warning != NULL) {
5553 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5555 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5556 g_free(reason);
5558 sip->gc->wants_to_die = TRUE;
5559 purple_connection_error(sip->gc, warning);
5560 g_free(warning);
5561 return TRUE;
5563 break;
5565 return TRUE;
5569 * Returns 2005-style activity and Availability.
5571 * @param status Sipe statis id.
5573 static void
5574 sipe_get_act_avail_by_status_2005(const char *status,
5575 int *activity,
5576 int *availability)
5578 int avail = 300; /* online */
5579 int act = 400; /* Available */
5581 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5582 act = 100;
5583 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5584 // act = 150;
5585 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5586 act = 300;
5587 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5588 act = 400;
5589 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5590 // act = 500;
5591 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5592 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5593 act = 600;
5594 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5595 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5596 avail = 0; /* offline */
5597 act = 100;
5598 } else {
5599 act = 400; /* Available */
5602 if (activity) *activity = act;
5603 if (availability) *availability = avail;
5607 * [MS-SIP] 2.2.1
5609 * @param activity 2005 aggregated activity. Ex.: 600
5610 * @param availablity 2005 aggregated availablity. Ex.: 300
5612 static const char *
5613 sipe_get_status_by_act_avail_2005(const int activity,
5614 const int availablity,
5615 char **activity_desc)
5617 const char *status_id = NULL;
5618 const char *act = NULL;
5620 if (activity < 150) {
5621 status_id = SIPE_STATUS_ID_AWAY;
5622 } else if (activity < 200) {
5623 //status_id = SIPE_STATUS_ID_LUNCH;
5624 status_id = SIPE_STATUS_ID_AWAY;
5625 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5626 } else if (activity < 300) {
5627 //status_id = SIPE_STATUS_ID_IDLE;
5628 status_id = SIPE_STATUS_ID_AWAY;
5629 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5630 } else if (activity < 400) {
5631 status_id = SIPE_STATUS_ID_BRB;
5632 } else if (activity < 500) {
5633 status_id = SIPE_STATUS_ID_AVAILABLE;
5634 } else if (activity < 600) {
5635 //status_id = SIPE_STATUS_ID_ON_PHONE;
5636 status_id = SIPE_STATUS_ID_BUSY;
5637 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5638 } else if (activity < 700) {
5639 status_id = SIPE_STATUS_ID_BUSY;
5640 } else if (activity < 800) {
5641 status_id = SIPE_STATUS_ID_AWAY;
5642 } else {
5643 status_id = SIPE_STATUS_ID_AVAILABLE;
5646 if (availablity < 100)
5647 status_id = SIPE_STATUS_ID_OFFLINE;
5649 if (activity_desc && act) {
5650 g_free(*activity_desc);
5651 *activity_desc = g_strdup(act);
5654 return status_id;
5658 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5660 static const char*
5661 sipe_get_status_by_availability(int avail,
5662 char** activity_desc)
5664 const char *status;
5665 const char *act = NULL;
5667 if (avail < 3000) {
5668 status = SIPE_STATUS_ID_OFFLINE;
5669 } else if (avail < 4500) {
5670 status = SIPE_STATUS_ID_AVAILABLE;
5671 } else if (avail < 6000) {
5672 //status = SIPE_STATUS_ID_IDLE;
5673 status = SIPE_STATUS_ID_AVAILABLE;
5674 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5675 } else if (avail < 7500) {
5676 status = SIPE_STATUS_ID_BUSY;
5677 } else if (avail < 9000) {
5678 //status = SIPE_STATUS_ID_BUSYIDLE;
5679 status = SIPE_STATUS_ID_BUSY;
5680 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5681 } else if (avail < 12000) {
5682 status = SIPE_STATUS_ID_DND;
5683 } else if (avail < 15000) {
5684 status = SIPE_STATUS_ID_BRB;
5685 } else if (avail < 18000) {
5686 status = SIPE_STATUS_ID_AWAY;
5687 } else {
5688 status = SIPE_STATUS_ID_OFFLINE;
5691 if (activity_desc && act) {
5692 g_free(*activity_desc);
5693 *activity_desc = g_strdup(act);
5696 return status;
5700 * Returns 2007-style availability value
5702 * @param sipe_status_id (in)
5703 * @param activity_token (out) Must be g_free()'d after use if consumed.
5705 static int
5706 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5708 int availability;
5709 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5711 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5712 availability = 15500;
5713 if (!activity_token || !(*activity_token)) {
5714 activity = SIPE_ACTIVITY_AWAY;
5716 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5717 availability = 12500;
5718 activity = SIPE_ACTIVITY_BRB;
5719 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5720 availability = 9500;
5721 activity = SIPE_ACTIVITY_DND;
5722 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5723 availability = 6500;
5724 if (!activity_token || !(*activity_token)) {
5725 activity = SIPE_ACTIVITY_BUSY;
5727 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5728 availability = 3500;
5729 activity = SIPE_ACTIVITY_ONLINE;
5730 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5731 availability = 0;
5732 } else {
5733 // Offline or invisible
5734 availability = 18500;
5735 activity = SIPE_ACTIVITY_OFFLINE;
5738 if (activity_token) {
5739 *activity_token = g_strdup(sipe_activity_map[activity].token);
5741 return availability;
5744 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5746 const char *uri;
5747 xmlnode *xn_categories;
5748 xmlnode *xn_category;
5749 xmlnode *xn_node;
5750 const char *status = NULL;
5751 gboolean do_update_status = FALSE;
5752 gboolean has_note_cleaned = FALSE;
5753 gboolean has_free_busy_cleaned = FALSE;
5755 xn_categories = xmlnode_from_str(data, len);
5756 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5758 for (xn_category = xmlnode_get_child(xn_categories, "category");
5759 xn_category ;
5760 xn_category = xmlnode_get_next_twin(xn_category) )
5762 const char *tmp;
5763 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5764 time_t publish_time = (tmp = xmlnode_get_attrib(xn_category, "publishTime")) ?
5765 sipe_utils_str_to_time(tmp) : 0;
5767 /* contactCard */
5768 if (sipe_strequal(attrVar, "contactCard"))
5770 xmlnode *node;
5771 /* identity - Display Name and email */
5772 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5773 if (node) {
5774 char* display_name = xmlnode_get_data(
5775 xmlnode_get_descendant(node, "name", "displayName", NULL));
5776 char* email = xmlnode_get_data(
5777 xmlnode_get_child(node, "email"));
5779 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5780 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5782 g_free(display_name);
5783 g_free(email);
5785 /* company */
5786 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5787 if (node) {
5788 char* company = xmlnode_get_data(node);
5789 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5790 g_free(company);
5792 /* department */
5793 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5794 if (node) {
5795 char* department = xmlnode_get_data(node);
5796 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5797 g_free(department);
5799 /* title */
5800 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5801 if (node) {
5802 char* title = xmlnode_get_data(node);
5803 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5804 g_free(title);
5806 /* office */
5807 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5808 if (node) {
5809 char* office = xmlnode_get_data(node);
5810 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5811 g_free(office);
5813 /* site (url) */
5814 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5815 if (node) {
5816 char* site = xmlnode_get_data(node);
5817 sipe_update_user_info(sip, uri, SITE_PROP, site);
5818 g_free(site);
5820 /* phone */
5821 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5822 node;
5823 node = xmlnode_get_next_twin(node))
5825 const char *phone_type = xmlnode_get_attrib(node, "type");
5826 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5827 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5829 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5831 g_free(phone);
5832 g_free(phone_display_string);
5834 /* address */
5835 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5836 node;
5837 node = xmlnode_get_next_twin(node))
5839 if (sipe_strequal(xmlnode_get_attrib(node, "type"), "work")) {
5840 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5841 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5842 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5843 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5844 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5846 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5847 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5848 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5849 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5850 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5852 g_free(street);
5853 g_free(city);
5854 g_free(state);
5855 g_free(zipcode);
5856 g_free(country_code);
5858 break;
5862 /* note */
5863 else if (sipe_strequal(attrVar, "note"))
5865 if (uri) {
5866 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5868 if (!has_note_cleaned) {
5869 has_note_cleaned = TRUE;
5871 g_free(sbuddy->note);
5872 sbuddy->note = NULL;
5873 sbuddy->is_oof_note = FALSE;
5874 sbuddy->note_since = publish_time;
5876 do_update_status = TRUE;
5878 if (sbuddy && (publish_time >= sbuddy->note_since)) {
5879 /* clean up in case no 'note' element is supplied
5880 * which indicate note removal in client
5882 g_free(sbuddy->note);
5883 sbuddy->note = NULL;
5884 sbuddy->is_oof_note = FALSE;
5885 sbuddy->note_since = publish_time;
5887 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
5888 if (xn_node) {
5889 char *tmp;
5890 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
5891 g_free(tmp);
5892 sbuddy->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_node, "type"), "OOF");
5893 sbuddy->note_since = publish_time;
5895 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
5896 uri, sbuddy->note ? sbuddy->note : "");
5898 /* to trigger UI refresh in case no status info is supplied in this update */
5899 do_update_status = TRUE;
5903 /* state */
5904 else if(sipe_strequal(attrVar, "state"))
5906 char *tmp;
5907 int availability;
5908 xmlnode *xn_availability;
5909 xmlnode *xn_activity;
5910 xmlnode *xn_meeting_subject;
5911 xmlnode *xn_meeting_location;
5912 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5914 xn_node = xmlnode_get_child(xn_category, "state");
5915 if (!xn_node) continue;
5916 xn_availability = xmlnode_get_child(xn_node, "availability");
5917 if (!xn_availability) continue;
5918 xn_activity = xmlnode_get_child(xn_node, "activity");
5919 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
5920 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
5922 tmp = xmlnode_get_data(xn_availability);
5923 availability = atoi(tmp);
5924 g_free(tmp);
5926 /* activity, meeting_subject, meeting_location */
5927 if (sbuddy) {
5928 char *tmp = NULL;
5930 /* activity */
5931 g_free(sbuddy->activity);
5932 sbuddy->activity = NULL;
5933 if (xn_activity) {
5934 const char *token = xmlnode_get_attrib(xn_activity, "token");
5935 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
5937 /* from token */
5938 if (!is_empty(token)) {
5939 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
5941 /* from custom element */
5942 if (xn_custom) {
5943 char *custom = xmlnode_get_data(xn_custom);
5945 if (!is_empty(custom)) {
5946 sbuddy->activity = custom;
5947 custom = NULL;
5949 g_free(custom);
5952 /* meeting_subject */
5953 g_free(sbuddy->meeting_subject);
5954 sbuddy->meeting_subject = NULL;
5955 if (xn_meeting_subject) {
5956 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
5958 if (!is_empty(meeting_subject)) {
5959 sbuddy->meeting_subject = meeting_subject;
5960 meeting_subject = NULL;
5962 g_free(meeting_subject);
5964 /* meeting_location */
5965 g_free(sbuddy->meeting_location);
5966 sbuddy->meeting_location = NULL;
5967 if (xn_meeting_location) {
5968 char *meeting_location = xmlnode_get_data(xn_meeting_location);
5970 if (!is_empty(meeting_location)) {
5971 sbuddy->meeting_location = meeting_location;
5972 meeting_location = NULL;
5974 g_free(meeting_location);
5977 status = sipe_get_status_by_availability(availability, &tmp);
5978 if (sbuddy->activity && tmp) {
5979 char *tmp2 = sbuddy->activity;
5981 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
5982 g_free(tmp);
5983 g_free(tmp2);
5984 } else if (tmp) {
5985 sbuddy->activity = tmp;
5989 do_update_status = TRUE;
5991 /* calendarData */
5992 else if(sipe_strequal(attrVar, "calendarData"))
5994 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5995 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
5996 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
5998 if (sbuddy && xn_free_busy) {
5999 if (!has_free_busy_cleaned) {
6000 has_free_busy_cleaned = TRUE;
6002 g_free(sbuddy->cal_start_time);
6003 sbuddy->cal_start_time = NULL;
6005 g_free(sbuddy->cal_free_busy_base64);
6006 sbuddy->cal_free_busy_base64 = NULL;
6008 g_free(sbuddy->cal_free_busy);
6009 sbuddy->cal_free_busy = NULL;
6011 sbuddy->cal_free_busy_published = publish_time;
6014 if (publish_time >= sbuddy->cal_free_busy_published) {
6015 g_free(sbuddy->cal_start_time);
6016 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
6018 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
6019 15 : 0;
6021 g_free(sbuddy->cal_free_busy_base64);
6022 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
6024 g_free(sbuddy->cal_free_busy);
6025 sbuddy->cal_free_busy = NULL;
6027 sbuddy->cal_free_busy_published = publish_time;
6029 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);
6033 if (sbuddy && xn_working_hours) {
6034 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6039 if (do_update_status) {
6040 if (!status) { /* no status category in this update, using contact's current status */
6041 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6042 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6043 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6044 status = purple_status_get_id(pstatus);
6047 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
6048 sipe_got_user_status(sip, uri, status);
6051 xmlnode_free(xn_categories);
6054 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6056 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6057 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
6058 payload->host = g_strdup(host);
6059 payload->buddies = server;
6060 sipe_subscribe_presence_batched_routed(sip, payload);
6061 sipe_subscribe_presence_batched_routed_free(payload);
6064 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6066 xmlnode *xn_list;
6067 xmlnode *xn_resource;
6068 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6069 g_free, NULL);
6070 GSList *server;
6071 gchar *host;
6073 xn_list = xmlnode_from_str(data, len);
6075 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6076 xn_resource;
6077 xn_resource = xmlnode_get_next_twin(xn_resource) )
6079 const char *uri, *state;
6080 xmlnode *xn_instance;
6082 xn_instance = xmlnode_get_child(xn_resource, "instance");
6083 if (!xn_instance) continue;
6085 uri = xmlnode_get_attrib(xn_resource, "uri");
6086 state = xmlnode_get_attrib(xn_instance, "state");
6087 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6089 if (strstr(state, "resubscribe")) {
6090 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6092 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6093 gchar *user = g_strdup(uri);
6094 host = g_strdup(poolFqdn);
6095 server = g_hash_table_lookup(servers, host);
6096 server = g_slist_append(server, user);
6097 g_hash_table_insert(servers, host, server);
6098 } else {
6099 sipe_subscribe_presence_single(sip, (void *) uri);
6104 /* Send out any deferred poolFqdn subscriptions */
6105 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6106 g_hash_table_destroy(servers);
6108 xmlnode_free(xn_list);
6111 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6113 gchar *uri;
6114 gchar *getbasic;
6115 gchar *activity = NULL;
6116 xmlnode *pidf;
6117 xmlnode *basicstatus = NULL, *tuple, *status;
6118 gboolean isonline = FALSE;
6119 xmlnode *display_name_node;
6121 pidf = xmlnode_from_str(data, len);
6122 if (!pidf) {
6123 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6124 return;
6127 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6129 if ((status = xmlnode_get_child(tuple, "status"))) {
6130 basicstatus = xmlnode_get_child(status, "basic");
6134 if (!basicstatus) {
6135 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6136 xmlnode_free(pidf);
6137 return;
6140 getbasic = xmlnode_get_data(basicstatus);
6141 if (!getbasic) {
6142 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6143 xmlnode_free(pidf);
6144 return;
6147 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6148 if (strstr(getbasic, "open")) {
6149 isonline = TRUE;
6151 g_free(getbasic);
6153 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6155 display_name_node = xmlnode_get_child(pidf, "display-name");
6156 if (display_name_node) {
6157 char * display_name = xmlnode_get_data(display_name_node);
6159 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6160 g_free(display_name);
6163 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6164 if ((status = xmlnode_get_child(tuple, "status"))) {
6165 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6166 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6167 activity = xmlnode_get_data(basicstatus);
6168 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6174 if (isonline) {
6175 const gchar * status_id = NULL;
6176 if (activity) {
6177 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6178 status_id = SIPE_STATUS_ID_BUSY;
6179 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6180 status_id = SIPE_STATUS_ID_AWAY;
6184 if (!status_id) {
6185 status_id = SIPE_STATUS_ID_AVAILABLE;
6188 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6189 sipe_got_user_status(sip, uri, status_id);
6190 } else {
6191 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6194 g_free(activity);
6195 g_free(uri);
6196 xmlnode_free(pidf);
6199 /** 2005 */
6200 static void
6201 sipe_user_info_has_updated(struct sipe_account_data *sip,
6202 xmlnode *xn_userinfo)
6204 if (sip->user_info) {
6205 xmlnode_free(sip->user_info);
6207 sip->user_info = xmlnode_copy(xn_userinfo);
6209 /* Publish initial state if not yet.
6210 * Assuming this happens on initial responce to self subscription
6211 * so we've already updated our UserInfo.
6213 if (!sip->initial_state_published) {
6214 send_presence_soap(sip, FALSE);
6215 /* dalayed run */
6216 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6220 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6222 char *activity = NULL;
6223 const char *epid;
6224 const char *status_id = NULL;
6225 const char *name;
6226 char *uri;
6227 char *self_uri = sip_uri_self(sip);
6228 int avl;
6229 int act;
6230 const char *device_name = NULL;
6231 const char *cal_start_time = NULL;
6232 const char *cal_granularity = NULL;
6233 char *cal_free_busy_base64 = NULL;
6234 struct sipe_buddy *sbuddy;
6235 xmlnode *node;
6236 xmlnode *xn_presentity;
6237 xmlnode *xn_availability;
6238 xmlnode *xn_activity;
6239 xmlnode *xn_display_name;
6240 xmlnode *xn_email;
6241 xmlnode *xn_phone_number;
6242 xmlnode *xn_userinfo;
6243 xmlnode *xn_note;
6244 xmlnode *xn_oof;
6245 xmlnode *xn_state;
6246 xmlnode *xn_contact;
6247 char *note;
6248 char *free_activity;
6249 int user_avail;
6250 const char *user_avail_nil;
6251 int res_avail;
6252 time_t user_avail_since = 0;
6253 time_t activity_since = 0;
6255 /* fix for Reuters environment on Linux */
6256 if (data && strstr(data, "encoding=\"utf-16\"")) {
6257 char *tmp_data;
6258 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6259 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6260 g_free(tmp_data);
6261 } else {
6262 xn_presentity = xmlnode_from_str(data, len);
6265 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6266 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6267 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6268 xn_email = xmlnode_get_child(xn_presentity, "email");
6269 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6270 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6271 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6272 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6273 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6274 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6275 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6276 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6277 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6278 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6280 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6281 user_avail = 0;
6282 user_avail_since = 0;
6285 free_activity = NULL;
6287 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6288 uri = sip_uri_from_name(name);
6289 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6290 epid = xmlnode_get_attrib(xn_availability, "epid");
6291 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6293 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6294 res_avail = sipe_get_availability_by_status(status_id, NULL);
6295 if (user_avail > res_avail) {
6296 res_avail = user_avail;
6297 status_id = sipe_get_status_by_availability(user_avail, NULL);
6300 if (xn_display_name) {
6301 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6302 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6303 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6304 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6305 char *tel_uri = sip_to_tel_uri(phone_number);
6307 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6308 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6309 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6310 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6312 g_free(tel_uri);
6313 g_free(phone_label);
6314 g_free(phone_number);
6315 g_free(email);
6316 g_free(display_name);
6319 if (xn_contact) {
6320 /* tel */
6321 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6323 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6324 const char *phone_type = xmlnode_get_attrib(node, "type");
6325 char* phone = xmlnode_get_data(node);
6327 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6329 g_free(phone);
6333 /* devicePresence */
6334 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6335 xmlnode *xn_device_name;
6336 xmlnode *xn_calendar_info;
6337 xmlnode *xn_state;
6338 char *state;
6340 /* deviceName */
6341 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6342 xn_device_name = xmlnode_get_child(node, "deviceName");
6343 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6346 /* calendarInfo */
6347 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6348 if (xn_calendar_info) {
6349 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6351 if (cal_start_time) {
6352 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6353 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6355 if (cal_start_time_t_tmp > cal_start_time_t) {
6356 cal_start_time = cal_start_time_tmp;
6357 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6358 g_free(cal_free_busy_base64);
6359 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6361 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);
6363 } else {
6364 cal_start_time = cal_start_time_tmp;
6365 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6366 g_free(cal_free_busy_base64);
6367 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6369 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);
6373 /* state */
6374 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6375 if (xn_state) {
6376 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6377 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6379 state = xmlnode_get_data(xn_state);
6380 if (dev_avail_since > user_avail_since &&
6381 dev_avail >= res_avail)
6383 res_avail = dev_avail;
6384 if (!is_empty(state))
6386 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6387 g_free(activity);
6388 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6389 } else if (sipe_strequal(state, "presenting")) {
6390 g_free(activity);
6391 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6392 } else {
6393 activity = state;
6394 state = NULL;
6396 activity_since = dev_avail_since;
6398 status_id = sipe_get_status_by_availability(res_avail, &activity);
6400 g_free(state);
6404 /* oof */
6405 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6406 g_free(activity);
6407 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6408 activity_since = 0;
6411 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6412 if (sbuddy)
6414 g_free(sbuddy->activity);
6415 sbuddy->activity = activity;
6416 activity = NULL;
6418 sbuddy->activity_since = activity_since;
6420 sbuddy->user_avail = user_avail;
6421 sbuddy->user_avail_since = user_avail_since;
6423 g_free(sbuddy->note);
6424 sbuddy->note = NULL;
6425 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6427 sbuddy->is_oof_note = (xn_oof != NULL);
6429 g_free(sbuddy->device_name);
6430 sbuddy->device_name = NULL;
6431 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6433 if (!is_empty(cal_free_busy_base64)) {
6434 g_free(sbuddy->cal_start_time);
6435 sbuddy->cal_start_time = g_strdup(cal_start_time);
6437 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6439 g_free(sbuddy->cal_free_busy_base64);
6440 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6441 cal_free_busy_base64 = NULL;
6443 g_free(sbuddy->cal_free_busy);
6444 sbuddy->cal_free_busy = NULL;
6447 sbuddy->last_non_cal_status_id = status_id;
6448 g_free(sbuddy->last_non_cal_activity);
6449 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6451 if (sipe_strequal(sbuddy->name, self_uri)) {
6452 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6454 sip->is_oof_note = sbuddy->is_oof_note;
6456 g_free(sip->note);
6457 sip->note = g_strdup(sbuddy->note);
6459 sip->note_since = time(NULL);
6462 g_free(sip->status);
6463 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6466 g_free(cal_free_busy_base64);
6467 g_free(activity);
6469 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6470 sipe_got_user_status(sip, uri, status_id);
6472 if (!sip->ocs2007 && sipe_strequal(self_uri, uri)) {
6473 sipe_user_info_has_updated(sip, xn_userinfo);
6476 g_free(note);
6477 xmlnode_free(xn_presentity);
6478 g_free(uri);
6479 g_free(self_uri);
6482 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6484 char *ctype = sipmsg_find_header(msg, "Content-Type");
6486 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6488 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6489 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6491 const char *content = msg->body;
6492 unsigned length = msg->bodylen;
6493 PurpleMimeDocument *mime = NULL;
6495 if (strstr(ctype, "multipart"))
6497 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6498 const char *content_type;
6499 GList* parts;
6500 mime = purple_mime_document_parse(doc);
6501 parts = purple_mime_document_get_parts(mime);
6502 while(parts) {
6503 content = purple_mime_part_get_data(parts->data);
6504 length = purple_mime_part_get_length(parts->data);
6505 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6506 if(content_type && strstr(content_type,"application/rlmi+xml"))
6508 process_incoming_notify_rlmi_resub(sip, content, length);
6510 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6512 process_incoming_notify_msrtc(sip, content, length);
6514 else
6516 process_incoming_notify_rlmi(sip, content, length);
6518 parts = parts->next;
6520 g_free(doc);
6522 if (mime)
6524 purple_mime_document_free(mime);
6527 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6529 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6531 else if(strstr(ctype, "application/rlmi+xml"))
6533 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6536 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6538 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6540 else
6542 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6546 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6548 char *ctype = sipmsg_find_header(msg, "Content-Type");
6549 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6551 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6553 if (ctype &&
6554 strstr(ctype, "multipart") &&
6555 (strstr(ctype, "application/rlmi+xml") ||
6556 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6557 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6558 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6559 GList *parts = purple_mime_document_get_parts(mime);
6560 GSList *buddies = NULL;
6561 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6563 while (parts) {
6564 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6565 purple_mime_part_get_length(parts->data));
6567 if (xml && !sipe_strequal(xml->name, "list")) {
6568 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6570 buddies = g_slist_append(buddies, uri);
6572 xmlnode_free(xml);
6574 parts = parts->next;
6576 g_free(doc);
6577 if (mime) purple_mime_document_free(mime);
6579 payload->host = g_strdup(who);
6580 payload->buddies = buddies;
6581 sipe_schedule_action(action_name, timeout,
6582 sipe_subscribe_presence_batched_routed,
6583 sipe_subscribe_presence_batched_routed_free,
6584 sip, payload);
6585 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6587 } else {
6588 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6589 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6591 g_free(action_name);
6595 * Dispatcher for all incoming subscription information
6596 * whether it comes from NOTIFY, BENOTIFY requests or
6597 * piggy-backed to subscription's OK responce.
6599 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6600 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6602 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6604 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6605 const gchar *event = sipmsg_find_header(msg, "Event");
6606 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6607 char *tmp;
6609 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6610 event ? event : "",
6611 tmp = fix_newlines(msg->body));
6612 g_free(tmp);
6613 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6615 /* implicit subscriptions */
6616 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6617 sipe_process_imdn(sip, msg);
6620 if (event) {
6621 /* for one off subscriptions (send with Expire: 0) */
6622 if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6624 sipe_process_provisioning_v2(sip, msg);
6626 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6628 sipe_process_provisioning(sip, msg);
6630 else if (!g_ascii_strcasecmp(event, "presence"))
6632 sipe_process_presence(sip, msg);
6634 else if (!g_ascii_strcasecmp(event, "registration-notify"))
6636 sipe_process_registration_notify(sip, msg);
6639 if (!subscription_state || strstr(subscription_state, "active"))
6641 if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6643 sipe_process_roaming_contacts(sip, msg);
6645 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6647 sipe_process_roaming_self(sip, msg);
6649 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6651 sipe_process_roaming_acl(sip, msg);
6653 else if (!g_ascii_strcasecmp(event, "presence.wpending"))
6655 sipe_process_presence_wpending(sip, msg);
6657 else if (!g_ascii_strcasecmp(event, "conference"))
6659 sipe_process_conference(sip, msg);
6664 /* The server sends status 'terminated' */
6665 if (subscription_state && strstr(subscription_state, "terminated") ) {
6666 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6667 gchar *key = sipe_get_subscription_key(event, who);
6669 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6670 g_free(who);
6672 if (g_hash_table_lookup(sip->subscriptions, key)) {
6673 g_hash_table_remove(sip->subscriptions, key);
6674 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6677 g_free(key);
6680 if (!request && event) {
6681 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6682 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6683 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6685 if (timeout) {
6686 /* 2 min ahead of expiration */
6687 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6689 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6690 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6692 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6693 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6694 g_free(action_name);
6696 else if (!g_ascii_strcasecmp(event, "presence") &&
6697 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6699 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6700 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6702 if (sip->batched_support) {
6703 sipe_process_presence_timeout(sip, msg, who, timeout);
6705 else {
6706 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6707 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6709 g_free(action_name);
6710 g_free(who);
6715 /* The client responses on received a NOTIFY message */
6716 if (request && !benotify)
6718 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6723 * Whether user manually changed status or
6724 * it was changed automatically due to user
6725 * became inactive/active again
6727 static gboolean
6728 sipe_is_user_state(struct sipe_account_data *sip)
6730 gboolean res;
6731 time_t now = time(NULL);
6733 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6734 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6736 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6738 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6739 return res;
6742 static void
6743 send_presence_soap0(struct sipe_account_data *sip,
6744 gboolean do_publish_calendar,
6745 gboolean do_reset_status)
6747 struct sipe_ews* ews = sip->ews;
6748 int availability = 0;
6749 int activity = 0;
6750 gchar *body;
6751 gchar *tmp;
6752 gchar *tmp2 = NULL;
6753 gchar *res_note = NULL;
6754 gchar *res_oof = NULL;
6755 const gchar *note_pub = NULL;
6756 gchar *states = NULL;
6757 gchar *calendar_data = NULL;
6758 gchar *epid = get_epid(sip);
6759 time_t now = time(NULL);
6760 gchar *since_time_str = sipe_utils_time_to_str(now);
6761 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6762 const char *user_input;
6763 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6765 if (oof_note && sip->note) {
6766 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6767 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6770 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6772 if (!sip->initial_state_published ||
6773 do_reset_status)
6775 g_free(sip->status);
6776 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6779 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6781 /* Note */
6782 if (pub_oof) {
6783 note_pub = oof_note;
6784 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6785 ews->published = TRUE;
6786 } else if (sip->note) {
6787 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6788 g_free(sip->note);
6789 sip->note = NULL;
6790 sip->is_oof_note = FALSE;
6791 sip->note_since = 0;
6792 } else {
6793 note_pub = sip->note;
6794 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6798 if (note_pub)
6800 /* to protocol internal plain text format */
6801 tmp = purple_markup_strip_html(note_pub);
6802 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6803 g_free(tmp);
6806 /* User State */
6807 if (!do_reset_status) {
6808 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6810 gchar *activity_token = NULL;
6811 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6813 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6814 avail_2007,
6815 since_time_str,
6816 epid,
6817 activity_token);
6818 g_free(activity_token);
6820 else /* preserve existing publication */
6822 xmlnode *xn_states;
6823 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6824 states = xmlnode_to_str(xn_states, NULL);
6825 /* this is a hack-around to remove added newline after inner element,
6826 * state in this case, where it shouldn't be.
6827 * After several use of xmlnode_to_str, amount of added newlines
6828 * grows significantly.
6830 purple_str_strip_char(states, '\n');
6831 //purple_str_strip_char(states, '\r');
6834 } else {
6835 /* do nothing - then User state will be erased */
6837 sip->initial_state_published = TRUE;
6839 /* CalendarInfo */
6840 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6842 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6843 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6844 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6845 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6846 fb_start_str,
6847 free_busy_base64);
6848 g_free(fb_start_str);
6849 g_free(free_busy_base64);
6852 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6854 /* forming resulting XML */
6855 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6856 sip->username,
6857 availability,
6858 activity,
6859 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6860 res_note ? res_note : "",
6861 res_oof ? res_oof : "",
6862 states ? states : "",
6863 calendar_data ? calendar_data : "",
6864 epid,
6865 since_time_str,
6866 since_time_str,
6867 user_input);
6868 g_free(tmp);
6869 g_free(tmp2);
6870 g_free(res_note);
6871 g_free(states);
6872 g_free(calendar_data);
6874 send_soap_request(sip, body);
6876 g_free(body);
6877 g_free(since_time_str);
6878 g_free(epid);
6881 void
6882 send_presence_soap(struct sipe_account_data *sip,
6883 gboolean do_publish_calendar)
6885 return send_presence_soap0(sip, do_publish_calendar, FALSE);
6889 static gboolean
6890 process_send_presence_category_publish_response(struct sipe_account_data *sip,
6891 struct sipmsg *msg,
6892 struct transaction *trans)
6894 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
6896 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
6897 xmlnode *xml;
6898 xmlnode *node;
6899 gchar *fault_code;
6900 GHashTable *faults;
6901 int index_our;
6902 gboolean has_device_publication = FALSE;
6904 xml = xmlnode_from_str(msg->body, msg->bodylen);
6906 /* test if version mismatch fault */
6907 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
6908 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
6909 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
6910 g_free(fault_code);
6911 xmlnode_free(xml);
6912 return TRUE;
6914 g_free(fault_code);
6916 /* accumulating information about faulty versions */
6917 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
6918 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
6919 node;
6920 node = xmlnode_get_next_twin(node))
6922 const gchar *index = xmlnode_get_attrib(node, "index");
6923 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
6925 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
6926 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
6928 xmlnode_free(xml);
6930 /* here we are parsing own request to figure out what publication
6931 * referensed here only by index went wrong
6933 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
6935 /* publication */
6936 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
6937 index_our = 1; /* starts with 1 - our first publication */
6938 node;
6939 node = xmlnode_get_next_twin(node), index_our++)
6941 gchar *idx = g_strdup_printf("%d", index_our);
6942 const gchar *curVersion = g_hash_table_lookup(faults, idx);
6943 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
6944 g_free(idx);
6946 if (sipe_strequal("device", categoryName)) {
6947 has_device_publication = TRUE;
6950 if (curVersion) { /* fault exist on this index */
6951 const gchar *container = xmlnode_get_attrib(node, "container");
6952 const gchar *instance = xmlnode_get_attrib(node, "instance");
6953 /* key is <category><instance><container> */
6954 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
6955 struct sipe_publication *publication =
6956 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
6958 purple_debug_info("sipe", "key is %s\n", key);
6960 if (publication) {
6961 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6962 key, curVersion, publication->version);
6963 /* updating publication's version to the correct one */
6964 publication->version = atoi(curVersion);
6966 g_free(key);
6969 xmlnode_free(xml);
6970 g_hash_table_destroy(faults);
6972 /* rebublishing with right versions */
6973 if (has_device_publication) {
6974 send_publish_category_initial(sip);
6975 } else {
6976 send_presence_status(sip);
6979 return TRUE;
6983 * Returns 'device' XML part for publication.
6984 * Must be g_free'd after use.
6986 static gchar *
6987 sipe_publish_get_category_device(struct sipe_account_data *sip)
6989 gchar *uri;
6990 gchar *doc;
6991 gchar *epid = get_epid(sip);
6992 gchar *uuid = generateUUIDfromEPID(epid);
6993 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
6994 /* key is <category><instance><container> */
6995 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
6996 struct sipe_publication *publication =
6997 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
6999 g_free(key);
7000 g_free(epid);
7002 uri = sip_uri_self(sip);
7003 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7004 device_instance,
7005 publication ? publication->version : 0,
7006 uuid,
7007 uri,
7008 "00:00:00+01:00", /* @TODO make timezone real*/
7009 sipe_get_host_name()
7012 g_free(uri);
7013 g_free(uuid);
7015 return doc;
7019 * A service method - use
7020 * - send_publish_get_category_state_machine and
7021 * - send_publish_get_category_state_user instead.
7022 * Must be g_free'd after use.
7024 static gchar *
7025 sipe_publish_get_category_state(struct sipe_account_data *sip,
7026 gboolean is_user_state)
7028 int availability = sipe_get_availability_by_status(sip->status, NULL);
7029 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7030 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7031 /* key is <category><instance><container> */
7032 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7033 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7034 struct sipe_publication *publication_2 =
7035 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7036 struct sipe_publication *publication_3 =
7037 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7039 g_free(key_2);
7040 g_free(key_3);
7042 if (publication_2 && (publication_2->availability == availability))
7044 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
7045 return NULL; /* nothing to update */
7048 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7049 instance,
7050 publication_2 ? publication_2->version : 0,
7051 availability,
7052 instance,
7053 publication_3 ? publication_3->version : 0,
7054 availability);
7058 * Only Busy and OOF calendar event are published.
7059 * Different instances are used for that.
7061 * Must be g_free'd after use.
7063 static gchar *
7064 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7065 struct sipe_cal_event *event,
7066 const char *uri,
7067 int cal_satus)
7069 gchar *start_time_str;
7070 int availability = 0;
7071 gchar *res;
7072 gchar *tmp = NULL;
7073 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7074 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7075 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7077 /* key is <category><instance><container> */
7078 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7079 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7080 struct sipe_publication *publication_2 =
7081 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7082 struct sipe_publication *publication_3 =
7083 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7085 g_free(key_2);
7086 g_free(key_3);
7088 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7089 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7090 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7091 return NULL;
7094 if (event &&
7095 publication_3 &&
7096 (publication_3->availability == availability) &&
7097 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7099 g_free(tmp);
7100 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7101 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7102 return NULL; /* nothing to update */
7104 g_free(tmp);
7106 if (event &&
7107 (event->cal_status == SIPE_CAL_BUSY ||
7108 event->cal_status == SIPE_CAL_OOF))
7110 gchar *availability_xml_str = NULL;
7111 gchar *activity_xml_str = NULL;
7113 if (event->cal_status == SIPE_CAL_BUSY) {
7114 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7117 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7118 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7119 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7120 "minAvailability=\"6500\"",
7121 "maxAvailability=\"8999\"");
7122 } else if (event->cal_status == SIPE_CAL_OOF) {
7123 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7124 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7125 "minAvailability=\"12000\"",
7126 "");
7128 start_time_str = sipe_utils_time_to_str(event->start_time);
7130 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7131 instance,
7132 publication_2 ? publication_2->version : 0,
7133 uri,
7134 start_time_str,
7135 availability_xml_str ? availability_xml_str : "",
7136 activity_xml_str ? activity_xml_str : "",
7137 event->subject ? event->subject : "",
7138 event->location ? event->location : "",
7140 instance,
7141 publication_3 ? publication_3->version : 0,
7142 uri,
7143 start_time_str,
7144 availability_xml_str ? availability_xml_str : "",
7145 activity_xml_str ? activity_xml_str : "",
7146 event->subject ? event->subject : "",
7147 event->location ? event->location : ""
7149 g_free(start_time_str);
7150 g_free(availability_xml_str);
7151 g_free(activity_xml_str);
7154 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7156 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7157 instance,
7158 publication_2 ? publication_2->version : 0,
7160 instance,
7161 publication_3 ? publication_3->version : 0
7165 return res;
7169 * Returns 'machineState' XML part for publication.
7170 * Must be g_free'd after use.
7172 static gchar *
7173 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7175 return sipe_publish_get_category_state(sip, FALSE);
7179 * Returns 'userState' XML part for publication.
7180 * Must be g_free'd after use.
7182 static gchar *
7183 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7185 return sipe_publish_get_category_state(sip, TRUE);
7189 * Returns 'note' XML part for publication.
7190 * Must be g_free'd after use.
7192 * Protocol format for Note is plain text.
7194 * @param note a note in Sipe internal HTML format
7195 * @param note_type either personal or OOF
7197 static gchar *
7198 sipe_publish_get_category_note(struct sipe_account_data *sip,
7199 const char *note, /* html */
7200 const char *note_type,
7201 time_t note_start,
7202 time_t note_end)
7204 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7205 /* key is <category><instance><container> */
7206 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7207 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7208 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7210 struct sipe_publication *publication_note_200 =
7211 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7212 struct sipe_publication *publication_note_300 =
7213 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7214 struct sipe_publication *publication_note_400 =
7215 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7217 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7218 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7219 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7220 char *res, *tmp1, *tmp2, *tmp3;
7221 char *start_time_attr;
7222 char *end_time_attr;
7224 g_free(tmp);
7225 tmp = NULL;
7226 g_free(key_note_200);
7227 g_free(key_note_300);
7228 g_free(key_note_400);
7230 /* we even need to republish empty note */
7231 if (sipe_strequal(n1, n2))
7233 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7234 g_free(n1);
7235 return NULL; /* nothing to update */
7238 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7239 g_free(tmp);
7240 tmp = NULL;
7241 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7242 g_free(tmp);
7244 if (n1) {
7245 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7246 instance,
7247 200,
7248 publication_note_200 ? publication_note_200->version : 0,
7249 note_type,
7250 start_time_attr ? start_time_attr : "",
7251 end_time_attr ? end_time_attr : "",
7252 n1);
7254 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7255 instance,
7256 300,
7257 publication_note_300 ? publication_note_300->version : 0,
7258 note_type,
7259 start_time_attr ? start_time_attr : "",
7260 end_time_attr ? end_time_attr : "",
7261 n1);
7263 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7264 instance,
7265 400,
7266 publication_note_400 ? publication_note_400->version : 0,
7267 note_type,
7268 start_time_attr ? start_time_attr : "",
7269 end_time_attr ? end_time_attr : "",
7270 n1);
7271 } else {
7272 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7273 "note",
7274 instance,
7275 200,
7276 publication_note_200 ? publication_note_200->version : 0,
7277 "static");
7278 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7279 "note",
7280 instance,
7281 300,
7282 publication_note_200 ? publication_note_200->version : 0,
7283 "static");
7284 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7285 "note",
7286 instance,
7287 400,
7288 publication_note_200 ? publication_note_200->version : 0,
7289 "static");
7291 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7293 g_free(start_time_attr);
7294 g_free(end_time_attr);
7295 g_free(tmp1);
7296 g_free(tmp2);
7297 g_free(tmp3);
7298 g_free(n1);
7300 return res;
7304 * Returns 'calendarData' XML part with WorkingHours for publication.
7305 * Must be g_free'd after use.
7307 static gchar *
7308 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7310 struct sipe_ews* ews = sip->ews;
7312 /* key is <category><instance><container> */
7313 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7314 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7315 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7316 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7317 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7318 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7320 struct sipe_publication *publication_cal_1 =
7321 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7322 struct sipe_publication *publication_cal_100 =
7323 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7324 struct sipe_publication *publication_cal_200 =
7325 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7326 struct sipe_publication *publication_cal_300 =
7327 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7328 struct sipe_publication *publication_cal_400 =
7329 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7330 struct sipe_publication *publication_cal_32000 =
7331 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7333 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7334 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7336 g_free(key_cal_1);
7337 g_free(key_cal_100);
7338 g_free(key_cal_200);
7339 g_free(key_cal_300);
7340 g_free(key_cal_400);
7341 g_free(key_cal_32000);
7343 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7344 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7345 return NULL;
7348 if (sipe_strequal(n1, n2))
7350 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7351 return NULL; /* nothing to update */
7354 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7355 /* 1 */
7356 publication_cal_1 ? publication_cal_1->version : 0,
7357 ews->email,
7358 ews->working_hours_xml_str,
7359 /* 100 - Public */
7360 publication_cal_100 ? publication_cal_100->version : 0,
7361 /* 200 - Company */
7362 publication_cal_200 ? publication_cal_200->version : 0,
7363 ews->email,
7364 ews->working_hours_xml_str,
7365 /* 300 - Team */
7366 publication_cal_300 ? publication_cal_300->version : 0,
7367 ews->email,
7368 ews->working_hours_xml_str,
7369 /* 400 - Personal */
7370 publication_cal_400 ? publication_cal_400->version : 0,
7371 ews->email,
7372 ews->working_hours_xml_str,
7373 /* 32000 - Blocked */
7374 publication_cal_32000 ? publication_cal_32000->version : 0
7379 * Returns 'calendarData' XML part with FreeBusy for publication.
7380 * Must be g_free'd after use.
7382 static gchar *
7383 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7385 struct sipe_ews* ews = sip->ews;
7386 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7387 char *fb_start_str;
7388 char *free_busy_base64;
7389 const char *st;
7390 const char *fb;
7391 char *res;
7393 /* key is <category><instance><container> */
7394 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7395 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7396 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7397 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7398 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7399 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7401 struct sipe_publication *publication_cal_1 =
7402 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7403 struct sipe_publication *publication_cal_100 =
7404 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7405 struct sipe_publication *publication_cal_200 =
7406 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7407 struct sipe_publication *publication_cal_300 =
7408 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7409 struct sipe_publication *publication_cal_400 =
7410 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7411 struct sipe_publication *publication_cal_32000 =
7412 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7414 g_free(key_cal_1);
7415 g_free(key_cal_100);
7416 g_free(key_cal_200);
7417 g_free(key_cal_300);
7418 g_free(key_cal_400);
7419 g_free(key_cal_32000);
7421 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7422 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7423 return NULL;
7426 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7427 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7429 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7430 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7432 /* we will rebuplish the same data to refresh publication time,
7433 * so if data from multiple sources, most recent will be choosen
7435 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7437 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7438 // g_free(fb_start_str);
7439 // g_free(free_busy_base64);
7440 // return NULL; /* nothing to update */
7443 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7444 /* 1 */
7445 cal_data_instance,
7446 publication_cal_1 ? publication_cal_1->version : 0,
7447 /* 100 - Public */
7448 cal_data_instance,
7449 publication_cal_100 ? publication_cal_100->version : 0,
7450 /* 200 - Company */
7451 cal_data_instance,
7452 publication_cal_200 ? publication_cal_200->version : 0,
7453 ews->email,
7454 fb_start_str,
7455 free_busy_base64,
7456 /* 300 - Team */
7457 cal_data_instance,
7458 publication_cal_300 ? publication_cal_300->version : 0,
7459 ews->email,
7460 fb_start_str,
7461 free_busy_base64,
7462 /* 400 - Personal */
7463 cal_data_instance,
7464 publication_cal_400 ? publication_cal_400->version : 0,
7465 ews->email,
7466 fb_start_str,
7467 free_busy_base64,
7468 /* 32000 - Blocked */
7469 cal_data_instance,
7470 publication_cal_32000 ? publication_cal_32000->version : 0
7473 g_free(fb_start_str);
7474 g_free(free_busy_base64);
7475 return res;
7478 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7480 gchar *uri;
7481 gchar *doc;
7482 gchar *tmp;
7483 gchar *hdr;
7485 uri = sip_uri_self(sip);
7486 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7487 uri,
7488 publications);
7490 tmp = get_contact(sip);
7491 hdr = g_strdup_printf("Contact: %s\r\n"
7492 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7494 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7496 g_free(tmp);
7497 g_free(hdr);
7498 g_free(uri);
7499 g_free(doc);
7502 static void
7503 send_publish_category_initial(struct sipe_account_data *sip)
7505 gchar *pub_device = sipe_publish_get_category_device(sip);
7506 gchar *pub_machine;
7507 gchar *publications;
7509 g_free(sip->status);
7510 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7512 pub_machine = sipe_publish_get_category_state_machine(sip);
7513 publications = g_strdup_printf("%s%s",
7514 pub_device,
7515 pub_machine ? pub_machine : "");
7516 g_free(pub_device);
7517 g_free(pub_machine);
7519 send_presence_publish(sip, publications);
7520 g_free(publications);
7523 static void
7524 send_presence_category_publish(struct sipe_account_data *sip)
7526 gchar *pub_state = sipe_is_user_state(sip) ?
7527 sipe_publish_get_category_state_user(sip) :
7528 sipe_publish_get_category_state_machine(sip);
7529 gchar *pub_note = sipe_publish_get_category_note(sip,
7530 sip->note,
7531 sip->is_oof_note ? "OOF" : "personal",
7534 gchar *publications;
7536 if (!pub_state && !pub_note) {
7537 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7538 return;
7541 publications = g_strdup_printf("%s%s",
7542 pub_state ? pub_state : "",
7543 pub_note ? pub_note : "");
7545 g_free(pub_state);
7546 g_free(pub_note);
7548 send_presence_publish(sip, publications);
7549 g_free(publications);
7553 * Publishes self status
7554 * based on own calendar information.
7556 * For 2007+
7558 void
7559 publish_calendar_status_self(struct sipe_account_data *sip)
7561 struct sipe_cal_event* event = NULL;
7562 gchar *pub_cal_working_hours = NULL;
7563 gchar *pub_cal_free_busy = NULL;
7564 gchar *pub_calendar = NULL;
7565 gchar *pub_calendar2 = NULL;
7566 gchar *pub_oof_note = NULL;
7567 const gchar *oof_note;
7568 time_t oof_start = 0;
7569 time_t oof_end = 0;
7571 if (!sip->ews) {
7572 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7573 return;
7576 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7577 if (sip->ews->cal_events) {
7578 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7581 if (!event) {
7582 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7583 } else {
7584 char *desc = sipe_cal_event_describe(event);
7585 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7586 g_free(desc);
7589 /* Logic
7590 if OOF
7591 OOF publish, Busy clean
7592 ilse if Busy
7593 OOF clean, Busy publish
7594 else
7595 OOF clean, Busy clean
7597 if (event && event->cal_status == SIPE_CAL_OOF) {
7598 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7599 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7600 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7601 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7602 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7603 } else {
7604 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7605 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7608 oof_note = sipe_ews_get_oof_note(sip->ews);
7609 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7610 oof_start = sip->ews->oof_start;
7611 oof_end = sip->ews->oof_end;
7613 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7615 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7616 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7618 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7619 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7620 } else {
7621 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7622 pub_cal_working_hours ? pub_cal_working_hours : "",
7623 pub_cal_free_busy ? pub_cal_free_busy : "",
7624 pub_calendar ? pub_calendar : "",
7625 pub_calendar2 ? pub_calendar2 : "",
7626 pub_oof_note ? pub_oof_note : "");
7628 send_presence_publish(sip, publications);
7629 g_free(publications);
7632 g_free(pub_cal_working_hours);
7633 g_free(pub_cal_free_busy);
7634 g_free(pub_calendar);
7635 g_free(pub_calendar2);
7636 g_free(pub_oof_note);
7638 /* repeat scheduling */
7639 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7642 static void send_presence_status(struct sipe_account_data *sip)
7644 PurpleStatus * status = purple_account_get_active_status(sip->account);
7646 if (!status) return;
7648 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7649 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7650 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7652 if (sip->ocs2007) {
7653 send_presence_category_publish(sip);
7654 } else {
7655 send_presence_soap(sip, FALSE);
7659 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7661 gboolean found = FALSE;
7662 const char *method = msg->method ? msg->method : "NOT FOUND";
7663 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7664 if (msg->response == 0) { /* request */
7665 if (sipe_strequal(method, "MESSAGE")) {
7666 process_incoming_message(sip, msg);
7667 found = TRUE;
7668 } else if (sipe_strequal(method, "NOTIFY")) {
7669 purple_debug_info("sipe","send->process_incoming_notify\n");
7670 process_incoming_notify(sip, msg, TRUE, FALSE);
7671 found = TRUE;
7672 } else if (sipe_strequal(method, "BENOTIFY")) {
7673 purple_debug_info("sipe","send->process_incoming_benotify\n");
7674 process_incoming_notify(sip, msg, TRUE, TRUE);
7675 found = TRUE;
7676 } else if (sipe_strequal(method, "INVITE")) {
7677 process_incoming_invite(sip, msg);
7678 found = TRUE;
7679 } else if (sipe_strequal(method, "REFER")) {
7680 process_incoming_refer(sip, msg);
7681 found = TRUE;
7682 } else if (sipe_strequal(method, "OPTIONS")) {
7683 process_incoming_options(sip, msg);
7684 found = TRUE;
7685 } else if (sipe_strequal(method, "INFO")) {
7686 process_incoming_info(sip, msg);
7687 found = TRUE;
7688 } else if (sipe_strequal(method, "ACK")) {
7689 // ACK's don't need any response
7690 found = TRUE;
7691 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7692 // LCS 2005 sends us these - just respond 200 OK
7693 found = TRUE;
7694 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7695 } else if (sipe_strequal(method, "BYE")) {
7696 process_incoming_bye(sip, msg);
7697 found = TRUE;
7698 } else {
7699 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7701 } else { /* response */
7702 struct transaction *trans = transactions_find(sip, msg);
7703 if (trans) {
7704 if (msg->response == 407) {
7705 gchar *resend, *auth, *ptmp;
7707 if (sip->proxy.retries > 30) return;
7708 sip->proxy.retries++;
7709 /* do proxy authentication */
7711 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7713 fill_auth(ptmp, &sip->proxy);
7714 auth = auth_header(sip, &sip->proxy, trans->msg);
7715 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7716 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7717 g_free(auth);
7718 resend = sipmsg_to_string(trans->msg);
7719 /* resend request */
7720 sendout_pkt(sip->gc, resend);
7721 g_free(resend);
7722 } else {
7723 if (msg->response < 200) {
7724 /* ignore provisional response */
7725 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7726 } else {
7727 sip->proxy.retries = 0;
7728 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7729 if (msg->response == 401)
7731 sip->registrar.retries++;
7733 else
7735 sip->registrar.retries = 0;
7737 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7738 } else {
7739 if (msg->response == 401) {
7740 gchar *resend, *auth, *ptmp;
7741 const char* auth_scheme;
7743 if (sip->registrar.retries > 4) return;
7744 sip->registrar.retries++;
7746 auth_scheme = sipe_get_auth_scheme_name(sip);
7747 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7749 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7750 if (!ptmp) {
7751 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7752 sip->gc->wants_to_die = TRUE;
7753 purple_connection_error(sip->gc, tmp2);
7754 g_free(tmp2);
7755 return;
7758 fill_auth(ptmp, &sip->registrar);
7759 auth = auth_header(sip, &sip->registrar, trans->msg);
7760 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7761 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7763 //sipmsg_remove_header_now(trans->msg, "Authorization");
7764 //sipmsg_add_header(trans->msg, "Authorization", auth);
7765 g_free(auth);
7766 resend = sipmsg_to_string(trans->msg);
7767 /* resend request */
7768 sendout_pkt(sip->gc, resend);
7769 g_free(resend);
7773 if (trans->callback) {
7774 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7775 /* call the callback to process response*/
7776 (trans->callback)(sip, msg, trans);
7779 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7780 transactions_remove(sip, trans);
7784 found = TRUE;
7785 } else {
7786 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7789 if (!found) {
7790 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7794 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7796 char *cur;
7797 char *dummy;
7798 char *tmp;
7799 struct sipmsg *msg;
7800 int restlen;
7801 cur = conn->inbuf;
7803 /* according to the RFC remove CRLF at the beginning */
7804 while (*cur == '\r' || *cur == '\n') {
7805 cur++;
7807 if (cur != conn->inbuf) {
7808 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7809 conn->inbufused = strlen(conn->inbuf);
7812 /* Received a full Header? */
7813 sip->processing_input = TRUE;
7814 while (sip->processing_input &&
7815 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7816 time_t currtime = time(NULL);
7817 cur += 2;
7818 cur[0] = '\0';
7819 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7820 g_free(tmp);
7821 msg = sipmsg_parse_header(conn->inbuf);
7822 cur[0] = '\r';
7823 cur += 2;
7824 restlen = conn->inbufused - (cur - conn->inbuf);
7825 if (msg && restlen >= msg->bodylen) {
7826 dummy = g_malloc(msg->bodylen + 1);
7827 memcpy(dummy, cur, msg->bodylen);
7828 dummy[msg->bodylen] = '\0';
7829 msg->body = dummy;
7830 cur += msg->bodylen;
7831 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7832 conn->inbufused = strlen(conn->inbuf);
7833 } else {
7834 if (msg){
7835 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7836 sipmsg_free(msg);
7838 return;
7841 /*if (msg->body) {
7842 purple_debug_info("sipe", "body:\n%s", msg->body);
7845 // Verify the signature before processing it
7846 if (sip->registrar.gssapi_context) {
7847 struct sipmsg_breakdown msgbd;
7848 gchar *signature_input_str;
7849 gchar *rspauth;
7850 msgbd.msg = msg;
7851 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7852 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
7854 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7856 if (rspauth != NULL) {
7857 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7858 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7859 process_input_message(sip, msg);
7860 } else {
7861 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7862 purple_connection_error(sip->gc, _("Invalid message signature received"));
7863 sip->gc->wants_to_die = TRUE;
7865 } else if (msg->response == 401) {
7866 purple_connection_error(sip->gc, _("Wrong password"));
7867 sip->gc->wants_to_die = TRUE;
7869 g_free(signature_input_str);
7871 g_free(rspauth);
7872 sipmsg_breakdown_free(&msgbd);
7873 } else {
7874 process_input_message(sip, msg);
7877 sipmsg_free(msg);
7881 static void sipe_udp_process(gpointer data, gint source,
7882 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
7884 PurpleConnection *gc = data;
7885 struct sipe_account_data *sip = gc->proto_data;
7886 int len;
7888 static char buffer[65536];
7889 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
7890 time_t currtime = time(NULL);
7891 struct sipmsg *msg;
7892 buffer[len] = '\0';
7893 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
7894 msg = sipmsg_parse_msg(buffer);
7895 if (msg) process_input_message(sip, msg);
7899 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
7901 struct sipe_account_data *sip = gc->proto_data;
7902 PurpleSslConnection *gsc = sip->gsc;
7904 purple_debug_error("sipe", "%s",debug);
7905 purple_connection_error(gc, msg);
7907 /* Invalidate this connection. Next send will open a new one */
7908 if (gsc) {
7909 connection_remove(sip, gsc->fd);
7910 purple_ssl_close(gsc);
7912 sip->gsc = NULL;
7913 sip->fd = -1;
7916 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7917 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7919 PurpleConnection *gc = data;
7920 struct sipe_account_data *sip;
7921 struct sip_connection *conn;
7922 int readlen, len;
7923 gboolean firstread = TRUE;
7925 /* NOTE: This check *IS* necessary */
7926 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
7927 purple_ssl_close(gsc);
7928 return;
7931 sip = gc->proto_data;
7932 conn = connection_find(sip, gsc->fd);
7933 if (conn == NULL) {
7934 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7935 gc->wants_to_die = TRUE;
7936 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
7937 return;
7940 /* Read all available data from the SSL connection */
7941 do {
7942 /* Increase input buffer size as needed */
7943 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7944 conn->inbuflen += SIMPLE_BUF_INC;
7945 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7946 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
7949 /* Try to read as much as there is space left in the buffer */
7950 readlen = conn->inbuflen - conn->inbufused - 1;
7951 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
7953 if (len < 0 && errno == EAGAIN) {
7954 /* Try again later */
7955 return;
7956 } else if (len < 0) {
7957 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
7958 return;
7959 } else if (firstread && (len == 0)) {
7960 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
7961 return;
7964 conn->inbufused += len;
7965 firstread = FALSE;
7967 /* Equivalence indicates that there is possibly more data to read */
7968 } while (len == readlen);
7970 conn->inbuf[conn->inbufused] = '\0';
7971 process_input(sip, conn);
7975 static void sipe_input_cb(gpointer data, gint source,
7976 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7978 PurpleConnection *gc = data;
7979 struct sipe_account_data *sip = gc->proto_data;
7980 int len;
7981 struct sip_connection *conn = connection_find(sip, source);
7982 if (!conn) {
7983 purple_debug_error("sipe", "Connection not found!\n");
7984 return;
7987 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7988 conn->inbuflen += SIMPLE_BUF_INC;
7989 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7992 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
7994 if (len < 0 && errno == EAGAIN)
7995 return;
7996 else if (len <= 0) {
7997 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7998 connection_remove(sip, source);
7999 if (sip->fd == source) sip->fd = -1;
8000 return;
8003 conn->inbufused += len;
8004 conn->inbuf[conn->inbufused] = '\0';
8006 process_input(sip, conn);
8009 /* Callback for new connections on incoming TCP port */
8010 static void sipe_newconn_cb(gpointer data, gint source,
8011 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8013 PurpleConnection *gc = data;
8014 struct sipe_account_data *sip = gc->proto_data;
8015 struct sip_connection *conn;
8017 int newfd = accept(source, NULL, NULL);
8019 conn = connection_create(sip, newfd);
8021 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8024 static void login_cb(gpointer data, gint source,
8025 SIPE_UNUSED_PARAMETER const gchar *error_message)
8027 PurpleConnection *gc = data;
8028 struct sipe_account_data *sip;
8029 struct sip_connection *conn;
8031 if (!PURPLE_CONNECTION_IS_VALID(gc))
8033 if (source >= 0)
8034 close(source);
8035 return;
8038 if (source < 0) {
8039 purple_connection_error(gc, _("Could not connect"));
8040 return;
8043 sip = gc->proto_data;
8044 sip->fd = source;
8045 sip->last_keepalive = time(NULL);
8047 conn = connection_create(sip, source);
8049 do_register(sip);
8051 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8054 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8055 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8057 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8058 if (sip == NULL) return;
8060 do_register(sip);
8063 static guint sipe_ht_hash_nick(const char *nick)
8065 char *lc = g_utf8_strdown(nick, -1);
8066 guint bucket = g_str_hash(lc);
8067 g_free(lc);
8069 return bucket;
8072 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8074 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8077 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8079 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8081 sip->listen_data = NULL;
8083 if (listenfd == -1) {
8084 purple_connection_error(sip->gc, _("Could not create listen socket"));
8085 return;
8088 sip->fd = listenfd;
8090 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8091 sip->listenfd = sip->fd;
8093 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8095 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8096 do_register(sip);
8099 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8100 SIPE_UNUSED_PARAMETER const char *error_message)
8102 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8104 sip->query_data = NULL;
8106 if (!hosts || !hosts->data) {
8107 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8108 return;
8111 hosts = g_slist_remove(hosts, hosts->data);
8112 g_free(sip->serveraddr);
8113 sip->serveraddr = hosts->data;
8114 hosts = g_slist_remove(hosts, hosts->data);
8115 while (hosts) {
8116 void *tmp = hosts->data;
8117 hosts = g_slist_remove(hosts, tmp);
8118 hosts = g_slist_remove(hosts, tmp);
8119 g_free(tmp);
8122 /* create socket for incoming connections */
8123 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8124 sipe_udp_host_resolved_listen_cb, sip);
8125 if (sip->listen_data == NULL) {
8126 purple_connection_error(sip->gc, _("Could not create listen socket"));
8127 return;
8131 static const struct sipe_service_data *current_service = NULL;
8133 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8134 PurpleSslErrorType error,
8135 gpointer data)
8137 PurpleConnection *gc = data;
8138 struct sipe_account_data *sip;
8140 /* If the connection is already disconnected, we don't need to do anything else */
8141 if (!PURPLE_CONNECTION_IS_VALID(gc))
8142 return;
8144 sip = gc->proto_data;
8145 current_service = sip->service_data;
8146 if (current_service) {
8147 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8148 current_service->transport ? current_service->transport : "NULL",
8149 current_service->service ? current_service->service : "NULL");
8152 sip->fd = -1;
8153 sip->gsc = NULL;
8155 switch(error) {
8156 case PURPLE_SSL_CONNECT_FAILED:
8157 purple_connection_error(gc, _("Connection failed"));
8158 break;
8159 case PURPLE_SSL_HANDSHAKE_FAILED:
8160 purple_connection_error(gc, _("SSL handshake failed"));
8161 break;
8162 case PURPLE_SSL_CERTIFICATE_INVALID:
8163 purple_connection_error(gc, _("SSL certificate invalid"));
8164 break;
8168 static void
8169 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8171 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8172 PurpleProxyConnectData *connect_data;
8174 sip->listen_data = NULL;
8176 sip->listenfd = listenfd;
8177 if (sip->listenfd == -1) {
8178 purple_connection_error(sip->gc, _("Could not create listen socket"));
8179 return;
8182 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8183 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8184 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8185 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8186 sipe_newconn_cb, sip->gc);
8187 purple_debug_info("sipe", "connecting to %s port %d\n",
8188 sip->realhostname, sip->realport);
8189 /* open tcp connection to the server */
8190 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8191 sip->realport, login_cb, sip->gc);
8193 if (connect_data == NULL) {
8194 purple_connection_error(sip->gc, _("Could not create socket"));
8198 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8200 PurpleAccount *account = sip->account;
8201 PurpleConnection *gc = sip->gc;
8203 if (port == 0) {
8204 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8207 sip->realhostname = hostname;
8208 sip->realport = port;
8210 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8211 hostname, port);
8213 /* TODO: is there a good default grow size? */
8214 if (sip->transport != SIPE_TRANSPORT_UDP)
8215 sip->txbuf = purple_circ_buffer_new(0);
8217 if (sip->transport == SIPE_TRANSPORT_TLS) {
8218 /* SSL case */
8219 if (!purple_ssl_is_supported()) {
8220 gc->wants_to_die = TRUE;
8221 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8222 return;
8225 purple_debug_info("sipe", "using SSL\n");
8227 sip->gsc = purple_ssl_connect(account, hostname, port,
8228 login_cb_ssl, sipe_ssl_connect_failure, gc);
8229 if (sip->gsc == NULL) {
8230 purple_connection_error(gc, _("Could not create SSL context"));
8231 return;
8233 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8234 /* UDP case */
8235 purple_debug_info("sipe", "using UDP\n");
8237 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8238 if (sip->query_data == NULL) {
8239 purple_connection_error(gc, _("Could not resolve hostname"));
8241 } else {
8242 /* TCP case */
8243 purple_debug_info("sipe", "using TCP\n");
8244 /* create socket for incoming connections */
8245 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8246 sipe_tcp_connect_listen_cb, sip);
8247 if (sip->listen_data == NULL) {
8248 purple_connection_error(gc, _("Could not create listen socket"));
8249 return;
8254 /* Service list for autodection */
8255 static const struct sipe_service_data service_autodetect[] = {
8256 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8257 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8258 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8259 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8260 { NULL, NULL, 0 }
8263 /* Service list for SSL/TLS */
8264 static const struct sipe_service_data service_tls[] = {
8265 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8266 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8267 { NULL, NULL, 0 }
8270 /* Service list for TCP */
8271 static const struct sipe_service_data service_tcp[] = {
8272 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8273 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8274 { NULL, NULL, 0 }
8277 /* Service list for UDP */
8278 static const struct sipe_service_data service_udp[] = {
8279 { "sip", "udp", SIPE_TRANSPORT_UDP },
8280 { NULL, NULL, 0 }
8283 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8284 static void resolve_next_service(struct sipe_account_data *sip,
8285 const struct sipe_service_data *start)
8287 if (start) {
8288 sip->service_data = start;
8289 } else {
8290 sip->service_data++;
8291 if (sip->service_data->service == NULL) {
8292 gchar *hostname;
8293 /* Try connecting to the SIP hostname directly */
8294 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8295 if (sip->auto_transport) {
8296 // If SSL is supported, default to using it; OCS servers aren't configured
8297 // by default to accept TCP
8298 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8299 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8300 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8303 hostname = g_strdup(sip->sipdomain);
8304 create_connection(sip, hostname, 0);
8305 return;
8309 /* Try to resolve next service */
8310 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8311 sip->service_data->transport,
8312 sip->sipdomain,
8313 srvresolved, sip);
8316 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8318 struct sipe_account_data *sip = data;
8320 sip->srv_query_data = NULL;
8322 /* find the host to connect to */
8323 if (results) {
8324 gchar *hostname = g_strdup(resp->hostname);
8325 int port = resp->port;
8326 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8327 hostname, port);
8328 g_free(resp);
8330 sip->transport = sip->service_data->type;
8332 create_connection(sip, hostname, port);
8333 } else {
8334 resolve_next_service(sip, NULL);
8338 static void sipe_login(PurpleAccount *account)
8340 PurpleConnection *gc;
8341 struct sipe_account_data *sip;
8342 gchar **signinname_login, **userserver;
8343 const char *transport;
8344 const char *email;
8346 const char *username = purple_account_get_username(account);
8347 gc = purple_account_get_connection(account);
8349 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8351 if (strpbrk(username, "\t\v\r\n") != NULL) {
8352 gc->wants_to_die = TRUE;
8353 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8354 return;
8357 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8358 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8359 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8360 sip->gc = gc;
8361 sip->account = account;
8362 sip->reregister_set = FALSE;
8363 sip->reauthenticate_set = FALSE;
8364 sip->subscribed = FALSE;
8365 sip->subscribed_buddies = FALSE;
8366 sip->initial_state_published = FALSE;
8368 /* username format: <username>,[<optional login>] */
8369 signinname_login = g_strsplit(username, ",", 2);
8370 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8372 /* ensure that username format is name@domain */
8373 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8374 g_strfreev(signinname_login);
8375 gc->wants_to_die = TRUE;
8376 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8377 return;
8379 sip->username = g_strdup(signinname_login[0]);
8381 /* ensure that email format is name@domain if provided */
8382 email = purple_account_get_string(sip->account, "email", NULL);
8383 if (!is_empty(email) &&
8384 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8386 gc->wants_to_die = TRUE;
8387 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8388 return;
8390 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8392 /* login name specified? */
8393 if (signinname_login[1] && strlen(signinname_login[1])) {
8394 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8395 gboolean has_domain = domain_user[1] != NULL;
8396 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8397 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8398 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8399 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8400 sip->authdomain ? sip->authdomain : "", sip->authuser);
8401 g_strfreev(domain_user);
8404 userserver = g_strsplit(signinname_login[0], "@", 2);
8405 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8406 purple_connection_set_display_name(gc, userserver[0]);
8407 sip->sipdomain = g_strdup(userserver[1]);
8408 g_strfreev(userserver);
8409 g_strfreev(signinname_login);
8411 if (strchr(sip->username, ' ') != NULL) {
8412 gc->wants_to_die = TRUE;
8413 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8414 return;
8417 sip->password = g_strdup(purple_connection_get_password(gc));
8419 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8420 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8421 g_free, (GDestroyNotify)g_hash_table_destroy);
8422 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8423 g_free, (GDestroyNotify)sipe_subscription_free);
8425 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8427 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8429 g_free(sip->status);
8430 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8432 sip->auto_transport = FALSE;
8433 transport = purple_account_get_string(account, "transport", "auto");
8434 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8435 if (userserver[0]) {
8436 /* Use user specified server[:port] */
8437 int port = 0;
8439 if (userserver[1])
8440 port = atoi(userserver[1]);
8442 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8443 userserver[0], port);
8445 if (sipe_strequal(transport, "auto")) {
8446 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8447 } else if (sipe_strequal(transport, "tls")) {
8448 sip->transport = SIPE_TRANSPORT_TLS;
8449 } else if (sipe_strequal(transport, "tcp")) {
8450 sip->transport = SIPE_TRANSPORT_TCP;
8451 } else {
8452 sip->transport = SIPE_TRANSPORT_UDP;
8455 create_connection(sip, g_strdup(userserver[0]), port);
8456 } else {
8457 /* Server auto-discovery */
8458 if (sipe_strequal(transport, "auto")) {
8459 sip->auto_transport = TRUE;
8460 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8461 current_service++;
8462 resolve_next_service(sip, current_service);
8463 } else {
8464 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8466 } else if (sipe_strequal(transport, "tls")) {
8467 resolve_next_service(sip, service_tls);
8468 } else if (sipe_strequal(transport, "tcp")) {
8469 resolve_next_service(sip, service_tcp);
8470 } else {
8471 resolve_next_service(sip, service_udp);
8474 g_strfreev(userserver);
8477 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8479 connection_free_all(sip);
8481 g_free(sip->epid);
8482 sip->epid = NULL;
8484 if (sip->query_data != NULL)
8485 purple_dnsquery_destroy(sip->query_data);
8486 sip->query_data = NULL;
8488 if (sip->srv_query_data != NULL)
8489 purple_srv_cancel(sip->srv_query_data);
8490 sip->srv_query_data = NULL;
8492 if (sip->listen_data != NULL)
8493 purple_network_listen_cancel(sip->listen_data);
8494 sip->listen_data = NULL;
8496 if (sip->gsc != NULL)
8497 purple_ssl_close(sip->gsc);
8498 sip->gsc = NULL;
8500 sipe_auth_free(&sip->registrar);
8501 sipe_auth_free(&sip->proxy);
8503 if (sip->txbuf)
8504 purple_circ_buffer_destroy(sip->txbuf);
8505 sip->txbuf = NULL;
8507 g_free(sip->realhostname);
8508 sip->realhostname = NULL;
8510 g_free(sip->server_version);
8511 sip->server_version = NULL;
8513 if (sip->listenpa)
8514 purple_input_remove(sip->listenpa);
8515 sip->listenpa = 0;
8516 if (sip->tx_handler)
8517 purple_input_remove(sip->tx_handler);
8518 sip->tx_handler = 0;
8519 if (sip->resendtimeout)
8520 purple_timeout_remove(sip->resendtimeout);
8521 sip->resendtimeout = 0;
8522 if (sip->timeouts) {
8523 GSList *entry = sip->timeouts;
8524 while (entry) {
8525 struct scheduled_action *sched_action = entry->data;
8526 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8527 purple_timeout_remove(sched_action->timeout_handler);
8528 if (sched_action->destroy) {
8529 (*sched_action->destroy)(sched_action->payload);
8531 g_free(sched_action->name);
8532 g_free(sched_action);
8533 entry = entry->next;
8536 g_slist_free(sip->timeouts);
8538 if (sip->allow_events) {
8539 GSList *entry = sip->allow_events;
8540 while (entry) {
8541 g_free(entry->data);
8542 entry = entry->next;
8545 g_slist_free(sip->allow_events);
8547 if (sip->containers) {
8548 GSList *entry = sip->containers;
8549 while (entry) {
8550 free_container((struct sipe_container *)entry->data);
8551 entry = entry->next;
8554 g_slist_free(sip->containers);
8556 if (sip->contact)
8557 g_free(sip->contact);
8558 sip->contact = NULL;
8559 if (sip->regcallid)
8560 g_free(sip->regcallid);
8561 sip->regcallid = NULL;
8563 if (sip->serveraddr)
8564 g_free(sip->serveraddr);
8565 sip->serveraddr = NULL;
8567 if (sip->focus_factory_uri)
8568 g_free(sip->focus_factory_uri);
8569 sip->focus_factory_uri = NULL;
8571 sip->fd = -1;
8572 sip->processing_input = FALSE;
8574 if (sip->ews) {
8575 sipe_ews_free(sip->ews);
8577 sip->ews = NULL;
8581 * A callback for g_hash_table_foreach_remove
8583 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8584 SIPE_UNUSED_PARAMETER gpointer user_data)
8586 sipe_free_buddy((struct sipe_buddy *) buddy);
8588 /* We must return TRUE as the key/value have already been deleted */
8589 return(TRUE);
8592 static void sipe_close(PurpleConnection *gc)
8594 struct sipe_account_data *sip = gc->proto_data;
8596 if (sip) {
8597 /* leave all conversations */
8598 sipe_session_close_all(sip);
8599 sipe_session_remove_all(sip);
8601 if (sip->csta) {
8602 sip_csta_close(sip);
8605 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8606 /* unsubscribe all */
8607 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8609 /* unregister */
8610 do_register_exp(sip, 0);
8613 sipe_connection_cleanup(sip);
8614 g_free(sip->sipdomain);
8615 g_free(sip->username);
8616 g_free(sip->email);
8617 g_free(sip->password);
8618 g_free(sip->authdomain);
8619 g_free(sip->authuser);
8620 g_free(sip->status);
8621 g_free(sip->note);
8623 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8624 g_hash_table_destroy(sip->buddies);
8625 g_hash_table_destroy(sip->our_publications);
8626 g_hash_table_destroy(sip->user_state_publications);
8627 g_hash_table_destroy(sip->subscriptions);
8628 g_hash_table_destroy(sip->filetransfers);
8630 if (sip->groups) {
8631 GSList *entry = sip->groups;
8632 while (entry) {
8633 struct sipe_group *group = entry->data;
8634 g_free(group->name);
8635 g_free(group);
8636 entry = entry->next;
8639 g_slist_free(sip->groups);
8641 if (sip->our_publication_keys) {
8642 GSList *entry = sip->our_publication_keys;
8643 while (entry) {
8644 g_free(entry->data);
8645 entry = entry->next;
8648 g_slist_free(sip->our_publication_keys);
8650 while (sip->transactions)
8651 transactions_remove(sip, sip->transactions->data);
8653 g_free(gc->proto_data);
8654 gc->proto_data = NULL;
8657 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8658 SIPE_UNUSED_PARAMETER void *user_data)
8660 PurpleAccount *acct = purple_connection_get_account(gc);
8661 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8662 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8663 if (conv == NULL)
8664 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8665 purple_conversation_present(conv);
8666 g_free(id);
8669 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8670 SIPE_UNUSED_PARAMETER void *user_data)
8673 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8674 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8677 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8678 SIPE_UNUSED_PARAMETER struct transaction *trans)
8680 PurpleNotifySearchResults *results;
8681 PurpleNotifySearchColumn *column;
8682 xmlnode *searchResults;
8683 xmlnode *mrow;
8684 int match_count = 0;
8685 gboolean more = FALSE;
8686 gchar *secondary;
8688 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8690 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8691 if (!searchResults) {
8692 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8693 return FALSE;
8696 results = purple_notify_searchresults_new();
8698 if (results == NULL) {
8699 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8700 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8702 xmlnode_free(searchResults);
8703 return FALSE;
8706 column = purple_notify_searchresults_column_new(_("User name"));
8707 purple_notify_searchresults_column_add(results, column);
8709 column = purple_notify_searchresults_column_new(_("Name"));
8710 purple_notify_searchresults_column_add(results, column);
8712 column = purple_notify_searchresults_column_new(_("Company"));
8713 purple_notify_searchresults_column_add(results, column);
8715 column = purple_notify_searchresults_column_new(_("Country"));
8716 purple_notify_searchresults_column_add(results, column);
8718 column = purple_notify_searchresults_column_new(_("Email"));
8719 purple_notify_searchresults_column_add(results, column);
8721 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8722 GList *row = NULL;
8724 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8725 row = g_list_append(row, g_strdup(uri_parts[1]));
8726 g_strfreev(uri_parts);
8728 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8729 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8730 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8731 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8733 purple_notify_searchresults_row_add(results, row);
8734 match_count++;
8737 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8738 char *data = xmlnode_get_data_unescaped(mrow);
8739 more = (g_strcasecmp(data, "true") == 0);
8740 g_free(data);
8743 secondary = g_strdup_printf(
8744 dngettext(GETTEXT_PACKAGE,
8745 "Found %d contact%s:",
8746 "Found %d contacts%s:", match_count),
8747 match_count, more ? _(" (more matched your query)") : "");
8749 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8750 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8751 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8753 g_free(secondary);
8754 xmlnode_free(searchResults);
8755 return TRUE;
8758 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8760 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8761 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8762 unsigned i = 0;
8764 if (!attrs) return;
8766 do {
8767 PurpleRequestField *field = entries->data;
8768 const char *id = purple_request_field_get_id(field);
8769 const char *value = purple_request_field_string_get_value(field);
8771 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8773 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8774 } while ((entries = g_list_next(entries)) != NULL);
8775 attrs[i] = NULL;
8777 if (i > 0) {
8778 struct sipe_account_data *sip = gc->proto_data;
8779 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8780 gchar *query = g_strjoinv(NULL, attrs);
8781 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8782 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8783 send_soap_request_with_cb(sip, domain_uri, body,
8784 (TransCallback) process_search_contact_response, NULL);
8785 g_free(domain_uri);
8786 g_free(body);
8787 g_free(query);
8790 g_strfreev(attrs);
8793 static void sipe_show_find_contact(PurplePluginAction *action)
8795 PurpleConnection *gc = (PurpleConnection *) action->context;
8796 PurpleRequestFields *fields;
8797 PurpleRequestFieldGroup *group;
8798 PurpleRequestField *field;
8800 fields = purple_request_fields_new();
8801 group = purple_request_field_group_new(NULL);
8802 purple_request_fields_add_group(fields, group);
8804 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8805 purple_request_field_group_add_field(group, field);
8806 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8807 purple_request_field_group_add_field(group, field);
8808 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8809 purple_request_field_group_add_field(group, field);
8810 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8811 purple_request_field_group_add_field(group, field);
8813 purple_request_fields(gc,
8814 _("Search"),
8815 _("Search for a contact"),
8816 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8817 fields,
8818 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8819 _("_Cancel"), NULL,
8820 purple_connection_get_account(gc), NULL, NULL, gc);
8823 static void sipe_show_about_plugin(PurplePluginAction *action)
8825 PurpleConnection *gc = (PurpleConnection *) action->context;
8826 char *tmp = g_strdup_printf(
8828 * Non-translatable parts, like markup, are hard-coded
8829 * into the format string. This requires more translatable
8830 * texts but it makes the translations less error prone.
8832 "<b><font size=\"+1\">SIPE " SIPE_VERSION " </font></b><br/>"
8833 "<br/>"
8834 /* 1 */ "%s:<br/>"
8835 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8836 "<li> - MS Office Communications Server 2007</li><br/>"
8837 "<li> - MS Live Communications Server 2005</li><br/>"
8838 "<li> - MS Live Communications Server 2003</li><br/>"
8839 "<li> - Reuters Messaging</li><br/>"
8840 "<br/>"
8841 /* 2 */ "%s: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8842 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8843 /* 5 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8844 /* 6 */ "%s: GPLv2+<br/>"
8845 "<br/>"
8846 /* 7 */ "%s:<br/>"
8847 " - CERN<br/>"
8848 " - Reuters Messaging network<br/>"
8849 " - Deutsche Bank<br/>"
8850 " - Merrill Lynch<br/>"
8851 " - Wachovia<br/>"
8852 " - Intel<br/>"
8853 " - Nokia<br/>"
8854 " - HP<br/>"
8855 " - Symantec<br/>"
8856 " - Accenture<br/>"
8857 " - Siemens<br/>"
8858 " - Alcatel-Lucent<br/>"
8859 " - BT<br/>"
8860 "<br/>"
8861 /* 8,9 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
8862 "<br/>"
8863 /* 10 */ "<b>%s:</b><br/>"
8864 " - Anibal Avelar<br/>"
8865 " - Gabriel Burt<br/>"
8866 " - Stefan Becker<br/>"
8867 " - pier11<br/>"
8868 " - Jakub Adam<br/>"
8869 " - Tomáš Hrabčík<br/>"
8870 "<br/>"
8871 /* 11 */ "%s<br/>"
8873 /* The next 11 texts make up the SIPE about note text */
8874 /* About note, part 1/11: introduction */
8875 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
8876 /* About note, part 2/11: home page URL (label) */
8877 _("Home"),
8878 /* About note, part 3/11: support forum URL (label) */
8879 _("Support"),
8880 /* About note, part 4/11: support forum name (hyperlink text) */
8881 _("Help Forum"),
8882 /* About note, part 5/11: translation service URL (label) */
8883 _("Translations"),
8884 /* About note, part 6/11: license type (label) */
8885 _("License"),
8886 /* About note, part 7/11: known users */
8887 _("We support users in such organizations as"),
8888 /* About note, part 8/11: translation request, text before Transifex.net URL */
8889 /* append a space if text is not empty */
8890 _("Please help us to translate SIPE to your native language here at "),
8891 /* About note, part 9/11: translation request, text after Transifex.net URL */
8892 /* start with a space if text is not empty */
8893 _(" using convenient web interface"),
8894 /* About note, part 10/11: author list (header) */
8895 _("Authors"),
8896 /* About note, part 11/11: Localization credit */
8897 /* PLEASE NOTE: do *NOT* simply translate the english original */
8898 /* but write something similar to the following sentence: */
8899 /* "Localization for <language name> (<language code>): <name>" */
8900 _("Original texts in English (en): SIPE developers")
8902 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
8903 g_free(tmp);
8906 static void sipe_republish_calendar(PurplePluginAction *action)
8908 PurpleConnection *gc = (PurpleConnection *) action->context;
8909 struct sipe_account_data *sip = gc->proto_data;
8911 sipe_update_calendar(sip);
8914 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
8915 gpointer value,
8916 GString* str)
8918 struct sipe_publication *publication = value;
8920 g_string_append_printf( str,
8921 SIPE_PUB_XML_PUBLICATION_CLEAR,
8922 publication->category,
8923 publication->instance,
8924 publication->container,
8925 publication->version,
8926 "static");
8929 static void sipe_reset_status(PurplePluginAction *action)
8931 PurpleConnection *gc = (PurpleConnection *) action->context;
8932 struct sipe_account_data *sip = gc->proto_data;
8934 if (sip->ocs2007) /* 2007+ */
8936 GString* str = g_string_new(NULL);
8937 gchar *publications;
8939 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
8940 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8941 return;
8944 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
8945 publications = g_string_free(str, FALSE);
8947 send_presence_publish(sip, publications);
8948 g_free(publications);
8950 else /* 2005 */
8952 send_presence_soap0(sip, FALSE, TRUE);
8956 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
8957 gpointer context)
8959 PurpleConnection *gc = (PurpleConnection *)context;
8960 struct sipe_account_data *sip = gc->proto_data;
8961 GList *menu = NULL;
8962 PurplePluginAction *act;
8963 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
8965 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
8966 menu = g_list_prepend(menu, act);
8968 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
8969 menu = g_list_prepend(menu, act);
8971 if (sipe_strequal(calendar, "EXCH")) {
8972 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
8973 menu = g_list_prepend(menu, act);
8976 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
8977 menu = g_list_prepend(menu, act);
8979 menu = g_list_reverse(menu);
8981 return menu;
8984 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
8988 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8990 return TRUE;
8994 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8996 return TRUE;
9000 static char *sipe_status_text(PurpleBuddy *buddy)
9002 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9003 const PurpleStatus *status = purple_presence_get_active_status(presence);
9004 const char *status_id = purple_status_get_id(status);
9005 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9006 struct sipe_buddy *sbuddy;
9007 char *text = NULL;
9009 if (!sip) return NULL; /* happens on pidgin exit */
9011 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9012 if (sbuddy) {
9013 const char *activity_str = sbuddy->activity ?
9014 sbuddy->activity :
9015 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9016 purple_status_get_name(status) : NULL;
9018 if (activity_str && sbuddy->note)
9020 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9022 else if (activity_str)
9024 text = g_strdup(activity_str);
9026 else if (sbuddy->note)
9028 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9032 return text;
9035 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9037 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9038 const PurpleStatus *status = purple_presence_get_active_status(presence);
9039 struct sipe_account_data *sip;
9040 struct sipe_buddy *sbuddy;
9041 char *note = NULL;
9042 gboolean is_oof_note = FALSE;
9043 char *activity = NULL;
9044 char *calendar = NULL;
9045 char *meeting_subject = NULL;
9046 char *meeting_location = NULL;
9048 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9049 if (sip) //happens on pidgin exit
9051 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9052 if (sbuddy)
9054 note = sbuddy->note;
9055 is_oof_note = sbuddy->is_oof_note;
9056 activity = sbuddy->activity;
9057 calendar = sipe_cal_get_description(sbuddy);
9058 meeting_subject = sbuddy->meeting_subject;
9059 meeting_location = sbuddy->meeting_location;
9063 //Layout
9064 if (purple_presence_is_online(presence))
9066 const char *status_str = activity ? activity : purple_status_get_name(status);
9068 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9070 if (purple_presence_is_online(presence) &&
9071 !is_empty(calendar))
9073 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9075 g_free(calendar);
9076 if (!is_empty(meeting_location))
9078 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9080 if (!is_empty(meeting_subject))
9082 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9085 if (note)
9087 char *tmp = g_strdup_printf("<i>%s</i>", note);
9088 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9090 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9091 g_free(tmp);
9096 #if PURPLE_VERSION_CHECK(2,5,0)
9097 static GHashTable *
9098 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9100 GHashTable *table;
9101 table = g_hash_table_new(g_str_hash, g_str_equal);
9102 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9103 return table;
9105 #endif
9107 static PurpleBuddy *
9108 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9110 PurpleBuddy *clone;
9111 const gchar *server_alias, *email;
9112 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9114 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9116 purple_blist_add_buddy(clone, NULL, group, NULL);
9118 server_alias = purple_buddy_get_server_alias(buddy);
9119 if (server_alias) {
9120 purple_blist_server_alias_buddy(clone, server_alias);
9123 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9124 if (email) {
9125 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9128 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9129 //for UI to update;
9130 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9131 return clone;
9134 static void
9135 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9137 PurpleBuddy *buddy, *b;
9138 PurpleConnection *gc;
9139 PurpleGroup * group = purple_find_group(group_name);
9141 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9143 buddy = (PurpleBuddy *)node;
9145 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9146 gc = purple_account_get_connection(buddy->account);
9148 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9149 if (!b){
9150 purple_blist_add_buddy_clone(group, buddy);
9153 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9156 static void
9157 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9159 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9161 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9163 /* 2007+ conference */
9164 if (sip->ocs2007)
9166 sipe_conf_add(sip, buddy->name);
9168 else /* 2005- multiparty chat */
9170 gchar *self = sip_uri_self(sip);
9171 struct sip_session *session;
9173 session = sipe_session_add_chat(sip);
9174 session->chat_title = sipe_chat_get_name(session->callid);
9175 session->roster_manager = g_strdup(self);
9177 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9178 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9179 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9180 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9182 g_free(self);
9186 static gboolean
9187 sipe_is_election_finished(struct sip_session *session)
9189 gboolean res = TRUE;
9191 SIPE_DIALOG_FOREACH {
9192 if (dialog->election_vote == 0) {
9193 res = FALSE;
9194 break;
9196 } SIPE_DIALOG_FOREACH_END;
9198 if (res) {
9199 session->is_voting_in_progress = FALSE;
9201 return res;
9204 static void
9205 sipe_election_start(struct sipe_account_data *sip,
9206 struct sip_session *session)
9208 int election_timeout;
9210 if (session->is_voting_in_progress) {
9211 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9212 return;
9213 } else {
9214 session->is_voting_in_progress = TRUE;
9216 session->bid = rand();
9218 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9220 SIPE_DIALOG_FOREACH {
9221 /* reset election_vote for each chat participant */
9222 dialog->election_vote = 0;
9224 /* send RequestRM to each chat participant*/
9225 sipe_send_election_request_rm(sip, dialog, session->bid);
9226 } SIPE_DIALOG_FOREACH_END;
9228 election_timeout = 15; /* sec */
9229 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9233 * @param who a URI to whom to invite to chat
9235 void
9236 sipe_invite_to_chat(struct sipe_account_data *sip,
9237 struct sip_session *session,
9238 const gchar *who)
9240 /* a conference */
9241 if (session->focus_uri)
9243 sipe_invite_conf(sip, session, who);
9245 else /* a multi-party chat */
9247 gchar *self = sip_uri_self(sip);
9248 if (session->roster_manager) {
9249 if (sipe_strequal(session->roster_manager, self)) {
9250 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9251 } else {
9252 sipe_refer(sip, session, who);
9254 } else {
9255 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9257 session->pending_invite_queue = slist_insert_unique_sorted(
9258 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9260 sipe_election_start(sip, session);
9262 g_free(self);
9266 void
9267 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9268 struct sip_session *session)
9270 gchar *invitee;
9271 GSList *entry = session->pending_invite_queue;
9273 while (entry) {
9274 invitee = entry->data;
9275 sipe_invite_to_chat(sip, session, invitee);
9276 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9277 g_free(invitee);
9281 static void
9282 sipe_election_result(struct sipe_account_data *sip,
9283 void *sess)
9285 struct sip_session *session = (struct sip_session *)sess;
9286 gchar *rival;
9287 gboolean has_won = TRUE;
9289 if (session->roster_manager) {
9290 purple_debug_info("sipe",
9291 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9292 return;
9295 session->is_voting_in_progress = FALSE;
9297 SIPE_DIALOG_FOREACH {
9298 if (dialog->election_vote < 0) {
9299 has_won = FALSE;
9300 rival = dialog->with;
9301 break;
9303 } SIPE_DIALOG_FOREACH_END;
9305 if (has_won) {
9306 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9308 session->roster_manager = sip_uri_self(sip);
9310 SIPE_DIALOG_FOREACH {
9311 /* send SetRM to each chat participant*/
9312 sipe_send_election_set_rm(sip, dialog);
9313 } SIPE_DIALOG_FOREACH_END;
9314 } else {
9315 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9317 session->bid = 0;
9319 sipe_process_pending_invite_queue(sip, session);
9323 * For 2007+ conference only.
9325 static void
9326 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9328 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9329 struct sip_session *session;
9331 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9332 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9334 session = sipe_session_find_chat_by_title(sip, chat_title);
9336 sipe_conf_modify_user_role(sip, session, buddy->name);
9340 * For 2007+ conference only.
9342 static void
9343 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9345 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9346 struct sip_session *session;
9348 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9349 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9351 session = sipe_session_find_chat_by_title(sip, chat_title);
9353 sipe_conf_delete_user(sip, session, buddy->name);
9356 static void
9357 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9359 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9360 struct sip_session *session;
9362 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9363 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9365 session = sipe_session_find_chat_by_title(sip, chat_title);
9367 sipe_invite_to_chat(sip, session, buddy->name);
9370 static void
9371 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9373 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9375 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9376 if (phone) {
9377 char *tel_uri = sip_to_tel_uri(phone);
9379 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9380 sip_csta_make_call(sip, tel_uri);
9382 g_free(tel_uri);
9386 static void
9387 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9389 const gchar *email;
9390 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9392 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9393 if (email)
9395 char *mailto = g_strdup_printf("mailto:%s", email);
9396 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9397 #ifndef _WIN32
9399 pid_t pid;
9400 char *const parmList[] = {"xdg-email", mailto, NULL};
9401 if ((pid = fork()) == -1)
9403 purple_debug_info("sipe", "fork() error\n");
9405 else if (pid == 0)
9407 execvp(parmList[0], parmList);
9408 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9411 #else
9413 BOOL ret;
9414 _flushall();
9415 errno = 0;
9416 //@TODO resolve env variable %WINDIR% first
9417 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9418 if (errno)
9420 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9423 #endif
9425 g_free(mailto);
9427 else
9429 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9434 * A menu which appear when right-clicking on buddy in contact list.
9436 static GList *
9437 sipe_buddy_menu(PurpleBuddy *buddy)
9439 PurpleBlistNode *g_node;
9440 PurpleGroup *group, *gr_parent;
9441 PurpleMenuAction *act;
9442 GList *menu = NULL;
9443 GList *menu_groups = NULL;
9444 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9445 const char *email;
9446 const char *phone;
9447 const char *phone_disp_str;
9448 gchar *self = sip_uri_self(sip);
9450 SIPE_SESSION_FOREACH {
9451 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9453 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9455 PurpleConvChatBuddyFlags flags;
9456 PurpleConvChatBuddyFlags flags_us;
9458 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9459 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9460 if (session->focus_uri
9461 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9462 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9464 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9465 act = purple_menu_action_new(label,
9466 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9467 session->chat_title, NULL);
9468 g_free(label);
9469 menu = g_list_prepend(menu, act);
9472 if (session->focus_uri
9473 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9475 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9476 act = purple_menu_action_new(label,
9477 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9478 session->chat_title, NULL);
9479 g_free(label);
9480 menu = g_list_prepend(menu, act);
9483 else
9485 if (!session->focus_uri
9486 || (session->focus_uri && !session->locked))
9488 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9489 act = purple_menu_action_new(label,
9490 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9491 session->chat_title, NULL);
9492 g_free(label);
9493 menu = g_list_prepend(menu, act);
9497 } SIPE_SESSION_FOREACH_END;
9499 act = purple_menu_action_new(_("New chat"),
9500 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9501 NULL, NULL);
9502 menu = g_list_prepend(menu, act);
9504 if (sip->csta && !sip->csta->line_status) {
9505 gchar *tmp = NULL;
9506 /* work phone */
9507 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9508 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9509 if (phone) {
9510 gchar *label = g_strdup_printf(_("Work %s"),
9511 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9512 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9513 g_free(tmp);
9514 tmp = NULL;
9515 g_free(label);
9516 menu = g_list_prepend(menu, act);
9519 /* mobile phone */
9520 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9521 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9522 if (phone) {
9523 gchar *label = g_strdup_printf(_("Mobile %s"),
9524 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9525 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9526 g_free(tmp);
9527 tmp = NULL;
9528 g_free(label);
9529 menu = g_list_prepend(menu, act);
9532 /* home phone */
9533 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9534 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9535 if (phone) {
9536 gchar *label = g_strdup_printf(_("Home %s"),
9537 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9538 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9539 g_free(tmp);
9540 tmp = NULL;
9541 g_free(label);
9542 menu = g_list_prepend(menu, act);
9545 /* other phone */
9546 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9547 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9548 if (phone) {
9549 gchar *label = g_strdup_printf(_("Other %s"),
9550 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9551 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9552 g_free(tmp);
9553 tmp = NULL;
9554 g_free(label);
9555 menu = g_list_prepend(menu, act);
9558 /* custom1 phone */
9559 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9560 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9561 if (phone) {
9562 gchar *label = g_strdup_printf(_("Custom1 %s"),
9563 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9564 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9565 g_free(tmp);
9566 tmp = NULL;
9567 g_free(label);
9568 menu = g_list_prepend(menu, act);
9572 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9573 if (email) {
9574 act = purple_menu_action_new(_("Send email..."),
9575 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9576 NULL, NULL);
9577 menu = g_list_prepend(menu, act);
9580 gr_parent = purple_buddy_get_group(buddy);
9581 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9582 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9583 continue;
9585 group = (PurpleGroup *)g_node;
9586 if (group == gr_parent)
9587 continue;
9589 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9590 continue;
9592 act = purple_menu_action_new(purple_group_get_name(group),
9593 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9594 group->name, NULL);
9595 menu_groups = g_list_prepend(menu_groups, act);
9597 menu_groups = g_list_reverse(menu_groups);
9599 act = purple_menu_action_new(_("Copy to"),
9600 NULL,
9601 NULL, menu_groups);
9602 menu = g_list_prepend(menu, act);
9603 menu = g_list_reverse(menu);
9605 g_free(self);
9606 return menu;
9609 static void
9610 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9612 struct sipe_account_data *sip = chat->account->gc->proto_data;
9613 struct sip_session *session;
9615 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9616 sipe_conf_modify_conference_lock(sip, session, locked);
9619 static void
9620 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9622 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9623 sipe_conf_modify_lock(chat, FALSE);
9626 static void
9627 sipe_chat_menu_lock_cb(PurpleChat *chat)
9629 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9630 sipe_conf_modify_lock(chat, TRUE);
9633 static GList *
9634 sipe_chat_menu(PurpleChat *chat)
9636 PurpleMenuAction *act;
9637 PurpleConvChatBuddyFlags flags_us;
9638 GList *menu = NULL;
9639 struct sipe_account_data *sip = chat->account->gc->proto_data;
9640 struct sip_session *session;
9641 gchar *self;
9643 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9644 if (!session) return NULL;
9646 self = sip_uri_self(sip);
9647 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9649 if (session->focus_uri
9650 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9652 if (session->locked) {
9653 act = purple_menu_action_new(_("Unlock"),
9654 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9655 NULL, NULL);
9656 menu = g_list_prepend(menu, act);
9657 } else {
9658 act = purple_menu_action_new(_("Lock"),
9659 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9660 NULL, NULL);
9661 menu = g_list_prepend(menu, act);
9665 menu = g_list_reverse(menu);
9667 g_free(self);
9668 return menu;
9671 static GList *
9672 sipe_blist_node_menu(PurpleBlistNode *node)
9674 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9675 return sipe_buddy_menu((PurpleBuddy *) node);
9676 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9677 return sipe_chat_menu((PurpleChat *)node);
9678 } else {
9679 return NULL;
9683 static gboolean
9684 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9686 char *uri = trans->payload->data;
9688 PurpleNotifyUserInfo *info;
9689 PurpleBuddy *pbuddy = NULL;
9690 struct sipe_buddy *sbuddy;
9691 const char *alias = NULL;
9692 char *device_name = NULL;
9693 char *server_alias = NULL;
9694 char *phone_number = NULL;
9695 char *email = NULL;
9696 const char *site;
9697 char *first_name = NULL;
9698 char *last_name = NULL;
9700 if (!sip) return FALSE;
9702 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9704 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9705 alias = purple_buddy_get_local_alias(pbuddy);
9707 //will query buddy UA's capabilities and send answer to log
9708 sipe_options_request(sip, uri);
9710 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9711 if (sbuddy) {
9712 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9715 info = purple_notify_user_info_new();
9717 if (msg->response != 200) {
9718 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9719 } else {
9720 xmlnode *searchResults;
9721 xmlnode *mrow;
9723 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9724 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9725 if (!searchResults) {
9726 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9727 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9728 const char *value;
9729 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9730 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9731 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9733 /* For 2007 system we will take this from ContactCard -
9734 * it has cleaner tel: URIs at least
9736 if (!sip->ocs2007) {
9737 char *tel_uri = sip_to_tel_uri(phone_number);
9738 /* trims its parameters, so call first */
9739 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9740 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9741 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9742 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9743 g_free(tel_uri);
9746 if (server_alias && strlen(server_alias) > 0) {
9747 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9749 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9750 purple_notify_user_info_add_pair(info, _("Job title"), value);
9752 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9753 purple_notify_user_info_add_pair(info, _("Office"), value);
9755 if (phone_number && strlen(phone_number) > 0) {
9756 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9758 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9759 purple_notify_user_info_add_pair(info, _("Company"), value);
9761 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9762 purple_notify_user_info_add_pair(info, _("City"), value);
9764 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9765 purple_notify_user_info_add_pair(info, _("State"), value);
9767 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9768 purple_notify_user_info_add_pair(info, _("Country"), value);
9770 if (email && strlen(email) > 0) {
9771 purple_notify_user_info_add_pair(info, _("Email address"), email);
9775 xmlnode_free(searchResults);
9778 purple_notify_user_info_add_section_break(info);
9780 if (is_empty(server_alias)) {
9781 g_free(server_alias);
9782 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9783 if (server_alias) {
9784 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9788 /* present alias if it differs from server alias */
9789 if (alias && !sipe_strequal(alias, server_alias))
9791 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9794 if (is_empty(email)) {
9795 g_free(email);
9796 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9797 if (email) {
9798 purple_notify_user_info_add_pair(info, _("Email address"), email);
9802 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9803 if (site) {
9804 purple_notify_user_info_add_pair(info, _("Site"), site);
9807 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9808 if (first_name && last_name) {
9809 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9811 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9812 g_free(link);
9814 g_free(first_name);
9815 g_free(last_name);
9817 if (device_name) {
9818 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9821 /* show a buddy's user info in a nice dialog box */
9822 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9823 uri, /* buddy's URI */
9824 info, /* body */
9825 NULL, /* callback called when dialog closed */
9826 NULL); /* userdata for callback */
9828 g_free(phone_number);
9829 g_free(server_alias);
9830 g_free(email);
9831 g_free(device_name);
9833 return TRUE;
9837 * AD search first, LDAP based
9839 static void sipe_get_info(PurpleConnection *gc, const char *username)
9841 struct sipe_account_data *sip = gc->proto_data;
9842 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9843 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9844 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9845 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9847 payload->destroy = g_free;
9848 payload->data = g_strdup(username);
9850 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9851 send_soap_request_with_cb(sip, domain_uri, body,
9852 (TransCallback) process_get_info_response, payload);
9853 g_free(domain_uri);
9854 g_free(body);
9855 g_free(row);
9858 static PurplePlugin *my_protocol = NULL;
9860 static PurplePluginProtocolInfo prpl_info =
9862 OPT_PROTO_CHAT_TOPIC,
9863 NULL, /* user_splits */
9864 NULL, /* protocol_options */
9865 NO_BUDDY_ICONS, /* icon_spec */
9866 sipe_list_icon, /* list_icon */
9867 NULL, /* list_emblems */
9868 sipe_status_text, /* status_text */
9869 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9870 sipe_status_types, /* away_states */
9871 sipe_blist_node_menu, /* blist_node_menu */
9872 NULL, /* chat_info */
9873 NULL, /* chat_info_defaults */
9874 sipe_login, /* login */
9875 sipe_close, /* close */
9876 sipe_im_send, /* send_im */
9877 NULL, /* set_info */ // TODO maybe
9878 sipe_send_typing, /* send_typing */
9879 sipe_get_info, /* get_info */
9880 sipe_set_status, /* set_status */
9881 sipe_set_idle, /* set_idle */
9882 NULL, /* change_passwd */
9883 sipe_add_buddy, /* add_buddy */
9884 NULL, /* add_buddies */
9885 sipe_remove_buddy, /* remove_buddy */
9886 NULL, /* remove_buddies */
9887 sipe_add_permit, /* add_permit */
9888 sipe_add_deny, /* add_deny */
9889 sipe_add_deny, /* rem_permit */
9890 sipe_add_permit, /* rem_deny */
9891 dummy_permit_deny, /* set_permit_deny */
9892 NULL, /* join_chat */
9893 NULL, /* reject_chat */
9894 NULL, /* get_chat_name */
9895 sipe_chat_invite, /* chat_invite */
9896 sipe_chat_leave, /* chat_leave */
9897 NULL, /* chat_whisper */
9898 sipe_chat_send, /* chat_send */
9899 sipe_keep_alive, /* keepalive */
9900 NULL, /* register_user */
9901 NULL, /* get_cb_info */ // deprecated
9902 NULL, /* get_cb_away */ // deprecated
9903 sipe_alias_buddy, /* alias_buddy */
9904 sipe_group_buddy, /* group_buddy */
9905 sipe_rename_group, /* rename_group */
9906 NULL, /* buddy_free */
9907 sipe_convo_closed, /* convo_closed */
9908 purple_normalize_nocase, /* normalize */
9909 NULL, /* set_buddy_icon */
9910 sipe_remove_group, /* remove_group */
9911 NULL, /* get_cb_real_name */ // TODO?
9912 NULL, /* set_chat_topic */
9913 NULL, /* find_blist_chat */
9914 NULL, /* roomlist_get_list */
9915 NULL, /* roomlist_cancel */
9916 NULL, /* roomlist_expand_category */
9917 NULL, /* can_receive_file */
9918 sipe_ft_send_file, /* send_file */
9919 sipe_ft_new_xfer, /* new_xfer */
9920 NULL, /* offline_message */
9921 NULL, /* whiteboard_prpl_ops */
9922 sipe_send_raw, /* send_raw */
9923 NULL, /* roomlist_room_serialize */
9924 NULL, /* unregister_user */
9925 NULL, /* send_attention */
9926 NULL, /* get_attention_types */
9927 #if !PURPLE_VERSION_CHECK(2,5,0)
9928 /* Backward compatibility when compiling against 2.4.x API */
9929 (void (*)(void)) /* _purple_reserved4 */
9930 #endif
9931 sizeof(PurplePluginProtocolInfo), /* struct_size */
9932 #if PURPLE_VERSION_CHECK(2,5,0)
9933 sipe_get_account_text_table, /* get_account_text_table */
9934 #if PURPLE_VERSION_CHECK(2,6,0)
9935 NULL, /* initiate_media */
9936 NULL, /* get_media_caps */
9937 #endif
9938 #endif
9942 static PurplePluginInfo info = {
9943 PURPLE_PLUGIN_MAGIC,
9944 PURPLE_MAJOR_VERSION,
9945 PURPLE_MINOR_VERSION,
9946 PURPLE_PLUGIN_PROTOCOL, /**< type */
9947 NULL, /**< ui_requirement */
9948 0, /**< flags */
9949 NULL, /**< dependencies */
9950 PURPLE_PRIORITY_DEFAULT, /**< priority */
9951 "prpl-sipe", /**< id */
9952 "Office Communicator", /**< name */
9953 SIPE_VERSION, /**< version */
9954 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9955 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9956 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9957 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9958 "Gabriel Burt <gburt@novell.com>, " /**< author */
9959 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9960 "pier11 <pier11@operamail.com>", /**< author */
9961 "http://sipe.sourceforge.net/", /**< homepage */
9962 sipe_plugin_load, /**< load */
9963 sipe_plugin_unload, /**< unload */
9964 sipe_plugin_destroy, /**< destroy */
9965 NULL, /**< ui_info */
9966 &prpl_info, /**< extra_info */
9967 NULL,
9968 sipe_actions,
9969 NULL,
9970 NULL,
9971 NULL,
9972 NULL
9975 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9977 GList *entry;
9979 entry = prpl_info.protocol_options;
9980 while (entry) {
9981 purple_account_option_destroy(entry->data);
9982 entry = g_list_delete_link(entry, entry);
9984 prpl_info.protocol_options = NULL;
9986 entry = prpl_info.user_splits;
9987 while (entry) {
9988 purple_account_user_split_destroy(entry->data);
9989 entry = g_list_delete_link(entry, entry);
9991 prpl_info.user_splits = NULL;
9994 static void init_plugin(PurplePlugin *plugin)
9996 PurpleAccountUserSplit *split;
9997 PurpleAccountOption *option;
9999 srand(time(NULL));
10001 #ifdef ENABLE_NLS
10002 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
10003 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
10004 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
10005 textdomain(GETTEXT_PACKAGE);
10006 #endif
10008 purple_plugin_register(plugin);
10010 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
10011 purple_account_user_split_set_reverse(split, FALSE);
10012 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
10014 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
10015 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10017 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
10018 purple_account_option_add_list_item(option, _("Auto"), "auto");
10019 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
10020 purple_account_option_add_list_item(option, _("TCP"), "tcp");
10021 purple_account_option_add_list_item(option, _("UDP"), "udp");
10022 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10024 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
10025 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
10027 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
10028 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10030 #ifdef USE_KERBEROS
10031 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
10032 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10034 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
10035 * No login/password is taken into account if this option present,
10036 * instead used default credentials stored in OS.
10038 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
10039 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10040 #endif
10042 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
10043 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
10044 purple_account_option_add_list_item(option, _("None"), "NONE");
10045 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10047 /** Example: https://server.company.com/EWS/Exchange.asmx */
10048 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
10049 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10051 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
10052 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10054 /** Example: DOMAIN\user or user@company.com */
10055 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
10056 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10058 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
10059 purple_account_option_set_masked(option, TRUE);
10060 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10062 my_protocol = plugin;
10065 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
10068 Local Variables:
10069 mode: c
10070 c-file-style: "bsd"
10071 indent-tabs-mode: t
10072 tab-width: 8
10073 End: