changed 'Wrong Password' string
[siplcs.git] / src / core / sipe.c
bloba24049ccead83f8d135c5e7c284c8a1098eec88f
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 #ifdef HAVE_CONFIG_H
36 #include "config.h"
37 #endif
39 #ifdef _WIN32
40 #ifdef _DLL
41 #define _WS2TCPIP_H_
42 #define _WINSOCK2API_
43 #define _LIBC_INTERNAL_
44 #endif /* _DLL */
45 #include "internal.h"
46 #else
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <netinet/in.h>
50 #endif /* _WIN32 */
52 #include <time.h>
53 #include <stdio.h>
54 #include <errno.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include <glib.h>
60 #include "accountopt.h"
61 #include "blist.h"
62 #include "conversation.h"
63 #include "dnsquery.h"
64 #include "debug.h"
65 #include "notify.h"
66 #include "savedstatuses.h"
67 #include "privacy.h"
68 #include "prpl.h"
69 #include "plugin.h"
70 #include "util.h"
71 #include "version.h"
72 #include "network.h"
73 #include "xmlnode.h"
74 #include "mime.h"
75 #include "core.h"
77 #include "sipe.h"
78 #include "sipe-cal.h"
79 #include "sipe-ews.h"
80 #include "sipe-chat.h"
81 #include "sipe-conf.h"
82 #include "sip-csta.h"
83 #include "sipe-dialog.h"
84 #include "sipe-nls.h"
85 #include "sipe-session.h"
86 #include "sipe-utils.h"
87 #include "sipe-ft.h"
88 #include "sipmsg.h"
89 #include "sipe-sign.h"
90 #include "dnssrv.h"
91 #include "request.h"
93 /* Backward compatibility when compiling against 2.4.x API */
94 #if !PURPLE_VERSION_CHECK(2,5,0)
95 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
96 #endif
98 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
100 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
101 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
103 /* Keep in sync with sipe_transport_type! */
104 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
105 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
107 /* Status identifiers (see also: sipe_status_types()) */
108 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
109 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
110 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
111 /* PURPLE_STATUS_UNAVAILABLE: */
112 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
113 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
114 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
115 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
116 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
117 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
118 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
119 /* PURPLE_STATUS_AWAY: */
120 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
121 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
122 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
123 /** Reuters status (user settable) */
124 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
125 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
126 /* ??? PURPLE_STATUS_MOBILE */
127 /* ??? PURPLE_STATUS_TUNE */
129 /* Status attributes (see also sipe_status_types() */
130 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
132 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
134 static struct sipe_activity_map_struct
136 sipe_activity type;
137 const char *token;
138 const char *desc;
139 const char *status_id;
141 } const sipe_activity_map[] =
143 /* This has nothing to do with Availability numbers, like 3500 (online).
144 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
146 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
147 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
148 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
149 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
150 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
151 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
152 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
153 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
154 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
155 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
156 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
157 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
158 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
159 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
160 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
162 /** @param x is sipe_activity */
163 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
166 /* Action name templates */
167 #define ACTION_NAME_PRESENCE "<presence><%s>"
169 static sipe_activity
170 sipe_get_activity_by_token(const char *token)
172 int i;
174 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
176 if (sipe_strequal(token, sipe_activity_map[i].token))
177 return sipe_activity_map[i].type;
180 return sipe_activity_map[0].type;
183 static const char *
184 sipe_get_activity_desc_by_token(const char *token)
186 if (!token) return NULL;
188 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
191 /** Allows to send typed messages from chat window again after account reinstantiation. */
192 static void
193 sipe_rejoin_chat(PurpleConversation *conv)
195 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
196 PURPLE_CONV_CHAT(conv)->left)
198 PURPLE_CONV_CHAT(conv)->left = FALSE;
199 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
203 static char *genbranch()
205 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
206 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
207 rand() & 0xFFFF, rand() & 0xFFFF);
211 static char *default_ua = NULL;
212 static const char*
213 sipe_get_useragent(struct sipe_account_data *sip)
215 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
216 if (is_empty(useragent)) {
217 if (!default_ua) {
218 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
219 /* ref: lzodefs.h */
220 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
221 #define SIPE_TARGET_PLATFORM "linux"
222 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
223 #define SIPE_TARGET_PLATFORM "bsd"
224 #elif defined(__APPLE__) || defined(__MACOS__)
225 #define SIPE_TARGET_PLATFORM "macosx"
226 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
227 #define SIPE_TARGET_PLATFORM "aix"
228 #elif defined(__solaris__) || defined(__sun)
229 #define SIPE_TARGET_PLATFORM "sun"
230 #elif defined(_WIN32)
231 #define SIPE_TARGET_PLATFORM "win"
232 #elif defined(__CYGWIN__)
233 #define SIPE_TARGET_PLATFORM "cygwin"
234 #elif defined(__hpux__)
235 #define SIPE_TARGET_PLATFORM "hpux"
236 #elif defined(__sgi__)
237 #define SIPE_TARGET_PLATFORM "irix"
238 #else
239 #define SIPE_TARGET_PLATFORM "unknown"
240 #endif
242 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
243 #define SIPE_TARGET_ARCH "x86_64"
244 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
245 #define SIPE_TARGET_ARCH "i386"
246 #elif defined(__ppc64__)
247 #define SIPE_TARGET_ARCH "ppc64"
248 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
249 #define SIPE_TARGET_ARCH "ppc"
250 #elif defined(__hppa__) || defined(__hppa)
251 #define SIPE_TARGET_ARCH "hppa"
252 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
253 #define SIPE_TARGET_ARCH "mips"
254 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
255 #define SIPE_TARGET_ARCH "s390"
256 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
257 #define SIPE_TARGET_ARCH "sparc"
258 #elif defined(__arm__)
259 #define SIPE_TARGET_ARCH "arm"
260 #else
261 #define SIPE_TARGET_ARCH "other"
262 #endif
264 default_ua = g_strdup_printf("Purple/%s Sipe/" SIPE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
265 purple_core_get_version(),
266 sip->server_version ? sip->server_version : "");
268 useragent = default_ua;
270 return useragent;
273 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
274 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
276 return "sipe";
279 static void sipe_plugin_destroy(PurplePlugin *plugin);
281 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
283 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
284 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
285 gpointer data);
287 static void sipe_close(PurpleConnection *gc);
289 static void send_presence_status(struct sipe_account_data *sip);
291 static void sendout_pkt(PurpleConnection *gc, const char *buf);
293 static void sipe_keep_alive(PurpleConnection *gc)
295 struct sipe_account_data *sip = gc->proto_data;
296 if (sip->transport == SIPE_TRANSPORT_UDP) {
297 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
298 gchar buf[2] = {0, 0};
299 purple_debug_info("sipe", "sending keep alive\n");
300 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
301 } else {
302 time_t now = time(NULL);
303 if ((sip->keepalive_timeout > 0) &&
304 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
305 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
307 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
308 sendout_pkt(gc, "\r\n\r\n");
309 sip->last_keepalive = now;
314 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
316 struct sip_connection *ret = NULL;
317 GSList *entry = sip->openconns;
318 while (entry) {
319 ret = entry->data;
320 if (ret->fd == fd) return ret;
321 entry = entry->next;
323 return NULL;
326 static void sipe_auth_free(struct sip_auth *auth)
328 g_free(auth->opaque);
329 auth->opaque = NULL;
330 g_free(auth->realm);
331 auth->realm = NULL;
332 g_free(auth->target);
333 auth->target = NULL;
334 auth->version = 0;
335 auth->type = AUTH_TYPE_UNSET;
336 auth->retries = 0;
337 auth->expires = 0;
338 g_free(auth->gssapi_data);
339 auth->gssapi_data = NULL;
340 sip_sec_destroy_context(auth->gssapi_context);
341 auth->gssapi_context = NULL;
344 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
346 struct sip_connection *ret = g_new0(struct sip_connection, 1);
347 ret->fd = fd;
348 sip->openconns = g_slist_append(sip->openconns, ret);
349 return ret;
352 static void connection_remove(struct sipe_account_data *sip, int fd)
354 struct sip_connection *conn = connection_find(sip, fd);
355 if (conn) {
356 sip->openconns = g_slist_remove(sip->openconns, conn);
357 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
358 g_free(conn->inbuf);
359 g_free(conn);
363 static void connection_free_all(struct sipe_account_data *sip)
365 struct sip_connection *ret = NULL;
366 GSList *entry = sip->openconns;
367 while (entry) {
368 ret = entry->data;
369 connection_remove(sip, ret->fd);
370 entry = sip->openconns;
374 static void
375 sipe_make_signature(struct sipe_account_data *sip,
376 struct sipmsg *msg);
378 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
380 gchar noncecount[9];
381 const char *authuser = sip->authuser;
382 gchar *response;
383 gchar *ret;
385 if (!authuser || strlen(authuser) < 1) {
386 authuser = sip->username;
389 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
390 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
391 gchar *version_str;
393 // If we have a signature for the message, include that
394 if (msg->signature) {
395 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);
398 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
399 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
400 gchar *gssapi_data;
401 gchar *opaque;
402 gchar *sign_str = NULL;
404 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
405 &(auth->expires),
406 auth->type,
407 purple_account_get_bool(sip->account, "sso", TRUE),
408 sip->authdomain ? sip->authdomain : "",
409 authuser,
410 sip->password,
411 auth->target,
412 auth->gssapi_data);
413 if (!gssapi_data || !auth->gssapi_context) {
414 sip->gc->wants_to_die = TRUE;
415 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
416 return NULL;
419 if (auth->version > 3) {
420 sipe_make_signature(sip, msg);
421 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
422 msg->rand, msg->num, msg->signature);
423 } else {
424 sign_str = g_strdup("");
427 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
428 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
429 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"%s%s", auth_protocol, opaque, auth->realm, auth->target, gssapi_data, version_str, sign_str);
430 g_free(opaque);
431 g_free(gssapi_data);
432 g_free(version_str);
433 g_free(sign_str);
434 return ret;
437 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
438 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
439 g_free(version_str);
440 return ret;
442 } else { /* Digest */
444 /* Calculate new session key */
445 if (!auth->opaque) {
446 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
447 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
448 authuser, auth->realm, sip->password,
449 auth->gssapi_data, NULL);
452 sprintf(noncecount, "%08d", auth->nc++);
453 response = purple_cipher_http_digest_calculate_response("md5",
454 msg->method, msg->target, NULL, NULL,
455 auth->gssapi_data, noncecount, NULL,
456 auth->opaque);
457 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
459 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);
460 g_free(response);
461 return ret;
465 static char *parse_attribute(const char *attrname, const char *source)
467 const char *tmp, *tmp2;
468 char *retval = NULL;
469 int len = strlen(attrname);
471 if (g_str_has_prefix(source, attrname)) {
472 tmp = source + len;
473 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
474 if (tmp2)
475 retval = g_strndup(tmp, tmp2 - tmp);
476 else
477 retval = g_strdup(tmp);
480 return retval;
483 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
485 int i;
486 gchar **parts;
488 if (!hdr) {
489 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
490 return;
493 if (!g_strncasecmp(hdr, "NTLM", 4)) {
494 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
495 auth->type = AUTH_TYPE_NTLM;
496 hdr += 5;
497 auth->nc = 1;
498 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
499 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
500 auth->type = AUTH_TYPE_KERBEROS;
501 hdr += 9;
502 auth->nc = 3;
503 } else {
504 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
505 auth->type = AUTH_TYPE_DIGEST;
506 hdr += 7;
509 parts = g_strsplit(hdr, "\", ", 0);
510 for (i = 0; parts[i]; i++) {
511 char *tmp;
513 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
515 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
516 g_free(auth->gssapi_data);
517 auth->gssapi_data = tmp;
519 if (auth->type == AUTH_TYPE_NTLM) {
520 /* NTLM module extracts nonce from gssapi-data */
521 auth->nc = 3;
524 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
525 /* Only used with AUTH_TYPE_DIGEST */
526 g_free(auth->gssapi_data);
527 auth->gssapi_data = tmp;
528 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
529 g_free(auth->opaque);
530 auth->opaque = tmp;
531 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
532 g_free(auth->realm);
533 auth->realm = tmp;
535 if (auth->type == AUTH_TYPE_DIGEST) {
536 /* Throw away old session key */
537 g_free(auth->opaque);
538 auth->opaque = NULL;
539 auth->nc = 1;
541 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
542 g_free(auth->target);
543 auth->target = tmp;
544 } else if ((tmp = parse_attribute("version=", parts[i]))) {
545 auth->version = atoi(tmp);
546 g_free(tmp);
548 // uncomment to revert to previous functionality if version 3+ does not work.
549 // auth->version = 2;
551 g_strfreev(parts);
553 return;
556 static void sipe_canwrite_cb(gpointer data,
557 SIPE_UNUSED_PARAMETER gint source,
558 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
560 PurpleConnection *gc = data;
561 struct sipe_account_data *sip = gc->proto_data;
562 gsize max_write;
563 gssize written;
565 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
567 if (max_write == 0) {
568 if (sip->tx_handler != 0){
569 purple_input_remove(sip->tx_handler);
570 sip->tx_handler = 0;
572 return;
575 written = write(sip->fd, sip->txbuf->outptr, max_write);
577 if (written < 0 && errno == EAGAIN)
578 written = 0;
579 else if (written <= 0) {
580 /*TODO: do we really want to disconnect on a failure to write?*/
581 purple_connection_error(gc, _("Could not write"));
582 return;
585 purple_circ_buffer_mark_read(sip->txbuf, written);
588 static void sipe_canwrite_cb_ssl(gpointer data,
589 SIPE_UNUSED_PARAMETER gint src,
590 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
592 PurpleConnection *gc = data;
593 struct sipe_account_data *sip = gc->proto_data;
594 gsize max_write;
595 gssize written;
597 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
599 if (max_write == 0) {
600 if (sip->tx_handler != 0) {
601 purple_input_remove(sip->tx_handler);
602 sip->tx_handler = 0;
603 return;
607 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
609 if (written < 0 && errno == EAGAIN)
610 written = 0;
611 else if (written <= 0) {
612 /*TODO: do we really want to disconnect on a failure to write?*/
613 purple_connection_error(gc, _("Could not write"));
614 return;
617 purple_circ_buffer_mark_read(sip->txbuf, written);
620 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
622 static void send_later_cb(gpointer data, gint source,
623 SIPE_UNUSED_PARAMETER const gchar *error)
625 PurpleConnection *gc = data;
626 struct sipe_account_data *sip;
627 struct sip_connection *conn;
629 if (!PURPLE_CONNECTION_IS_VALID(gc))
631 if (source >= 0)
632 close(source);
633 return;
636 if (source < 0) {
637 purple_connection_error(gc, _("Could not connect"));
638 return;
641 sip = gc->proto_data;
642 sip->fd = source;
643 sip->connecting = FALSE;
644 sip->last_keepalive = time(NULL);
646 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
648 /* If there is more to write now, we need to register a handler */
649 if (sip->txbuf->bufused > 0)
650 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
652 conn = connection_create(sip, source);
653 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
656 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
658 struct sipe_account_data *sip;
660 if (!PURPLE_CONNECTION_IS_VALID(gc))
662 if (gsc) purple_ssl_close(gsc);
663 return NULL;
666 sip = gc->proto_data;
667 sip->fd = gsc->fd;
668 sip->gsc = gsc;
669 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
670 sip->connecting = FALSE;
671 sip->last_keepalive = time(NULL);
673 connection_create(sip, gsc->fd);
675 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
677 return sip;
680 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
681 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
683 PurpleConnection *gc = data;
684 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
685 if (sip == NULL) return;
687 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
689 /* If there is more to write now */
690 if (sip->txbuf->bufused > 0) {
691 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
696 static void sendlater(PurpleConnection *gc, const char *buf)
698 struct sipe_account_data *sip = gc->proto_data;
700 if (!sip->connecting) {
701 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
702 if (sip->transport == SIPE_TRANSPORT_TLS){
703 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
704 } else {
705 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
706 purple_connection_error(gc, _("Could not create socket"));
709 sip->connecting = TRUE;
712 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
713 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
715 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
718 static void sendout_pkt(PurpleConnection *gc, const char *buf)
720 struct sipe_account_data *sip = gc->proto_data;
721 time_t currtime = time(NULL);
722 int writelen = strlen(buf);
723 char *tmp;
725 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
726 g_free(tmp);
727 if (sip->transport == SIPE_TRANSPORT_UDP) {
728 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
729 purple_debug_info("sipe", "could not send packet\n");
731 } else {
732 int ret;
733 if (sip->fd < 0) {
734 sendlater(gc, buf);
735 return;
738 if (sip->tx_handler) {
739 ret = -1;
740 errno = EAGAIN;
741 } else{
742 if (sip->gsc){
743 ret = purple_ssl_write(sip->gsc, buf, writelen);
744 }else{
745 ret = write(sip->fd, buf, writelen);
749 if (ret < 0 && errno == EAGAIN)
750 ret = 0;
751 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
752 sendlater(gc, buf);
753 return;
756 if (ret < writelen) {
757 if (!sip->tx_handler){
758 if (sip->gsc){
759 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
761 else{
762 sip->tx_handler = purple_input_add(sip->fd,
763 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
764 gc);
768 /* XXX: is it OK to do this? You might get part of a request sent
769 with part of another. */
770 if (sip->txbuf->bufused > 0)
771 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
773 purple_circ_buffer_append(sip->txbuf, buf + ret,
774 writelen - ret);
779 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
781 sendout_pkt(gc, buf);
782 return len;
785 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
787 GSList *tmp = msg->headers;
788 gchar *name;
789 gchar *value;
790 GString *outstr = g_string_new("");
791 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
792 while (tmp) {
793 name = ((struct sipnameval*) (tmp->data))->name;
794 value = ((struct sipnameval*) (tmp->data))->value;
795 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
796 tmp = g_slist_next(tmp);
798 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
799 sendout_pkt(sip->gc, outstr->str);
800 g_string_free(outstr, TRUE);
803 static void
804 sipe_make_signature(struct sipe_account_data *sip,
805 struct sipmsg *msg)
807 if (sip->registrar.gssapi_context) {
808 struct sipmsg_breakdown msgbd;
809 gchar *signature_input_str;
810 msgbd.msg = msg;
811 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
812 msgbd.rand = g_strdup_printf("%08x", g_random_int());
813 sip->registrar.ntlm_num++;
814 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
815 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
816 if (signature_input_str != NULL) {
817 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
818 msg->signature = signature_hex;
819 msg->rand = g_strdup(msgbd.rand);
820 msg->num = g_strdup(msgbd.num);
821 g_free(signature_input_str);
823 sipmsg_breakdown_free(&msgbd);
827 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
829 gchar * buf;
831 if (sip->registrar.type == AUTH_TYPE_UNSET) {
832 return;
835 sipe_make_signature(sip, msg);
837 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
838 buf = auth_header(sip, &sip->registrar, msg);
839 if (buf) {
840 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
842 g_free(buf);
843 } 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")) {
844 sip->registrar.nc = 3;
845 sip->registrar.type = AUTH_TYPE_NTLM;
846 #ifdef USE_KERBEROS
847 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
848 sip->registrar.type = AUTH_TYPE_KERBEROS;
850 #endif
853 buf = auth_header(sip, &sip->registrar, msg);
854 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
855 g_free(buf);
856 } else {
857 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
861 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
862 const char *text, const char *body)
864 gchar *name;
865 gchar *value;
866 GString *outstr = g_string_new("");
867 struct sipe_account_data *sip = gc->proto_data;
868 gchar *contact;
869 GSList *tmp;
870 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
872 /* Can return NULL! */
873 contact = get_contact(sip);
874 if (contact) {
875 sipmsg_add_header(msg, "Contact", contact);
876 g_free(contact);
879 if (body) {
880 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
881 sipmsg_add_header(msg, "Content-Length", len);
882 g_free(len);
883 } else {
884 sipmsg_add_header(msg, "Content-Length", "0");
887 msg->response = code;
889 sipmsg_strip_headers(msg, keepers);
890 sipmsg_merge_new_headers(msg);
891 sign_outgoing_message(msg, sip, msg->method);
893 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
894 tmp = msg->headers;
895 while (tmp) {
896 name = ((struct sipnameval*) (tmp->data))->name;
897 value = ((struct sipnameval*) (tmp->data))->value;
899 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
900 tmp = g_slist_next(tmp);
902 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
903 sendout_pkt(gc, outstr->str);
904 g_string_free(outstr, TRUE);
907 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
909 if (sip->transactions) {
910 sip->transactions = g_slist_remove(sip->transactions, trans);
911 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
913 if (trans->msg) sipmsg_free(trans->msg);
914 if (trans->payload) {
915 (*trans->payload->destroy)(trans->payload->data);
916 g_free(trans->payload);
918 g_free(trans->key);
919 g_free(trans);
923 static struct transaction *
924 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
926 const gchar *call_id;
927 const gchar *cseq;
928 struct transaction *trans = g_new0(struct transaction, 1);
930 trans->time = time(NULL);
931 trans->msg = (struct sipmsg *)msg;
932 call_id = sipmsg_find_header(trans->msg, "Call-ID");
933 cseq = sipmsg_find_header(trans->msg, "CSeq");
934 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
935 trans->callback = callback;
936 sip->transactions = g_slist_append(sip->transactions, trans);
937 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
938 return trans;
941 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
943 struct transaction *trans;
944 GSList *transactions = sip->transactions;
945 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
946 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
947 gchar *key;
949 if (!call_id || !cseq) {
950 purple_debug(PURPLE_DEBUG_ERROR, "sipe", "transaction_find: no Call-ID or CSeq!\n");
951 return NULL;
954 key = g_strdup_printf("<%s><%s>", call_id, cseq);
955 while (transactions) {
956 trans = transactions->data;
957 if (!g_strcasecmp(trans->key, key)) {
958 g_free(key);
959 return trans;
961 transactions = transactions->next;
964 g_free(key);
965 return NULL;
968 struct transaction *
969 send_sip_request(PurpleConnection *gc, const gchar *method,
970 const gchar *url, const gchar *to, const gchar *addheaders,
971 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
973 struct sipe_account_data *sip = gc->proto_data;
974 const char *addh = "";
975 char *buf;
976 struct sipmsg *msg;
977 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
978 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
979 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
980 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
981 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
982 gchar *route = g_strdup("");
983 gchar *epid = get_epid(sip);
984 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
985 struct transaction *trans = NULL;
987 if (dialog && dialog->routes)
989 GSList *iter = dialog->routes;
991 while(iter)
993 char *tmp = route;
994 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
995 g_free(tmp);
996 iter = g_slist_next(iter);
1000 if (!ourtag && !dialog) {
1001 ourtag = gentag();
1004 if (sipe_strequal(method, "REGISTER")) {
1005 if (sip->regcallid) {
1006 g_free(callid);
1007 callid = g_strdup(sip->regcallid);
1008 } else {
1009 sip->regcallid = g_strdup(callid);
1011 cseq = ++sip->cseq;
1014 if (addheaders) addh = addheaders;
1016 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1017 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1018 "From: <sip:%s>%s%s;epid=%s\r\n"
1019 "To: <%s>%s%s%s%s\r\n"
1020 "Max-Forwards: 70\r\n"
1021 "CSeq: %d %s\r\n"
1022 "User-Agent: %s\r\n"
1023 "Call-ID: %s\r\n"
1024 "%s%s"
1025 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1026 method,
1027 dialog && dialog->request ? dialog->request : url,
1028 TRANSPORT_DESCRIPTOR,
1029 purple_network_get_my_ip(-1),
1030 sip->listenport,
1031 branch ? ";branch=" : "",
1032 branch ? branch : "",
1033 sip->username,
1034 ourtag ? ";tag=" : "",
1035 ourtag ? ourtag : "",
1036 epid,
1038 theirtag ? ";tag=" : "",
1039 theirtag ? theirtag : "",
1040 theirepid ? ";epid=" : "",
1041 theirepid ? theirepid : "",
1042 cseq,
1043 method,
1044 sipe_get_useragent(sip),
1045 callid,
1046 route,
1047 addh,
1048 body ? (gsize) strlen(body) : 0,
1049 body ? body : "");
1052 //printf ("parsing msg buf:\n%s\n\n", buf);
1053 msg = sipmsg_parse_msg(buf);
1055 g_free(buf);
1056 g_free(ourtag);
1057 g_free(theirtag);
1058 g_free(theirepid);
1059 g_free(branch);
1060 g_free(callid);
1061 g_free(route);
1062 g_free(epid);
1064 sign_outgoing_message (msg, sip, method);
1066 buf = sipmsg_to_string (msg);
1068 /* add to ongoing transactions */
1069 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1070 if (!sipe_strequal(method, "ACK")) {
1071 trans = transactions_add_buf(sip, msg, tc);
1072 } else {
1073 sipmsg_free(msg);
1075 sendout_pkt(gc, buf);
1076 g_free(buf);
1078 return trans;
1082 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1084 static void
1085 send_soap_request_with_cb(struct sipe_account_data *sip,
1086 gchar *from0,
1087 gchar *body,
1088 TransCallback callback,
1089 struct transaction_payload *payload)
1091 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1092 gchar *contact = get_contact(sip);
1093 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1094 "Content-Type: application/SOAP+xml\r\n",contact);
1096 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1097 trans->payload = payload;
1099 g_free(from);
1100 g_free(contact);
1101 g_free(hdr);
1104 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1106 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1109 static char *get_contact_register(struct sipe_account_data *sip)
1111 char *epid = get_epid(sip);
1112 char *uuid = generateUUIDfromEPID(epid);
1113 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);
1114 g_free(uuid);
1115 g_free(epid);
1116 return(buf);
1119 static void do_register_exp(struct sipe_account_data *sip, int expire)
1121 char *uri;
1122 char *expires;
1123 char *to;
1124 char *contact;
1125 char *hdr;
1127 if (!sip->sipdomain) return;
1129 uri = sip_uri_from_name(sip->sipdomain);
1130 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1131 to = sip_uri_self(sip);
1132 contact = get_contact_register(sip);
1133 hdr = g_strdup_printf("Contact: %s\r\n"
1134 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1135 "Event: registration\r\n"
1136 "Allow-Events: presence\r\n"
1137 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1138 "%s", contact, expires);
1139 g_free(contact);
1140 g_free(expires);
1142 sip->registerstatus = 1;
1144 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1145 process_register_response);
1147 g_free(hdr);
1148 g_free(uri);
1149 g_free(to);
1152 static void do_register_cb(struct sipe_account_data *sip,
1153 SIPE_UNUSED_PARAMETER void *unused)
1155 do_register_exp(sip, -1);
1156 sip->reregister_set = FALSE;
1159 static void do_register(struct sipe_account_data *sip)
1161 do_register_exp(sip, -1);
1164 static void
1165 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1167 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1168 send_soap_request(sip, body);
1169 g_free(body);
1172 static void
1173 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1175 if (allow) {
1176 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1177 } else {
1178 purple_debug_info("sipe", "Blocking contact %s\n", who);
1181 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1184 static
1185 void sipe_auth_user_cb(void * data)
1187 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1188 if (!job) return;
1190 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1191 g_free(job);
1194 static
1195 void sipe_deny_user_cb(void * data)
1197 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1198 if (!job) return;
1200 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1201 g_free(job);
1204 static void
1205 sipe_add_permit(PurpleConnection *gc, const char *name)
1207 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1208 sipe_contact_allow_deny(sip, name, TRUE);
1211 static void
1212 sipe_add_deny(PurpleConnection *gc, const char *name)
1214 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1215 sipe_contact_allow_deny(sip, name, FALSE);
1218 /*static void
1219 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1221 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1222 sipe_contact_set_acl(sip, name, "");
1225 static void
1226 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1228 xmlnode *watchers;
1229 xmlnode *watcher;
1230 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1231 if (msg->response != 0 && msg->response != 200) return;
1233 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1235 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1236 if (!watchers) return;
1238 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1239 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1240 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1241 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1243 // TODO pull out optional displayName to pass as alias
1244 if (remote_user) {
1245 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1246 job->who = remote_user;
1247 job->sip = sip;
1248 purple_account_request_authorization(
1249 sip->account,
1250 remote_user,
1251 _("you"), /* id */
1252 alias,
1253 NULL, /* message */
1254 on_list,
1255 sipe_auth_user_cb,
1256 sipe_deny_user_cb,
1257 (void *) job);
1262 xmlnode_free(watchers);
1263 return;
1266 static void
1267 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1269 PurpleGroup * purple_group = purple_find_group(group->name);
1270 if (!purple_group) {
1271 purple_group = purple_group_new(group->name);
1272 purple_blist_add_group(purple_group, NULL);
1275 if (purple_group) {
1276 group->purple_group = purple_group;
1277 sip->groups = g_slist_append(sip->groups, group);
1278 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1279 } else {
1280 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1284 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1286 struct sipe_group *group;
1287 GSList *entry;
1288 if (sip == NULL) {
1289 return NULL;
1292 entry = sip->groups;
1293 while (entry) {
1294 group = entry->data;
1295 if (group->id == id) {
1296 return group;
1298 entry = entry->next;
1300 return NULL;
1303 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1305 struct sipe_group *group;
1306 GSList *entry;
1307 if (!sip || !name) {
1308 return NULL;
1311 entry = sip->groups;
1312 while (entry) {
1313 group = entry->data;
1314 if (sipe_strequal(group->name, name)) {
1315 return group;
1317 entry = entry->next;
1319 return NULL;
1322 static void
1323 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1325 gchar *body;
1326 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1327 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1328 send_soap_request(sip, body);
1329 g_free(body);
1330 g_free(group->name);
1331 group->name = g_strdup(name);
1335 * Only appends if no such value already stored.
1336 * Like Set in Java.
1338 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1339 GSList * res = list;
1340 if (!g_slist_find_custom(list, data, func)) {
1341 res = g_slist_insert_sorted(list, data, func);
1343 return res;
1346 static int
1347 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1348 return group1->id - group2->id;
1352 * Returns string like "2 4 7 8" - group ids buddy belong to.
1354 static gchar *
1355 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1356 int i = 0;
1357 gchar *res;
1358 //creating array from GList, converting int to gchar*
1359 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1360 GSList *entry = buddy->groups;
1362 if (!ids_arr) return NULL;
1364 while (entry) {
1365 struct sipe_group * group = entry->data;
1366 ids_arr[i] = g_strdup_printf("%d", group->id);
1367 entry = entry->next;
1368 i++;
1370 ids_arr[i] = NULL;
1371 res = g_strjoinv(" ", ids_arr);
1372 g_strfreev(ids_arr);
1373 return res;
1377 * Sends buddy update to server
1379 static void
1380 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1382 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1383 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1385 if (buddy && purple_buddy) {
1386 const char *alias = purple_buddy_get_alias(purple_buddy);
1387 gchar *groups = sipe_get_buddy_groups_string(buddy);
1388 if (groups) {
1389 gchar *body;
1390 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1392 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1393 alias, groups, "true", buddy->name, sip->contacts_delta++
1395 send_soap_request(sip, body);
1396 g_free(groups);
1397 g_free(body);
1402 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1404 if (msg->response == 200) {
1405 struct sipe_group *group;
1406 struct group_user_context *ctx = trans->payload->data;
1407 xmlnode *xml;
1408 xmlnode *node;
1409 char *group_id;
1410 struct sipe_buddy *buddy;
1412 xml = xmlnode_from_str(msg->body, msg->bodylen);
1413 if (!xml) {
1414 return FALSE;
1417 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1418 if (!node) {
1419 xmlnode_free(xml);
1420 return FALSE;
1423 group_id = xmlnode_get_data(node);
1424 if (!group_id) {
1425 xmlnode_free(xml);
1426 return FALSE;
1429 group = g_new0(struct sipe_group, 1);
1430 group->id = (int)g_ascii_strtod(group_id, NULL);
1431 g_free(group_id);
1432 group->name = g_strdup(ctx->group_name);
1434 sipe_group_add(sip, group);
1436 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1437 if (buddy) {
1438 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1441 sipe_group_set_user(sip, ctx->user_name);
1443 xmlnode_free(xml);
1444 return TRUE;
1446 return FALSE;
1449 static void sipe_group_context_destroy(gpointer data)
1451 struct group_user_context *ctx = data;
1452 g_free(ctx->group_name);
1453 g_free(ctx->user_name);
1454 g_free(ctx);
1457 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1459 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1460 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1461 gchar *body;
1462 ctx->group_name = g_strdup(name);
1463 ctx->user_name = g_strdup(who);
1464 payload->destroy = sipe_group_context_destroy;
1465 payload->data = ctx;
1467 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1468 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1469 g_free(body);
1473 * Data structure for scheduled actions
1476 struct scheduled_action {
1478 * Name of action.
1479 * Format is <Event>[<Data>...]
1480 * Example: <presence><sip:user@domain.com> or <registration>
1482 gchar *name;
1483 guint timeout_handler;
1484 gboolean repetitive;
1485 Action action;
1486 GDestroyNotify destroy;
1487 struct sipe_account_data *sip;
1488 void *payload;
1492 * A timer callback
1493 * Should return FALSE if repetitive action is not needed
1495 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1497 gboolean ret;
1498 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1499 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1500 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1501 (sched_action->action)(sched_action->sip, sched_action->payload);
1502 ret = sched_action->repetitive;
1503 if (sched_action->destroy) {
1504 (*sched_action->destroy)(sched_action->payload);
1506 g_free(sched_action->name);
1507 g_free(sched_action);
1508 return ret;
1512 * Kills action timer effectively cancelling
1513 * scheduled action
1515 * @param name of action
1517 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1519 GSList *entry;
1521 if (!sip->timeouts || !name) return;
1523 entry = sip->timeouts;
1524 while (entry) {
1525 struct scheduled_action *sched_action = entry->data;
1526 if(sipe_strequal(sched_action->name, name)) {
1527 GSList *to_delete = entry;
1528 entry = entry->next;
1529 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1530 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1531 purple_timeout_remove(sched_action->timeout_handler);
1532 if (sched_action->destroy) {
1533 (*sched_action->destroy)(sched_action->payload);
1535 g_free(sched_action->name);
1536 g_free(sched_action);
1537 } else {
1538 entry = entry->next;
1543 static void
1544 sipe_schedule_action0(const gchar *name,
1545 int timeout,
1546 gboolean isSeconds,
1547 Action action,
1548 GDestroyNotify destroy,
1549 struct sipe_account_data *sip,
1550 void *payload)
1552 struct scheduled_action *sched_action;
1554 /* Make sure each action only exists once */
1555 sipe_cancel_scheduled_action(sip, name);
1557 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1558 sched_action = g_new0(struct scheduled_action, 1);
1559 sched_action->repetitive = FALSE;
1560 sched_action->name = g_strdup(name);
1561 sched_action->action = action;
1562 sched_action->destroy = destroy;
1563 sched_action->sip = sip;
1564 sched_action->payload = payload;
1565 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1566 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1567 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1568 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1571 void
1572 sipe_schedule_action(const gchar *name,
1573 int timeout,
1574 Action action,
1575 GDestroyNotify destroy,
1576 struct sipe_account_data *sip,
1577 void *payload)
1579 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1583 * Same as sipe_schedule_action() but timeout is in milliseconds.
1585 static void
1586 sipe_schedule_action_msec(const gchar *name,
1587 int timeout,
1588 Action action,
1589 GDestroyNotify destroy,
1590 struct sipe_account_data *sip,
1591 void *payload)
1593 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1596 static void
1597 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1598 time_t calculate_from);
1600 static int
1601 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1603 static const char*
1604 sipe_get_status_by_availability(int avail,
1605 char** activity);
1607 static void
1608 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1609 const char *status_id,
1610 const char *message,
1611 time_t do_not_publish[]);
1613 static void
1614 sipe_apply_calendar_status(struct sipe_account_data *sip,
1615 struct sipe_buddy *sbuddy,
1616 const char *status_id)
1618 time_t cal_avail_since;
1619 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1620 int avail;
1621 gchar *self_uri;
1623 if (!sbuddy) return;
1625 if (cal_status < SIPE_CAL_NO_DATA) {
1626 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_status : %d for %s\n", cal_status, sbuddy->name);
1627 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1630 /* scheduled Cal update call */
1631 if (!status_id) {
1632 status_id = sbuddy->last_non_cal_status_id;
1633 g_free(sbuddy->activity);
1634 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1637 if (!status_id) {
1638 purple_debug_info("sipe", "sipe_apply_calendar_status: status_id is NULL for %s, exiting.\n",
1639 sbuddy->name ? sbuddy->name : "" );
1640 return;
1643 /* adjust to calendar status */
1644 if (cal_status != SIPE_CAL_NO_DATA) {
1645 purple_debug_info("sipe", "sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1647 if (cal_status == SIPE_CAL_BUSY
1648 && cal_avail_since > sbuddy->user_avail_since
1649 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1651 status_id = SIPE_STATUS_ID_BUSY;
1652 g_free(sbuddy->activity);
1653 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1655 avail = sipe_get_availability_by_status(status_id, NULL);
1657 purple_debug_info("sipe", "sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1658 if (cal_avail_since > sbuddy->activity_since) {
1659 if (cal_status == SIPE_CAL_OOF
1660 && avail >= 15000) /* 12000 in 2007 */
1662 g_free(sbuddy->activity);
1663 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1668 /* then set status_id actually */
1669 purple_debug_info("sipe", "sipe_apply_calendar_status: to %s for %s\n", status_id, sbuddy->name ? sbuddy->name : "" );
1670 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1672 /* set our account state to the one in roaming (including calendar info) */
1673 self_uri = sip_uri_self(sip);
1674 if (sip->initial_state_published && sipe_strequal(sbuddy->name, self_uri)) {
1675 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1676 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1679 purple_debug_info("sipe", "sipe_apply_calendar_status: switch to '%s' for the account\n", sip->status);
1680 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1682 g_free(self_uri);
1685 static void
1686 sipe_got_user_status(struct sipe_account_data *sip,
1687 const char* uri,
1688 const char *status_id)
1690 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1692 if (!sbuddy) return;
1694 /* Check if on 2005 system contact's calendar,
1695 * then set/preserve it.
1697 if (!sip->ocs2007) {
1698 sipe_apply_calendar_status(sip, sbuddy, status_id);
1699 } else {
1700 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1704 static void
1705 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1706 struct sipe_buddy *sbuddy,
1707 struct sipe_account_data *sip)
1709 sipe_apply_calendar_status(sip, sbuddy, NULL);
1713 * Updates contact's status
1714 * based on their calendar information.
1716 * Applicability: 2005 systems
1718 static void
1719 update_calendar_status(struct sipe_account_data *sip)
1721 purple_debug_info("sipe", "update_calendar_status() started.\n");
1722 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1724 /* repeat scheduling */
1725 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1729 * Schedules process of contacts' status update
1730 * based on their calendar information.
1731 * Should be scheduled to the beginning of every
1732 * 15 min interval, like:
1733 * 13:00, 13:15, 13:30, 13:45, etc.
1735 * Applicability: 2005 systems
1737 static void
1738 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1739 time_t calculate_from)
1741 int interval = 15*60;
1742 /** start of the beginning of closest 15 min interval. */
1743 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1745 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1746 asctime(localtime(&calculate_from)));
1747 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1748 asctime(localtime(&next_start)));
1750 sipe_schedule_action("<+2005-cal-status>",
1751 (int)(next_start - time(NULL)),
1752 (Action)update_calendar_status,
1753 NULL,
1754 sip,
1755 NULL);
1759 * Schedules process of self status publish
1760 * based on own calendar information.
1761 * Should be scheduled to the beginning of every
1762 * 15 min interval, like:
1763 * 13:00, 13:15, 13:30, 13:45, etc.
1765 * Applicability: 2007+ systems
1767 static void
1768 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1769 time_t calculate_from)
1771 int interval = 5*60;
1772 /** start of the beginning of closest 5 min interval. */
1773 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1775 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1776 asctime(localtime(&calculate_from)));
1777 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1778 asctime(localtime(&next_start)));
1780 sipe_schedule_action("<+2007-cal-status>",
1781 (int)(next_start - time(NULL)),
1782 (Action)publish_calendar_status_self,
1783 NULL,
1784 sip,
1785 NULL);
1788 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1790 /** Should be g_free()'d
1792 static gchar *
1793 sipe_get_subscription_key(const gchar *event,
1794 const gchar *with)
1796 gchar *key = NULL;
1798 if (is_empty(event)) return NULL;
1800 if (event && !g_ascii_strcasecmp(event, "presence")) {
1801 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1802 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1804 /* @TODO drop participated buddies' just_added flag */
1805 } else if (event) {
1806 /* Subscription is identified by <event> key */
1807 key = g_strdup_printf("<%s>", event);
1810 return key;
1813 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1814 SIPE_UNUSED_PARAMETER struct transaction *trans)
1816 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1817 const gchar *event = sipmsg_find_header(msg, "Event");
1818 gchar *key;
1820 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1821 if (!event) {
1822 struct sipmsg *request_msg = trans->msg;
1823 event = sipmsg_find_header(request_msg, "Event");
1826 key = sipe_get_subscription_key(event, with);
1828 /* 200 OK; 481 Call Leg Does Not Exist */
1829 if (key && (msg->response == 200 || msg->response == 481)) {
1830 if (g_hash_table_lookup(sip->subscriptions, key)) {
1831 g_hash_table_remove(sip->subscriptions, key);
1832 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1836 /* create/store subscription dialog if not yet */
1837 if (msg->response == 200) {
1838 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1839 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1841 if (key) {
1842 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1843 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1845 subscription->dialog.callid = g_strdup(callid);
1846 subscription->dialog.cseq = atoi(cseq);
1847 subscription->dialog.with = g_strdup(with);
1848 subscription->event = g_strdup(event);
1849 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1851 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1854 g_free(cseq);
1857 g_free(key);
1858 g_free(with);
1860 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1862 process_incoming_notify(sip, msg, FALSE, FALSE);
1864 return TRUE;
1867 static void sipe_subscribe_resource_uri(const char *name,
1868 SIPE_UNUSED_PARAMETER gpointer value,
1869 gchar **resources_uri)
1871 gchar *tmp = *resources_uri;
1872 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1873 g_free(tmp);
1876 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1878 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1879 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1880 gchar *tmp = *resources_uri;
1882 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1884 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1885 g_free(tmp);
1889 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1890 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1891 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1892 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1893 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1896 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1898 gchar *key;
1899 gchar *contact = get_contact(sip);
1900 gchar *request;
1901 gchar *content;
1902 gchar *require = "";
1903 gchar *accept = "";
1904 gchar *autoextend = "";
1905 gchar *content_type;
1906 struct sip_dialog *dialog;
1908 if (sip->ocs2007) {
1909 require = ", categoryList";
1910 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1911 content_type = "application/msrtc-adrl-categorylist+xml";
1912 content = g_strdup_printf(
1913 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1914 "<action name=\"subscribe\" id=\"63792024\">\n"
1915 "<adhocList>\n%s</adhocList>\n"
1916 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1917 "<category name=\"calendarData\"/>\n"
1918 "<category name=\"contactCard\"/>\n"
1919 "<category name=\"note\"/>\n"
1920 "<category name=\"state\"/>\n"
1921 "</categoryList>\n"
1922 "</action>\n"
1923 "</batchSub>", sip->username, resources_uri);
1924 } else {
1925 autoextend = "Supported: com.microsoft.autoextend\r\n";
1926 content_type = "application/adrl+xml";
1927 content = g_strdup_printf(
1928 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1929 "<create xmlns=\"\">\n%s</create>\n"
1930 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1932 g_free(resources_uri);
1934 request = g_strdup_printf(
1935 "Require: adhoclist%s\r\n"
1936 "Supported: eventlist\r\n"
1937 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1938 "Supported: ms-piggyback-first-notify\r\n"
1939 "%sSupported: ms-benotify\r\n"
1940 "Proxy-Require: ms-benotify\r\n"
1941 "Event: presence\r\n"
1942 "Content-Type: %s\r\n"
1943 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1944 g_free(contact);
1946 /* subscribe to buddy presence */
1947 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1948 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1949 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1950 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1952 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1954 g_free(content);
1955 g_free(to);
1956 g_free(request);
1957 g_free(key);
1960 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1961 SIPE_UNUSED_PARAMETER void *unused)
1963 gchar *to = sip_uri_self(sip);
1964 gchar *resources_uri = g_strdup("");
1965 if (sip->ocs2007) {
1966 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1967 } else {
1968 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1971 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1974 struct presence_batched_routed {
1975 gchar *host;
1976 GSList *buddies;
1979 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1981 struct presence_batched_routed *data = payload;
1982 GSList *buddies = data->buddies;
1983 while (buddies) {
1984 g_free(buddies->data);
1985 buddies = buddies->next;
1987 g_slist_free(data->buddies);
1988 g_free(data->host);
1989 g_free(payload);
1992 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1994 struct presence_batched_routed *data = payload;
1995 GSList *buddies = data->buddies;
1996 gchar *resources_uri = g_strdup("");
1997 while (buddies) {
1998 gchar *tmp = resources_uri;
1999 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2000 g_free(tmp);
2001 buddies = buddies->next;
2003 sipe_subscribe_presence_batched_to(sip, resources_uri,
2004 g_strdup(data->host));
2008 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2009 * The user sends a single SUBSCRIBE request to the subscribed contact.
2010 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2014 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2017 gchar *key;
2018 gchar *to = sip_uri((char *)buddy_name);
2019 gchar *tmp = get_contact(sip);
2020 gchar *request;
2021 gchar *content = NULL;
2022 gchar *autoextend = "";
2023 gchar *content_type = "";
2024 struct sip_dialog *dialog;
2025 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2026 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2028 if (sbuddy) sbuddy->just_added = FALSE;
2030 if (sip->ocs2007) {
2031 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2032 } else {
2033 autoextend = "Supported: com.microsoft.autoextend\r\n";
2036 request = g_strdup_printf(
2037 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2038 "Supported: ms-piggyback-first-notify\r\n"
2039 "%s%sSupported: ms-benotify\r\n"
2040 "Proxy-Require: ms-benotify\r\n"
2041 "Event: presence\r\n"
2042 "Contact: %s\r\n", autoextend, content_type, tmp);
2044 if (sip->ocs2007) {
2045 content = g_strdup_printf(
2046 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2047 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2048 "<resource uri=\"%s\"%s\n"
2049 "</adhocList>\n"
2050 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2051 "<category name=\"calendarData\"/>\n"
2052 "<category name=\"contactCard\"/>\n"
2053 "<category name=\"note\"/>\n"
2054 "<category name=\"state\"/>\n"
2055 "</categoryList>\n"
2056 "</action>\n"
2057 "</batchSub>", sip->username, to, context);
2060 g_free(tmp);
2062 /* subscribe to buddy presence */
2063 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2064 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2065 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2066 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2068 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2070 g_free(content);
2071 g_free(to);
2072 g_free(request);
2073 g_free(key);
2076 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2078 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2080 if (!purple_status_is_active(status))
2081 return;
2083 if (account->gc) {
2084 struct sipe_account_data *sip = account->gc->proto_data;
2086 if (sip) {
2087 gchar *action_name;
2088 gchar *tmp;
2089 time_t now = time(NULL);
2090 const char *status_id = purple_status_get_id(status);
2091 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2092 sipe_activity activity = sipe_get_activity_by_token(status_id);
2093 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2095 /* when other point of presence clears note, but we are keeping
2096 * state if OOF note.
2098 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2099 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2100 do_not_publish = FALSE;
2103 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2104 status_id, (int)sip->do_not_publish[activity], (int)now);
2106 sip->do_not_publish[activity] = 0;
2107 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2108 status_id, (int)sip->do_not_publish[activity]);
2110 if (do_not_publish)
2112 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2113 return;
2116 g_free(sip->status);
2117 sip->status = g_strdup(status_id);
2119 /* hack to escape apostrof before comparison */
2120 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2122 /* this will preserve OOF flag as well */
2123 if (!sipe_strequal(tmp, sip->note)) {
2124 sip->is_oof_note = FALSE;
2125 g_free(sip->note);
2126 sip->note = g_strdup(note);
2127 sip->note_since = time(NULL);
2129 g_free(tmp);
2131 /* schedule 2 sec to capture idle flag */
2132 action_name = g_strdup_printf("<%s>", "+set-status");
2133 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2134 g_free(action_name);
2138 static void
2139 sipe_set_idle(PurpleConnection * gc,
2140 int interval)
2142 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2144 if (gc) {
2145 struct sipe_account_data *sip = gc->proto_data;
2147 if (sip) {
2148 sip->idle_switch = time(NULL);
2149 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2154 static void
2155 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2156 SIPE_UNUSED_PARAMETER const char *alias)
2158 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2159 sipe_group_set_user(sip, name);
2162 static void
2163 sipe_group_buddy(PurpleConnection *gc,
2164 const char *who,
2165 const char *old_group_name,
2166 const char *new_group_name)
2168 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2169 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2170 struct sipe_group * old_group = NULL;
2171 struct sipe_group * new_group;
2173 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2174 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2176 if(!buddy) { // buddy not in roaming list
2177 return;
2180 if (old_group_name) {
2181 old_group = sipe_group_find_by_name(sip, old_group_name);
2183 new_group = sipe_group_find_by_name(sip, new_group_name);
2185 if (old_group) {
2186 buddy->groups = g_slist_remove(buddy->groups, old_group);
2187 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2190 if (!new_group) {
2191 sipe_group_create(sip, new_group_name, who);
2192 } else {
2193 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2194 sipe_group_set_user(sip, who);
2198 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2200 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2202 /* libpurple can call us with undefined buddy or group */
2203 if (buddy && group) {
2204 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2206 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2207 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2208 purple_blist_rename_buddy(buddy, buddy_name);
2209 g_free(buddy_name);
2211 /* Prepend sip: if needed */
2212 if (!g_str_has_prefix(buddy->name, "sip:")) {
2213 gchar *buf = sip_uri_from_name(buddy->name);
2214 purple_blist_rename_buddy(buddy, buf);
2215 g_free(buf);
2218 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2219 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2220 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2221 b->name = g_strdup(buddy->name);
2222 b->just_added = TRUE;
2223 g_hash_table_insert(sip->buddies, b->name, b);
2224 sipe_group_buddy(gc, b->name, NULL, group->name);
2225 /* @TODO should go to callback */
2226 sipe_subscribe_presence_single(sip, b->name);
2227 } else {
2228 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2233 static void sipe_free_buddy(struct sipe_buddy *buddy)
2235 #ifndef _WIN32
2237 * We are calling g_hash_table_foreach_steal(). That means that no
2238 * key/value deallocation functions are called. Therefore the glib
2239 * hash code does not touch the key (buddy->name) or value (buddy)
2240 * of the to-be-deleted hash node at all. It follows that we
2242 * - MUST free the memory for the key ourselves and
2243 * - ARE allowed to do it in this function
2245 * Conclusion: glib must be broken on the Windows platform if sipe
2246 * crashes with SIGTRAP when closing. You'll have to live
2247 * with the memory leak until this is fixed.
2249 g_free(buddy->name);
2250 #endif
2251 g_free(buddy->activity);
2252 g_free(buddy->meeting_subject);
2253 g_free(buddy->meeting_location);
2254 g_free(buddy->note);
2256 g_free(buddy->cal_start_time);
2257 g_free(buddy->cal_free_busy_base64);
2258 g_free(buddy->cal_free_busy);
2259 g_free(buddy->last_non_cal_activity);
2261 sipe_cal_free_working_hours(buddy->cal_working_hours);
2263 g_free(buddy->device_name);
2264 g_slist_free(buddy->groups);
2265 g_free(buddy);
2269 * Unassociates buddy from group first.
2270 * Then see if no groups left, removes buddy completely.
2271 * Otherwise updates buddy groups on server.
2273 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2275 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2276 struct sipe_buddy *b;
2277 struct sipe_group *g = NULL;
2279 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2280 if (!buddy) return;
2282 b = g_hash_table_lookup(sip->buddies, buddy->name);
2283 if (!b) return;
2285 if (group) {
2286 g = sipe_group_find_by_name(sip, group->name);
2289 if (g) {
2290 b->groups = g_slist_remove(b->groups, g);
2291 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2294 if (g_slist_length(b->groups) < 1) {
2295 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2296 sipe_cancel_scheduled_action(sip, action_name);
2297 g_free(action_name);
2299 g_hash_table_remove(sip->buddies, buddy->name);
2301 if (b->name) {
2302 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2303 send_soap_request(sip, body);
2304 g_free(body);
2307 sipe_free_buddy(b);
2308 } else {
2309 //updates groups on server
2310 sipe_group_set_user(sip, b->name);
2315 static void
2316 sipe_rename_group(PurpleConnection *gc,
2317 const char *old_name,
2318 PurpleGroup *group,
2319 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2321 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2322 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2323 if (s_group) {
2324 sipe_group_rename(sip, s_group, group->name);
2325 } else {
2326 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2330 static void
2331 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2333 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2334 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2335 if (s_group) {
2336 gchar *body;
2337 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2338 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2339 send_soap_request(sip, body);
2340 g_free(body);
2342 sip->groups = g_slist_remove(sip->groups, s_group);
2343 g_free(s_group->name);
2344 g_free(s_group);
2345 } else {
2346 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2350 /** All statuses need message attribute to pass Note */
2351 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2353 PurpleStatusType *type;
2354 GList *types = NULL;
2356 /* Macros to reduce code repetition.
2357 Translators: noun */
2358 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2359 prim, id, name, \
2360 TRUE, user, FALSE, \
2361 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2362 NULL); \
2363 types = g_list_append(types, type);
2365 /* Online */
2366 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2367 NULL,
2368 NULL,
2369 TRUE);
2371 /* Busy */
2372 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2373 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2374 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2375 TRUE);
2377 /* Do Not Disturb */
2378 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2379 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2380 NULL,
2381 TRUE);
2383 /* Away */
2384 /* Goes first in the list as
2385 * purple picks the first status with the AWAY type
2386 * for idle.
2388 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2389 NULL,
2390 NULL,
2391 TRUE);
2393 /* Be Right Back */
2394 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2395 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2396 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2397 TRUE);
2399 /* Appear Offline */
2400 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2401 NULL,
2402 NULL,
2403 TRUE);
2405 /* Offline */
2406 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2407 NULL,
2408 NULL,
2409 TRUE);
2410 types = g_list_append(types, type);
2412 return types;
2416 * A callback for g_hash_table_foreach
2418 static void
2419 sipe_buddy_subscribe_cb(char *buddy_name,
2420 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2421 struct sipe_account_data *sip)
2423 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2424 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2425 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2426 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2428 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2429 g_free(action_name);
2433 * Removes entries from purple buddy list
2434 * that does not correspond ones in the roaming contact list.
2436 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2437 GSList *buddies = purple_find_buddies(sip->account, NULL);
2438 GSList *entry = buddies;
2439 struct sipe_buddy *buddy;
2440 PurpleBuddy *b;
2441 PurpleGroup *g;
2443 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2444 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2445 while (entry) {
2446 b = entry->data;
2447 g = purple_buddy_get_group(b);
2448 buddy = g_hash_table_lookup(sip->buddies, b->name);
2449 if(buddy) {
2450 gboolean in_sipe_groups = FALSE;
2451 GSList *entry2 = buddy->groups;
2452 while (entry2) {
2453 struct sipe_group *group = entry2->data;
2454 if (sipe_strequal(group->name, g->name)) {
2455 in_sipe_groups = TRUE;
2456 break;
2458 entry2 = entry2->next;
2460 if(!in_sipe_groups) {
2461 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2462 purple_blist_remove_buddy(b);
2464 } else {
2465 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2466 purple_blist_remove_buddy(b);
2468 entry = entry->next;
2470 g_slist_free(buddies);
2473 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2475 int len = msg->bodylen;
2477 const gchar *tmp = sipmsg_find_header(msg, "Event");
2478 xmlnode *item;
2479 xmlnode *isc;
2480 const gchar *contacts_delta;
2481 xmlnode *group_node;
2482 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2483 return FALSE;
2486 /* Convert the contact from XML to Purple Buddies */
2487 isc = xmlnode_from_str(msg->body, len);
2488 if (!isc) {
2489 return FALSE;
2492 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2493 if (contacts_delta) {
2494 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2497 if (sipe_strequal(isc->name, "contactList")) {
2499 /* Parse groups */
2500 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2501 struct sipe_group * group = g_new0(struct sipe_group, 1);
2502 const char *name = xmlnode_get_attrib(group_node, "name");
2504 if (g_str_has_prefix(name, "~")) {
2505 name = _("Other Contacts");
2507 group->name = g_strdup(name);
2508 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2510 sipe_group_add(sip, group);
2513 // Make sure we have at least one group
2514 if (g_slist_length(sip->groups) == 0) {
2515 struct sipe_group * group = g_new0(struct sipe_group, 1);
2516 PurpleGroup *purple_group;
2517 group->name = g_strdup(_("Other Contacts"));
2518 group->id = 1;
2519 purple_group = purple_group_new(group->name);
2520 purple_blist_add_group(purple_group, NULL);
2521 sip->groups = g_slist_append(sip->groups, group);
2524 /* Parse contacts */
2525 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2526 const gchar *uri = xmlnode_get_attrib(item, "uri");
2527 const gchar *name = xmlnode_get_attrib(item, "name");
2528 gchar *buddy_name;
2529 struct sipe_buddy *buddy = NULL;
2530 gchar *tmp;
2531 gchar **item_groups;
2532 int i = 0;
2534 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2535 tmp = sip_uri_from_name(uri);
2536 buddy_name = g_ascii_strdown(tmp, -1);
2537 g_free(tmp);
2539 /* assign to group Other Contacts if nothing else received */
2540 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2541 if(is_empty(tmp)) {
2542 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2543 g_free(tmp);
2544 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2546 item_groups = g_strsplit(tmp, " ", 0);
2547 g_free(tmp);
2549 while (item_groups[i]) {
2550 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2552 // If couldn't find the right group for this contact, just put them in the first group we have
2553 if (group == NULL && g_slist_length(sip->groups) > 0) {
2554 group = sip->groups->data;
2557 if (group != NULL) {
2558 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2559 if (!b){
2560 b = purple_buddy_new(sip->account, buddy_name, uri);
2561 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2563 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2566 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2567 if (name != NULL && strlen(name) != 0) {
2568 purple_blist_alias_buddy(b, name);
2570 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2574 if (!buddy) {
2575 buddy = g_new0(struct sipe_buddy, 1);
2576 buddy->name = g_strdup(b->name);
2577 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2580 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2582 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2583 } else {
2584 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2585 name);
2588 i++;
2589 } // while, contact groups
2590 g_strfreev(item_groups);
2591 g_free(buddy_name);
2593 } // for, contacts
2595 sipe_cleanup_local_blist(sip);
2597 /* Add self-contact if not there yet. 2005 systems. */
2598 /* This will resemble subscription to roaming_self in 2007 systems */
2599 if (!sip->ocs2007) {
2600 gchar *self_uri = sip_uri_self(sip);
2601 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2603 if (!buddy) {
2604 buddy = g_new0(struct sipe_buddy, 1);
2605 buddy->name = g_strdup(self_uri);
2606 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2608 g_free(self_uri);
2611 xmlnode_free(isc);
2613 /* subscribe to buddies */
2614 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2615 if (sip->batched_support) {
2616 sipe_subscribe_presence_batched(sip, NULL);
2617 } else {
2618 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2620 sip->subscribed_buddies = TRUE;
2622 /* for 2005 systems schedule contacts' status update
2623 * based on their calendar information
2625 if (!sip->ocs2007) {
2626 sipe_sched_calendar_status_update(sip, time(NULL));
2629 return 0;
2633 * Subscribe roaming contacts
2635 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2637 gchar *to = sip_uri_self(sip);
2638 gchar *tmp = get_contact(sip);
2639 gchar *hdr = g_strdup_printf(
2640 "Event: vnd-microsoft-roaming-contacts\r\n"
2641 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2642 "Supported: com.microsoft.autoextend\r\n"
2643 "Supported: ms-benotify\r\n"
2644 "Proxy-Require: ms-benotify\r\n"
2645 "Supported: ms-piggyback-first-notify\r\n"
2646 "Contact: %s\r\n", tmp);
2647 g_free(tmp);
2649 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2650 g_free(to);
2651 g_free(hdr);
2654 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2655 SIPE_UNUSED_PARAMETER void *unused)
2657 gchar *key;
2658 struct sip_dialog *dialog;
2659 gchar *to = sip_uri_self(sip);
2660 gchar *tmp = get_contact(sip);
2661 gchar *hdr = g_strdup_printf(
2662 "Event: presence.wpending\r\n"
2663 "Accept: text/xml+msrtc.wpending\r\n"
2664 "Supported: com.microsoft.autoextend\r\n"
2665 "Supported: ms-benotify\r\n"
2666 "Proxy-Require: ms-benotify\r\n"
2667 "Supported: ms-piggyback-first-notify\r\n"
2668 "Contact: %s\r\n", tmp);
2669 g_free(tmp);
2671 /* Subscription is identified by <event> key */
2672 key = g_strdup_printf("<%s>", "presence.wpending");
2673 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2674 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2676 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2678 g_free(to);
2679 g_free(hdr);
2680 g_free(key);
2684 * Fires on deregistration event initiated by server.
2685 * [MS-SIPREGE] SIP extension.
2688 // 2007 Example
2690 // Content-Type: text/registration-event
2691 // subscription-state: terminated;expires=0
2692 // ms-diagnostics-public: 4141;reason="User disabled"
2694 // deregistered;event=rejected
2696 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2698 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2699 gchar *event = NULL;
2700 gchar *reason = NULL;
2701 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2702 gchar *warning;
2704 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2705 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2707 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2708 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2709 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2710 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2711 } else {
2712 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2713 return;
2716 if (diagnostics != NULL) {
2717 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2718 } else { // for LCS2005
2719 int error_id = 0;
2720 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2721 error_id = 4140; // [MS-SIPREGE]
2722 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2723 reason = g_strdup(_("you are already signed in at another location"));
2724 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2725 error_id = 4141;
2726 reason = g_strdup(_("user disabled")); // [MS-OCER]
2727 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2728 error_id = 4142;
2729 reason = g_strdup(_("user moved")); // [MS-OCER]
2732 g_free(event);
2733 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2734 g_free(reason);
2736 sip->gc->wants_to_die = TRUE;
2737 purple_connection_error(sip->gc, warning);
2738 g_free(warning);
2742 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2744 xmlnode *xn_provision_group_list;
2745 xmlnode *node;
2747 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2749 /* provisionGroup */
2750 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2751 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2752 g_free(sip->focus_factory_uri);
2753 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2754 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2755 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2756 break;
2759 xmlnode_free(xn_provision_group_list);
2762 /** for 2005 system */
2763 static void
2764 sipe_process_provisioning(struct sipe_account_data *sip,
2765 struct sipmsg *msg)
2767 xmlnode *xn_provision;
2768 xmlnode *node;
2770 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2771 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2772 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2773 if ((node = xmlnode_get_child(node, "line"))) {
2774 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2775 const gchar *server = xmlnode_get_attrib(node, "server");
2776 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2777 sip_csta_open(sip, line_uri, server);
2780 xmlnode_free(xn_provision);
2783 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2785 const gchar *contacts_delta;
2786 xmlnode *xml;
2788 xml = xmlnode_from_str(msg->body, msg->bodylen);
2789 if (!xml)
2791 return;
2794 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2795 if (contacts_delta)
2797 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2800 xmlnode_free(xml);
2803 static void
2804 free_container(struct sipe_container *container)
2806 GSList *entry;
2808 if (!container) return;
2810 entry = container->members;
2811 while (entry) {
2812 void *data = entry->data;
2813 entry = g_slist_remove(entry, data);
2814 g_free(data);
2816 g_free(container);
2820 * Finds locally stored MS-PRES container member
2822 static struct sipe_container_member *
2823 sipe_find_container_member(struct sipe_container *container,
2824 const gchar *type,
2825 const gchar *value)
2827 struct sipe_container_member *member;
2828 GSList *entry;
2830 if (container == NULL || type == NULL) {
2831 return NULL;
2834 entry = container->members;
2835 while (entry) {
2836 member = entry->data;
2837 if (!g_strcasecmp(member->type, type)
2838 && ((!member->value && !value)
2839 || (value && member->value && !g_strcasecmp(member->value, value)))
2841 return member;
2843 entry = entry->next;
2845 return NULL;
2849 * Finds locally stored MS-PRES container by id
2851 static struct sipe_container *
2852 sipe_find_container(struct sipe_account_data *sip,
2853 guint id)
2855 struct sipe_container *container;
2856 GSList *entry;
2858 if (sip == NULL) {
2859 return NULL;
2862 entry = sip->containers;
2863 while (entry) {
2864 container = entry->data;
2865 if (id == container->id) {
2866 return container;
2868 entry = entry->next;
2870 return NULL;
2874 * Access Levels
2875 * 32000 - Blocked
2876 * 400 - Personal
2877 * 300 - Team
2878 * 200 - Company
2879 * 100 - Public
2881 static int
2882 sipe_find_access_level(struct sipe_account_data *sip,
2883 const gchar *type,
2884 const gchar *value)
2886 guint containers[] = {32000, 400, 300, 200, 100};
2887 int i = 0;
2889 for (i = 0; i < 5; i++) {
2890 struct sipe_container_member *member;
2891 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2892 if (!container) continue;
2894 member = sipe_find_container_member(container, type, value);
2895 if (member) {
2896 return containers[i];
2900 return -1;
2903 static void
2904 sipe_send_set_container_members(struct sipe_account_data *sip,
2905 guint container_id,
2906 guint container_version,
2907 const gchar* action,
2908 const gchar* type,
2909 const gchar* value)
2911 gchar *self = sip_uri_self(sip);
2912 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2913 gchar *contact;
2914 gchar *hdr;
2915 gchar *body = g_strdup_printf(
2916 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2917 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2918 "</setContainerMembers>",
2919 container_id,
2920 container_version,
2921 action,
2922 type,
2923 value_str);
2924 g_free(value_str);
2926 contact = get_contact(sip);
2927 hdr = g_strdup_printf("Contact: %s\r\n"
2928 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2929 g_free(contact);
2931 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2933 g_free(hdr);
2934 g_free(body);
2935 g_free(self);
2938 static void
2939 free_publication(struct sipe_publication *publication)
2941 g_free(publication->category);
2942 g_free(publication->cal_event_hash);
2943 g_free(publication->note);
2945 g_free(publication->working_hours_xml_str);
2946 g_free(publication->fb_start_str);
2947 g_free(publication->free_busy_base64);
2949 g_free(publication);
2952 /* key is <category><instance><container> */
2953 static gboolean
2954 sipe_is_our_publication(struct sipe_account_data *sip,
2955 const gchar *key)
2957 GSList *entry;
2959 /* filling keys for our publications if not yet cached */
2960 if (!sip->our_publication_keys) {
2961 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2962 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2963 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2964 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2965 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2966 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2967 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2969 purple_debug_info("sipe", "* Our Publication Instances *\n");
2970 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2971 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2972 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2973 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2974 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2975 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2976 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2977 purple_debug_info("sipe", "\tNote : %u\n", 0);
2978 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2980 /* device */
2981 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2982 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2984 /* state:machineState */
2985 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2986 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2987 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2988 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2990 /* state:userState */
2991 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2992 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2993 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2994 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2996 /* state:calendarState */
2997 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2998 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2999 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3000 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3002 /* state:calendarState OOF */
3003 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3004 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3005 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3006 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3008 /* note */
3009 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3010 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3011 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3012 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3013 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3014 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3016 /* note OOF */
3017 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3018 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3019 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3020 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3021 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3022 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3024 /* calendarData:WorkingHours */
3025 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3026 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3027 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3028 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3029 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3030 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3031 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3032 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3033 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3034 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3035 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3036 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3038 /* calendarData:FreeBusy */
3039 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3040 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3041 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3042 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3043 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3044 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3045 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3046 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3047 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3048 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3049 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3050 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3052 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
3053 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3056 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3058 entry = sip->our_publication_keys;
3059 while (entry) {
3060 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3061 if (sipe_strequal(entry->data, key)) {
3062 return TRUE;
3064 entry = entry->next;
3066 return FALSE;
3069 /** Property names to store in blist.xml */
3070 #define ALIAS_PROP "alias"
3071 #define EMAIL_PROP "email"
3072 #define PHONE_PROP "phone"
3073 #define PHONE_DISPLAY_PROP "phone-display"
3074 #define PHONE_MOBILE_PROP "phone-mobile"
3075 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3076 #define PHONE_HOME_PROP "phone-home"
3077 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3078 #define PHONE_OTHER_PROP "phone-other"
3079 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3080 #define PHONE_CUSTOM1_PROP "phone-custom1"
3081 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3082 #define SITE_PROP "site"
3083 #define COMPANY_PROP "company"
3084 #define DEPARTMENT_PROP "department"
3085 #define TITLE_PROP "title"
3086 #define OFFICE_PROP "office"
3087 /** implies work address */
3088 #define ADDRESS_STREET_PROP "address-street"
3089 #define ADDRESS_CITY_PROP "address-city"
3090 #define ADDRESS_STATE_PROP "address-state"
3091 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3092 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3095 * Tries to figure out user first and last name
3096 * based on Display Name and email properties.
3098 * Allocates memory - must be g_free()'d
3100 * Examples to parse:
3101 * First Last
3102 * First Last - Company Name
3103 * Last, First
3104 * Last, First M.
3105 * Last, First (C)(STP) (Company)
3106 * first.last@company.com (preprocessed as "first last")
3107 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3109 * Unusable examples:
3110 * user@company.com (preprocessed as "user")
3111 * first.m.last@company.com (preprocessed as "first m last")
3112 * user.company.com@reuters.net (preprocessed as "user company com")
3114 static void
3115 sipe_get_first_last_names(struct sipe_account_data *sip,
3116 const char *uri,
3117 char **first_name,
3118 char **last_name)
3120 PurpleBuddy *p_buddy;
3121 char *display_name;
3122 const char *email;
3123 const char *first, *last;
3124 char *tmp;
3125 char **parts;
3126 gboolean has_comma = FALSE;
3128 if (!sip || !uri) return;
3130 p_buddy = purple_find_buddy(sip->account, uri);
3132 if (!p_buddy) return;
3134 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3135 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3137 if (!display_name && !email) return;
3139 /* if no display name, make "first last anything_else" out of email */
3140 if (email && !display_name) {
3141 display_name = g_strndup(email, strstr(email, "@") - email);
3142 display_name = purple_strreplace((tmp = display_name), ".", " ");
3143 g_free(tmp);
3146 if (display_name) {
3147 has_comma = (strstr(display_name, ",") != NULL);
3148 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3149 g_free(tmp);
3150 display_name = purple_strreplace((tmp = display_name), ",", " ");
3151 g_free(tmp);
3154 parts = g_strsplit(display_name, " ", 0);
3156 if (!parts[0] || !parts[1]) {
3157 g_free(display_name);
3158 g_strfreev(parts);
3159 return;
3162 if (has_comma) {
3163 last = parts[0];
3164 first = parts[1];
3165 } else {
3166 first = parts[0];
3167 last = parts[1];
3170 if (first_name) {
3171 *first_name = g_strstrip(g_strdup(first));
3174 if (last_name) {
3175 *last_name = g_strstrip(g_strdup(last));
3178 g_free(display_name);
3179 g_strfreev(parts);
3183 * Update user information
3185 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3186 * @param property_name
3187 * @param property_value may be modified to strip white space
3189 static void
3190 sipe_update_user_info(struct sipe_account_data *sip,
3191 const char *uri,
3192 const char *property_name,
3193 char *property_value)
3195 GSList *buddies, *entry;
3197 if (!property_name || strlen(property_name) == 0) return;
3199 if (property_value)
3200 property_value = g_strstrip(property_value);
3202 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3203 while (entry) {
3204 const char *prop_str;
3205 const char *server_alias;
3206 PurpleBuddy *p_buddy = entry->data;
3208 /* for Display Name */
3209 if (sipe_strequal(property_name, ALIAS_PROP)) {
3210 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3211 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3212 purple_blist_alias_buddy(p_buddy, property_value);
3215 server_alias = purple_buddy_get_server_alias(p_buddy);
3216 if (!is_empty(property_value) &&
3217 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3219 purple_blist_server_alias_buddy(p_buddy, property_value);
3222 /* for other properties */
3223 else {
3224 if (!is_empty(property_value)) {
3225 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3226 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3227 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3232 entry = entry->next;
3234 g_slist_free(buddies);
3238 * Update user phone
3239 * Suitable for both 2005 and 2007 systems.
3241 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3242 * @param phone_type
3243 * @param phone may be modified to strip white space
3244 * @param phone_display_string may be modified to strip white space
3246 static void
3247 sipe_update_user_phone(struct sipe_account_data *sip,
3248 const char *uri,
3249 const gchar *phone_type,
3250 gchar *phone,
3251 gchar *phone_display_string)
3253 const char *phone_node = PHONE_PROP; /* work phone by default */
3254 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3256 if(!phone || strlen(phone) == 0) return;
3258 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3259 phone_node = PHONE_MOBILE_PROP;
3260 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3261 } else if (sipe_strequal(phone_type, "home")) {
3262 phone_node = PHONE_HOME_PROP;
3263 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3264 } else if (sipe_strequal(phone_type, "other")) {
3265 phone_node = PHONE_OTHER_PROP;
3266 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3267 } else if (sipe_strequal(phone_type, "custom1")) {
3268 phone_node = PHONE_CUSTOM1_PROP;
3269 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3272 sipe_update_user_info(sip, uri, phone_node, phone);
3273 if (phone_display_string) {
3274 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3278 static void
3279 sipe_update_calendar(struct sipe_account_data *sip)
3281 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3283 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3285 if (sipe_strequal(calendar, "EXCH")) {
3286 sipe_ews_update_calendar(sip);
3289 /* schedule repeat */
3290 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3292 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3296 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3297 * by using standard Purple's means of signals and saved statuses.
3299 * Thus all UI elements get updated: Status Button with Note, docklet.
3300 * This is ablolutely important as both our status and note can come
3301 * inbound (roaming) or be updated programmatically (e.g. based on our
3302 * calendar data).
3304 static void
3305 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3306 const char *status_id,
3307 const char *message,
3308 time_t do_not_publish[])
3310 PurpleStatus *status = purple_account_get_active_status(account);
3311 gboolean changed = TRUE;
3313 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3314 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3316 changed = FALSE;
3319 if (purple_savedstatus_is_idleaway()) {
3320 changed = FALSE;
3323 if (changed) {
3324 PurpleSavedStatus *saved_status;
3325 const PurpleStatusType *acct_status_type =
3326 purple_status_type_find_with_id(account->status_types, status_id);
3327 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3328 sipe_activity activity = sipe_get_activity_by_token(status_id);
3330 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3331 if (saved_status) {
3332 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3335 /* If this type+message is unique then create a new transient saved status
3336 * Ref: gtkstatusbox.c
3338 if (!saved_status) {
3339 GList *tmp;
3340 GList *active_accts = purple_accounts_get_all_active();
3342 saved_status = purple_savedstatus_new(NULL, primitive);
3343 purple_savedstatus_set_message(saved_status, message);
3345 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3346 purple_savedstatus_set_substatus(saved_status,
3347 (PurpleAccount *)tmp->data, acct_status_type, message);
3349 g_list_free(active_accts);
3352 do_not_publish[activity] = time(NULL);
3353 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3354 status_id, (int)do_not_publish[activity]);
3356 /* Set the status for each account */
3357 purple_savedstatus_activate(saved_status);
3361 struct hash_table_delete_payload {
3362 GHashTable *hash_table;
3363 guint container;
3366 static void
3367 sipe_remove_category_container_publications_cb(const char *name,
3368 struct sipe_publication *publication,
3369 struct hash_table_delete_payload *payload)
3371 if (publication->container == payload->container) {
3372 g_hash_table_remove(payload->hash_table, name);
3375 static void
3376 sipe_remove_category_container_publications(GHashTable *our_publications,
3377 const char *category,
3378 guint container)
3380 struct hash_table_delete_payload payload;
3381 payload.hash_table = g_hash_table_lookup(our_publications, category);
3383 if (!payload.hash_table) return;
3385 payload.container = container;
3386 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3389 static void
3390 send_publish_category_initial(struct sipe_account_data *sip);
3393 * When we receive some self (BE) NOTIFY with a new subscriber
3394 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3397 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3399 gchar *contact;
3400 gchar *to;
3401 xmlnode *xml;
3402 xmlnode *node;
3403 xmlnode *node2;
3404 char *display_name = NULL;
3405 char *uri;
3406 GSList *category_names = NULL;
3407 int aggreg_avail = 0;
3408 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3409 gboolean do_update_status = FALSE;
3410 gboolean has_note_cleaned = FALSE;
3412 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3414 xml = xmlnode_from_str(msg->body, msg->bodylen);
3415 if (!xml) return;
3417 contact = get_contact(sip);
3418 to = sip_uri_self(sip);
3421 /* categories */
3422 /* set list of categories participating in this XML */
3423 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3424 const gchar *name = xmlnode_get_attrib(node, "name");
3425 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3427 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3428 category_names ? (int) g_slist_length(category_names) : -1);
3429 /* drop category information */
3430 if (category_names) {
3431 GSList *entry = category_names;
3432 while (entry) {
3433 GHashTable *cat_publications;
3434 const gchar *category = entry->data;
3435 entry = entry->next;
3436 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3437 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3438 if (cat_publications) {
3439 g_hash_table_remove(sip->our_publications, category);
3440 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3444 g_slist_free(category_names);
3445 /* filling our categories reflected in roaming data */
3446 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3447 const char *tmp;
3448 const gchar *name = xmlnode_get_attrib(node, "name");
3449 guint container = xmlnode_get_int_attrib(node, "container", -1);
3450 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3451 guint version = xmlnode_get_int_attrib(node, "version", 0);
3452 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3453 sipe_utils_str_to_time(tmp) : 0;
3454 gchar *key;
3455 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3457 /* Ex. clear note: <category name="note"/> */
3458 if (container == (guint)-1) {
3459 g_free(sip->note);
3460 sip->note = NULL;
3461 do_update_status = TRUE;
3462 continue;
3465 /* Ex. clear note: <category name="note" container="200"/> */
3466 if (instance == (guint)-1) {
3467 if (container == 200) {
3468 g_free(sip->note);
3469 sip->note = NULL;
3470 do_update_status = TRUE;
3472 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3473 sipe_remove_category_container_publications(
3474 sip->our_publications, name, container);
3475 continue;
3478 /* key is <category><instance><container> */
3479 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3480 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3482 /* capture all userState publication for later clean up if required */
3483 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3484 xmlnode *xn_state = xmlnode_get_child(node, "state");
3486 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3487 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3488 publication->category = g_strdup(name);
3489 publication->instance = instance;
3490 publication->container = container;
3491 publication->version = version;
3493 if (!sip->user_state_publications) {
3494 sip->user_state_publications = g_hash_table_new_full(
3495 g_str_hash, g_str_equal,
3496 g_free, (GDestroyNotify)free_publication);
3498 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3499 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3500 key, version);
3504 if (sipe_is_our_publication(sip, key)) {
3505 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3507 publication->category = g_strdup(name);
3508 publication->instance = instance;
3509 publication->container = container;
3510 publication->version = version;
3512 /* filling publication->availability */
3513 if (sipe_strequal(name, "state")) {
3514 xmlnode *xn_state = xmlnode_get_child(node, "state");
3515 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3517 if (xn_avail) {
3518 gchar *avail_str = xmlnode_get_data(xn_avail);
3519 if (avail_str) {
3520 publication->availability = atoi(avail_str);
3522 g_free(avail_str);
3524 /* for calendarState */
3525 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3526 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3527 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3529 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3530 if (xn_activity) {
3531 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3532 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3534 event->is_meeting = TRUE;
3537 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3538 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3540 publication->cal_event_hash = sipe_cal_event_hash(event);
3541 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3542 publication->cal_event_hash);
3543 sipe_cal_event_free(event);
3546 /* filling publication->note */
3547 if (sipe_strequal(name, "note")) {
3548 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3550 if (!has_note_cleaned) {
3551 has_note_cleaned = TRUE;
3553 g_free(sip->note);
3554 sip->note = NULL;
3555 sip->note_since = publish_time;
3557 do_update_status = TRUE;
3560 g_free(publication->note);
3561 publication->note = NULL;
3562 if (xn_body) {
3563 char *tmp;
3565 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3566 g_free(tmp);
3567 if (publish_time >= sip->note_since) {
3568 g_free(sip->note);
3569 sip->note = g_strdup(publication->note);
3570 sip->note_since = publish_time;
3571 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3573 do_update_status = TRUE;
3578 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3579 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3580 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3581 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3582 if (xn_free_busy) {
3583 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3584 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3586 if (xn_working_hours) {
3587 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3591 if (!cat_publications) {
3592 cat_publications = g_hash_table_new_full(
3593 g_str_hash, g_str_equal,
3594 g_free, (GDestroyNotify)free_publication);
3595 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3596 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3598 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3599 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3601 g_free(key);
3603 /* aggregateState (not an our publication) from 2-nd container */
3604 if (sipe_strequal(name, "state") && container == 2) {
3605 xmlnode *xn_state = xmlnode_get_child(node, "state");
3607 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3608 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3609 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3611 if (xn_avail) {
3612 gchar *avail_str = xmlnode_get_data(xn_avail);
3613 if (avail_str) {
3614 aggreg_avail = atoi(avail_str);
3616 g_free(avail_str);
3619 if (xn_activity) {
3620 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3622 aggreg_activity = sipe_get_activity_by_token(activity_token);
3625 do_update_status = TRUE;
3629 /* userProperties published by server from AD */
3630 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3631 xmlnode *line;
3632 /* line, for Remote Call Control (RCC) */
3633 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3634 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3635 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3636 gchar *line_uri;
3638 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3640 line_uri = xmlnode_get_data(line);
3641 if (line_uri) {
3642 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3643 sip_csta_open(sip, line_uri, line_server);
3645 g_free(line_uri);
3647 break;
3651 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3652 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3654 /* containers */
3655 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3656 guint id = xmlnode_get_int_attrib(node, "id", 0);
3657 struct sipe_container *container = sipe_find_container(sip, id);
3659 if (container) {
3660 sip->containers = g_slist_remove(sip->containers, container);
3661 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3662 free_container(container);
3664 container = g_new0(struct sipe_container, 1);
3665 container->id = id;
3666 container->version = xmlnode_get_int_attrib(node, "version", 0);
3667 sip->containers = g_slist_append(sip->containers, container);
3668 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3670 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3671 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3672 member->type = xmlnode_get_attrib(node2, "type");
3673 member->value = xmlnode_get_attrib(node2, "value");
3674 container->members = g_slist_append(container->members, member);
3675 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3676 member->type, member->value ? member->value : "");
3680 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3681 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3682 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3683 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3684 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3685 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3686 /* initial set-up to let counterparties see your status */
3687 if (sameEnterpriseAL < 0) {
3688 struct sipe_container *container = sipe_find_container(sip, 200);
3689 guint version = container ? container->version : 0;
3690 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3692 if (federatedAL < 0) {
3693 struct sipe_container *container = sipe_find_container(sip, 100);
3694 guint version = container ? container->version : 0;
3695 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3697 sip->access_level_set = TRUE;
3700 /* subscribers */
3701 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3702 const char *user;
3703 const char *acknowledged;
3704 gchar *hdr;
3705 gchar *body;
3707 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3708 if (!user) continue;
3709 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3710 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3711 uri = sip_uri_from_name(user);
3713 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3715 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3716 if(!g_ascii_strcasecmp(acknowledged,"false")){
3717 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3718 if (!purple_find_buddy(sip->account, uri)) {
3719 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3722 hdr = g_strdup_printf(
3723 "Contact: %s\r\n"
3724 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3726 body = g_strdup_printf(
3727 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3728 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3729 "</setSubscribers>", user);
3731 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3732 g_free(body);
3733 g_free(hdr);
3735 g_free(display_name);
3736 g_free(uri);
3739 g_free(contact);
3740 xmlnode_free(xml);
3742 /* Publish initial state if not yet.
3743 * Assuming this happens on initial responce to subscription to roaming-self
3744 * so we've already updated our roaming data in full.
3745 * Only for 2007+
3747 if (!sip->initial_state_published) {
3748 send_publish_category_initial(sip);
3749 sip->initial_state_published = TRUE;
3750 /* dalayed run */
3751 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3752 do_update_status = FALSE;
3753 } else if (aggreg_avail) {
3755 g_free(sip->status);
3756 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3757 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3758 } else {
3759 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3763 if (do_update_status) {
3764 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3765 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3768 g_free(to);
3771 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3773 gchar *to = sip_uri_self(sip);
3774 gchar *tmp = get_contact(sip);
3775 gchar *hdr = g_strdup_printf(
3776 "Event: vnd-microsoft-roaming-ACL\r\n"
3777 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3778 "Supported: com.microsoft.autoextend\r\n"
3779 "Supported: ms-benotify\r\n"
3780 "Proxy-Require: ms-benotify\r\n"
3781 "Supported: ms-piggyback-first-notify\r\n"
3782 "Contact: %s\r\n", tmp);
3783 g_free(tmp);
3785 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3786 g_free(to);
3787 g_free(hdr);
3791 * To request for presence information about the user, access level settings that have already been configured by the user
3792 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3793 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3796 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3798 gchar *to = sip_uri_self(sip);
3799 gchar *tmp = get_contact(sip);
3800 gchar *hdr = g_strdup_printf(
3801 "Event: vnd-microsoft-roaming-self\r\n"
3802 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3803 "Supported: ms-benotify\r\n"
3804 "Proxy-Require: ms-benotify\r\n"
3805 "Supported: ms-piggyback-first-notify\r\n"
3806 "Contact: %s\r\n"
3807 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3809 gchar *body=g_strdup(
3810 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3811 "<roaming type=\"categories\"/>"
3812 "<roaming type=\"containers\"/>"
3813 "<roaming type=\"subscribers\"/></roamingList>");
3815 g_free(tmp);
3816 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3817 g_free(body);
3818 g_free(to);
3819 g_free(hdr);
3823 * For 2005 version
3825 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3827 gchar *to = sip_uri_self(sip);
3828 gchar *tmp = get_contact(sip);
3829 gchar *hdr = g_strdup_printf(
3830 "Event: vnd-microsoft-provisioning\r\n"
3831 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3832 "Supported: com.microsoft.autoextend\r\n"
3833 "Supported: ms-benotify\r\n"
3834 "Proxy-Require: ms-benotify\r\n"
3835 "Supported: ms-piggyback-first-notify\r\n"
3836 "Expires: 0\r\n"
3837 "Contact: %s\r\n", tmp);
3839 g_free(tmp);
3840 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3841 g_free(to);
3842 g_free(hdr);
3845 /** Subscription for provisioning information to help with initial
3846 * configuration. This subscription is a one-time query (denoted by the Expires header,
3847 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3848 * configuration, meeting policies, and policy settings that Communicator must enforce.
3849 * TODO: for what we need this information.
3852 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3854 gchar *to = sip_uri_self(sip);
3855 gchar *tmp = get_contact(sip);
3856 gchar *hdr = g_strdup_printf(
3857 "Event: vnd-microsoft-provisioning-v2\r\n"
3858 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3859 "Supported: com.microsoft.autoextend\r\n"
3860 "Supported: ms-benotify\r\n"
3861 "Proxy-Require: ms-benotify\r\n"
3862 "Supported: ms-piggyback-first-notify\r\n"
3863 "Expires: 0\r\n"
3864 "Contact: %s\r\n"
3865 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3866 gchar *body = g_strdup(
3867 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3868 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3869 "<provisioningGroup name=\"ucPolicy\"/>"
3870 "</provisioningGroupList>");
3872 g_free(tmp);
3873 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3874 g_free(body);
3875 g_free(to);
3876 g_free(hdr);
3879 static void
3880 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3881 gpointer value, gpointer user_data)
3883 struct sip_subscription *subscription = value;
3884 struct sip_dialog *dialog = &subscription->dialog;
3885 struct sipe_account_data *sip = user_data;
3886 gchar *tmp = get_contact(sip);
3887 gchar *hdr = g_strdup_printf(
3888 "Event: %s\r\n"
3889 "Expires: 0\r\n"
3890 "Contact: %s\r\n", subscription->event, tmp);
3891 g_free(tmp);
3893 /* Rate limit to max. 25 requests per seconds */
3894 g_usleep(1000000 / 25);
3896 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3897 g_free(hdr);
3900 /* IM Session (INVITE and MESSAGE methods) */
3902 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3903 static gchar *
3904 get_end_points (struct sipe_account_data *sip,
3905 struct sip_session *session)
3907 gchar *res;
3909 if (session == NULL) {
3910 return NULL;
3913 res = g_strdup_printf("<sip:%s>", sip->username);
3915 SIPE_DIALOG_FOREACH {
3916 gchar *tmp = res;
3917 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3918 g_free(tmp);
3920 if (dialog->theirepid) {
3921 tmp = res;
3922 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3923 g_free(tmp);
3925 } SIPE_DIALOG_FOREACH_END;
3927 return res;
3930 static gboolean
3931 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3932 struct sipmsg *msg,
3933 SIPE_UNUSED_PARAMETER struct transaction *trans)
3935 gboolean ret = TRUE;
3937 if (msg->response != 200) {
3938 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3939 return FALSE;
3942 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3944 return ret;
3948 * Asks UA/proxy about its capabilities.
3950 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3952 gchar *to = sip_uri(who);
3953 gchar *contact = get_contact(sip);
3954 gchar *request = g_strdup_printf(
3955 "Accept: application/sdp\r\n"
3956 "Contact: %s\r\n", contact);
3957 g_free(contact);
3959 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3961 g_free(to);
3962 g_free(request);
3965 static void
3966 sipe_notify_user(struct sipe_account_data *sip,
3967 struct sip_session *session,
3968 PurpleMessageFlags flags,
3969 const gchar *message)
3971 PurpleConversation *conv;
3973 if (!session->conv) {
3974 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3975 } else {
3976 conv = session->conv;
3978 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3981 void
3982 sipe_present_info(struct sipe_account_data *sip,
3983 struct sip_session *session,
3984 const gchar *message)
3986 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3989 static void
3990 sipe_present_err(struct sipe_account_data *sip,
3991 struct sip_session *session,
3992 const gchar *message)
3994 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3997 void
3998 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3999 struct sip_session *session,
4000 int sip_error,
4001 int sip_warning,
4002 const gchar *who,
4003 const gchar *message)
4005 char *msg, *msg_tmp, *msg_tmp2;
4006 const char *label;
4008 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
4009 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4010 g_free(msg_tmp);
4011 /* Service unavailable; Server Internal Error; Server Time-out */
4012 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4013 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4014 g_free(msg);
4015 msg = NULL;
4016 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4017 label = _("This message was not delivered to %s because the service is not available");
4018 } else if (sip_error == 486) { /* Busy Here */
4019 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4020 } else if (sip_error == 415) { /* Unsupported media type */
4021 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4022 } else {
4023 label = _("This message was not delivered to %s because one or more recipients are offline");
4026 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4027 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4028 msg ? ":" : "",
4029 msg ? msg : "");
4030 sipe_present_err(sip, session, msg_tmp);
4031 g_free(msg_tmp2);
4032 g_free(msg_tmp);
4033 g_free(msg);
4037 static gboolean
4038 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4039 SIPE_UNUSED_PARAMETER struct transaction *trans)
4041 gboolean ret = TRUE;
4042 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4043 struct sip_session *session = sipe_session_find_im(sip, with);
4044 struct sip_dialog *dialog;
4045 gchar *cseq;
4046 char *key;
4047 struct queued_message *message;
4049 if (!session) {
4050 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
4051 g_free(with);
4052 return FALSE;
4055 dialog = sipe_dialog_find(session, with);
4056 if (!dialog) {
4057 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
4058 g_free(with);
4059 return FALSE;
4062 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4063 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4064 g_free(cseq);
4065 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4067 if (msg->response >= 400) {
4068 PurpleBuddy *pbuddy;
4069 const char *alias = with;
4070 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4071 int warning = -1;
4073 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
4075 if (warn_hdr) {
4076 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4077 if (parts[0]) {
4078 warning = atoi(parts[0]);
4080 g_strfreev(parts);
4083 /* cancel file transfer as rejected by server */
4084 if (msg->response == 606 && /* Not acceptable all. */
4085 warning == 309 && /* Message contents not allowed by policy */
4086 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4088 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4089 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4090 sipe_utils_nameval_free(parsed_body);
4093 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4094 alias = purple_buddy_get_alias(pbuddy);
4097 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4099 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4100 if (msg->response == 408 || /* Request timeout */
4101 msg->response == 480 || /* Temporarily Unavailable */
4102 msg->response == 481) { /* Call/Transaction Does Not Exist */
4103 purple_debug_info("sipe", "process_message_response: assuming dangling IM session, dropping it.\n");
4104 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4107 ret = FALSE;
4108 } else {
4109 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4110 if (message_id) {
4111 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4112 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4113 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4116 g_hash_table_remove(session->unconfirmed_messages, key);
4117 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4118 key, g_hash_table_size(session->unconfirmed_messages));
4121 g_free(key);
4122 g_free(with);
4124 if (ret) sipe_im_process_queue(sip, session);
4125 return ret;
4128 static gboolean
4129 sipe_is_election_finished(struct sip_session *session);
4131 static void
4132 sipe_election_result(struct sipe_account_data *sip,
4133 void *sess);
4135 static gboolean
4136 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4137 SIPE_UNUSED_PARAMETER struct transaction *trans)
4139 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4140 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4141 struct sip_dialog *dialog;
4142 struct sip_session *session;
4144 session = sipe_session_find_chat_by_callid(sip, callid);
4145 if (!session) {
4146 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4147 return FALSE;
4150 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4151 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4152 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4153 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4155 if (xn_request_rm_response) {
4156 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4157 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4159 dialog = sipe_dialog_find(session, with);
4160 if (!dialog) {
4161 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4162 xmlnode_free(xn_action);
4163 return FALSE;
4166 if (allow && !g_strcasecmp(allow, "true")) {
4167 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4168 dialog->election_vote = 1;
4169 } else if (allow && !g_strcasecmp(allow, "false")) {
4170 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4171 dialog->election_vote = -1;
4174 if (sipe_is_election_finished(session)) {
4175 sipe_election_result(sip, session);
4178 } else if (xn_set_rm_response) {
4181 xmlnode_free(xn_action);
4185 return TRUE;
4188 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4190 gchar *hdr;
4191 gchar *tmp;
4192 char *msgtext = NULL;
4193 const gchar *msgr = "";
4194 gchar *tmp2 = NULL;
4196 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4197 char *msgformat;
4198 gchar *msgr_value;
4200 sipe_parse_html(msg, &msgformat, &msgtext);
4201 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4203 msgr_value = sipmsg_get_msgr_string(msgformat);
4204 g_free(msgformat);
4205 if (msgr_value) {
4206 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4207 g_free(msgr_value);
4209 } else {
4210 msgtext = g_strdup(msg);
4213 tmp = get_contact(sip);
4214 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4215 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4216 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4217 if (content_type == NULL)
4218 content_type = "text/plain";
4220 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4221 g_free(tmp);
4222 g_free(tmp2);
4224 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4225 g_free(msgtext);
4226 g_free(hdr);
4230 void
4231 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4233 GSList *entry2 = session->outgoing_message_queue;
4234 while (entry2) {
4235 struct queued_message *msg = entry2->data;
4237 /* for multiparty chat or conference */
4238 if (session->is_multiparty || session->focus_uri) {
4239 gchar *who = sip_uri_self(sip);
4240 serv_got_chat_in(sip->gc, session->chat_id, who,
4241 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4242 g_free(who);
4245 SIPE_DIALOG_FOREACH {
4246 char *key;
4247 struct queued_message *message;
4249 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4251 message = g_new0(struct queued_message,1);
4252 message->body = g_strdup(msg->body);
4253 if (msg->content_type != NULL)
4254 message->content_type = g_strdup(msg->content_type);
4256 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4257 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4258 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4259 key, g_hash_table_size(session->unconfirmed_messages));
4260 g_free(key);
4262 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4263 } SIPE_DIALOG_FOREACH_END;
4265 entry2 = sipe_session_dequeue_message(session);
4269 static void
4270 sipe_refer_notify(struct sipe_account_data *sip,
4271 struct sip_session *session,
4272 const gchar *who,
4273 int status,
4274 const gchar *desc)
4276 gchar *hdr;
4277 gchar *body;
4278 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4280 hdr = g_strdup_printf(
4281 "Event: refer\r\n"
4282 "Subscription-State: %s\r\n"
4283 "Content-Type: message/sipfrag\r\n",
4284 status >= 200 ? "terminated" : "active");
4286 body = g_strdup_printf(
4287 "SIP/2.0 %d %s\r\n",
4288 status, desc);
4290 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4292 g_free(hdr);
4293 g_free(body);
4296 static gboolean
4297 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4299 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4300 struct sip_session *session;
4301 struct sip_dialog *dialog;
4302 char *cseq;
4303 char *key;
4304 struct queued_message *message;
4305 struct sipmsg *request_msg = trans->msg;
4307 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4308 gchar *referred_by;
4310 session = sipe_session_find_chat_by_callid(sip, callid);
4311 if (!session) {
4312 session = sipe_session_find_im(sip, with);
4314 if (!session) {
4315 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4316 g_free(with);
4317 return FALSE;
4320 dialog = sipe_dialog_find(session, with);
4321 if (!dialog) {
4322 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4323 g_free(with);
4324 return FALSE;
4327 sipe_dialog_parse(dialog, msg, TRUE);
4329 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4330 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4331 g_free(cseq);
4332 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4334 if (msg->response != 200) {
4335 PurpleBuddy *pbuddy;
4336 const char *alias = with;
4337 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4338 int warning = -1;
4340 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4342 if (warn_hdr) {
4343 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4344 if (parts[0]) {
4345 warning = atoi(parts[0]);
4347 g_strfreev(parts);
4350 /* cancel file transfer as rejected by server */
4351 if (msg->response == 606 && /* Not acceptable all. */
4352 warning == 309 && /* Message contents not allowed by policy */
4353 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4355 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4356 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4357 sipe_utils_nameval_free(parsed_body);
4360 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4361 alias = purple_buddy_get_alias(pbuddy);
4364 if (message) {
4365 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4366 } else {
4367 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4368 sipe_present_err(sip, session, tmp_msg);
4369 g_free(tmp_msg);
4372 sipe_dialog_remove(session, with);
4374 g_free(key);
4375 g_free(with);
4376 return FALSE;
4379 dialog->cseq = 0;
4380 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4381 dialog->outgoing_invite = NULL;
4382 dialog->is_established = TRUE;
4384 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4385 if (referred_by) {
4386 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4387 g_free(referred_by);
4390 /* add user to chat if it is a multiparty session */
4391 if (session->is_multiparty) {
4392 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4393 with, NULL,
4394 PURPLE_CBFLAGS_NONE, TRUE);
4397 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4398 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4399 sipe_session_dequeue_message(session);
4402 sipe_im_process_queue(sip, session);
4404 g_hash_table_remove(session->unconfirmed_messages, key);
4405 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4406 key, g_hash_table_size(session->unconfirmed_messages));
4408 g_free(key);
4409 g_free(with);
4410 return TRUE;
4414 void
4415 sipe_invite(struct sipe_account_data *sip,
4416 struct sip_session *session,
4417 const gchar *who,
4418 const gchar *msg_body,
4419 const gchar *msg_content_type,
4420 const gchar *referred_by,
4421 const gboolean is_triggered)
4423 gchar *hdr;
4424 gchar *to;
4425 gchar *contact;
4426 gchar *body;
4427 gchar *self;
4428 char *ms_text_format = NULL;
4429 gchar *roster_manager;
4430 gchar *end_points;
4431 gchar *referred_by_str;
4432 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4434 if (dialog && dialog->is_established) {
4435 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4436 return;
4439 if (!dialog) {
4440 dialog = sipe_dialog_add(session);
4441 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4442 dialog->with = g_strdup(who);
4445 if (!(dialog->ourtag)) {
4446 dialog->ourtag = gentag();
4449 to = sip_uri(who);
4451 if (msg_body) {
4452 char *msgtext = NULL;
4453 char *base64_msg;
4454 const gchar *msgr = "";
4455 char *key;
4456 struct queued_message *message;
4457 gchar *tmp = NULL;
4459 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4460 char *msgformat;
4461 gchar *msgr_value;
4463 sipe_parse_html(msg_body, &msgformat, &msgtext);
4464 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4466 msgr_value = sipmsg_get_msgr_string(msgformat);
4467 g_free(msgformat);
4468 if (msgr_value) {
4469 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4470 g_free(msgr_value);
4472 } else {
4473 msgtext = g_strdup(msg_body);
4476 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4477 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4478 msg_content_type ? msg_content_type : "text/plain",
4479 msgr,
4480 base64_msg);
4481 g_free(msgtext);
4482 g_free(tmp);
4483 g_free(base64_msg);
4485 message = g_new0(struct queued_message,1);
4486 message->body = g_strdup(msg_body);
4487 if (msg_content_type != NULL)
4488 message->content_type = g_strdup(msg_content_type);
4490 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4491 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4492 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4493 key, g_hash_table_size(session->unconfirmed_messages));
4494 g_free(key);
4497 contact = get_contact(sip);
4498 end_points = get_end_points(sip, session);
4499 self = sip_uri_self(sip);
4500 roster_manager = g_strdup_printf(
4501 "Roster-Manager: %s\r\n"
4502 "EndPoints: %s\r\n",
4503 self,
4504 end_points);
4505 referred_by_str = referred_by ?
4506 g_strdup_printf(
4507 "Referred-By: %s\r\n",
4508 referred_by)
4509 : g_strdup("");
4510 hdr = g_strdup_printf(
4511 "Supported: ms-sender\r\n"
4512 "%s"
4513 "%s"
4514 "%s"
4515 "%s"
4516 "Contact: %s\r\n%s"
4517 "Content-Type: application/sdp\r\n",
4518 sipe_strequal(session->roster_manager, self) ? roster_manager : "",
4519 referred_by_str,
4520 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4521 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4522 contact,
4523 ms_text_format ? ms_text_format : "");
4524 g_free(ms_text_format);
4525 g_free(self);
4527 body = g_strdup_printf(
4528 "v=0\r\n"
4529 "o=- 0 0 IN IP4 %s\r\n"
4530 "s=session\r\n"
4531 "c=IN IP4 %s\r\n"
4532 "t=0 0\r\n"
4533 "m=%s %d sip null\r\n"
4534 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4535 purple_network_get_my_ip(-1),
4536 purple_network_get_my_ip(-1),
4537 sip->ocs2007 ? "message" : "x-ms-message",
4538 sip->realport);
4540 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4541 to, to, hdr, body, dialog, process_invite_response);
4543 g_free(to);
4544 g_free(roster_manager);
4545 g_free(end_points);
4546 g_free(referred_by_str);
4547 g_free(body);
4548 g_free(hdr);
4549 g_free(contact);
4552 static void
4553 sipe_refer(struct sipe_account_data *sip,
4554 struct sip_session *session,
4555 const gchar *who)
4557 gchar *hdr;
4558 gchar *contact;
4559 gchar *epid = get_epid(sip);
4560 struct sip_dialog *dialog = sipe_dialog_find(session,
4561 session->roster_manager);
4562 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4564 contact = get_contact(sip);
4565 hdr = g_strdup_printf(
4566 "Contact: %s\r\n"
4567 "Refer-to: <%s>\r\n"
4568 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4569 "Require: com.microsoft.rtc-multiparty\r\n",
4570 contact,
4571 who,
4572 sip->username,
4573 ourtag ? ";tag=" : "",
4574 ourtag ? ourtag : "",
4575 epid);
4576 g_free(epid);
4578 send_sip_request(sip->gc, "REFER",
4579 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4581 g_free(hdr);
4582 g_free(contact);
4585 static void
4586 sipe_send_election_request_rm(struct sipe_account_data *sip,
4587 struct sip_dialog *dialog,
4588 int bid)
4590 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4592 gchar *body = g_strdup_printf(
4593 "<?xml version=\"1.0\"?>\r\n"
4594 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4595 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4596 sip->username, bid);
4598 send_sip_request(sip->gc, "INFO",
4599 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4601 g_free(body);
4604 static void
4605 sipe_send_election_set_rm(struct sipe_account_data *sip,
4606 struct sip_dialog *dialog)
4608 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4610 gchar *body = g_strdup_printf(
4611 "<?xml version=\"1.0\"?>\r\n"
4612 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4613 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4614 sip->username);
4616 send_sip_request(sip->gc, "INFO",
4617 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4619 g_free(body);
4622 static void
4623 sipe_session_close(struct sipe_account_data *sip,
4624 struct sip_session * session)
4626 if (session && session->focus_uri) {
4627 sipe_conf_immcu_closed(sip, session);
4628 conf_session_close(sip, session);
4631 if (session) {
4632 SIPE_DIALOG_FOREACH {
4633 /* @TODO slow down BYE message sending rate */
4634 /* @see single subscription code */
4635 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4636 } SIPE_DIALOG_FOREACH_END;
4638 sipe_session_remove(sip, session);
4642 static void
4643 sipe_session_close_all(struct sipe_account_data *sip)
4645 GSList *entry;
4646 while ((entry = sip->sessions) != NULL) {
4647 sipe_session_close(sip, entry->data);
4651 static void
4652 sipe_convo_closed(PurpleConnection * gc, const char *who)
4654 struct sipe_account_data *sip = gc->proto_data;
4656 purple_debug_info("sipe", "conversation with %s closed\n", who);
4657 sipe_session_close(sip, sipe_session_find_im(sip, who));
4660 static void
4661 sipe_chat_leave (PurpleConnection *gc, int id)
4663 struct sipe_account_data *sip = gc->proto_data;
4664 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4666 sipe_session_close(sip, session);
4669 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4670 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4672 struct sipe_account_data *sip = gc->proto_data;
4673 struct sip_session *session;
4674 struct sip_dialog *dialog;
4675 gchar *uri = sip_uri(who);
4677 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4679 session = sipe_session_find_or_add_im(sip, uri);
4680 dialog = sipe_dialog_find(session, uri);
4682 // Queue the message
4683 sipe_session_enqueue_message(session, what, NULL);
4685 if (dialog && !dialog->outgoing_invite) {
4686 sipe_im_process_queue(sip, session);
4687 } else if (!dialog || !dialog->outgoing_invite) {
4688 // Need to send the INVITE to get the outgoing dialog setup
4689 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4692 g_free(uri);
4693 return 1;
4696 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4697 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4699 struct sipe_account_data *sip = gc->proto_data;
4700 struct sip_session *session;
4702 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4704 session = sipe_session_find_chat_by_id(sip, id);
4706 // Queue the message
4707 if (session && session->dialogs) {
4708 sipe_session_enqueue_message(session,what,NULL);
4709 sipe_im_process_queue(sip, session);
4710 } else if (sip) {
4711 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4712 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4714 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4715 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4717 if (sip->ocs2007) {
4718 struct sip_session *session = sipe_session_add_chat(sip);
4720 session->is_multiparty = FALSE;
4721 session->focus_uri = g_strdup(proto_chat_id);
4722 sipe_session_enqueue_message(session, what, NULL);
4723 sipe_invite_conf_focus(sip, session);
4727 return 1;
4730 /* End IM Session (INVITE and MESSAGE methods) */
4732 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4734 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4735 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4736 gchar *from;
4737 struct sip_session *session;
4739 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4741 /* Call Control protocol */
4742 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4744 process_incoming_info_csta(sip, msg);
4745 return;
4748 from = parse_from(sipmsg_find_header(msg, "From"));
4749 session = sipe_session_find_chat_by_callid(sip, callid);
4750 if (!session) {
4751 session = sipe_session_find_im(sip, from);
4753 if (!session) {
4754 g_free(from);
4755 return;
4758 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4760 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4761 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4762 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4764 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4766 if (xn_request_rm) {
4767 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4768 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4769 gchar *body = g_strdup_printf(
4770 "<?xml version=\"1.0\"?>\r\n"
4771 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4772 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4773 sip->username,
4774 session->bid < bid ? "true" : "false");
4775 send_sip_response(sip->gc, msg, 200, "OK", body);
4776 g_free(body);
4777 } else if (xn_set_rm) {
4778 gchar *body;
4779 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4780 g_free(session->roster_manager);
4781 session->roster_manager = g_strdup(rm);
4783 body = g_strdup_printf(
4784 "<?xml version=\"1.0\"?>\r\n"
4785 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4786 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4787 sip->username);
4788 send_sip_response(sip->gc, msg, 200, "OK", body);
4789 g_free(body);
4791 xmlnode_free(xn_action);
4794 else
4796 /* looks like purple lacks typing notification for chat */
4797 if (!session->is_multiparty && !session->focus_uri) {
4798 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4799 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4800 "status");
4801 if (sipe_strequal(status, "type")) {
4802 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4803 } else if (sipe_strequal(status, "idle")) {
4804 serv_got_typing_stopped(sip->gc, from);
4806 xmlnode_free(xn_keyboard_activity);
4809 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4811 g_free(from);
4814 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4816 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4817 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4818 struct sip_session *session;
4819 struct sip_dialog *dialog;
4821 /* collect dialog identification
4822 * we need callid, ourtag and theirtag to unambiguously identify dialog
4824 /* take data before 'msg' will be modified by send_sip_response */
4825 dialog = g_new0(struct sip_dialog, 1);
4826 dialog->callid = g_strdup(callid);
4827 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4828 dialog->with = g_strdup(from);
4829 sipe_dialog_parse(dialog, msg, FALSE);
4831 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4833 session = sipe_session_find_chat_by_callid(sip, callid);
4834 if (!session) {
4835 session = sipe_session_find_im(sip, from);
4837 if (!session) {
4838 sipe_dialog_free(dialog);
4839 g_free(from);
4840 return;
4843 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4844 g_free(session->roster_manager);
4845 session->roster_manager = NULL;
4848 /* This what BYE is essentially for - terminating dialog */
4849 sipe_dialog_remove_3(session, dialog);
4850 sipe_dialog_free(dialog);
4851 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4852 sipe_conf_immcu_closed(sip, session);
4853 } else if (session->is_multiparty) {
4854 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4857 g_free(from);
4860 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4862 gchar *self = sip_uri_self(sip);
4863 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4864 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4865 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4866 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4867 struct sip_session *session;
4868 struct sip_dialog *dialog;
4870 session = sipe_session_find_chat_by_callid(sip, callid);
4871 dialog = sipe_dialog_find(session, from);
4873 if (!session || !dialog || !session->roster_manager || !sipe_strequal(session->roster_manager, self)) {
4874 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4875 } else {
4876 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4878 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4881 g_free(self);
4882 g_free(from);
4883 g_free(refer_to);
4884 g_free(referred_by);
4887 static unsigned int
4888 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4890 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4891 struct sip_session *session;
4892 struct sip_dialog *dialog;
4894 if (state == PURPLE_NOT_TYPING)
4895 return 0;
4897 session = sipe_session_find_im(sip, who);
4898 dialog = sipe_dialog_find(session, who);
4900 if (session && dialog && dialog->is_established) {
4901 send_sip_request(gc, "INFO", who, who,
4902 "Content-Type: application/xml\r\n",
4903 SIPE_SEND_TYPING, dialog, NULL);
4905 return SIPE_TYPING_SEND_TIMEOUT;
4908 static gboolean resend_timeout(struct sipe_account_data *sip)
4910 GSList *tmp = sip->transactions;
4911 time_t currtime = time(NULL);
4912 while (tmp) {
4913 struct transaction *trans = tmp->data;
4914 tmp = tmp->next;
4915 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4916 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4917 /* TODO 408 */
4918 } else {
4919 if ((currtime - trans->time > 2) && trans->retries == 0) {
4920 trans->retries++;
4921 sendout_sipmsg(sip, trans->msg);
4925 return TRUE;
4928 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4929 SIPE_UNUSED_PARAMETER void *unused)
4931 /* register again when security token expires */
4932 /* we have to start a new authentication as the security token
4933 * is almost expired by sending a not signed REGISTER message */
4934 purple_debug_info("sipe", "do a full reauthentication\n");
4935 sipe_auth_free(&sip->registrar);
4936 sipe_auth_free(&sip->proxy);
4937 sip->registerstatus = 0;
4938 do_register(sip);
4939 sip->reauthenticate_set = FALSE;
4942 static gboolean
4943 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4944 struct sipmsg *msg,
4945 GSList *parsed_body)
4947 gboolean found = FALSE;
4949 if (parsed_body) {
4950 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4952 if (sipe_strequal(invitation_command, "INVITE")) {
4953 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4954 found = TRUE;
4955 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4956 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4957 found = TRUE;
4958 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4959 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4960 found = TRUE;
4963 return found;
4966 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4968 gchar *from;
4969 const gchar *contenttype;
4970 gboolean found = FALSE;
4972 from = parse_from(sipmsg_find_header(msg, "From"));
4974 if (!from) return;
4976 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4978 contenttype = sipmsg_find_header(msg, "Content-Type");
4979 if (g_str_has_prefix(contenttype, "text/plain")
4980 || g_str_has_prefix(contenttype, "text/html")
4981 || g_str_has_prefix(contenttype, "multipart/related")
4982 || g_str_has_prefix(contenttype, "multipart/alternative"))
4984 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4985 gchar *html = get_html_message(contenttype, msg->body);
4987 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4988 if (!session) {
4989 session = sipe_session_find_im(sip, from);
4992 if (session && session->focus_uri) { /* a conference */
4993 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4994 gchar *sender = parse_from(tmp);
4995 g_free(tmp);
4996 serv_got_chat_in(sip->gc, session->chat_id, sender,
4997 PURPLE_MESSAGE_RECV, html, time(NULL));
4998 g_free(sender);
4999 } else if (session && session->is_multiparty) { /* a multiparty chat */
5000 serv_got_chat_in(sip->gc, session->chat_id, from,
5001 PURPLE_MESSAGE_RECV, html, time(NULL));
5002 } else {
5003 serv_got_im(sip->gc, from, html, 0, time(NULL));
5005 g_free(html);
5006 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5007 found = TRUE;
5009 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5010 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5011 xmlnode *state;
5012 gchar *statedata;
5014 if (!isc) {
5015 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
5016 g_free(from);
5017 return;
5020 state = xmlnode_get_child(isc, "state");
5022 if (!state) {
5023 purple_debug_info("sipe", "process_incoming_message: no state found\n");
5024 xmlnode_free(isc);
5025 g_free(from);
5026 return;
5029 statedata = xmlnode_get_data(state);
5030 if (statedata) {
5031 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5032 else serv_got_typing_stopped(sip->gc, from);
5034 g_free(statedata);
5036 xmlnode_free(isc);
5037 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5038 found = TRUE;
5039 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5040 GSList *body = sipe_ft_parse_msg_body(msg->body);
5041 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5042 sipe_utils_nameval_free(body);
5043 if (found) {
5044 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5047 if (!found) {
5048 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5049 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5050 if (!session) {
5051 session = sipe_session_find_im(sip, from);
5053 if (session) {
5054 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5055 from);
5056 sipe_present_err(sip, session, errmsg);
5057 g_free(errmsg);
5060 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
5061 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5063 g_free(from);
5066 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5068 gchar *body;
5069 gchar *newTag;
5070 const gchar *oldHeader;
5071 gchar *newHeader;
5072 gboolean is_multiparty = FALSE;
5073 gboolean is_triggered = FALSE;
5074 gboolean was_multiparty = TRUE;
5075 gboolean just_joined = FALSE;
5076 gchar *from;
5077 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5078 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5079 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5080 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5081 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5082 GSList *end_points = NULL;
5083 char *tmp = NULL;
5084 struct sip_session *session;
5085 const gchar *ms_text_format;
5087 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
5088 g_free(tmp);
5090 /* Invitation to join conference */
5091 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5092 process_incoming_invite_conf(sip, msg);
5093 return;
5096 /* Only accept text invitations */
5097 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5098 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5099 return;
5102 // TODO There *must* be a better way to clean up the To header to add a tag...
5103 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
5104 oldHeader = sipmsg_find_header(msg, "To");
5105 newTag = gentag();
5106 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5107 sipmsg_remove_header_now(msg, "To");
5108 sipmsg_add_header_now(msg, "To", newHeader);
5109 g_free(newHeader);
5111 if (end_points_hdr) {
5112 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5114 if (g_slist_length(end_points) > 2) {
5115 is_multiparty = TRUE;
5118 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5119 is_triggered = TRUE;
5120 is_multiparty = TRUE;
5123 session = sipe_session_find_chat_by_callid(sip, callid);
5124 /* Convert to multiparty */
5125 if (session && is_multiparty && !session->is_multiparty) {
5126 g_free(session->with);
5127 session->with = NULL;
5128 was_multiparty = FALSE;
5129 session->is_multiparty = TRUE;
5130 session->chat_id = rand();
5133 if (!session && is_multiparty) {
5134 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5136 /* IM session */
5137 from = parse_from(sipmsg_find_header(msg, "From"));
5138 if (!session) {
5139 session = sipe_session_find_or_add_im(sip, from);
5142 if (session) {
5143 g_free(session->callid);
5144 session->callid = g_strdup(callid);
5146 session->is_multiparty = is_multiparty;
5147 if (roster_manager) {
5148 session->roster_manager = g_strdup(roster_manager);
5152 if (is_multiparty && end_points) {
5153 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5154 GSList *entry = end_points;
5155 while (entry) {
5156 struct sip_dialog *dialog;
5157 struct sipendpoint *end_point = entry->data;
5158 entry = entry->next;
5160 if (!g_strcasecmp(from, end_point->contact) ||
5161 !g_strcasecmp(to, end_point->contact))
5162 continue;
5164 dialog = sipe_dialog_find(session, end_point->contact);
5165 if (dialog) {
5166 g_free(dialog->theirepid);
5167 dialog->theirepid = end_point->epid;
5168 end_point->epid = NULL;
5169 } else {
5170 dialog = sipe_dialog_add(session);
5172 dialog->callid = g_strdup(session->callid);
5173 dialog->with = end_point->contact;
5174 end_point->contact = NULL;
5175 dialog->theirepid = end_point->epid;
5176 end_point->epid = NULL;
5178 just_joined = TRUE;
5180 /* send triggered INVITE */
5181 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5184 g_free(to);
5187 if (end_points) {
5188 GSList *entry = end_points;
5189 while (entry) {
5190 struct sipendpoint *end_point = entry->data;
5191 entry = entry->next;
5192 g_free(end_point->contact);
5193 g_free(end_point->epid);
5194 g_free(end_point);
5196 g_slist_free(end_points);
5199 if (session) {
5200 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5201 if (dialog) {
5202 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5203 sipe_dialog_parse_routes(dialog, msg, FALSE);
5204 } else {
5205 dialog = sipe_dialog_add(session);
5207 dialog->callid = g_strdup(session->callid);
5208 dialog->with = g_strdup(from);
5209 sipe_dialog_parse(dialog, msg, FALSE);
5211 if (!dialog->ourtag) {
5212 dialog->ourtag = newTag;
5213 newTag = NULL;
5216 just_joined = TRUE;
5218 } else {
5219 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5221 g_free(newTag);
5223 if (is_multiparty && !session->conv) {
5224 gchar *chat_title = sipe_chat_get_name(callid);
5225 gchar *self = sip_uri_self(sip);
5226 /* create prpl chat */
5227 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5228 session->chat_title = g_strdup(chat_title);
5229 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5230 /* add self */
5231 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5232 self, NULL,
5233 PURPLE_CBFLAGS_NONE, FALSE);
5234 g_free(chat_title);
5235 g_free(self);
5238 if (is_multiparty && !was_multiparty) {
5239 /* add current IM counterparty to chat */
5240 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5241 sipe_dialog_first(session)->with, NULL,
5242 PURPLE_CBFLAGS_NONE, FALSE);
5245 /* add inviting party to chat */
5246 if (just_joined && session->conv) {
5247 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5248 from, NULL,
5249 PURPLE_CBFLAGS_NONE, TRUE);
5252 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5254 /* This used only in 2005 official client, not 2007 or Reuters.
5255 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5256 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5258 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5259 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5260 if (is_multiparty ||
5261 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5263 if (ms_text_format) {
5264 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5266 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5267 if (tmp) {
5268 gchar *body = (gchar *) purple_base64_decode(tmp, NULL);
5270 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5272 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5273 sipe_utils_nameval_free(parsed_body);
5274 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5276 g_free(tmp);
5278 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5280 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5281 gchar *html = get_html_message(ms_text_format, NULL);
5282 if (html) {
5283 if (is_multiparty) {
5284 serv_got_chat_in(sip->gc, session->chat_id, from,
5285 PURPLE_MESSAGE_RECV, html, time(NULL));
5286 } else {
5287 serv_got_im(sip->gc, from, html, 0, time(NULL));
5289 g_free(html);
5290 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5296 g_free(from);
5298 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5299 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5300 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5302 body = g_strdup_printf(
5303 "v=0\r\n"
5304 "o=- 0 0 IN IP4 %s\r\n"
5305 "s=session\r\n"
5306 "c=IN IP4 %s\r\n"
5307 "t=0 0\r\n"
5308 "m=%s %d sip sip:%s\r\n"
5309 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5310 purple_network_get_my_ip(-1),
5311 purple_network_get_my_ip(-1),
5312 sip->ocs2007 ? "message" : "x-ms-message",
5313 sip->realport,
5314 sip->username);
5315 send_sip_response(sip->gc, msg, 200, "OK", body);
5316 g_free(body);
5319 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5321 gchar *body;
5323 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5324 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5325 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5327 body = g_strdup_printf(
5328 "v=0\r\n"
5329 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5330 "s=session\r\n"
5331 "c=IN IP4 0.0.0.0\r\n"
5332 "t=0 0\r\n"
5333 "m=%s %d sip sip:%s\r\n"
5334 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5335 sip->ocs2007 ? "message" : "x-ms-message",
5336 sip->realport,
5337 sip->username);
5338 send_sip_response(sip->gc, msg, 200, "OK", body);
5339 g_free(body);
5342 static const char*
5343 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5345 const char *res = "NTLM";
5346 #ifdef USE_KERBEROS
5347 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5348 res = "Kerberos";
5350 #else
5351 (void) sip; /* make compiler happy */
5352 #endif
5353 return res;
5356 static void sipe_connection_cleanup(struct sipe_account_data *);
5357 static void create_connection(struct sipe_account_data *, gchar *, int);
5359 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5360 SIPE_UNUSED_PARAMETER struct transaction *trans)
5362 gchar *tmp;
5363 const gchar *expires_header;
5364 int expires, i;
5365 GSList *hdr = msg->headers;
5366 struct sipnameval *elem;
5368 expires_header = sipmsg_find_header(msg, "Expires");
5369 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5370 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5372 switch (msg->response) {
5373 case 200:
5374 if (expires == 0) {
5375 sip->registerstatus = 0;
5376 } else {
5377 const gchar *contact_hdr;
5378 gchar *gruu = NULL;
5379 gchar *epid;
5380 gchar *uuid;
5381 gchar *timeout;
5382 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5383 const char *auth_scheme;
5385 if (!sip->reregister_set) {
5386 gchar *action_name = g_strdup_printf("<%s>", "registration");
5387 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5388 g_free(action_name);
5389 sip->reregister_set = TRUE;
5392 sip->registerstatus = 3;
5394 if (server_hdr && !sip->server_version) {
5395 sip->server_version = g_strdup(server_hdr);
5396 g_free(default_ua);
5397 default_ua = NULL;
5400 auth_scheme = sipe_get_auth_scheme_name(sip);
5401 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5403 if (tmp) {
5404 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5405 fill_auth(tmp, &sip->registrar);
5408 if (!sip->reauthenticate_set) {
5409 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5410 guint reauth_timeout;
5411 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5412 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5413 reauth_timeout = sip->registrar.expires - 300;
5414 } else {
5415 /* NTLM: we have to reauthenticate as our security token expires
5416 after eight hours (be five minutes early) */
5417 reauth_timeout = (8 * 3600) - 300;
5419 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5420 g_free(action_name);
5421 sip->reauthenticate_set = TRUE;
5424 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5426 epid = get_epid(sip);
5427 uuid = generateUUIDfromEPID(epid);
5428 g_free(epid);
5430 // There can be multiple Contact headers (one per location where the user is logged in) so
5431 // make sure to only get the one for this uuid
5432 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5433 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5434 if (valid_contact) {
5435 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5436 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5437 g_free(valid_contact);
5438 break;
5439 } else {
5440 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5443 g_free(uuid);
5445 g_free(sip->contact);
5446 if(gruu) {
5447 sip->contact = g_strdup_printf("<%s>", gruu);
5448 g_free(gruu);
5449 } else {
5450 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5451 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);
5453 sip->ocs2007 = FALSE;
5454 sip->batched_support = FALSE;
5456 while(hdr)
5458 elem = hdr->data;
5459 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5460 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5461 /* We interpret this as OCS2007+ indicator */
5462 sip->ocs2007 = TRUE;
5463 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5465 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5466 sip->batched_support = TRUE;
5467 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5470 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5471 gchar **caps = g_strsplit(elem->value,",",0);
5472 i = 0;
5473 while (caps[i]) {
5474 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5475 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5476 i++;
5478 g_strfreev(caps);
5480 hdr = g_slist_next(hdr);
5483 /* rejoin open chats to be able to use them by continue to send messages */
5484 purple_conversation_foreach(sipe_rejoin_chat);
5486 /* subscriptions */
5487 if (!sip->subscribed) { //do it just once, not every re-register
5489 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5490 (GCompareFunc)g_ascii_strcasecmp)) {
5491 sipe_subscribe_roaming_contacts(sip);
5494 /* For 2007+ it does not make sence to subscribe to:
5495 * vnd-microsoft-roaming-ACL
5496 * vnd-microsoft-provisioning (not v2)
5497 * presence.wpending
5498 * These are for backward compatibility.
5500 if (sip->ocs2007)
5502 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5503 (GCompareFunc)g_ascii_strcasecmp)) {
5504 sipe_subscribe_roaming_self(sip);
5506 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5507 (GCompareFunc)g_ascii_strcasecmp)) {
5508 sipe_subscribe_roaming_provisioning_v2(sip);
5511 /* For 2005- servers */
5512 else
5514 //sipe_options_request(sip, sip->sipdomain);
5516 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5517 (GCompareFunc)g_ascii_strcasecmp)) {
5518 sipe_subscribe_roaming_acl(sip);
5520 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5521 (GCompareFunc)g_ascii_strcasecmp)) {
5522 sipe_subscribe_roaming_provisioning(sip);
5524 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5525 (GCompareFunc)g_ascii_strcasecmp)) {
5526 sipe_subscribe_presence_wpending(sip, msg);
5529 /* For 2007+ we publish our initial statuses and calendar data only after
5530 * received our existing publications in sipe_process_roaming_self()
5531 * Only in this case we know versions of current publications made
5532 * on our behalf.
5534 /* For 2005- we publish our initial statuses only after
5535 * received our existing UserInfo data in response to
5536 * self subscription.
5537 * Only in this case we won't override existing UserInfo data
5538 * set earlier or by other client on our behalf.
5542 sip->subscribed = TRUE;
5545 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5546 "timeout=", ";", NULL);
5547 if (timeout != NULL) {
5548 sscanf(timeout, "%u", &sip->keepalive_timeout);
5549 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5550 sip->keepalive_timeout);
5551 g_free(timeout);
5554 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5556 break;
5557 case 301:
5559 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5561 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5562 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5563 gchar **tmp;
5564 gchar *hostname;
5565 int port = 0;
5566 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5567 int i = 1;
5569 tmp = g_strsplit(parts[0], ":", 0);
5570 hostname = g_strdup(tmp[0]);
5571 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5572 g_strfreev(tmp);
5574 while (parts[i]) {
5575 tmp = g_strsplit(parts[i], "=", 0);
5576 if (tmp[1]) {
5577 if (g_strcasecmp("transport", tmp[0]) == 0) {
5578 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5579 transport = SIPE_TRANSPORT_TCP;
5580 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5581 transport = SIPE_TRANSPORT_UDP;
5585 g_strfreev(tmp);
5586 i++;
5588 g_strfreev(parts);
5590 /* Close old connection */
5591 sipe_connection_cleanup(sip);
5593 /* Create new connection */
5594 sip->transport = transport;
5595 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5596 hostname, port, TRANSPORT_DESCRIPTOR);
5597 create_connection(sip, hostname, port);
5599 g_free(redirect);
5601 break;
5602 case 401:
5603 if (sip->registerstatus != 2) {
5604 const char *auth_scheme;
5605 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5606 if (sip->registrar.retries > 3) {
5607 sip->gc->wants_to_die = TRUE;
5608 purple_connection_error(sip->gc, _("Authentication failed"));
5609 return TRUE;
5612 auth_scheme = sipe_get_auth_scheme_name(sip);
5613 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5615 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5616 if (!tmp) {
5617 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5618 sip->gc->wants_to_die = TRUE;
5619 purple_connection_error(sip->gc, tmp2);
5620 g_free(tmp2);
5621 return TRUE;
5623 fill_auth(tmp, &sip->registrar);
5624 sip->registerstatus = 2;
5625 if (sip->account->disconnecting) {
5626 do_register_exp(sip, 0);
5627 } else {
5628 do_register(sip);
5631 break;
5632 case 403:
5634 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5635 gchar **reason = NULL;
5636 gchar *warning;
5637 if (diagnostics != NULL) {
5638 /* Example header:
5639 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5641 reason = g_strsplit(diagnostics, "\"", 0);
5643 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5644 (reason && reason[1]) ? reason[1] : _("no reason given"));
5645 g_strfreev(reason);
5647 sip->gc->wants_to_die = TRUE;
5648 purple_connection_error(sip->gc, warning);
5649 g_free(warning);
5650 return TRUE;
5652 break;
5653 case 404:
5655 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5656 gchar *reason = NULL;
5657 gchar *warning;
5658 if (diagnostics != NULL) {
5659 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5661 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5662 diagnostics ? (reason ? reason : _("no reason given")) :
5663 _("SIP is either not enabled for the destination URI or it does not exist"));
5664 g_free(reason);
5666 sip->gc->wants_to_die = TRUE;
5667 purple_connection_error(sip->gc, warning);
5668 g_free(warning);
5669 return TRUE;
5671 break;
5672 case 503:
5673 case 504: /* Server time-out */
5675 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5676 gchar *reason = NULL;
5677 gchar *warning;
5678 if (diagnostics != NULL) {
5679 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5681 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5682 g_free(reason);
5684 sip->gc->wants_to_die = TRUE;
5685 purple_connection_error(sip->gc, warning);
5686 g_free(warning);
5687 return TRUE;
5689 break;
5691 return TRUE;
5695 * Returns 2005-style activity and Availability.
5697 * @param status Sipe statis id.
5699 static void
5700 sipe_get_act_avail_by_status_2005(const char *status,
5701 int *activity,
5702 int *availability)
5704 int avail = 300; /* online */
5705 int act = 400; /* Available */
5707 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5708 act = 100;
5709 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5710 // act = 150;
5711 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5712 act = 300;
5713 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5714 act = 400;
5715 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5716 // act = 500;
5717 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5718 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5719 act = 600;
5720 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5721 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5722 avail = 0; /* offline */
5723 act = 100;
5724 } else {
5725 act = 400; /* Available */
5728 if (activity) *activity = act;
5729 if (availability) *availability = avail;
5733 * [MS-SIP] 2.2.1
5735 * @param activity 2005 aggregated activity. Ex.: 600
5736 * @param availablity 2005 aggregated availablity. Ex.: 300
5738 static const char *
5739 sipe_get_status_by_act_avail_2005(const int activity,
5740 const int availablity,
5741 char **activity_desc)
5743 const char *status_id = NULL;
5744 const char *act = NULL;
5746 if (activity < 150) {
5747 status_id = SIPE_STATUS_ID_AWAY;
5748 } else if (activity < 200) {
5749 //status_id = SIPE_STATUS_ID_LUNCH;
5750 status_id = SIPE_STATUS_ID_AWAY;
5751 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5752 } else if (activity < 300) {
5753 //status_id = SIPE_STATUS_ID_IDLE;
5754 status_id = SIPE_STATUS_ID_AWAY;
5755 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5756 } else if (activity < 400) {
5757 status_id = SIPE_STATUS_ID_BRB;
5758 } else if (activity < 500) {
5759 status_id = SIPE_STATUS_ID_AVAILABLE;
5760 } else if (activity < 600) {
5761 //status_id = SIPE_STATUS_ID_ON_PHONE;
5762 status_id = SIPE_STATUS_ID_BUSY;
5763 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5764 } else if (activity < 700) {
5765 status_id = SIPE_STATUS_ID_BUSY;
5766 } else if (activity < 800) {
5767 status_id = SIPE_STATUS_ID_AWAY;
5768 } else {
5769 status_id = SIPE_STATUS_ID_AVAILABLE;
5772 if (availablity < 100)
5773 status_id = SIPE_STATUS_ID_OFFLINE;
5775 if (activity_desc && act) {
5776 g_free(*activity_desc);
5777 *activity_desc = g_strdup(act);
5780 return status_id;
5784 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5786 static const char*
5787 sipe_get_status_by_availability(int avail,
5788 char** activity_desc)
5790 const char *status;
5791 const char *act = NULL;
5793 if (avail < 3000) {
5794 status = SIPE_STATUS_ID_OFFLINE;
5795 } else if (avail < 4500) {
5796 status = SIPE_STATUS_ID_AVAILABLE;
5797 } else if (avail < 6000) {
5798 //status = SIPE_STATUS_ID_IDLE;
5799 status = SIPE_STATUS_ID_AVAILABLE;
5800 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5801 } else if (avail < 7500) {
5802 status = SIPE_STATUS_ID_BUSY;
5803 } else if (avail < 9000) {
5804 //status = SIPE_STATUS_ID_BUSYIDLE;
5805 status = SIPE_STATUS_ID_BUSY;
5806 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5807 } else if (avail < 12000) {
5808 status = SIPE_STATUS_ID_DND;
5809 } else if (avail < 15000) {
5810 status = SIPE_STATUS_ID_BRB;
5811 } else if (avail < 18000) {
5812 status = SIPE_STATUS_ID_AWAY;
5813 } else {
5814 status = SIPE_STATUS_ID_OFFLINE;
5817 if (activity_desc && act) {
5818 g_free(*activity_desc);
5819 *activity_desc = g_strdup(act);
5822 return status;
5826 * Returns 2007-style availability value
5828 * @param sipe_status_id (in)
5829 * @param activity_token (out) Must be g_free()'d after use if consumed.
5831 static int
5832 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5834 int availability;
5835 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5837 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5838 availability = 15500;
5839 if (!activity_token || !(*activity_token)) {
5840 activity = SIPE_ACTIVITY_AWAY;
5842 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5843 availability = 12500;
5844 activity = SIPE_ACTIVITY_BRB;
5845 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5846 availability = 9500;
5847 activity = SIPE_ACTIVITY_DND;
5848 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5849 availability = 6500;
5850 if (!activity_token || !(*activity_token)) {
5851 activity = SIPE_ACTIVITY_BUSY;
5853 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5854 availability = 3500;
5855 activity = SIPE_ACTIVITY_ONLINE;
5856 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5857 availability = 0;
5858 } else {
5859 // Offline or invisible
5860 availability = 18500;
5861 activity = SIPE_ACTIVITY_OFFLINE;
5864 if (activity_token) {
5865 *activity_token = g_strdup(sipe_activity_map[activity].token);
5867 return availability;
5870 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5872 const char *uri;
5873 xmlnode *xn_categories;
5874 xmlnode *xn_category;
5875 xmlnode *xn_node;
5876 const char *status = NULL;
5877 gboolean do_update_status = FALSE;
5878 gboolean has_note_cleaned = FALSE;
5879 gboolean has_free_busy_cleaned = FALSE;
5881 xn_categories = xmlnode_from_str(data, len);
5882 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5884 for (xn_category = xmlnode_get_child(xn_categories, "category");
5885 xn_category ;
5886 xn_category = xmlnode_get_next_twin(xn_category) )
5888 const char *tmp;
5889 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5890 time_t publish_time = (tmp = xmlnode_get_attrib(xn_category, "publishTime")) ?
5891 sipe_utils_str_to_time(tmp) : 0;
5893 /* contactCard */
5894 if (sipe_strequal(attrVar, "contactCard"))
5896 xmlnode *node;
5897 /* identity - Display Name and email */
5898 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5899 if (node) {
5900 char* display_name = xmlnode_get_data(
5901 xmlnode_get_descendant(node, "name", "displayName", NULL));
5902 char* email = xmlnode_get_data(
5903 xmlnode_get_child(node, "email"));
5905 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5906 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5908 g_free(display_name);
5909 g_free(email);
5911 /* company */
5912 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5913 if (node) {
5914 char* company = xmlnode_get_data(node);
5915 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5916 g_free(company);
5918 /* department */
5919 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5920 if (node) {
5921 char* department = xmlnode_get_data(node);
5922 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5923 g_free(department);
5925 /* title */
5926 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5927 if (node) {
5928 char* title = xmlnode_get_data(node);
5929 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5930 g_free(title);
5932 /* office */
5933 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5934 if (node) {
5935 char* office = xmlnode_get_data(node);
5936 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5937 g_free(office);
5939 /* site (url) */
5940 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5941 if (node) {
5942 char* site = xmlnode_get_data(node);
5943 sipe_update_user_info(sip, uri, SITE_PROP, site);
5944 g_free(site);
5946 /* phone */
5947 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5948 node;
5949 node = xmlnode_get_next_twin(node))
5951 const char *phone_type = xmlnode_get_attrib(node, "type");
5952 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5953 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5955 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5957 g_free(phone);
5958 g_free(phone_display_string);
5960 /* address */
5961 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5962 node;
5963 node = xmlnode_get_next_twin(node))
5965 if (sipe_strequal(xmlnode_get_attrib(node, "type"), "work")) {
5966 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5967 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5968 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5969 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5970 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5972 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5973 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5974 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5975 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5976 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5978 g_free(street);
5979 g_free(city);
5980 g_free(state);
5981 g_free(zipcode);
5982 g_free(country_code);
5984 break;
5988 /* note */
5989 else if (sipe_strequal(attrVar, "note"))
5991 if (uri) {
5992 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5994 if (!has_note_cleaned) {
5995 has_note_cleaned = TRUE;
5997 g_free(sbuddy->note);
5998 sbuddy->note = NULL;
5999 sbuddy->is_oof_note = FALSE;
6000 sbuddy->note_since = publish_time;
6002 do_update_status = TRUE;
6004 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6005 /* clean up in case no 'note' element is supplied
6006 * which indicate note removal in client
6008 g_free(sbuddy->note);
6009 sbuddy->note = NULL;
6010 sbuddy->is_oof_note = FALSE;
6011 sbuddy->note_since = publish_time;
6013 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
6014 if (xn_node) {
6015 char *tmp;
6016 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
6017 g_free(tmp);
6018 sbuddy->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_node, "type"), "OOF");
6019 sbuddy->note_since = publish_time;
6021 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
6022 uri, sbuddy->note ? sbuddy->note : "");
6024 /* to trigger UI refresh in case no status info is supplied in this update */
6025 do_update_status = TRUE;
6029 /* state */
6030 else if(sipe_strequal(attrVar, "state"))
6032 char *tmp;
6033 int availability;
6034 xmlnode *xn_availability;
6035 xmlnode *xn_activity;
6036 xmlnode *xn_meeting_subject;
6037 xmlnode *xn_meeting_location;
6038 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6040 xn_node = xmlnode_get_child(xn_category, "state");
6041 if (!xn_node) continue;
6042 xn_availability = xmlnode_get_child(xn_node, "availability");
6043 if (!xn_availability) continue;
6044 xn_activity = xmlnode_get_child(xn_node, "activity");
6045 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
6046 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
6048 tmp = xmlnode_get_data(xn_availability);
6049 availability = atoi(tmp);
6050 g_free(tmp);
6052 /* activity, meeting_subject, meeting_location */
6053 if (sbuddy) {
6054 char *tmp = NULL;
6056 /* activity */
6057 g_free(sbuddy->activity);
6058 sbuddy->activity = NULL;
6059 if (xn_activity) {
6060 const char *token = xmlnode_get_attrib(xn_activity, "token");
6061 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
6063 /* from token */
6064 if (!is_empty(token)) {
6065 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6067 /* from custom element */
6068 if (xn_custom) {
6069 char *custom = xmlnode_get_data(xn_custom);
6071 if (!is_empty(custom)) {
6072 sbuddy->activity = custom;
6073 custom = NULL;
6075 g_free(custom);
6078 /* meeting_subject */
6079 g_free(sbuddy->meeting_subject);
6080 sbuddy->meeting_subject = NULL;
6081 if (xn_meeting_subject) {
6082 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
6084 if (!is_empty(meeting_subject)) {
6085 sbuddy->meeting_subject = meeting_subject;
6086 meeting_subject = NULL;
6088 g_free(meeting_subject);
6090 /* meeting_location */
6091 g_free(sbuddy->meeting_location);
6092 sbuddy->meeting_location = NULL;
6093 if (xn_meeting_location) {
6094 char *meeting_location = xmlnode_get_data(xn_meeting_location);
6096 if (!is_empty(meeting_location)) {
6097 sbuddy->meeting_location = meeting_location;
6098 meeting_location = NULL;
6100 g_free(meeting_location);
6103 status = sipe_get_status_by_availability(availability, &tmp);
6104 if (sbuddy->activity && tmp) {
6105 char *tmp2 = sbuddy->activity;
6107 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6108 g_free(tmp);
6109 g_free(tmp2);
6110 } else if (tmp) {
6111 sbuddy->activity = tmp;
6115 do_update_status = TRUE;
6117 /* calendarData */
6118 else if(sipe_strequal(attrVar, "calendarData"))
6120 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6121 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
6122 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
6124 if (sbuddy && xn_free_busy) {
6125 if (!has_free_busy_cleaned) {
6126 has_free_busy_cleaned = TRUE;
6128 g_free(sbuddy->cal_start_time);
6129 sbuddy->cal_start_time = NULL;
6131 g_free(sbuddy->cal_free_busy_base64);
6132 sbuddy->cal_free_busy_base64 = NULL;
6134 g_free(sbuddy->cal_free_busy);
6135 sbuddy->cal_free_busy = NULL;
6137 sbuddy->cal_free_busy_published = publish_time;
6140 if (publish_time >= sbuddy->cal_free_busy_published) {
6141 g_free(sbuddy->cal_start_time);
6142 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
6144 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
6145 15 : 0;
6147 g_free(sbuddy->cal_free_busy_base64);
6148 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
6150 g_free(sbuddy->cal_free_busy);
6151 sbuddy->cal_free_busy = NULL;
6153 sbuddy->cal_free_busy_published = publish_time;
6155 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);
6159 if (sbuddy && xn_working_hours) {
6160 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6165 if (do_update_status) {
6166 if (!status) { /* no status category in this update, using contact's current status */
6167 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6168 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6169 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6170 status = purple_status_get_id(pstatus);
6173 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
6174 sipe_got_user_status(sip, uri, status);
6177 xmlnode_free(xn_categories);
6180 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6182 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6183 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
6184 payload->host = g_strdup(host);
6185 payload->buddies = server;
6186 sipe_subscribe_presence_batched_routed(sip, payload);
6187 sipe_subscribe_presence_batched_routed_free(payload);
6190 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6192 xmlnode *xn_list;
6193 xmlnode *xn_resource;
6194 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6195 g_free, NULL);
6196 GSList *server;
6197 gchar *host;
6199 xn_list = xmlnode_from_str(data, len);
6201 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6202 xn_resource;
6203 xn_resource = xmlnode_get_next_twin(xn_resource) )
6205 const char *uri, *state;
6206 xmlnode *xn_instance;
6208 xn_instance = xmlnode_get_child(xn_resource, "instance");
6209 if (!xn_instance) continue;
6211 uri = xmlnode_get_attrib(xn_resource, "uri");
6212 state = xmlnode_get_attrib(xn_instance, "state");
6213 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6215 if (strstr(state, "resubscribe")) {
6216 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6218 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6219 gchar *user = g_strdup(uri);
6220 host = g_strdup(poolFqdn);
6221 server = g_hash_table_lookup(servers, host);
6222 server = g_slist_append(server, user);
6223 g_hash_table_insert(servers, host, server);
6224 } else {
6225 sipe_subscribe_presence_single(sip, (void *) uri);
6230 /* Send out any deferred poolFqdn subscriptions */
6231 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6232 g_hash_table_destroy(servers);
6234 xmlnode_free(xn_list);
6237 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6239 gchar *uri;
6240 gchar *getbasic;
6241 gchar *activity = NULL;
6242 xmlnode *pidf;
6243 xmlnode *basicstatus = NULL, *tuple, *status;
6244 gboolean isonline = FALSE;
6245 xmlnode *display_name_node;
6247 pidf = xmlnode_from_str(data, len);
6248 if (!pidf) {
6249 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6250 return;
6253 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6255 if ((status = xmlnode_get_child(tuple, "status"))) {
6256 basicstatus = xmlnode_get_child(status, "basic");
6260 if (!basicstatus) {
6261 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6262 xmlnode_free(pidf);
6263 return;
6266 getbasic = xmlnode_get_data(basicstatus);
6267 if (!getbasic) {
6268 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6269 xmlnode_free(pidf);
6270 return;
6273 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6274 if (strstr(getbasic, "open")) {
6275 isonline = TRUE;
6277 g_free(getbasic);
6279 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6281 display_name_node = xmlnode_get_child(pidf, "display-name");
6282 if (display_name_node) {
6283 char * display_name = xmlnode_get_data(display_name_node);
6285 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6286 g_free(display_name);
6289 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6290 if ((status = xmlnode_get_child(tuple, "status"))) {
6291 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6292 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6293 activity = xmlnode_get_data(basicstatus);
6294 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6300 if (isonline) {
6301 const gchar * status_id = NULL;
6302 if (activity) {
6303 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6304 status_id = SIPE_STATUS_ID_BUSY;
6305 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6306 status_id = SIPE_STATUS_ID_AWAY;
6310 if (!status_id) {
6311 status_id = SIPE_STATUS_ID_AVAILABLE;
6314 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6315 sipe_got_user_status(sip, uri, status_id);
6316 } else {
6317 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6320 g_free(activity);
6321 g_free(uri);
6322 xmlnode_free(pidf);
6325 /** 2005 */
6326 static void
6327 sipe_user_info_has_updated(struct sipe_account_data *sip,
6328 xmlnode *xn_userinfo)
6330 if (sip->user_info) {
6331 xmlnode_free(sip->user_info);
6333 sip->user_info = xmlnode_copy(xn_userinfo);
6335 /* Publish initial state if not yet.
6336 * Assuming this happens on initial responce to self subscription
6337 * so we've already updated our UserInfo.
6339 if (!sip->initial_state_published) {
6340 send_presence_soap(sip, FALSE);
6341 /* dalayed run */
6342 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6346 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6348 char *activity = NULL;
6349 const char *epid;
6350 const char *status_id = NULL;
6351 const char *name;
6352 char *uri;
6353 char *self_uri = sip_uri_self(sip);
6354 int avl;
6355 int act;
6356 const char *device_name = NULL;
6357 const char *cal_start_time = NULL;
6358 const char *cal_granularity = NULL;
6359 char *cal_free_busy_base64 = NULL;
6360 struct sipe_buddy *sbuddy;
6361 xmlnode *node;
6362 xmlnode *xn_presentity;
6363 xmlnode *xn_availability;
6364 xmlnode *xn_activity;
6365 xmlnode *xn_display_name;
6366 xmlnode *xn_email;
6367 xmlnode *xn_phone_number;
6368 xmlnode *xn_userinfo;
6369 xmlnode *xn_note;
6370 xmlnode *xn_oof;
6371 xmlnode *xn_state;
6372 xmlnode *xn_contact;
6373 char *note;
6374 char *free_activity;
6375 int user_avail;
6376 const char *user_avail_nil;
6377 int res_avail;
6378 time_t user_avail_since = 0;
6379 time_t activity_since = 0;
6381 /* fix for Reuters environment on Linux */
6382 if (data && strstr(data, "encoding=\"utf-16\"")) {
6383 char *tmp_data;
6384 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6385 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6386 g_free(tmp_data);
6387 } else {
6388 xn_presentity = xmlnode_from_str(data, len);
6391 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6392 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6393 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6394 xn_email = xmlnode_get_child(xn_presentity, "email");
6395 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6396 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6397 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6398 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6399 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6400 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6401 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6402 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6403 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6404 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6406 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6407 user_avail = 0;
6408 user_avail_since = 0;
6411 free_activity = NULL;
6413 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6414 uri = sip_uri_from_name(name);
6415 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6416 epid = xmlnode_get_attrib(xn_availability, "epid");
6417 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6419 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6420 res_avail = sipe_get_availability_by_status(status_id, NULL);
6421 if (user_avail > res_avail) {
6422 res_avail = user_avail;
6423 status_id = sipe_get_status_by_availability(user_avail, NULL);
6426 if (xn_display_name) {
6427 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6428 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6429 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6430 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6431 char *tel_uri = sip_to_tel_uri(phone_number);
6433 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6434 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6435 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6436 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6438 g_free(tel_uri);
6439 g_free(phone_label);
6440 g_free(phone_number);
6441 g_free(email);
6442 g_free(display_name);
6445 if (xn_contact) {
6446 /* tel */
6447 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6449 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6450 const char *phone_type = xmlnode_get_attrib(node, "type");
6451 char* phone = xmlnode_get_data(node);
6453 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6455 g_free(phone);
6459 /* devicePresence */
6460 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6461 xmlnode *xn_device_name;
6462 xmlnode *xn_calendar_info;
6463 xmlnode *xn_state;
6464 char *state;
6466 /* deviceName */
6467 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6468 xn_device_name = xmlnode_get_child(node, "deviceName");
6469 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6472 /* calendarInfo */
6473 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6474 if (xn_calendar_info) {
6475 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6477 if (cal_start_time) {
6478 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6479 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6481 if (cal_start_time_t_tmp > cal_start_time_t) {
6482 cal_start_time = cal_start_time_tmp;
6483 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6484 g_free(cal_free_busy_base64);
6485 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6487 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);
6489 } else {
6490 cal_start_time = cal_start_time_tmp;
6491 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6492 g_free(cal_free_busy_base64);
6493 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6495 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);
6499 /* state */
6500 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6501 if (xn_state) {
6502 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6503 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6505 state = xmlnode_get_data(xn_state);
6506 if (dev_avail_since > user_avail_since &&
6507 dev_avail >= res_avail)
6509 res_avail = dev_avail;
6510 if (!is_empty(state))
6512 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6513 g_free(activity);
6514 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6515 } else if (sipe_strequal(state, "presenting")) {
6516 g_free(activity);
6517 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6518 } else {
6519 activity = state;
6520 state = NULL;
6522 activity_since = dev_avail_since;
6524 status_id = sipe_get_status_by_availability(res_avail, &activity);
6526 g_free(state);
6530 /* oof */
6531 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6532 g_free(activity);
6533 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6534 activity_since = 0;
6537 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6538 if (sbuddy)
6540 g_free(sbuddy->activity);
6541 sbuddy->activity = activity;
6542 activity = NULL;
6544 sbuddy->activity_since = activity_since;
6546 sbuddy->user_avail = user_avail;
6547 sbuddy->user_avail_since = user_avail_since;
6549 g_free(sbuddy->note);
6550 sbuddy->note = NULL;
6551 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6553 sbuddy->is_oof_note = (xn_oof != NULL);
6555 g_free(sbuddy->device_name);
6556 sbuddy->device_name = NULL;
6557 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6559 if (!is_empty(cal_free_busy_base64)) {
6560 g_free(sbuddy->cal_start_time);
6561 sbuddy->cal_start_time = g_strdup(cal_start_time);
6563 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6565 g_free(sbuddy->cal_free_busy_base64);
6566 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6567 cal_free_busy_base64 = NULL;
6569 g_free(sbuddy->cal_free_busy);
6570 sbuddy->cal_free_busy = NULL;
6573 sbuddy->last_non_cal_status_id = status_id;
6574 g_free(sbuddy->last_non_cal_activity);
6575 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6577 if (sipe_strequal(sbuddy->name, self_uri)) {
6578 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6580 sip->is_oof_note = sbuddy->is_oof_note;
6582 g_free(sip->note);
6583 sip->note = g_strdup(sbuddy->note);
6585 sip->note_since = time(NULL);
6588 g_free(sip->status);
6589 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6592 g_free(cal_free_busy_base64);
6593 g_free(activity);
6595 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6596 sipe_got_user_status(sip, uri, status_id);
6598 if (!sip->ocs2007 && sipe_strequal(self_uri, uri)) {
6599 sipe_user_info_has_updated(sip, xn_userinfo);
6602 g_free(note);
6603 xmlnode_free(xn_presentity);
6604 g_free(uri);
6605 g_free(self_uri);
6608 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6610 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6612 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6614 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6615 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6617 const char *content = msg->body;
6618 unsigned length = msg->bodylen;
6619 PurpleMimeDocument *mime = NULL;
6621 if (strstr(ctype, "multipart"))
6623 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6624 const char *content_type;
6625 GList* parts;
6626 mime = purple_mime_document_parse(doc);
6627 parts = purple_mime_document_get_parts(mime);
6628 while(parts) {
6629 content = purple_mime_part_get_data(parts->data);
6630 length = purple_mime_part_get_length(parts->data);
6631 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6632 if(content_type && strstr(content_type,"application/rlmi+xml"))
6634 process_incoming_notify_rlmi_resub(sip, content, length);
6636 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6638 process_incoming_notify_msrtc(sip, content, length);
6640 else
6642 process_incoming_notify_rlmi(sip, content, length);
6644 parts = parts->next;
6646 g_free(doc);
6648 if (mime)
6650 purple_mime_document_free(mime);
6653 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6655 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6657 else if(strstr(ctype, "application/rlmi+xml"))
6659 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6662 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6664 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6666 else
6668 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6672 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6674 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6675 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6677 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6679 if (ctype &&
6680 strstr(ctype, "multipart") &&
6681 (strstr(ctype, "application/rlmi+xml") ||
6682 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6683 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6684 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6685 GList *parts = purple_mime_document_get_parts(mime);
6686 GSList *buddies = NULL;
6687 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6689 while (parts) {
6690 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6691 purple_mime_part_get_length(parts->data));
6693 if (xml && !sipe_strequal(xml->name, "list")) {
6694 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6696 buddies = g_slist_append(buddies, uri);
6698 xmlnode_free(xml);
6700 parts = parts->next;
6702 g_free(doc);
6703 if (mime) purple_mime_document_free(mime);
6705 payload->host = g_strdup(who);
6706 payload->buddies = buddies;
6707 sipe_schedule_action(action_name, timeout,
6708 sipe_subscribe_presence_batched_routed,
6709 sipe_subscribe_presence_batched_routed_free,
6710 sip, payload);
6711 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6713 } else {
6714 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6715 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6717 g_free(action_name);
6721 * Dispatcher for all incoming subscription information
6722 * whether it comes from NOTIFY, BENOTIFY requests or
6723 * piggy-backed to subscription's OK responce.
6725 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6726 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6728 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6730 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6731 const gchar *event = sipmsg_find_header(msg, "Event");
6732 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6733 char *tmp;
6735 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6736 event ? event : "",
6737 tmp = fix_newlines(msg->body));
6738 g_free(tmp);
6739 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6741 /* implicit subscriptions */
6742 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6743 sipe_process_imdn(sip, msg);
6746 if (event) {
6747 /* for one off subscriptions (send with Expire: 0) */
6748 if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6750 sipe_process_provisioning_v2(sip, msg);
6752 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6754 sipe_process_provisioning(sip, msg);
6756 else if (!g_ascii_strcasecmp(event, "presence"))
6758 sipe_process_presence(sip, msg);
6760 else if (!g_ascii_strcasecmp(event, "registration-notify"))
6762 sipe_process_registration_notify(sip, msg);
6765 if (!subscription_state || strstr(subscription_state, "active"))
6767 if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6769 sipe_process_roaming_contacts(sip, msg);
6771 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6773 sipe_process_roaming_self(sip, msg);
6775 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6777 sipe_process_roaming_acl(sip, msg);
6779 else if (!g_ascii_strcasecmp(event, "presence.wpending"))
6781 sipe_process_presence_wpending(sip, msg);
6783 else if (!g_ascii_strcasecmp(event, "conference"))
6785 sipe_process_conference(sip, msg);
6790 /* The server sends status 'terminated' */
6791 if (subscription_state && strstr(subscription_state, "terminated") ) {
6792 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6793 gchar *key = sipe_get_subscription_key(event, who);
6795 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6796 g_free(who);
6798 if (g_hash_table_lookup(sip->subscriptions, key)) {
6799 g_hash_table_remove(sip->subscriptions, key);
6800 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6803 g_free(key);
6806 if (!request && event) {
6807 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6808 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6809 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6811 if (timeout) {
6812 /* 2 min ahead of expiration */
6813 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6815 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6816 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6818 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6819 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6820 g_free(action_name);
6822 else if (!g_ascii_strcasecmp(event, "presence") &&
6823 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6825 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6826 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6828 if (sip->batched_support) {
6829 sipe_process_presence_timeout(sip, msg, who, timeout);
6831 else {
6832 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6833 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6835 g_free(action_name);
6836 g_free(who);
6841 /* The client responses on received a NOTIFY message */
6842 if (request && !benotify)
6844 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6849 * Whether user manually changed status or
6850 * it was changed automatically due to user
6851 * became inactive/active again
6853 static gboolean
6854 sipe_is_user_state(struct sipe_account_data *sip)
6856 gboolean res;
6857 time_t now = time(NULL);
6859 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6860 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6862 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6864 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6865 return res;
6868 static void
6869 send_presence_soap0(struct sipe_account_data *sip,
6870 gboolean do_publish_calendar,
6871 gboolean do_reset_status)
6873 struct sipe_ews* ews = sip->ews;
6874 int availability = 0;
6875 int activity = 0;
6876 gchar *body;
6877 gchar *tmp;
6878 gchar *tmp2 = NULL;
6879 gchar *res_note = NULL;
6880 gchar *res_oof = NULL;
6881 const gchar *note_pub = NULL;
6882 gchar *states = NULL;
6883 gchar *calendar_data = NULL;
6884 gchar *epid = get_epid(sip);
6885 time_t now = time(NULL);
6886 gchar *since_time_str = sipe_utils_time_to_str(now);
6887 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6888 const char *user_input;
6889 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6891 if (oof_note && sip->note) {
6892 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6893 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6896 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6898 if (!sip->initial_state_published ||
6899 do_reset_status)
6901 g_free(sip->status);
6902 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6905 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6907 /* Note */
6908 if (pub_oof) {
6909 note_pub = oof_note;
6910 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6911 ews->published = TRUE;
6912 } else if (sip->note) {
6913 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6914 g_free(sip->note);
6915 sip->note = NULL;
6916 sip->is_oof_note = FALSE;
6917 sip->note_since = 0;
6918 } else {
6919 note_pub = sip->note;
6920 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6924 if (note_pub)
6926 /* to protocol internal plain text format */
6927 tmp = purple_markup_strip_html(note_pub);
6928 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6929 g_free(tmp);
6932 /* User State */
6933 if (!do_reset_status) {
6934 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6936 gchar *activity_token = NULL;
6937 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6939 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6940 avail_2007,
6941 since_time_str,
6942 epid,
6943 activity_token);
6944 g_free(activity_token);
6946 else /* preserve existing publication */
6948 xmlnode *xn_states;
6949 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6950 states = xmlnode_to_str(xn_states, NULL);
6951 /* this is a hack-around to remove added newline after inner element,
6952 * state in this case, where it shouldn't be.
6953 * After several use of xmlnode_to_str, amount of added newlines
6954 * grows significantly.
6956 purple_str_strip_char(states, '\n');
6957 //purple_str_strip_char(states, '\r');
6960 } else {
6961 /* do nothing - then User state will be erased */
6963 sip->initial_state_published = TRUE;
6965 /* CalendarInfo */
6966 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6968 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6969 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6970 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6971 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6972 fb_start_str,
6973 free_busy_base64);
6974 g_free(fb_start_str);
6975 g_free(free_busy_base64);
6978 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6980 /* forming resulting XML */
6981 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6982 sip->username,
6983 availability,
6984 activity,
6985 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6986 res_note ? res_note : "",
6987 res_oof ? res_oof : "",
6988 states ? states : "",
6989 calendar_data ? calendar_data : "",
6990 epid,
6991 since_time_str,
6992 since_time_str,
6993 user_input);
6994 g_free(tmp);
6995 g_free(tmp2);
6996 g_free(res_note);
6997 g_free(states);
6998 g_free(calendar_data);
7000 send_soap_request(sip, body);
7002 g_free(body);
7003 g_free(since_time_str);
7004 g_free(epid);
7007 void
7008 send_presence_soap(struct sipe_account_data *sip,
7009 gboolean do_publish_calendar)
7011 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7015 static gboolean
7016 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7017 struct sipmsg *msg,
7018 struct transaction *trans)
7020 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7022 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7023 xmlnode *xml;
7024 xmlnode *node;
7025 gchar *fault_code;
7026 GHashTable *faults;
7027 int index_our;
7028 gboolean has_device_publication = FALSE;
7030 xml = xmlnode_from_str(msg->body, msg->bodylen);
7032 /* test if version mismatch fault */
7033 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7034 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7035 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
7036 g_free(fault_code);
7037 xmlnode_free(xml);
7038 return TRUE;
7040 g_free(fault_code);
7042 /* accumulating information about faulty versions */
7043 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7044 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7045 node;
7046 node = xmlnode_get_next_twin(node))
7048 const gchar *index = xmlnode_get_attrib(node, "index");
7049 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7051 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7052 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
7054 xmlnode_free(xml);
7056 /* here we are parsing own request to figure out what publication
7057 * referensed here only by index went wrong
7059 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7061 /* publication */
7062 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7063 index_our = 1; /* starts with 1 - our first publication */
7064 node;
7065 node = xmlnode_get_next_twin(node), index_our++)
7067 gchar *idx = g_strdup_printf("%d", index_our);
7068 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7069 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7070 g_free(idx);
7072 if (sipe_strequal("device", categoryName)) {
7073 has_device_publication = TRUE;
7076 if (curVersion) { /* fault exist on this index */
7077 const gchar *container = xmlnode_get_attrib(node, "container");
7078 const gchar *instance = xmlnode_get_attrib(node, "instance");
7079 /* key is <category><instance><container> */
7080 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7081 struct sipe_publication *publication =
7082 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
7084 purple_debug_info("sipe", "key is %s\n", key);
7086 if (publication) {
7087 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
7088 key, curVersion, publication->version);
7089 /* updating publication's version to the correct one */
7090 publication->version = atoi(curVersion);
7092 g_free(key);
7095 xmlnode_free(xml);
7096 g_hash_table_destroy(faults);
7098 /* rebublishing with right versions */
7099 if (has_device_publication) {
7100 send_publish_category_initial(sip);
7101 } else {
7102 send_presence_status(sip);
7105 return TRUE;
7109 * Returns 'device' XML part for publication.
7110 * Must be g_free'd after use.
7112 static gchar *
7113 sipe_publish_get_category_device(struct sipe_account_data *sip)
7115 gchar *uri;
7116 gchar *doc;
7117 gchar *epid = get_epid(sip);
7118 gchar *uuid = generateUUIDfromEPID(epid);
7119 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7120 /* key is <category><instance><container> */
7121 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7122 struct sipe_publication *publication =
7123 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7125 g_free(key);
7126 g_free(epid);
7128 uri = sip_uri_self(sip);
7129 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7130 device_instance,
7131 publication ? publication->version : 0,
7132 uuid,
7133 uri,
7134 "00:00:00+01:00", /* @TODO make timezone real*/
7135 sipe_get_host_name()
7138 g_free(uri);
7139 g_free(uuid);
7141 return doc;
7145 * A service method - use
7146 * - send_publish_get_category_state_machine and
7147 * - send_publish_get_category_state_user instead.
7148 * Must be g_free'd after use.
7150 static gchar *
7151 sipe_publish_get_category_state(struct sipe_account_data *sip,
7152 gboolean is_user_state)
7154 int availability = sipe_get_availability_by_status(sip->status, NULL);
7155 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7156 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7157 /* key is <category><instance><container> */
7158 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7159 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7160 struct sipe_publication *publication_2 =
7161 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7162 struct sipe_publication *publication_3 =
7163 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7165 g_free(key_2);
7166 g_free(key_3);
7168 if (publication_2 && (publication_2->availability == availability))
7170 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
7171 return NULL; /* nothing to update */
7174 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7175 instance,
7176 publication_2 ? publication_2->version : 0,
7177 availability,
7178 instance,
7179 publication_3 ? publication_3->version : 0,
7180 availability);
7184 * Only Busy and OOF calendar event are published.
7185 * Different instances are used for that.
7187 * Must be g_free'd after use.
7189 static gchar *
7190 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7191 struct sipe_cal_event *event,
7192 const char *uri,
7193 int cal_satus)
7195 gchar *start_time_str;
7196 int availability = 0;
7197 gchar *res;
7198 gchar *tmp = NULL;
7199 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7200 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7201 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7203 /* key is <category><instance><container> */
7204 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7205 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7206 struct sipe_publication *publication_2 =
7207 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7208 struct sipe_publication *publication_3 =
7209 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7211 g_free(key_2);
7212 g_free(key_3);
7214 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7215 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7216 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7217 return NULL;
7220 if (event &&
7221 publication_3 &&
7222 (publication_3->availability == availability) &&
7223 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7225 g_free(tmp);
7226 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7227 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7228 return NULL; /* nothing to update */
7230 g_free(tmp);
7232 if (event &&
7233 (event->cal_status == SIPE_CAL_BUSY ||
7234 event->cal_status == SIPE_CAL_OOF))
7236 gchar *availability_xml_str = NULL;
7237 gchar *activity_xml_str = NULL;
7239 if (event->cal_status == SIPE_CAL_BUSY) {
7240 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7243 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7244 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7245 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7246 "minAvailability=\"6500\"",
7247 "maxAvailability=\"8999\"");
7248 } else if (event->cal_status == SIPE_CAL_OOF) {
7249 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7250 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7251 "minAvailability=\"12000\"",
7252 "");
7254 start_time_str = sipe_utils_time_to_str(event->start_time);
7256 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7257 instance,
7258 publication_2 ? publication_2->version : 0,
7259 uri,
7260 start_time_str,
7261 availability_xml_str ? availability_xml_str : "",
7262 activity_xml_str ? activity_xml_str : "",
7263 event->subject ? event->subject : "",
7264 event->location ? event->location : "",
7266 instance,
7267 publication_3 ? publication_3->version : 0,
7268 uri,
7269 start_time_str,
7270 availability_xml_str ? availability_xml_str : "",
7271 activity_xml_str ? activity_xml_str : "",
7272 event->subject ? event->subject : "",
7273 event->location ? event->location : ""
7275 g_free(start_time_str);
7276 g_free(availability_xml_str);
7277 g_free(activity_xml_str);
7280 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7282 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7283 instance,
7284 publication_2 ? publication_2->version : 0,
7286 instance,
7287 publication_3 ? publication_3->version : 0
7291 return res;
7295 * Returns 'machineState' XML part for publication.
7296 * Must be g_free'd after use.
7298 static gchar *
7299 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7301 return sipe_publish_get_category_state(sip, FALSE);
7305 * Returns 'userState' XML part for publication.
7306 * Must be g_free'd after use.
7308 static gchar *
7309 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7311 return sipe_publish_get_category_state(sip, TRUE);
7315 * Returns 'note' XML part for publication.
7316 * Must be g_free'd after use.
7318 * Protocol format for Note is plain text.
7320 * @param note a note in Sipe internal HTML format
7321 * @param note_type either personal or OOF
7323 static gchar *
7324 sipe_publish_get_category_note(struct sipe_account_data *sip,
7325 const char *note, /* html */
7326 const char *note_type,
7327 time_t note_start,
7328 time_t note_end)
7330 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7331 /* key is <category><instance><container> */
7332 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7333 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7334 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7336 struct sipe_publication *publication_note_200 =
7337 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7338 struct sipe_publication *publication_note_300 =
7339 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7340 struct sipe_publication *publication_note_400 =
7341 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7343 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7344 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7345 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7346 char *res, *tmp1, *tmp2, *tmp3;
7347 char *start_time_attr;
7348 char *end_time_attr;
7350 g_free(tmp);
7351 tmp = NULL;
7352 g_free(key_note_200);
7353 g_free(key_note_300);
7354 g_free(key_note_400);
7356 /* we even need to republish empty note */
7357 if (sipe_strequal(n1, n2))
7359 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7360 g_free(n1);
7361 return NULL; /* nothing to update */
7364 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7365 g_free(tmp);
7366 tmp = NULL;
7367 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7368 g_free(tmp);
7370 if (n1) {
7371 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7372 instance,
7373 200,
7374 publication_note_200 ? publication_note_200->version : 0,
7375 note_type,
7376 start_time_attr ? start_time_attr : "",
7377 end_time_attr ? end_time_attr : "",
7378 n1);
7380 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7381 instance,
7382 300,
7383 publication_note_300 ? publication_note_300->version : 0,
7384 note_type,
7385 start_time_attr ? start_time_attr : "",
7386 end_time_attr ? end_time_attr : "",
7387 n1);
7389 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7390 instance,
7391 400,
7392 publication_note_400 ? publication_note_400->version : 0,
7393 note_type,
7394 start_time_attr ? start_time_attr : "",
7395 end_time_attr ? end_time_attr : "",
7396 n1);
7397 } else {
7398 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7399 "note",
7400 instance,
7401 200,
7402 publication_note_200 ? publication_note_200->version : 0,
7403 "static");
7404 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7405 "note",
7406 instance,
7407 300,
7408 publication_note_200 ? publication_note_200->version : 0,
7409 "static");
7410 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7411 "note",
7412 instance,
7413 400,
7414 publication_note_200 ? publication_note_200->version : 0,
7415 "static");
7417 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7419 g_free(start_time_attr);
7420 g_free(end_time_attr);
7421 g_free(tmp1);
7422 g_free(tmp2);
7423 g_free(tmp3);
7424 g_free(n1);
7426 return res;
7430 * Returns 'calendarData' XML part with WorkingHours for publication.
7431 * Must be g_free'd after use.
7433 static gchar *
7434 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7436 struct sipe_ews* ews = sip->ews;
7438 /* key is <category><instance><container> */
7439 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7440 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7441 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7442 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7443 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7444 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7446 struct sipe_publication *publication_cal_1 =
7447 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7448 struct sipe_publication *publication_cal_100 =
7449 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7450 struct sipe_publication *publication_cal_200 =
7451 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7452 struct sipe_publication *publication_cal_300 =
7453 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7454 struct sipe_publication *publication_cal_400 =
7455 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7456 struct sipe_publication *publication_cal_32000 =
7457 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7459 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7460 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7462 g_free(key_cal_1);
7463 g_free(key_cal_100);
7464 g_free(key_cal_200);
7465 g_free(key_cal_300);
7466 g_free(key_cal_400);
7467 g_free(key_cal_32000);
7469 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7470 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7471 return NULL;
7474 if (sipe_strequal(n1, n2))
7476 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7477 return NULL; /* nothing to update */
7480 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7481 /* 1 */
7482 publication_cal_1 ? publication_cal_1->version : 0,
7483 ews->email,
7484 ews->working_hours_xml_str,
7485 /* 100 - Public */
7486 publication_cal_100 ? publication_cal_100->version : 0,
7487 /* 200 - Company */
7488 publication_cal_200 ? publication_cal_200->version : 0,
7489 ews->email,
7490 ews->working_hours_xml_str,
7491 /* 300 - Team */
7492 publication_cal_300 ? publication_cal_300->version : 0,
7493 ews->email,
7494 ews->working_hours_xml_str,
7495 /* 400 - Personal */
7496 publication_cal_400 ? publication_cal_400->version : 0,
7497 ews->email,
7498 ews->working_hours_xml_str,
7499 /* 32000 - Blocked */
7500 publication_cal_32000 ? publication_cal_32000->version : 0
7505 * Returns 'calendarData' XML part with FreeBusy for publication.
7506 * Must be g_free'd after use.
7508 static gchar *
7509 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7511 struct sipe_ews* ews = sip->ews;
7512 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7513 char *fb_start_str;
7514 char *free_busy_base64;
7515 const char *st;
7516 const char *fb;
7517 char *res;
7519 /* key is <category><instance><container> */
7520 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7521 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7522 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7523 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7524 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7525 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7527 struct sipe_publication *publication_cal_1 =
7528 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7529 struct sipe_publication *publication_cal_100 =
7530 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7531 struct sipe_publication *publication_cal_200 =
7532 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7533 struct sipe_publication *publication_cal_300 =
7534 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7535 struct sipe_publication *publication_cal_400 =
7536 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7537 struct sipe_publication *publication_cal_32000 =
7538 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7540 g_free(key_cal_1);
7541 g_free(key_cal_100);
7542 g_free(key_cal_200);
7543 g_free(key_cal_300);
7544 g_free(key_cal_400);
7545 g_free(key_cal_32000);
7547 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7548 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7549 return NULL;
7552 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7553 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7555 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7556 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7558 /* we will rebuplish the same data to refresh publication time,
7559 * so if data from multiple sources, most recent will be choosen
7561 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7563 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7564 // g_free(fb_start_str);
7565 // g_free(free_busy_base64);
7566 // return NULL; /* nothing to update */
7569 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7570 /* 1 */
7571 cal_data_instance,
7572 publication_cal_1 ? publication_cal_1->version : 0,
7573 /* 100 - Public */
7574 cal_data_instance,
7575 publication_cal_100 ? publication_cal_100->version : 0,
7576 /* 200 - Company */
7577 cal_data_instance,
7578 publication_cal_200 ? publication_cal_200->version : 0,
7579 ews->email,
7580 fb_start_str,
7581 free_busy_base64,
7582 /* 300 - Team */
7583 cal_data_instance,
7584 publication_cal_300 ? publication_cal_300->version : 0,
7585 ews->email,
7586 fb_start_str,
7587 free_busy_base64,
7588 /* 400 - Personal */
7589 cal_data_instance,
7590 publication_cal_400 ? publication_cal_400->version : 0,
7591 ews->email,
7592 fb_start_str,
7593 free_busy_base64,
7594 /* 32000 - Blocked */
7595 cal_data_instance,
7596 publication_cal_32000 ? publication_cal_32000->version : 0
7599 g_free(fb_start_str);
7600 g_free(free_busy_base64);
7601 return res;
7604 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7606 gchar *uri;
7607 gchar *doc;
7608 gchar *tmp;
7609 gchar *hdr;
7611 uri = sip_uri_self(sip);
7612 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7613 uri,
7614 publications);
7616 tmp = get_contact(sip);
7617 hdr = g_strdup_printf("Contact: %s\r\n"
7618 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7620 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7622 g_free(tmp);
7623 g_free(hdr);
7624 g_free(uri);
7625 g_free(doc);
7628 static void
7629 send_publish_category_initial(struct sipe_account_data *sip)
7631 gchar *pub_device = sipe_publish_get_category_device(sip);
7632 gchar *pub_machine;
7633 gchar *publications;
7635 g_free(sip->status);
7636 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7638 pub_machine = sipe_publish_get_category_state_machine(sip);
7639 publications = g_strdup_printf("%s%s",
7640 pub_device,
7641 pub_machine ? pub_machine : "");
7642 g_free(pub_device);
7643 g_free(pub_machine);
7645 send_presence_publish(sip, publications);
7646 g_free(publications);
7649 static void
7650 send_presence_category_publish(struct sipe_account_data *sip)
7652 gchar *pub_state = sipe_is_user_state(sip) ?
7653 sipe_publish_get_category_state_user(sip) :
7654 sipe_publish_get_category_state_machine(sip);
7655 gchar *pub_note = sipe_publish_get_category_note(sip,
7656 sip->note,
7657 sip->is_oof_note ? "OOF" : "personal",
7660 gchar *publications;
7662 if (!pub_state && !pub_note) {
7663 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7664 return;
7667 publications = g_strdup_printf("%s%s",
7668 pub_state ? pub_state : "",
7669 pub_note ? pub_note : "");
7671 g_free(pub_state);
7672 g_free(pub_note);
7674 send_presence_publish(sip, publications);
7675 g_free(publications);
7679 * Publishes self status
7680 * based on own calendar information.
7682 * For 2007+
7684 void
7685 publish_calendar_status_self(struct sipe_account_data *sip)
7687 struct sipe_cal_event* event = NULL;
7688 gchar *pub_cal_working_hours = NULL;
7689 gchar *pub_cal_free_busy = NULL;
7690 gchar *pub_calendar = NULL;
7691 gchar *pub_calendar2 = NULL;
7692 gchar *pub_oof_note = NULL;
7693 const gchar *oof_note;
7694 time_t oof_start = 0;
7695 time_t oof_end = 0;
7697 if (!sip->ews) {
7698 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7699 return;
7702 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7703 if (sip->ews->cal_events) {
7704 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7707 if (!event) {
7708 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7709 } else {
7710 char *desc = sipe_cal_event_describe(event);
7711 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7712 g_free(desc);
7715 /* Logic
7716 if OOF
7717 OOF publish, Busy clean
7718 ilse if Busy
7719 OOF clean, Busy publish
7720 else
7721 OOF clean, Busy clean
7723 if (event && event->cal_status == SIPE_CAL_OOF) {
7724 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7725 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7726 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7727 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7728 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7729 } else {
7730 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7731 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7734 oof_note = sipe_ews_get_oof_note(sip->ews);
7735 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7736 oof_start = sip->ews->oof_start;
7737 oof_end = sip->ews->oof_end;
7739 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7741 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7742 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7744 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7745 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7746 } else {
7747 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7748 pub_cal_working_hours ? pub_cal_working_hours : "",
7749 pub_cal_free_busy ? pub_cal_free_busy : "",
7750 pub_calendar ? pub_calendar : "",
7751 pub_calendar2 ? pub_calendar2 : "",
7752 pub_oof_note ? pub_oof_note : "");
7754 send_presence_publish(sip, publications);
7755 g_free(publications);
7758 g_free(pub_cal_working_hours);
7759 g_free(pub_cal_free_busy);
7760 g_free(pub_calendar);
7761 g_free(pub_calendar2);
7762 g_free(pub_oof_note);
7764 /* repeat scheduling */
7765 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7768 static void send_presence_status(struct sipe_account_data *sip)
7770 PurpleStatus * status = purple_account_get_active_status(sip->account);
7772 if (!status) return;
7774 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7775 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7776 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7778 if (sip->ocs2007) {
7779 send_presence_category_publish(sip);
7780 } else {
7781 send_presence_soap(sip, FALSE);
7785 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7787 gboolean found = FALSE;
7788 const char *method = msg->method ? msg->method : "NOT FOUND";
7789 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7790 if (msg->response == 0) { /* request */
7791 if (sipe_strequal(method, "MESSAGE")) {
7792 process_incoming_message(sip, msg);
7793 found = TRUE;
7794 } else if (sipe_strequal(method, "NOTIFY")) {
7795 purple_debug_info("sipe","send->process_incoming_notify\n");
7796 process_incoming_notify(sip, msg, TRUE, FALSE);
7797 found = TRUE;
7798 } else if (sipe_strequal(method, "BENOTIFY")) {
7799 purple_debug_info("sipe","send->process_incoming_benotify\n");
7800 process_incoming_notify(sip, msg, TRUE, TRUE);
7801 found = TRUE;
7802 } else if (sipe_strequal(method, "INVITE")) {
7803 process_incoming_invite(sip, msg);
7804 found = TRUE;
7805 } else if (sipe_strequal(method, "REFER")) {
7806 process_incoming_refer(sip, msg);
7807 found = TRUE;
7808 } else if (sipe_strequal(method, "OPTIONS")) {
7809 process_incoming_options(sip, msg);
7810 found = TRUE;
7811 } else if (sipe_strequal(method, "INFO")) {
7812 process_incoming_info(sip, msg);
7813 found = TRUE;
7814 } else if (sipe_strequal(method, "ACK")) {
7815 // ACK's don't need any response
7816 found = TRUE;
7817 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7818 // LCS 2005 sends us these - just respond 200 OK
7819 found = TRUE;
7820 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7821 } else if (sipe_strequal(method, "BYE")) {
7822 process_incoming_bye(sip, msg);
7823 found = TRUE;
7824 } else {
7825 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7827 } else { /* response */
7828 struct transaction *trans = transactions_find(sip, msg);
7829 if (trans) {
7830 if (msg->response == 407) {
7831 gchar *resend, *auth;
7832 const gchar *ptmp;
7834 if (sip->proxy.retries > 30) return;
7835 sip->proxy.retries++;
7836 /* do proxy authentication */
7838 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7840 fill_auth(ptmp, &sip->proxy);
7841 auth = auth_header(sip, &sip->proxy, trans->msg);
7842 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7843 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7844 g_free(auth);
7845 resend = sipmsg_to_string(trans->msg);
7846 /* resend request */
7847 sendout_pkt(sip->gc, resend);
7848 g_free(resend);
7849 } else {
7850 if (msg->response < 200) {
7851 /* ignore provisional response */
7852 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7853 } else {
7854 sip->proxy.retries = 0;
7855 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7856 if (msg->response == 401)
7858 sip->registrar.retries++;
7860 else
7862 sip->registrar.retries = 0;
7864 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7865 } else {
7866 if (msg->response == 401) {
7867 gchar *resend, *auth, *ptmp;
7868 const char* auth_scheme;
7870 if (sip->registrar.retries > 4) return;
7871 sip->registrar.retries++;
7873 auth_scheme = sipe_get_auth_scheme_name(sip);
7874 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7876 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7877 if (!ptmp) {
7878 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7879 sip->gc->wants_to_die = TRUE;
7880 purple_connection_error(sip->gc, tmp2);
7881 g_free(tmp2);
7882 return;
7885 fill_auth(ptmp, &sip->registrar);
7886 auth = auth_header(sip, &sip->registrar, trans->msg);
7887 sipmsg_remove_header_now(trans->msg, "Authorization");
7888 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7889 g_free(auth);
7890 resend = sipmsg_to_string(trans->msg);
7891 /* resend request */
7892 sendout_pkt(sip->gc, resend);
7893 g_free(resend);
7897 if (trans->callback) {
7898 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7899 /* call the callback to process response*/
7900 (trans->callback)(sip, msg, trans);
7903 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7904 transactions_remove(sip, trans);
7908 found = TRUE;
7909 } else {
7910 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7913 if (!found) {
7914 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7918 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7920 char *cur;
7921 char *dummy;
7922 char *tmp;
7923 struct sipmsg *msg;
7924 int restlen;
7925 cur = conn->inbuf;
7927 /* according to the RFC remove CRLF at the beginning */
7928 while (*cur == '\r' || *cur == '\n') {
7929 cur++;
7931 if (cur != conn->inbuf) {
7932 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7933 conn->inbufused = strlen(conn->inbuf);
7936 /* Received a full Header? */
7937 sip->processing_input = TRUE;
7938 while (sip->processing_input &&
7939 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7940 time_t currtime = time(NULL);
7941 cur += 2;
7942 cur[0] = '\0';
7943 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7944 g_free(tmp);
7945 msg = sipmsg_parse_header(conn->inbuf);
7946 cur[0] = '\r';
7947 cur += 2;
7948 restlen = conn->inbufused - (cur - conn->inbuf);
7949 if (msg && restlen >= msg->bodylen) {
7950 dummy = g_malloc(msg->bodylen + 1);
7951 memcpy(dummy, cur, msg->bodylen);
7952 dummy[msg->bodylen] = '\0';
7953 msg->body = dummy;
7954 cur += msg->bodylen;
7955 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7956 conn->inbufused = strlen(conn->inbuf);
7957 } else {
7958 if (msg){
7959 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7960 sipmsg_free(msg);
7962 return;
7965 /*if (msg->body) {
7966 purple_debug_info("sipe", "body:\n%s", msg->body);
7969 // Verify the signature before processing it
7970 if (sip->registrar.gssapi_context) {
7971 struct sipmsg_breakdown msgbd;
7972 gchar *signature_input_str;
7973 gchar *rspauth;
7974 msgbd.msg = msg;
7975 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7976 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
7978 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7980 if (rspauth != NULL) {
7981 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7982 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7983 process_input_message(sip, msg);
7984 } else {
7985 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7986 purple_connection_error(sip->gc, _("Invalid message signature received"));
7987 sip->gc->wants_to_die = TRUE;
7989 } else if (msg->response == 401) {
7990 purple_connection_error(sip->gc, _("Authentication failed"));
7991 sip->gc->wants_to_die = TRUE;
7993 g_free(signature_input_str);
7995 g_free(rspauth);
7996 sipmsg_breakdown_free(&msgbd);
7997 } else {
7998 process_input_message(sip, msg);
8001 sipmsg_free(msg);
8005 static void sipe_udp_process(gpointer data, gint source,
8006 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8008 PurpleConnection *gc = data;
8009 struct sipe_account_data *sip = gc->proto_data;
8010 int len;
8012 static char buffer[65536];
8013 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8014 time_t currtime = time(NULL);
8015 struct sipmsg *msg;
8016 buffer[len] = '\0';
8017 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
8018 msg = sipmsg_parse_msg(buffer);
8019 if (msg) process_input_message(sip, msg);
8023 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8025 struct sipe_account_data *sip = gc->proto_data;
8026 PurpleSslConnection *gsc = sip->gsc;
8028 purple_debug_error("sipe", "%s",debug);
8029 purple_connection_error(gc, msg);
8031 /* Invalidate this connection. Next send will open a new one */
8032 if (gsc) {
8033 connection_remove(sip, gsc->fd);
8034 purple_ssl_close(gsc);
8036 sip->gsc = NULL;
8037 sip->fd = -1;
8040 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8041 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8043 PurpleConnection *gc = data;
8044 struct sipe_account_data *sip;
8045 struct sip_connection *conn;
8046 int readlen, len;
8047 gboolean firstread = TRUE;
8049 /* NOTE: This check *IS* necessary */
8050 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8051 purple_ssl_close(gsc);
8052 return;
8055 sip = gc->proto_data;
8056 conn = connection_find(sip, gsc->fd);
8057 if (conn == NULL) {
8058 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
8059 gc->wants_to_die = TRUE;
8060 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8061 return;
8064 /* Read all available data from the SSL connection */
8065 do {
8066 /* Increase input buffer size as needed */
8067 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8068 conn->inbuflen += SIMPLE_BUF_INC;
8069 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8070 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
8073 /* Try to read as much as there is space left in the buffer */
8074 readlen = conn->inbuflen - conn->inbufused - 1;
8075 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8077 if (len < 0 && errno == EAGAIN) {
8078 /* Try again later */
8079 return;
8080 } else if (len < 0) {
8081 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8082 return;
8083 } else if (firstread && (len == 0)) {
8084 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8085 return;
8088 conn->inbufused += len;
8089 firstread = FALSE;
8091 /* Equivalence indicates that there is possibly more data to read */
8092 } while (len == readlen);
8094 conn->inbuf[conn->inbufused] = '\0';
8095 process_input(sip, conn);
8099 static void sipe_input_cb(gpointer data, gint source,
8100 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8102 PurpleConnection *gc = data;
8103 struct sipe_account_data *sip = gc->proto_data;
8104 int len;
8105 struct sip_connection *conn = connection_find(sip, source);
8106 if (!conn) {
8107 purple_debug_error("sipe", "Connection not found!\n");
8108 return;
8111 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8112 conn->inbuflen += SIMPLE_BUF_INC;
8113 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8116 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8118 if (len < 0 && errno == EAGAIN)
8119 return;
8120 else if (len <= 0) {
8121 purple_debug_info("sipe", "sipe_input_cb: read error\n");
8122 connection_remove(sip, source);
8123 if (sip->fd == source) sip->fd = -1;
8124 return;
8127 conn->inbufused += len;
8128 conn->inbuf[conn->inbufused] = '\0';
8130 process_input(sip, conn);
8133 /* Callback for new connections on incoming TCP port */
8134 static void sipe_newconn_cb(gpointer data, gint source,
8135 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8137 PurpleConnection *gc = data;
8138 struct sipe_account_data *sip = gc->proto_data;
8139 struct sip_connection *conn;
8141 int newfd = accept(source, NULL, NULL);
8143 conn = connection_create(sip, newfd);
8145 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8148 static void login_cb(gpointer data, gint source,
8149 SIPE_UNUSED_PARAMETER const gchar *error_message)
8151 PurpleConnection *gc = data;
8152 struct sipe_account_data *sip;
8153 struct sip_connection *conn;
8155 if (!PURPLE_CONNECTION_IS_VALID(gc))
8157 if (source >= 0)
8158 close(source);
8159 return;
8162 if (source < 0) {
8163 purple_connection_error(gc, _("Could not connect"));
8164 return;
8167 sip = gc->proto_data;
8168 sip->fd = source;
8169 sip->last_keepalive = time(NULL);
8171 conn = connection_create(sip, source);
8173 do_register(sip);
8175 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8178 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8179 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8181 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8182 if (sip == NULL) return;
8184 do_register(sip);
8187 static guint sipe_ht_hash_nick(const char *nick)
8189 char *lc = g_utf8_strdown(nick, -1);
8190 guint bucket = g_str_hash(lc);
8191 g_free(lc);
8193 return bucket;
8196 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8198 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8201 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8203 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8205 sip->listen_data = NULL;
8207 if (listenfd == -1) {
8208 purple_connection_error(sip->gc, _("Could not create listen socket"));
8209 return;
8212 sip->fd = listenfd;
8214 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8215 sip->listenfd = sip->fd;
8217 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8219 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8220 do_register(sip);
8223 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8224 SIPE_UNUSED_PARAMETER const char *error_message)
8226 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8228 sip->query_data = NULL;
8230 if (!hosts || !hosts->data) {
8231 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8232 return;
8235 hosts = g_slist_remove(hosts, hosts->data);
8236 g_free(sip->serveraddr);
8237 sip->serveraddr = hosts->data;
8238 hosts = g_slist_remove(hosts, hosts->data);
8239 while (hosts) {
8240 void *tmp = hosts->data;
8241 hosts = g_slist_remove(hosts, tmp);
8242 hosts = g_slist_remove(hosts, tmp);
8243 g_free(tmp);
8246 /* create socket for incoming connections */
8247 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8248 sipe_udp_host_resolved_listen_cb, sip);
8249 if (sip->listen_data == NULL) {
8250 purple_connection_error(sip->gc, _("Could not create listen socket"));
8251 return;
8255 static const struct sipe_service_data *current_service = NULL;
8257 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8258 PurpleSslErrorType error,
8259 gpointer data)
8261 PurpleConnection *gc = data;
8262 struct sipe_account_data *sip;
8264 /* If the connection is already disconnected, we don't need to do anything else */
8265 if (!PURPLE_CONNECTION_IS_VALID(gc))
8266 return;
8268 sip = gc->proto_data;
8269 current_service = sip->service_data;
8270 if (current_service) {
8271 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8272 current_service->transport ? current_service->transport : "NULL",
8273 current_service->service ? current_service->service : "NULL");
8276 sip->fd = -1;
8277 sip->gsc = NULL;
8279 switch(error) {
8280 case PURPLE_SSL_CONNECT_FAILED:
8281 purple_connection_error(gc, _("Connection failed"));
8282 break;
8283 case PURPLE_SSL_HANDSHAKE_FAILED:
8284 purple_connection_error(gc, _("SSL handshake failed"));
8285 break;
8286 case PURPLE_SSL_CERTIFICATE_INVALID:
8287 purple_connection_error(gc, _("SSL certificate invalid"));
8288 break;
8292 static void
8293 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8295 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8296 PurpleProxyConnectData *connect_data;
8298 sip->listen_data = NULL;
8300 sip->listenfd = listenfd;
8301 if (sip->listenfd == -1) {
8302 purple_connection_error(sip->gc, _("Could not create listen socket"));
8303 return;
8306 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8307 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8308 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8309 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8310 sipe_newconn_cb, sip->gc);
8311 purple_debug_info("sipe", "connecting to %s port %d\n",
8312 sip->realhostname, sip->realport);
8313 /* open tcp connection to the server */
8314 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8315 sip->realport, login_cb, sip->gc);
8317 if (connect_data == NULL) {
8318 purple_connection_error(sip->gc, _("Could not create socket"));
8322 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8324 PurpleAccount *account = sip->account;
8325 PurpleConnection *gc = sip->gc;
8327 if (port == 0) {
8328 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8331 sip->realhostname = hostname;
8332 sip->realport = port;
8334 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8335 hostname, port);
8337 /* TODO: is there a good default grow size? */
8338 if (sip->transport != SIPE_TRANSPORT_UDP)
8339 sip->txbuf = purple_circ_buffer_new(0);
8341 if (sip->transport == SIPE_TRANSPORT_TLS) {
8342 /* SSL case */
8343 if (!purple_ssl_is_supported()) {
8344 gc->wants_to_die = TRUE;
8345 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8346 return;
8349 purple_debug_info("sipe", "using SSL\n");
8351 sip->gsc = purple_ssl_connect(account, hostname, port,
8352 login_cb_ssl, sipe_ssl_connect_failure, gc);
8353 if (sip->gsc == NULL) {
8354 purple_connection_error(gc, _("Could not create SSL context"));
8355 return;
8357 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8358 /* UDP case */
8359 purple_debug_info("sipe", "using UDP\n");
8361 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8362 if (sip->query_data == NULL) {
8363 purple_connection_error(gc, _("Could not resolve hostname"));
8365 } else {
8366 /* TCP case */
8367 purple_debug_info("sipe", "using TCP\n");
8368 /* create socket for incoming connections */
8369 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8370 sipe_tcp_connect_listen_cb, sip);
8371 if (sip->listen_data == NULL) {
8372 purple_connection_error(gc, _("Could not create listen socket"));
8373 return;
8378 /* Service list for autodection */
8379 static const struct sipe_service_data service_autodetect[] = {
8380 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8381 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8382 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8383 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8384 { NULL, NULL, 0 }
8387 /* Service list for SSL/TLS */
8388 static const struct sipe_service_data service_tls[] = {
8389 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8390 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8391 { NULL, NULL, 0 }
8394 /* Service list for TCP */
8395 static const struct sipe_service_data service_tcp[] = {
8396 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8397 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8398 { NULL, NULL, 0 }
8401 /* Service list for UDP */
8402 static const struct sipe_service_data service_udp[] = {
8403 { "sip", "udp", SIPE_TRANSPORT_UDP },
8404 { NULL, NULL, 0 }
8407 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8408 static void resolve_next_service(struct sipe_account_data *sip,
8409 const struct sipe_service_data *start)
8411 if (start) {
8412 sip->service_data = start;
8413 } else {
8414 sip->service_data++;
8415 if (sip->service_data->service == NULL) {
8416 gchar *hostname;
8417 /* Try connecting to the SIP hostname directly */
8418 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8419 if (sip->auto_transport) {
8420 // If SSL is supported, default to using it; OCS servers aren't configured
8421 // by default to accept TCP
8422 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8423 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8424 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8427 hostname = g_strdup(sip->sipdomain);
8428 create_connection(sip, hostname, 0);
8429 return;
8433 /* Try to resolve next service */
8434 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8435 sip->service_data->transport,
8436 sip->sipdomain,
8437 srvresolved, sip);
8440 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8442 struct sipe_account_data *sip = data;
8444 sip->srv_query_data = NULL;
8446 /* find the host to connect to */
8447 if (results) {
8448 gchar *hostname = g_strdup(resp->hostname);
8449 int port = resp->port;
8450 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8451 hostname, port);
8452 g_free(resp);
8454 sip->transport = sip->service_data->type;
8456 create_connection(sip, hostname, port);
8457 } else {
8458 resolve_next_service(sip, NULL);
8462 static void sipe_login(PurpleAccount *account)
8464 PurpleConnection *gc;
8465 struct sipe_account_data *sip;
8466 gchar **signinname_login, **userserver;
8467 const char *transport;
8468 const char *email;
8470 const char *username = purple_account_get_username(account);
8471 gc = purple_account_get_connection(account);
8473 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8475 if (strpbrk(username, "\t\v\r\n") != NULL) {
8476 gc->wants_to_die = TRUE;
8477 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8478 return;
8481 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8482 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8483 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8484 sip->gc = gc;
8485 sip->account = account;
8486 sip->reregister_set = FALSE;
8487 sip->reauthenticate_set = FALSE;
8488 sip->subscribed = FALSE;
8489 sip->subscribed_buddies = FALSE;
8490 sip->initial_state_published = FALSE;
8492 /* username format: <username>,[<optional login>] */
8493 signinname_login = g_strsplit(username, ",", 2);
8494 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8496 /* ensure that username format is name@domain */
8497 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8498 g_strfreev(signinname_login);
8499 gc->wants_to_die = TRUE;
8500 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8501 return;
8503 sip->username = g_strdup(signinname_login[0]);
8505 /* ensure that email format is name@domain if provided */
8506 email = purple_account_get_string(sip->account, "email", NULL);
8507 if (!is_empty(email) &&
8508 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8510 gc->wants_to_die = TRUE;
8511 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8512 return;
8514 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8516 /* login name specified? */
8517 if (signinname_login[1] && strlen(signinname_login[1])) {
8518 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8519 gboolean has_domain = domain_user[1] != NULL;
8520 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8521 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8522 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8523 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8524 sip->authdomain ? sip->authdomain : "", sip->authuser);
8525 g_strfreev(domain_user);
8528 userserver = g_strsplit(signinname_login[0], "@", 2);
8529 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8530 purple_connection_set_display_name(gc, userserver[0]);
8531 sip->sipdomain = g_strdup(userserver[1]);
8532 g_strfreev(userserver);
8533 g_strfreev(signinname_login);
8535 if (strchr(sip->username, ' ') != NULL) {
8536 gc->wants_to_die = TRUE;
8537 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8538 return;
8541 sip->password = g_strdup(purple_connection_get_password(gc));
8543 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8544 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8545 g_free, (GDestroyNotify)g_hash_table_destroy);
8546 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8547 g_free, (GDestroyNotify)sipe_subscription_free);
8549 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8551 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8553 g_free(sip->status);
8554 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8556 sip->auto_transport = FALSE;
8557 transport = purple_account_get_string(account, "transport", "auto");
8558 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8559 if (userserver[0]) {
8560 /* Use user specified server[:port] */
8561 int port = 0;
8563 if (userserver[1])
8564 port = atoi(userserver[1]);
8566 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8567 userserver[0], port);
8569 if (sipe_strequal(transport, "auto")) {
8570 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8571 } else if (sipe_strequal(transport, "tls")) {
8572 sip->transport = SIPE_TRANSPORT_TLS;
8573 } else if (sipe_strequal(transport, "tcp")) {
8574 sip->transport = SIPE_TRANSPORT_TCP;
8575 } else {
8576 sip->transport = SIPE_TRANSPORT_UDP;
8579 create_connection(sip, g_strdup(userserver[0]), port);
8580 } else {
8581 /* Server auto-discovery */
8582 if (sipe_strequal(transport, "auto")) {
8583 sip->auto_transport = TRUE;
8584 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8585 current_service++;
8586 resolve_next_service(sip, current_service);
8587 } else {
8588 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8590 } else if (sipe_strequal(transport, "tls")) {
8591 resolve_next_service(sip, service_tls);
8592 } else if (sipe_strequal(transport, "tcp")) {
8593 resolve_next_service(sip, service_tcp);
8594 } else {
8595 resolve_next_service(sip, service_udp);
8598 g_strfreev(userserver);
8601 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8603 connection_free_all(sip);
8605 g_free(sip->epid);
8606 sip->epid = NULL;
8608 if (sip->query_data != NULL)
8609 purple_dnsquery_destroy(sip->query_data);
8610 sip->query_data = NULL;
8612 if (sip->srv_query_data != NULL)
8613 purple_srv_cancel(sip->srv_query_data);
8614 sip->srv_query_data = NULL;
8616 if (sip->listen_data != NULL)
8617 purple_network_listen_cancel(sip->listen_data);
8618 sip->listen_data = NULL;
8620 if (sip->gsc != NULL)
8621 purple_ssl_close(sip->gsc);
8622 sip->gsc = NULL;
8624 sipe_auth_free(&sip->registrar);
8625 sipe_auth_free(&sip->proxy);
8627 if (sip->txbuf)
8628 purple_circ_buffer_destroy(sip->txbuf);
8629 sip->txbuf = NULL;
8631 g_free(sip->realhostname);
8632 sip->realhostname = NULL;
8634 g_free(sip->server_version);
8635 sip->server_version = NULL;
8637 if (sip->listenpa)
8638 purple_input_remove(sip->listenpa);
8639 sip->listenpa = 0;
8640 if (sip->tx_handler)
8641 purple_input_remove(sip->tx_handler);
8642 sip->tx_handler = 0;
8643 if (sip->resendtimeout)
8644 purple_timeout_remove(sip->resendtimeout);
8645 sip->resendtimeout = 0;
8646 if (sip->timeouts) {
8647 GSList *entry = sip->timeouts;
8648 while (entry) {
8649 struct scheduled_action *sched_action = entry->data;
8650 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8651 purple_timeout_remove(sched_action->timeout_handler);
8652 if (sched_action->destroy) {
8653 (*sched_action->destroy)(sched_action->payload);
8655 g_free(sched_action->name);
8656 g_free(sched_action);
8657 entry = entry->next;
8660 g_slist_free(sip->timeouts);
8662 if (sip->allow_events) {
8663 GSList *entry = sip->allow_events;
8664 while (entry) {
8665 g_free(entry->data);
8666 entry = entry->next;
8669 g_slist_free(sip->allow_events);
8671 if (sip->containers) {
8672 GSList *entry = sip->containers;
8673 while (entry) {
8674 free_container((struct sipe_container *)entry->data);
8675 entry = entry->next;
8678 g_slist_free(sip->containers);
8680 if (sip->contact)
8681 g_free(sip->contact);
8682 sip->contact = NULL;
8683 if (sip->regcallid)
8684 g_free(sip->regcallid);
8685 sip->regcallid = NULL;
8687 if (sip->serveraddr)
8688 g_free(sip->serveraddr);
8689 sip->serveraddr = NULL;
8691 if (sip->focus_factory_uri)
8692 g_free(sip->focus_factory_uri);
8693 sip->focus_factory_uri = NULL;
8695 sip->fd = -1;
8696 sip->processing_input = FALSE;
8698 if (sip->ews) {
8699 sipe_ews_free(sip->ews);
8701 sip->ews = NULL;
8705 * A callback for g_hash_table_foreach_remove
8707 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8708 SIPE_UNUSED_PARAMETER gpointer user_data)
8710 sipe_free_buddy((struct sipe_buddy *) buddy);
8712 /* We must return TRUE as the key/value have already been deleted */
8713 return(TRUE);
8716 static void sipe_close(PurpleConnection *gc)
8718 struct sipe_account_data *sip = gc->proto_data;
8720 if (sip) {
8721 /* leave all conversations */
8722 sipe_session_close_all(sip);
8723 sipe_session_remove_all(sip);
8725 if (sip->csta) {
8726 sip_csta_close(sip);
8729 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8730 /* unsubscribe all */
8731 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8733 /* unregister */
8734 do_register_exp(sip, 0);
8737 sipe_connection_cleanup(sip);
8738 g_free(sip->sipdomain);
8739 g_free(sip->username);
8740 g_free(sip->email);
8741 g_free(sip->password);
8742 g_free(sip->authdomain);
8743 g_free(sip->authuser);
8744 g_free(sip->status);
8745 g_free(sip->note);
8747 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8748 g_hash_table_destroy(sip->buddies);
8749 g_hash_table_destroy(sip->our_publications);
8750 g_hash_table_destroy(sip->user_state_publications);
8751 g_hash_table_destroy(sip->subscriptions);
8752 g_hash_table_destroy(sip->filetransfers);
8754 if (sip->groups) {
8755 GSList *entry = sip->groups;
8756 while (entry) {
8757 struct sipe_group *group = entry->data;
8758 g_free(group->name);
8759 g_free(group);
8760 entry = entry->next;
8763 g_slist_free(sip->groups);
8765 if (sip->our_publication_keys) {
8766 GSList *entry = sip->our_publication_keys;
8767 while (entry) {
8768 g_free(entry->data);
8769 entry = entry->next;
8772 g_slist_free(sip->our_publication_keys);
8774 while (sip->transactions)
8775 transactions_remove(sip, sip->transactions->data);
8777 g_free(gc->proto_data);
8778 gc->proto_data = NULL;
8781 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8782 SIPE_UNUSED_PARAMETER void *user_data)
8784 PurpleAccount *acct = purple_connection_get_account(gc);
8785 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8786 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8787 if (conv == NULL)
8788 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8789 purple_conversation_present(conv);
8790 g_free(id);
8793 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8794 SIPE_UNUSED_PARAMETER void *user_data)
8797 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8798 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8801 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8802 SIPE_UNUSED_PARAMETER struct transaction *trans)
8804 PurpleNotifySearchResults *results;
8805 PurpleNotifySearchColumn *column;
8806 xmlnode *searchResults;
8807 xmlnode *mrow;
8808 int match_count = 0;
8809 gboolean more = FALSE;
8810 gchar *secondary;
8812 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8814 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8815 if (!searchResults) {
8816 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8817 return FALSE;
8820 results = purple_notify_searchresults_new();
8822 if (results == NULL) {
8823 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8824 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8826 xmlnode_free(searchResults);
8827 return FALSE;
8830 column = purple_notify_searchresults_column_new(_("User name"));
8831 purple_notify_searchresults_column_add(results, column);
8833 column = purple_notify_searchresults_column_new(_("Name"));
8834 purple_notify_searchresults_column_add(results, column);
8836 column = purple_notify_searchresults_column_new(_("Company"));
8837 purple_notify_searchresults_column_add(results, column);
8839 column = purple_notify_searchresults_column_new(_("Country"));
8840 purple_notify_searchresults_column_add(results, column);
8842 column = purple_notify_searchresults_column_new(_("Email"));
8843 purple_notify_searchresults_column_add(results, column);
8845 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8846 GList *row = NULL;
8848 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8849 row = g_list_append(row, g_strdup(uri_parts[1]));
8850 g_strfreev(uri_parts);
8852 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8853 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8854 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8855 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8857 purple_notify_searchresults_row_add(results, row);
8858 match_count++;
8861 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8862 char *data = xmlnode_get_data_unescaped(mrow);
8863 more = (g_strcasecmp(data, "true") == 0);
8864 g_free(data);
8867 secondary = g_strdup_printf(
8868 dngettext(GETTEXT_PACKAGE,
8869 "Found %d contact%s:",
8870 "Found %d contacts%s:", match_count),
8871 match_count, more ? _(" (more matched your query)") : "");
8873 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8874 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8875 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8877 g_free(secondary);
8878 xmlnode_free(searchResults);
8879 return TRUE;
8882 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8884 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8885 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8886 unsigned i = 0;
8888 if (!attrs) return;
8890 do {
8891 PurpleRequestField *field = entries->data;
8892 const char *id = purple_request_field_get_id(field);
8893 const char *value = purple_request_field_string_get_value(field);
8895 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8897 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8898 } while ((entries = g_list_next(entries)) != NULL);
8899 attrs[i] = NULL;
8901 if (i > 0) {
8902 struct sipe_account_data *sip = gc->proto_data;
8903 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8904 gchar *query = g_strjoinv(NULL, attrs);
8905 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8906 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8907 send_soap_request_with_cb(sip, domain_uri, body,
8908 (TransCallback) process_search_contact_response, NULL);
8909 g_free(domain_uri);
8910 g_free(body);
8911 g_free(query);
8914 g_strfreev(attrs);
8917 static void sipe_show_find_contact(PurplePluginAction *action)
8919 PurpleConnection *gc = (PurpleConnection *) action->context;
8920 PurpleRequestFields *fields;
8921 PurpleRequestFieldGroup *group;
8922 PurpleRequestField *field;
8924 fields = purple_request_fields_new();
8925 group = purple_request_field_group_new(NULL);
8926 purple_request_fields_add_group(fields, group);
8928 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8929 purple_request_field_group_add_field(group, field);
8930 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8931 purple_request_field_group_add_field(group, field);
8932 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8933 purple_request_field_group_add_field(group, field);
8934 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8935 purple_request_field_group_add_field(group, field);
8937 purple_request_fields(gc,
8938 _("Search"),
8939 _("Search for a contact"),
8940 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8941 fields,
8942 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8943 _("_Cancel"), NULL,
8944 purple_connection_get_account(gc), NULL, NULL, gc);
8947 static void sipe_show_about_plugin(PurplePluginAction *action)
8949 PurpleConnection *gc = (PurpleConnection *) action->context;
8950 char *tmp = g_strdup_printf(
8952 * Non-translatable parts, like markup, are hard-coded
8953 * into the format string. This requires more translatable
8954 * texts but it makes the translations less error prone.
8956 "<b><font size=\"+1\">SIPE " SIPE_VERSION " </font></b><br/>"
8957 "<br/>"
8958 /* 1 */ "%s:<br/>"
8959 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8960 "<li> - MS Office Communications Server 2007</li><br/>"
8961 "<li> - MS Live Communications Server 2005</li><br/>"
8962 "<li> - MS Live Communications Server 2003</li><br/>"
8963 "<li> - Reuters Messaging</li><br/>"
8964 "<br/>"
8965 /* 2 */ "%s: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8966 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8967 /* 5 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8968 /* 6 */ "%s: GPLv2+<br/>"
8969 "<br/>"
8970 /* 7 */ "%s:<br/>"
8971 " - CERN<br/>"
8972 " - Reuters Messaging network<br/>"
8973 " - Deutsche Bank<br/>"
8974 " - Merrill Lynch<br/>"
8975 " - Wachovia<br/>"
8976 " - Intel<br/>"
8977 " - Nokia<br/>"
8978 " - HP<br/>"
8979 " - Symantec<br/>"
8980 " - Accenture<br/>"
8981 " - Siemens<br/>"
8982 " - Alcatel-Lucent<br/>"
8983 " - BT<br/>"
8984 "<br/>"
8985 /* 8,9 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
8986 "<br/>"
8987 /* 10 */ "<b>%s:</b><br/>"
8988 " - Anibal Avelar<br/>"
8989 " - Gabriel Burt<br/>"
8990 " - Stefan Becker<br/>"
8991 " - pier11<br/>"
8992 " - Jakub Adam<br/>"
8993 " - Tomáš Hrabčík<br/>"
8994 "<br/>"
8995 /* 11 */ "%s<br/>"
8997 /* The next 11 texts make up the SIPE about note text */
8998 /* About note, part 1/11: introduction */
8999 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9000 /* About note, part 2/11: home page URL (label) */
9001 _("Home"),
9002 /* About note, part 3/11: support forum URL (label) */
9003 _("Support"),
9004 /* About note, part 4/11: support forum name (hyperlink text) */
9005 _("Help Forum"),
9006 /* About note, part 5/11: translation service URL (label) */
9007 _("Translations"),
9008 /* About note, part 6/11: license type (label) */
9009 _("License"),
9010 /* About note, part 7/11: known users */
9011 _("We support users in such organizations as"),
9012 /* About note, part 8/11: translation request, text before Transifex.net URL */
9013 /* append a space if text is not empty */
9014 _("Please help us to translate SIPE to your native language here at "),
9015 /* About note, part 9/11: translation request, text after Transifex.net URL */
9016 /* start with a space if text is not empty */
9017 _(" using convenient web interface"),
9018 /* About note, part 10/11: author list (header) */
9019 _("Authors"),
9020 /* About note, part 11/11: Localization credit */
9021 /* PLEASE NOTE: do *NOT* simply translate the english original */
9022 /* but write something similar to the following sentence: */
9023 /* "Localization for <language name> (<language code>): <name>" */
9024 _("Original texts in English (en): SIPE developers")
9026 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9027 g_free(tmp);
9030 static void sipe_republish_calendar(PurplePluginAction *action)
9032 PurpleConnection *gc = (PurpleConnection *) action->context;
9033 struct sipe_account_data *sip = gc->proto_data;
9035 sipe_update_calendar(sip);
9038 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9039 gpointer value,
9040 GString* str)
9042 struct sipe_publication *publication = value;
9044 g_string_append_printf( str,
9045 SIPE_PUB_XML_PUBLICATION_CLEAR,
9046 publication->category,
9047 publication->instance,
9048 publication->container,
9049 publication->version,
9050 "static");
9053 static void sipe_reset_status(PurplePluginAction *action)
9055 PurpleConnection *gc = (PurpleConnection *) action->context;
9056 struct sipe_account_data *sip = gc->proto_data;
9058 if (sip->ocs2007) /* 2007+ */
9060 GString* str = g_string_new(NULL);
9061 gchar *publications;
9063 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9064 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
9065 return;
9068 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9069 publications = g_string_free(str, FALSE);
9071 send_presence_publish(sip, publications);
9072 g_free(publications);
9074 else /* 2005 */
9076 send_presence_soap0(sip, FALSE, TRUE);
9080 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9081 gpointer context)
9083 PurpleConnection *gc = (PurpleConnection *)context;
9084 struct sipe_account_data *sip = gc->proto_data;
9085 GList *menu = NULL;
9086 PurplePluginAction *act;
9087 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9089 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9090 menu = g_list_prepend(menu, act);
9092 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9093 menu = g_list_prepend(menu, act);
9095 if (sipe_strequal(calendar, "EXCH")) {
9096 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9097 menu = g_list_prepend(menu, act);
9100 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9101 menu = g_list_prepend(menu, act);
9103 menu = g_list_reverse(menu);
9105 return menu;
9108 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9112 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9114 return TRUE;
9118 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9120 return TRUE;
9124 static char *sipe_status_text(PurpleBuddy *buddy)
9126 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9127 const PurpleStatus *status = purple_presence_get_active_status(presence);
9128 const char *status_id = purple_status_get_id(status);
9129 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9130 struct sipe_buddy *sbuddy;
9131 char *text = NULL;
9133 if (!sip) return NULL; /* happens on pidgin exit */
9135 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9136 if (sbuddy) {
9137 const char *activity_str = sbuddy->activity ?
9138 sbuddy->activity :
9139 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9140 purple_status_get_name(status) : NULL;
9142 if (activity_str && sbuddy->note)
9144 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9146 else if (activity_str)
9148 text = g_strdup(activity_str);
9150 else if (sbuddy->note)
9152 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9156 return text;
9159 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9161 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9162 const PurpleStatus *status = purple_presence_get_active_status(presence);
9163 struct sipe_account_data *sip;
9164 struct sipe_buddy *sbuddy;
9165 char *note = NULL;
9166 gboolean is_oof_note = FALSE;
9167 char *activity = NULL;
9168 char *calendar = NULL;
9169 char *meeting_subject = NULL;
9170 char *meeting_location = NULL;
9172 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9173 if (sip) //happens on pidgin exit
9175 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9176 if (sbuddy)
9178 note = sbuddy->note;
9179 is_oof_note = sbuddy->is_oof_note;
9180 activity = sbuddy->activity;
9181 calendar = sipe_cal_get_description(sbuddy);
9182 meeting_subject = sbuddy->meeting_subject;
9183 meeting_location = sbuddy->meeting_location;
9187 //Layout
9188 if (purple_presence_is_online(presence))
9190 const char *status_str = activity ? activity : purple_status_get_name(status);
9192 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9194 if (purple_presence_is_online(presence) &&
9195 !is_empty(calendar))
9197 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9199 g_free(calendar);
9200 if (!is_empty(meeting_location))
9202 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9204 if (!is_empty(meeting_subject))
9206 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9209 if (note)
9211 char *tmp = g_strdup_printf("<i>%s</i>", note);
9212 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9214 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9215 g_free(tmp);
9220 #if PURPLE_VERSION_CHECK(2,5,0)
9221 static GHashTable *
9222 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9224 GHashTable *table;
9225 table = g_hash_table_new(g_str_hash, g_str_equal);
9226 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9227 return table;
9229 #endif
9231 static PurpleBuddy *
9232 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9234 PurpleBuddy *clone;
9235 const gchar *server_alias, *email;
9236 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9238 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9240 purple_blist_add_buddy(clone, NULL, group, NULL);
9242 server_alias = purple_buddy_get_server_alias(buddy);
9243 if (server_alias) {
9244 purple_blist_server_alias_buddy(clone, server_alias);
9247 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9248 if (email) {
9249 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9252 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9253 //for UI to update;
9254 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9255 return clone;
9258 static void
9259 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9261 PurpleBuddy *buddy, *b;
9262 PurpleConnection *gc;
9263 PurpleGroup * group = purple_find_group(group_name);
9265 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9267 buddy = (PurpleBuddy *)node;
9269 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9270 gc = purple_account_get_connection(buddy->account);
9272 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9273 if (!b){
9274 purple_blist_add_buddy_clone(group, buddy);
9277 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9280 static void
9281 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9283 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9285 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9287 /* 2007+ conference */
9288 if (sip->ocs2007)
9290 sipe_conf_add(sip, buddy->name);
9292 else /* 2005- multiparty chat */
9294 gchar *self = sip_uri_self(sip);
9295 struct sip_session *session;
9297 session = sipe_session_add_chat(sip);
9298 session->chat_title = sipe_chat_get_name(session->callid);
9299 session->roster_manager = g_strdup(self);
9301 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9302 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9303 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9304 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9306 g_free(self);
9310 static gboolean
9311 sipe_is_election_finished(struct sip_session *session)
9313 gboolean res = TRUE;
9315 SIPE_DIALOG_FOREACH {
9316 if (dialog->election_vote == 0) {
9317 res = FALSE;
9318 break;
9320 } SIPE_DIALOG_FOREACH_END;
9322 if (res) {
9323 session->is_voting_in_progress = FALSE;
9325 return res;
9328 static void
9329 sipe_election_start(struct sipe_account_data *sip,
9330 struct sip_session *session)
9332 int election_timeout;
9334 if (session->is_voting_in_progress) {
9335 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9336 return;
9337 } else {
9338 session->is_voting_in_progress = TRUE;
9340 session->bid = rand();
9342 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9344 SIPE_DIALOG_FOREACH {
9345 /* reset election_vote for each chat participant */
9346 dialog->election_vote = 0;
9348 /* send RequestRM to each chat participant*/
9349 sipe_send_election_request_rm(sip, dialog, session->bid);
9350 } SIPE_DIALOG_FOREACH_END;
9352 election_timeout = 15; /* sec */
9353 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9357 * @param who a URI to whom to invite to chat
9359 void
9360 sipe_invite_to_chat(struct sipe_account_data *sip,
9361 struct sip_session *session,
9362 const gchar *who)
9364 /* a conference */
9365 if (session->focus_uri)
9367 sipe_invite_conf(sip, session, who);
9369 else /* a multi-party chat */
9371 gchar *self = sip_uri_self(sip);
9372 if (session->roster_manager) {
9373 if (sipe_strequal(session->roster_manager, self)) {
9374 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9375 } else {
9376 sipe_refer(sip, session, who);
9378 } else {
9379 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9381 session->pending_invite_queue = slist_insert_unique_sorted(
9382 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9384 sipe_election_start(sip, session);
9386 g_free(self);
9390 void
9391 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9392 struct sip_session *session)
9394 gchar *invitee;
9395 GSList *entry = session->pending_invite_queue;
9397 while (entry) {
9398 invitee = entry->data;
9399 sipe_invite_to_chat(sip, session, invitee);
9400 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9401 g_free(invitee);
9405 static void
9406 sipe_election_result(struct sipe_account_data *sip,
9407 void *sess)
9409 struct sip_session *session = (struct sip_session *)sess;
9410 gchar *rival;
9411 gboolean has_won = TRUE;
9413 if (session->roster_manager) {
9414 purple_debug_info("sipe",
9415 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9416 return;
9419 session->is_voting_in_progress = FALSE;
9421 SIPE_DIALOG_FOREACH {
9422 if (dialog->election_vote < 0) {
9423 has_won = FALSE;
9424 rival = dialog->with;
9425 break;
9427 } SIPE_DIALOG_FOREACH_END;
9429 if (has_won) {
9430 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9432 session->roster_manager = sip_uri_self(sip);
9434 SIPE_DIALOG_FOREACH {
9435 /* send SetRM to each chat participant*/
9436 sipe_send_election_set_rm(sip, dialog);
9437 } SIPE_DIALOG_FOREACH_END;
9438 } else {
9439 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9441 session->bid = 0;
9443 sipe_process_pending_invite_queue(sip, session);
9447 * For 2007+ conference only.
9449 static void
9450 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9452 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9453 struct sip_session *session;
9455 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9456 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9458 session = sipe_session_find_chat_by_title(sip, chat_title);
9460 sipe_conf_modify_user_role(sip, session, buddy->name);
9464 * For 2007+ conference only.
9466 static void
9467 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9469 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9470 struct sip_session *session;
9472 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9473 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9475 session = sipe_session_find_chat_by_title(sip, chat_title);
9477 sipe_conf_delete_user(sip, session, buddy->name);
9480 static void
9481 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9483 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9484 struct sip_session *session;
9486 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9487 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9489 session = sipe_session_find_chat_by_title(sip, chat_title);
9491 sipe_invite_to_chat(sip, session, buddy->name);
9494 static void
9495 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9497 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9499 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9500 if (phone) {
9501 char *tel_uri = sip_to_tel_uri(phone);
9503 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9504 sip_csta_make_call(sip, tel_uri);
9506 g_free(tel_uri);
9510 static void
9511 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9513 const gchar *email;
9514 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9516 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9517 if (email)
9519 char *mailto = g_strdup_printf("mailto:%s", email);
9520 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9521 #ifndef _WIN32
9523 pid_t pid;
9524 char *const parmList[] = {"xdg-email", mailto, NULL};
9525 if ((pid = fork()) == -1)
9527 purple_debug_info("sipe", "fork() error\n");
9529 else if (pid == 0)
9531 execvp(parmList[0], parmList);
9532 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9535 #else
9537 BOOL ret;
9538 _flushall();
9539 errno = 0;
9540 //@TODO resolve env variable %WINDIR% first
9541 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9542 if (errno)
9544 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9547 #endif
9549 g_free(mailto);
9551 else
9553 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9558 * A menu which appear when right-clicking on buddy in contact list.
9560 static GList *
9561 sipe_buddy_menu(PurpleBuddy *buddy)
9563 PurpleBlistNode *g_node;
9564 PurpleGroup *group, *gr_parent;
9565 PurpleMenuAction *act;
9566 GList *menu = NULL;
9567 GList *menu_groups = NULL;
9568 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9569 const char *email;
9570 const char *phone;
9571 const char *phone_disp_str;
9572 gchar *self = sip_uri_self(sip);
9574 SIPE_SESSION_FOREACH {
9575 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9577 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9579 PurpleConvChatBuddyFlags flags;
9580 PurpleConvChatBuddyFlags flags_us;
9582 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9583 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9584 if (session->focus_uri
9585 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9586 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9588 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9589 act = purple_menu_action_new(label,
9590 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9591 session->chat_title, NULL);
9592 g_free(label);
9593 menu = g_list_prepend(menu, act);
9596 if (session->focus_uri
9597 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9599 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9600 act = purple_menu_action_new(label,
9601 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9602 session->chat_title, NULL);
9603 g_free(label);
9604 menu = g_list_prepend(menu, act);
9607 else
9609 if (!session->focus_uri
9610 || (session->focus_uri && !session->locked))
9612 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9613 act = purple_menu_action_new(label,
9614 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9615 session->chat_title, NULL);
9616 g_free(label);
9617 menu = g_list_prepend(menu, act);
9621 } SIPE_SESSION_FOREACH_END;
9623 act = purple_menu_action_new(_("New chat"),
9624 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9625 NULL, NULL);
9626 menu = g_list_prepend(menu, act);
9628 if (sip->csta && !sip->csta->line_status) {
9629 gchar *tmp = NULL;
9630 /* work phone */
9631 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9632 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9633 if (phone) {
9634 gchar *label = g_strdup_printf(_("Work %s"),
9635 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9636 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9637 g_free(tmp);
9638 tmp = NULL;
9639 g_free(label);
9640 menu = g_list_prepend(menu, act);
9643 /* mobile phone */
9644 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9645 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9646 if (phone) {
9647 gchar *label = g_strdup_printf(_("Mobile %s"),
9648 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9649 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9650 g_free(tmp);
9651 tmp = NULL;
9652 g_free(label);
9653 menu = g_list_prepend(menu, act);
9656 /* home phone */
9657 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9658 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9659 if (phone) {
9660 gchar *label = g_strdup_printf(_("Home %s"),
9661 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9662 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9663 g_free(tmp);
9664 tmp = NULL;
9665 g_free(label);
9666 menu = g_list_prepend(menu, act);
9669 /* other phone */
9670 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9671 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9672 if (phone) {
9673 gchar *label = g_strdup_printf(_("Other %s"),
9674 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9675 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9676 g_free(tmp);
9677 tmp = NULL;
9678 g_free(label);
9679 menu = g_list_prepend(menu, act);
9682 /* custom1 phone */
9683 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9684 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9685 if (phone) {
9686 gchar *label = g_strdup_printf(_("Custom1 %s"),
9687 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9688 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9689 g_free(tmp);
9690 tmp = NULL;
9691 g_free(label);
9692 menu = g_list_prepend(menu, act);
9696 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9697 if (email) {
9698 act = purple_menu_action_new(_("Send email..."),
9699 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9700 NULL, NULL);
9701 menu = g_list_prepend(menu, act);
9704 gr_parent = purple_buddy_get_group(buddy);
9705 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9706 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9707 continue;
9709 group = (PurpleGroup *)g_node;
9710 if (group == gr_parent)
9711 continue;
9713 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9714 continue;
9716 act = purple_menu_action_new(purple_group_get_name(group),
9717 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9718 group->name, NULL);
9719 menu_groups = g_list_prepend(menu_groups, act);
9721 menu_groups = g_list_reverse(menu_groups);
9723 act = purple_menu_action_new(_("Copy to"),
9724 NULL,
9725 NULL, menu_groups);
9726 menu = g_list_prepend(menu, act);
9727 menu = g_list_reverse(menu);
9729 g_free(self);
9730 return menu;
9733 static void
9734 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9736 struct sipe_account_data *sip = chat->account->gc->proto_data;
9737 struct sip_session *session;
9739 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9740 sipe_conf_modify_conference_lock(sip, session, locked);
9743 static void
9744 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9746 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9747 sipe_conf_modify_lock(chat, FALSE);
9750 static void
9751 sipe_chat_menu_lock_cb(PurpleChat *chat)
9753 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9754 sipe_conf_modify_lock(chat, TRUE);
9757 static GList *
9758 sipe_chat_menu(PurpleChat *chat)
9760 PurpleMenuAction *act;
9761 PurpleConvChatBuddyFlags flags_us;
9762 GList *menu = NULL;
9763 struct sipe_account_data *sip = chat->account->gc->proto_data;
9764 struct sip_session *session;
9765 gchar *self;
9767 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9768 if (!session) return NULL;
9770 self = sip_uri_self(sip);
9771 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9773 if (session->focus_uri
9774 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9776 if (session->locked) {
9777 act = purple_menu_action_new(_("Unlock"),
9778 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9779 NULL, NULL);
9780 menu = g_list_prepend(menu, act);
9781 } else {
9782 act = purple_menu_action_new(_("Lock"),
9783 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9784 NULL, NULL);
9785 menu = g_list_prepend(menu, act);
9789 menu = g_list_reverse(menu);
9791 g_free(self);
9792 return menu;
9795 static GList *
9796 sipe_blist_node_menu(PurpleBlistNode *node)
9798 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9799 return sipe_buddy_menu((PurpleBuddy *) node);
9800 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9801 return sipe_chat_menu((PurpleChat *)node);
9802 } else {
9803 return NULL;
9807 static gboolean
9808 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9810 char *uri = trans->payload->data;
9812 PurpleNotifyUserInfo *info;
9813 PurpleBuddy *pbuddy = NULL;
9814 struct sipe_buddy *sbuddy;
9815 const char *alias = NULL;
9816 char *device_name = NULL;
9817 char *server_alias = NULL;
9818 char *phone_number = NULL;
9819 char *email = NULL;
9820 const char *site;
9821 char *first_name = NULL;
9822 char *last_name = NULL;
9824 if (!sip) return FALSE;
9826 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9828 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9829 alias = purple_buddy_get_local_alias(pbuddy);
9831 //will query buddy UA's capabilities and send answer to log
9832 sipe_options_request(sip, uri);
9834 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9835 if (sbuddy) {
9836 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9839 info = purple_notify_user_info_new();
9841 if (msg->response != 200) {
9842 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9843 } else {
9844 xmlnode *searchResults;
9845 xmlnode *mrow;
9847 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9848 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9849 if (!searchResults) {
9850 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9851 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9852 const char *value;
9853 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9854 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9855 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9857 /* For 2007 system we will take this from ContactCard -
9858 * it has cleaner tel: URIs at least
9860 if (!sip->ocs2007) {
9861 char *tel_uri = sip_to_tel_uri(phone_number);
9862 /* trims its parameters, so call first */
9863 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9864 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9865 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9866 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9867 g_free(tel_uri);
9870 if (server_alias && strlen(server_alias) > 0) {
9871 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9873 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9874 purple_notify_user_info_add_pair(info, _("Job title"), value);
9876 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9877 purple_notify_user_info_add_pair(info, _("Office"), value);
9879 if (phone_number && strlen(phone_number) > 0) {
9880 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9882 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9883 purple_notify_user_info_add_pair(info, _("Company"), value);
9885 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9886 purple_notify_user_info_add_pair(info, _("City"), value);
9888 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9889 purple_notify_user_info_add_pair(info, _("State"), value);
9891 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9892 purple_notify_user_info_add_pair(info, _("Country"), value);
9894 if (email && strlen(email) > 0) {
9895 purple_notify_user_info_add_pair(info, _("Email address"), email);
9899 xmlnode_free(searchResults);
9902 purple_notify_user_info_add_section_break(info);
9904 if (is_empty(server_alias)) {
9905 g_free(server_alias);
9906 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9907 if (server_alias) {
9908 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9912 /* present alias if it differs from server alias */
9913 if (alias && !sipe_strequal(alias, server_alias))
9915 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9918 if (is_empty(email)) {
9919 g_free(email);
9920 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9921 if (email) {
9922 purple_notify_user_info_add_pair(info, _("Email address"), email);
9926 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9927 if (site) {
9928 purple_notify_user_info_add_pair(info, _("Site"), site);
9931 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9932 if (first_name && last_name) {
9933 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9935 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9936 g_free(link);
9938 g_free(first_name);
9939 g_free(last_name);
9941 if (device_name) {
9942 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9945 /* show a buddy's user info in a nice dialog box */
9946 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9947 uri, /* buddy's URI */
9948 info, /* body */
9949 NULL, /* callback called when dialog closed */
9950 NULL); /* userdata for callback */
9952 g_free(phone_number);
9953 g_free(server_alias);
9954 g_free(email);
9955 g_free(device_name);
9957 return TRUE;
9961 * AD search first, LDAP based
9963 static void sipe_get_info(PurpleConnection *gc, const char *username)
9965 struct sipe_account_data *sip = gc->proto_data;
9966 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9967 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9968 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9969 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9971 payload->destroy = g_free;
9972 payload->data = g_strdup(username);
9974 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9975 send_soap_request_with_cb(sip, domain_uri, body,
9976 (TransCallback) process_get_info_response, payload);
9977 g_free(domain_uri);
9978 g_free(body);
9979 g_free(row);
9982 static PurplePlugin *my_protocol = NULL;
9984 static PurplePluginProtocolInfo prpl_info =
9986 OPT_PROTO_CHAT_TOPIC,
9987 NULL, /* user_splits */
9988 NULL, /* protocol_options */
9989 NO_BUDDY_ICONS, /* icon_spec */
9990 sipe_list_icon, /* list_icon */
9991 NULL, /* list_emblems */
9992 sipe_status_text, /* status_text */
9993 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9994 sipe_status_types, /* away_states */
9995 sipe_blist_node_menu, /* blist_node_menu */
9996 NULL, /* chat_info */
9997 NULL, /* chat_info_defaults */
9998 sipe_login, /* login */
9999 sipe_close, /* close */
10000 sipe_im_send, /* send_im */
10001 NULL, /* set_info */ // TODO maybe
10002 sipe_send_typing, /* send_typing */
10003 sipe_get_info, /* get_info */
10004 sipe_set_status, /* set_status */
10005 sipe_set_idle, /* set_idle */
10006 NULL, /* change_passwd */
10007 sipe_add_buddy, /* add_buddy */
10008 NULL, /* add_buddies */
10009 sipe_remove_buddy, /* remove_buddy */
10010 NULL, /* remove_buddies */
10011 sipe_add_permit, /* add_permit */
10012 sipe_add_deny, /* add_deny */
10013 sipe_add_deny, /* rem_permit */
10014 sipe_add_permit, /* rem_deny */
10015 dummy_permit_deny, /* set_permit_deny */
10016 NULL, /* join_chat */
10017 NULL, /* reject_chat */
10018 NULL, /* get_chat_name */
10019 sipe_chat_invite, /* chat_invite */
10020 sipe_chat_leave, /* chat_leave */
10021 NULL, /* chat_whisper */
10022 sipe_chat_send, /* chat_send */
10023 sipe_keep_alive, /* keepalive */
10024 NULL, /* register_user */
10025 NULL, /* get_cb_info */ // deprecated
10026 NULL, /* get_cb_away */ // deprecated
10027 sipe_alias_buddy, /* alias_buddy */
10028 sipe_group_buddy, /* group_buddy */
10029 sipe_rename_group, /* rename_group */
10030 NULL, /* buddy_free */
10031 sipe_convo_closed, /* convo_closed */
10032 purple_normalize_nocase, /* normalize */
10033 NULL, /* set_buddy_icon */
10034 sipe_remove_group, /* remove_group */
10035 NULL, /* get_cb_real_name */ // TODO?
10036 NULL, /* set_chat_topic */
10037 NULL, /* find_blist_chat */
10038 NULL, /* roomlist_get_list */
10039 NULL, /* roomlist_cancel */
10040 NULL, /* roomlist_expand_category */
10041 NULL, /* can_receive_file */
10042 sipe_ft_send_file, /* send_file */
10043 sipe_ft_new_xfer, /* new_xfer */
10044 NULL, /* offline_message */
10045 NULL, /* whiteboard_prpl_ops */
10046 sipe_send_raw, /* send_raw */
10047 NULL, /* roomlist_room_serialize */
10048 NULL, /* unregister_user */
10049 NULL, /* send_attention */
10050 NULL, /* get_attention_types */
10051 #if !PURPLE_VERSION_CHECK(2,5,0)
10052 /* Backward compatibility when compiling against 2.4.x API */
10053 (void (*)(void)) /* _purple_reserved4 */
10054 #endif
10055 sizeof(PurplePluginProtocolInfo), /* struct_size */
10056 #if PURPLE_VERSION_CHECK(2,5,0)
10057 sipe_get_account_text_table, /* get_account_text_table */
10058 #if PURPLE_VERSION_CHECK(2,6,0)
10059 NULL, /* initiate_media */
10060 NULL, /* get_media_caps */
10061 #endif
10062 #endif
10066 static PurplePluginInfo info = {
10067 PURPLE_PLUGIN_MAGIC,
10068 PURPLE_MAJOR_VERSION,
10069 PURPLE_MINOR_VERSION,
10070 PURPLE_PLUGIN_PROTOCOL, /**< type */
10071 NULL, /**< ui_requirement */
10072 0, /**< flags */
10073 NULL, /**< dependencies */
10074 PURPLE_PRIORITY_DEFAULT, /**< priority */
10075 "prpl-sipe", /**< id */
10076 "Office Communicator", /**< name */
10077 SIPE_VERSION, /**< version */
10078 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10079 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10080 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10081 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10082 "Gabriel Burt <gburt@novell.com>, " /**< author */
10083 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10084 "pier11 <pier11@operamail.com>", /**< author */
10085 "http://sipe.sourceforge.net/", /**< homepage */
10086 sipe_plugin_load, /**< load */
10087 sipe_plugin_unload, /**< unload */
10088 sipe_plugin_destroy, /**< destroy */
10089 NULL, /**< ui_info */
10090 &prpl_info, /**< extra_info */
10091 NULL,
10092 sipe_actions,
10093 NULL,
10094 NULL,
10095 NULL,
10096 NULL
10099 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
10101 GList *entry;
10103 sip_sec_destroy();
10105 entry = prpl_info.protocol_options;
10106 while (entry) {
10107 purple_account_option_destroy(entry->data);
10108 entry = g_list_delete_link(entry, entry);
10110 prpl_info.protocol_options = NULL;
10112 entry = prpl_info.user_splits;
10113 while (entry) {
10114 purple_account_user_split_destroy(entry->data);
10115 entry = g_list_delete_link(entry, entry);
10117 prpl_info.user_splits = NULL;
10120 static void init_plugin(PurplePlugin *plugin)
10122 PurpleAccountUserSplit *split;
10123 PurpleAccountOption *option;
10125 srand(time(NULL));
10126 sip_sec_init();
10128 #ifdef ENABLE_NLS
10129 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
10130 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
10131 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
10132 textdomain(GETTEXT_PACKAGE);
10133 #endif
10135 purple_plugin_register(plugin);
10137 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
10138 purple_account_user_split_set_reverse(split, FALSE);
10139 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
10141 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
10142 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10144 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
10145 purple_account_option_add_list_item(option, _("Auto"), "auto");
10146 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
10147 purple_account_option_add_list_item(option, _("TCP"), "tcp");
10148 purple_account_option_add_list_item(option, _("UDP"), "udp");
10149 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10151 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
10152 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
10154 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
10155 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10157 #ifdef USE_KERBEROS
10158 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
10159 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10161 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
10162 * No login/password is taken into account if this option present,
10163 * instead used default credentials stored in OS.
10165 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
10166 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10167 #endif
10169 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
10170 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
10171 purple_account_option_add_list_item(option, _("None"), "NONE");
10172 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10174 /** Example: https://server.company.com/EWS/Exchange.asmx */
10175 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
10176 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10178 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
10179 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10181 /** Example: DOMAIN\user or user@company.com */
10182 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
10183 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10185 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
10186 purple_account_option_set_masked(option, TRUE);
10187 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10189 my_protocol = plugin;
10192 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
10195 Local Variables:
10196 mode: c
10197 c-file-style: "bsd"
10198 indent-tabs-mode: t
10199 tab-width: 8
10200 End: