Kerberos: fix config in SIPE again
[siplcs.git] / src / core / sipe.c
blob0023912aea5121d0f8b7481cdbb689591404d271
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #ifdef _WIN32
41 #include "win32dep.h" /* for LOCALEDIR */
42 #ifdef _DLL
43 #define _WS2TCPIP_H_
44 #define _WINSOCK2API_
45 #define _LIBC_INTERNAL_
46 #endif /* _DLL */
47 /* for network */
48 #include "libc_interface.h"
49 #else
50 #include <sys/types.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #endif /* _WIN32 */
55 #include <time.h>
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <errno.h>
59 #include <string.h>
60 #include <unistd.h>
62 #include <glib.h>
63 #ifdef HAVE_GMIME
64 #include <gmime/gmime.h>
65 #endif
67 #include "sipe-common.h"
69 #include "account.h"
70 #include "blist.h"
71 #include "connection.h"
72 #include "conversation.h"
73 #include "core.h"
74 #include "cipher.h"
75 #include "circbuffer.h"
76 #include "dnsquery.h"
77 #include "dnssrv.h"
78 #include "ft.h"
79 #include "network.h"
80 #include "notify.h"
81 #include "plugin.h"
82 #include "privacy.h"
83 #include "request.h"
84 #include "savedstatuses.h"
85 #include "sslconn.h"
86 #include "version.h"
88 #include "core-depurple.h" /* Temporary for the core de-purple transition */
90 #include "sipmsg.h"
91 #include "sip-csta.h"
92 #include "sip-sec.h"
93 #include "sipe-backend.h"
94 #include "sipe-cal.h"
95 #include "sipe-chat.h"
96 #include "sipe-conf.h"
97 #include "sipe-core.h"
98 #include "sipe-dialog.h"
99 #include "sipe-ews.h"
100 #include "sipe-ft.h"
101 #include "sipe-mime.h"
102 #include "sipe-nls.h"
103 #include "sipe-session.h"
104 #include "sipe-sign.h"
105 #include "sipe-utils.h"
106 #include "sipe-xml.h"
107 #include "http-conn.h"
108 #include "uuid.h"
109 #include "sipe.h"
111 /* Backward compatibility when compiling against 2.4.x API */
112 #if !PURPLE_VERSION_CHECK(2,5,0)
113 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
114 #endif
116 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
118 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
119 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
121 /* Keep in sync with sipe_transport_type! */
122 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
123 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
125 /* Status identifiers (see also: sipe_status_types()) */
126 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
127 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
128 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
129 /* PURPLE_STATUS_UNAVAILABLE: */
130 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
131 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
132 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
133 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
134 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
135 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
136 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
137 /* PURPLE_STATUS_AWAY: */
138 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
139 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
140 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
141 /** Reuters status (user settable) */
142 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
143 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
144 /* ??? PURPLE_STATUS_MOBILE */
145 /* ??? PURPLE_STATUS_TUNE */
147 /* Status attributes (see also sipe_status_types() */
148 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
150 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
152 static struct sipe_activity_map_struct
154 sipe_activity type;
155 const char *token;
156 const char *desc;
157 const char *status_id;
159 } const sipe_activity_map[] =
161 /* This has nothing to do with Availability numbers, like 3500 (online).
162 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
164 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
165 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
166 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
167 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
168 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
169 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
170 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
171 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
172 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
173 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
174 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
175 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
176 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
177 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
178 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
180 /** @param x is sipe_activity */
181 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
184 /* Action name templates */
185 #define ACTION_NAME_PRESENCE "<presence><%s>"
187 static sipe_activity
188 sipe_get_activity_by_token(const char *token)
190 int i;
192 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
194 if (sipe_strequal(token, sipe_activity_map[i].token))
195 return sipe_activity_map[i].type;
198 return sipe_activity_map[0].type;
201 static const char *
202 sipe_get_activity_desc_by_token(const char *token)
204 if (!token) return NULL;
206 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
209 /** Allows to send typed messages from chat window again after account reinstantiation. */
210 static void
211 sipe_rejoin_chat(PurpleConversation *conv)
213 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
214 PURPLE_CONV_CHAT(conv)->left)
216 PURPLE_CONV_CHAT(conv)->left = FALSE;
217 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
221 static char *genbranch()
223 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
224 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
225 rand() & 0xFFFF, rand() & 0xFFFF);
229 static char *default_ua = NULL;
230 static const char*
231 sipe_get_useragent(struct sipe_account_data *sip)
233 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
234 if (is_empty(useragent)) {
235 if (!default_ua) {
236 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
237 /* ref: lzodefs.h */
238 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
239 #define SIPE_TARGET_PLATFORM "linux"
240 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
241 #define SIPE_TARGET_PLATFORM "bsd"
242 #elif defined(__APPLE__) || defined(__MACOS__)
243 #define SIPE_TARGET_PLATFORM "macosx"
244 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
245 #define SIPE_TARGET_PLATFORM "aix"
246 #elif defined(__solaris__) || defined(__sun)
247 #define SIPE_TARGET_PLATFORM "sun"
248 #elif defined(_WIN32)
249 #define SIPE_TARGET_PLATFORM "win"
250 #elif defined(__CYGWIN__)
251 #define SIPE_TARGET_PLATFORM "cygwin"
252 #elif defined(__hpux__)
253 #define SIPE_TARGET_PLATFORM "hpux"
254 #elif defined(__sgi__)
255 #define SIPE_TARGET_PLATFORM "irix"
256 #else
257 #define SIPE_TARGET_PLATFORM "unknown"
258 #endif
260 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
261 #define SIPE_TARGET_ARCH "x86_64"
262 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
263 #define SIPE_TARGET_ARCH "i386"
264 #elif defined(__ppc64__)
265 #define SIPE_TARGET_ARCH "ppc64"
266 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
267 #define SIPE_TARGET_ARCH "ppc"
268 #elif defined(__hppa__) || defined(__hppa)
269 #define SIPE_TARGET_ARCH "hppa"
270 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
271 #define SIPE_TARGET_ARCH "mips"
272 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
273 #define SIPE_TARGET_ARCH "s390"
274 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
275 #define SIPE_TARGET_ARCH "sparc"
276 #elif defined(__arm__)
277 #define SIPE_TARGET_ARCH "arm"
278 #else
279 #define SIPE_TARGET_ARCH "other"
280 #endif
282 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
283 purple_core_get_version(),
284 sip->server_version ? sip->server_version : "");
286 useragent = default_ua;
288 return useragent;
291 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
292 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
294 return "sipe";
297 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
299 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
300 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
301 gpointer data);
303 static void sipe_close(PurpleConnection *gc);
305 static void send_presence_status(struct sipe_account_data *sip);
307 static void sendout_pkt(PurpleConnection *gc, const char *buf);
309 static void sipe_keep_alive(PurpleConnection *gc)
311 struct sipe_account_data *sip = gc->proto_data;
312 if (sip->transport == SIPE_TRANSPORT_UDP) {
313 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
314 gchar buf[2] = {0, 0};
315 SIPE_DEBUG_INFO_NOFORMAT("sending keep alive");
316 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
317 } else {
318 time_t now = time(NULL);
319 if ((sip->keepalive_timeout > 0) &&
320 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
321 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
323 SIPE_DEBUG_INFO("sending keep alive %d", sip->keepalive_timeout);
324 sendout_pkt(gc, "\r\n\r\n");
325 sip->last_keepalive = now;
330 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
332 struct sip_connection *ret = NULL;
333 GSList *entry = sip->openconns;
334 while (entry) {
335 ret = entry->data;
336 if (ret->fd == fd) return ret;
337 entry = entry->next;
339 return NULL;
342 static void sipe_auth_free(struct sip_auth *auth)
344 g_free(auth->opaque);
345 auth->opaque = NULL;
346 g_free(auth->realm);
347 auth->realm = NULL;
348 g_free(auth->target);
349 auth->target = NULL;
350 auth->version = 0;
351 auth->type = AUTH_TYPE_UNSET;
352 auth->retries = 0;
353 auth->expires = 0;
354 g_free(auth->gssapi_data);
355 auth->gssapi_data = NULL;
356 sip_sec_destroy_context(auth->gssapi_context);
357 auth->gssapi_context = NULL;
360 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
362 struct sip_connection *ret = g_new0(struct sip_connection, 1);
363 ret->fd = fd;
364 sip->openconns = g_slist_append(sip->openconns, ret);
365 return ret;
368 static void connection_remove(struct sipe_account_data *sip, int fd)
370 struct sip_connection *conn = connection_find(sip, fd);
371 if (conn) {
372 sip->openconns = g_slist_remove(sip->openconns, conn);
373 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
374 g_free(conn->inbuf);
375 g_free(conn);
379 static void connection_free_all(struct sipe_account_data *sip)
381 struct sip_connection *ret = NULL;
382 GSList *entry = sip->openconns;
383 while (entry) {
384 ret = entry->data;
385 connection_remove(sip, ret->fd);
386 entry = sip->openconns;
390 static void
391 sipe_make_signature(struct sipe_account_data *sip,
392 struct sipmsg *msg);
394 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
396 gchar noncecount[9];
397 const char *authuser = sip->authuser;
398 gchar *response;
399 gchar *ret;
401 if (!authuser || strlen(authuser) < 1) {
402 authuser = sip->username;
405 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
406 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
407 gchar *version_str;
409 // If we have a signature for the message, include that
410 if (msg->signature) {
411 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);
414 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
415 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
416 gchar *gssapi_data;
417 gchar *opaque;
418 gchar *sign_str = NULL;
420 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
421 &(auth->expires),
422 auth->type,
423 purple_account_get_bool(sip->account, "sso", TRUE),
424 sip->authdomain ? sip->authdomain : "",
425 authuser,
426 sip->password,
427 auth->target,
428 auth->gssapi_data);
429 if (!gssapi_data || !auth->gssapi_context) {
430 sip->gc->wants_to_die = TRUE;
431 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
432 return NULL;
435 if (auth->version > 3) {
436 sipe_make_signature(sip, msg);
437 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
438 msg->rand, msg->num, msg->signature);
439 } else {
440 sign_str = g_strdup("");
443 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
444 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
445 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);
446 g_free(opaque);
447 g_free(gssapi_data);
448 g_free(version_str);
449 g_free(sign_str);
450 return ret;
453 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
454 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
455 g_free(version_str);
456 return ret;
458 } else { /* Digest */
460 /* Calculate new session key */
461 if (!auth->opaque) {
462 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth->gssapi_data, auth->realm);
463 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
464 authuser, auth->realm, sip->password,
465 auth->gssapi_data, NULL);
468 sprintf(noncecount, "%08d", auth->nc++);
469 response = purple_cipher_http_digest_calculate_response("md5",
470 msg->method, msg->target, NULL, NULL,
471 auth->gssapi_data, noncecount, NULL,
472 auth->opaque);
473 SIPE_DEBUG_INFO("Digest response %s", response);
475 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);
476 g_free(response);
477 return ret;
481 static char *parse_attribute(const char *attrname, const char *source)
483 const char *tmp, *tmp2;
484 char *retval = NULL;
485 int len = strlen(attrname);
487 if (g_str_has_prefix(source, attrname)) {
488 tmp = source + len;
489 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
490 if (tmp2)
491 retval = g_strndup(tmp, tmp2 - tmp);
492 else
493 retval = g_strdup(tmp);
496 return retval;
499 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
501 int i;
502 gchar **parts;
504 if (!hdr) {
505 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
506 return;
509 if (!g_strncasecmp(hdr, "NTLM", 4)) {
510 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
511 auth->type = AUTH_TYPE_NTLM;
512 hdr += 5;
513 auth->nc = 1;
514 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
515 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
516 auth->type = AUTH_TYPE_KERBEROS;
517 hdr += 9;
518 auth->nc = 3;
519 } else {
520 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
521 auth->type = AUTH_TYPE_DIGEST;
522 hdr += 7;
525 parts = g_strsplit(hdr, "\", ", 0);
526 for (i = 0; parts[i]; i++) {
527 char *tmp;
529 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
531 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
532 g_free(auth->gssapi_data);
533 auth->gssapi_data = tmp;
535 if (auth->type == AUTH_TYPE_NTLM) {
536 /* NTLM module extracts nonce from gssapi-data */
537 auth->nc = 3;
540 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
541 /* Only used with AUTH_TYPE_DIGEST */
542 g_free(auth->gssapi_data);
543 auth->gssapi_data = tmp;
544 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
545 g_free(auth->opaque);
546 auth->opaque = tmp;
547 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
548 g_free(auth->realm);
549 auth->realm = tmp;
551 if (auth->type == AUTH_TYPE_DIGEST) {
552 /* Throw away old session key */
553 g_free(auth->opaque);
554 auth->opaque = NULL;
555 auth->nc = 1;
557 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
558 g_free(auth->target);
559 auth->target = tmp;
560 } else if ((tmp = parse_attribute("version=", parts[i]))) {
561 auth->version = atoi(tmp);
562 g_free(tmp);
564 // uncomment to revert to previous functionality if version 3+ does not work.
565 // auth->version = 2;
567 g_strfreev(parts);
569 return;
572 static void sipe_canwrite_cb(gpointer data,
573 SIPE_UNUSED_PARAMETER gint source,
574 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
576 PurpleConnection *gc = data;
577 struct sipe_account_data *sip = gc->proto_data;
578 gsize max_write;
579 gssize written;
581 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
583 if (max_write == 0) {
584 if (sip->tx_handler != 0){
585 purple_input_remove(sip->tx_handler);
586 sip->tx_handler = 0;
588 return;
591 written = write(sip->fd, sip->txbuf->outptr, max_write);
593 if (written < 0 && errno == EAGAIN)
594 written = 0;
595 else if (written <= 0) {
596 /*TODO: do we really want to disconnect on a failure to write?*/
597 purple_connection_error(gc, _("Could not write"));
598 return;
601 purple_circ_buffer_mark_read(sip->txbuf, written);
604 static void sipe_canwrite_cb_ssl(gpointer data,
605 SIPE_UNUSED_PARAMETER gint src,
606 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
608 PurpleConnection *gc = data;
609 struct sipe_account_data *sip = gc->proto_data;
610 gsize max_write;
611 gssize written;
613 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
615 if (max_write == 0) {
616 if (sip->tx_handler != 0) {
617 purple_input_remove(sip->tx_handler);
618 sip->tx_handler = 0;
619 return;
623 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
625 if (written < 0 && errno == EAGAIN)
626 written = 0;
627 else if (written <= 0) {
628 /*TODO: do we really want to disconnect on a failure to write?*/
629 purple_connection_error(gc, _("Could not write"));
630 return;
633 purple_circ_buffer_mark_read(sip->txbuf, written);
636 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
638 static void send_later_cb(gpointer data, gint source,
639 SIPE_UNUSED_PARAMETER const gchar *error)
641 PurpleConnection *gc = data;
642 struct sipe_account_data *sip;
643 struct sip_connection *conn;
645 if (!PURPLE_CONNECTION_IS_VALID(gc))
647 if (source >= 0)
648 close(source);
649 return;
652 if (source < 0) {
653 purple_connection_error(gc, _("Could not connect"));
654 return;
657 sip = gc->proto_data;
658 sip->fd = source;
659 sip->connecting = FALSE;
660 sip->last_keepalive = time(NULL);
662 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
664 /* If there is more to write now, we need to register a handler */
665 if (sip->txbuf->bufused > 0)
666 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
668 conn = connection_create(sip, source);
669 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
672 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
674 struct sipe_account_data *sip;
676 if (!PURPLE_CONNECTION_IS_VALID(gc))
678 if (gsc) purple_ssl_close(gsc);
679 return NULL;
682 sip = gc->proto_data;
683 sip->fd = gsc->fd;
684 sip->gsc = gsc;
685 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
686 sip->connecting = FALSE;
687 sip->last_keepalive = time(NULL);
689 connection_create(sip, gsc->fd);
691 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
693 return sip;
696 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
697 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
699 PurpleConnection *gc = data;
700 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
701 if (sip == NULL) return;
703 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
705 /* If there is more to write now */
706 if (sip->txbuf->bufused > 0) {
707 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
712 static void sendlater(PurpleConnection *gc, const char *buf)
714 struct sipe_account_data *sip = gc->proto_data;
716 if (!sip->connecting) {
717 SIPE_DEBUG_INFO("connecting to %s port %d", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
718 if (sip->transport == SIPE_TRANSPORT_TLS){
719 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
720 } else {
721 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
722 purple_connection_error(gc, _("Could not create socket"));
725 sip->connecting = TRUE;
728 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
729 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
731 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
734 static void sendout_pkt(PurpleConnection *gc, const char *buf)
736 struct sipe_account_data *sip = gc->proto_data;
737 time_t currtime = time(NULL);
738 int writelen = strlen(buf);
739 char *tmp;
741 SIPE_DEBUG_INFO("sending - %s######\n%s######", ctime(&currtime), tmp = fix_newlines(buf));
742 g_free(tmp);
743 if (sip->transport == SIPE_TRANSPORT_UDP) {
744 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
745 SIPE_DEBUG_INFO_NOFORMAT("could not send packet");
747 } else {
748 int ret;
749 if (sip->fd < 0) {
750 sendlater(gc, buf);
751 return;
754 if (sip->tx_handler) {
755 ret = -1;
756 errno = EAGAIN;
757 } else{
758 if (sip->gsc){
759 ret = purple_ssl_write(sip->gsc, buf, writelen);
760 }else{
761 ret = write(sip->fd, buf, writelen);
765 if (ret < 0 && errno == EAGAIN)
766 ret = 0;
767 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
768 sendlater(gc, buf);
769 return;
772 if (ret < writelen) {
773 if (!sip->tx_handler){
774 if (sip->gsc){
775 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
777 else{
778 sip->tx_handler = purple_input_add(sip->fd,
779 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
780 gc);
784 /* XXX: is it OK to do this? You might get part of a request sent
785 with part of another. */
786 if (sip->txbuf->bufused > 0)
787 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
789 purple_circ_buffer_append(sip->txbuf, buf + ret,
790 writelen - ret);
795 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
797 sendout_pkt(gc, buf);
798 return len;
801 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
803 GSList *tmp = msg->headers;
804 gchar *name;
805 gchar *value;
806 GString *outstr = g_string_new("");
807 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
808 while (tmp) {
809 name = ((struct sipnameval*) (tmp->data))->name;
810 value = ((struct sipnameval*) (tmp->data))->value;
811 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
812 tmp = g_slist_next(tmp);
814 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
815 sendout_pkt(sip->gc, outstr->str);
816 g_string_free(outstr, TRUE);
819 static void
820 sipe_make_signature(struct sipe_account_data *sip,
821 struct sipmsg *msg)
823 if (sip->registrar.gssapi_context) {
824 struct sipmsg_breakdown msgbd;
825 gchar *signature_input_str;
826 msgbd.msg = msg;
827 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
828 msgbd.rand = g_strdup_printf("%08x", g_random_int());
829 sip->registrar.ntlm_num++;
830 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
831 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
832 if (signature_input_str != NULL) {
833 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
834 msg->signature = signature_hex;
835 msg->rand = g_strdup(msgbd.rand);
836 msg->num = g_strdup(msgbd.num);
837 g_free(signature_input_str);
839 sipmsg_breakdown_free(&msgbd);
843 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
845 gchar * buf;
847 if (sip->registrar.type == AUTH_TYPE_UNSET) {
848 return;
851 sipe_make_signature(sip, msg);
853 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
854 buf = auth_header(sip, &sip->registrar, msg);
855 if (buf) {
856 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
858 g_free(buf);
859 } 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")) {
860 sip->registrar.nc = 3;
861 sip->registrar.type = AUTH_TYPE_NTLM;
862 #ifdef HAVE_LIBKRB5
863 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
864 sip->registrar.type = AUTH_TYPE_KERBEROS;
866 #endif
869 buf = auth_header(sip, &sip->registrar, msg);
870 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
871 g_free(buf);
872 } else {
873 SIPE_DEBUG_INFO("not adding auth header to msg w/ method %s", method);
877 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
878 const char *text, const char *body)
880 gchar *name;
881 gchar *value;
882 GString *outstr = g_string_new("");
883 struct sipe_account_data *sip = gc->proto_data;
884 gchar *contact;
885 GSList *tmp;
886 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
888 /* Can return NULL! */
889 contact = get_contact(sip);
890 if (contact) {
891 sipmsg_add_header(msg, "Contact", contact);
892 g_free(contact);
895 if (body) {
896 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
897 sipmsg_add_header(msg, "Content-Length", len);
898 g_free(len);
899 } else {
900 sipmsg_add_header(msg, "Content-Length", "0");
903 msg->response = code;
905 sipmsg_strip_headers(msg, keepers);
906 sipmsg_merge_new_headers(msg);
907 sign_outgoing_message(msg, sip, msg->method);
909 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
910 tmp = msg->headers;
911 while (tmp) {
912 name = ((struct sipnameval*) (tmp->data))->name;
913 value = ((struct sipnameval*) (tmp->data))->value;
915 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
916 tmp = g_slist_next(tmp);
918 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
919 sendout_pkt(gc, outstr->str);
920 g_string_free(outstr, TRUE);
923 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
925 if (sip->transactions) {
926 sip->transactions = g_slist_remove(sip->transactions, trans);
927 SIPE_DEBUG_INFO("sip->transactions count:%d after removal", g_slist_length(sip->transactions));
929 if (trans->msg) sipmsg_free(trans->msg);
930 if (trans->payload) {
931 (*trans->payload->destroy)(trans->payload->data);
932 g_free(trans->payload);
934 g_free(trans->key);
935 g_free(trans);
939 static struct transaction *
940 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
942 const gchar *call_id;
943 const gchar *cseq;
944 struct transaction *trans = g_new0(struct transaction, 1);
946 trans->time = time(NULL);
947 trans->msg = (struct sipmsg *)msg;
948 call_id = sipmsg_find_header(trans->msg, "Call-ID");
949 cseq = sipmsg_find_header(trans->msg, "CSeq");
950 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
951 trans->callback = callback;
952 sip->transactions = g_slist_append(sip->transactions, trans);
953 SIPE_DEBUG_INFO("sip->transactions count:%d after addition", g_slist_length(sip->transactions));
954 return trans;
957 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
959 struct transaction *trans;
960 GSList *transactions = sip->transactions;
961 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
962 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
963 gchar *key;
965 if (!call_id || !cseq) {
966 SIPE_DEBUG_ERROR_NOFORMAT("transaction_find: no Call-ID or CSeq!");
967 return NULL;
970 key = g_strdup_printf("<%s><%s>", call_id, cseq);
971 while (transactions) {
972 trans = transactions->data;
973 if (!g_strcasecmp(trans->key, key)) {
974 g_free(key);
975 return trans;
977 transactions = transactions->next;
980 g_free(key);
981 return NULL;
984 struct transaction *
985 send_sip_request(PurpleConnection *gc, const gchar *method,
986 const gchar *url, const gchar *to, const gchar *addheaders,
987 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
989 struct sipe_account_data *sip = gc->proto_data;
990 const char *addh = "";
991 char *buf;
992 struct sipmsg *msg;
993 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
994 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
995 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
996 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
997 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
998 gchar *route = g_strdup("");
999 gchar *epid = get_epid(sip);
1000 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
1001 struct transaction *trans = NULL;
1003 if (dialog && dialog->routes)
1005 GSList *iter = dialog->routes;
1007 while(iter)
1009 char *tmp = route;
1010 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1011 g_free(tmp);
1012 iter = g_slist_next(iter);
1016 if (!ourtag && !dialog) {
1017 ourtag = gentag();
1020 if (sipe_strequal(method, "REGISTER")) {
1021 if (sip->regcallid) {
1022 g_free(callid);
1023 callid = g_strdup(sip->regcallid);
1024 } else {
1025 sip->regcallid = g_strdup(callid);
1027 cseq = ++sip->cseq;
1030 if (addheaders) addh = addheaders;
1032 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1033 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1034 "From: <sip:%s>%s%s;epid=%s\r\n"
1035 "To: <%s>%s%s%s%s\r\n"
1036 "Max-Forwards: 70\r\n"
1037 "CSeq: %d %s\r\n"
1038 "User-Agent: %s\r\n"
1039 "Call-ID: %s\r\n"
1040 "%s%s"
1041 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1042 method,
1043 dialog && dialog->request ? dialog->request : url,
1044 TRANSPORT_DESCRIPTOR,
1045 sipe_backend_network_ip_address(),
1046 sip->listenport,
1047 branch ? ";branch=" : "",
1048 branch ? branch : "",
1049 sip->username,
1050 ourtag ? ";tag=" : "",
1051 ourtag ? ourtag : "",
1052 epid,
1054 theirtag ? ";tag=" : "",
1055 theirtag ? theirtag : "",
1056 theirepid ? ";epid=" : "",
1057 theirepid ? theirepid : "",
1058 cseq,
1059 method,
1060 sipe_get_useragent(sip),
1061 callid,
1062 route,
1063 addh,
1064 body ? (gsize) strlen(body) : 0,
1065 body ? body : "");
1068 //printf ("parsing msg buf:\n%s\n\n", buf);
1069 msg = sipmsg_parse_msg(buf);
1071 g_free(buf);
1072 g_free(ourtag);
1073 g_free(theirtag);
1074 g_free(theirepid);
1075 g_free(branch);
1076 g_free(callid);
1077 g_free(route);
1078 g_free(epid);
1080 sign_outgoing_message (msg, sip, method);
1082 buf = sipmsg_to_string (msg);
1084 /* add to ongoing transactions */
1085 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1086 if (!sipe_strequal(method, "ACK")) {
1087 trans = transactions_add_buf(sip, msg, tc);
1088 } else {
1089 sipmsg_free(msg);
1091 sendout_pkt(gc, buf);
1092 g_free(buf);
1094 return trans;
1098 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1100 static void
1101 send_soap_request_with_cb(struct sipe_account_data *sip,
1102 gchar *from0,
1103 gchar *body,
1104 TransCallback callback,
1105 struct transaction_payload *payload)
1107 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1108 gchar *contact = get_contact(sip);
1109 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1110 "Content-Type: application/SOAP+xml\r\n",contact);
1112 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1113 trans->payload = payload;
1115 g_free(from);
1116 g_free(contact);
1117 g_free(hdr);
1120 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1122 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1125 static char *get_contact_register(struct sipe_account_data *sip)
1127 char *epid = get_epid(sip);
1128 char *uuid = generateUUIDfromEPID(epid);
1129 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>\"", sipe_backend_network_ip_address(), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1130 g_free(uuid);
1131 g_free(epid);
1132 return(buf);
1135 static void do_register_exp(struct sipe_account_data *sip, int expire)
1137 char *uri;
1138 char *expires;
1139 char *to;
1140 char *contact;
1141 char *hdr;
1143 if (!sip->sipdomain) return;
1145 uri = sip_uri_from_name(sip->sipdomain);
1146 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1147 to = sip_uri_self(sip);
1148 contact = get_contact_register(sip);
1149 hdr = g_strdup_printf("Contact: %s\r\n"
1150 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1151 "Event: registration\r\n"
1152 "Allow-Events: presence\r\n"
1153 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1154 "%s", contact, expires);
1155 g_free(contact);
1156 g_free(expires);
1158 sip->registerstatus = 1;
1160 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1161 process_register_response);
1163 g_free(hdr);
1164 g_free(uri);
1165 g_free(to);
1168 static void do_register_cb(struct sipe_account_data *sip,
1169 SIPE_UNUSED_PARAMETER void *unused)
1171 do_register_exp(sip, -1);
1172 sip->reregister_set = FALSE;
1175 static void do_register(struct sipe_account_data *sip)
1177 do_register_exp(sip, -1);
1180 /**
1181 * Returns pointer to URI without sip: prefix if any
1183 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
1184 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
1186 * Doesn't allocate memory
1188 static const char *
1189 sipe_get_no_sip_uri(const char *sip_uri)
1191 const char *prefix = "sip:";
1192 if (!sip_uri) return NULL;
1194 if (g_str_has_prefix(sip_uri, prefix)) {
1195 return (sip_uri+strlen(prefix));
1196 } else {
1197 return sip_uri;
1201 static void
1202 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1204 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1205 send_soap_request(sip, body);
1206 g_free(body);
1209 static void
1210 sipe_change_access_level(struct sipe_account_data *sip,
1211 const int container_id,
1212 const gchar *type,
1213 const gchar *value);
1215 static void
1216 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1218 if (allow) {
1219 SIPE_DEBUG_INFO("Authorizing contact %s", who);
1220 } else {
1221 SIPE_DEBUG_INFO("Blocking contact %s", who);
1224 if (sip->ocs2007) {
1225 sipe_change_access_level(sip, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
1226 } else {
1227 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1231 static
1232 void sipe_auth_user_cb(void * data)
1234 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1235 if (!job) return;
1237 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1238 g_free(job);
1241 static
1242 void sipe_deny_user_cb(void * data)
1244 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1245 if (!job) return;
1247 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1248 g_free(job);
1251 static void
1252 sipe_add_permit(PurpleConnection *gc, const char *name)
1254 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1256 sipe_contact_allow_deny(sip, name, TRUE);
1259 static void
1260 sipe_add_deny(PurpleConnection *gc, const char *name)
1262 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1263 sipe_contact_allow_deny(sip, name, FALSE);
1266 /*static void
1267 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1269 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1270 sipe_contact_set_acl(sip, name, "");
1273 /** @applicable: 2005-
1275 static void
1276 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1278 sipe_xml *watchers;
1279 const sipe_xml *watcher;
1280 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1281 if (msg->response != 0 && msg->response != 200) return;
1283 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1285 watchers = sipe_xml_parse(msg->body, msg->bodylen);
1286 if (!watchers) return;
1288 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
1289 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
1290 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
1291 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1293 // TODO pull out optional displayName to pass as alias
1294 if (remote_user) {
1295 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1296 job->who = remote_user;
1297 job->sip = sip;
1298 purple_account_request_authorization(
1299 sip->account,
1300 remote_user,
1301 _("you"), /* id */
1302 alias,
1303 NULL, /* message */
1304 on_list,
1305 sipe_auth_user_cb,
1306 sipe_deny_user_cb,
1307 (void *) job);
1312 sipe_xml_free(watchers);
1313 return;
1316 static void
1317 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1319 PurpleGroup * purple_group = purple_find_group(group->name);
1320 if (!purple_group) {
1321 purple_group = purple_group_new(group->name);
1322 purple_blist_add_group(purple_group, NULL);
1325 if (purple_group) {
1326 group->purple_group = purple_group;
1327 sip->groups = g_slist_append(sip->groups, group);
1328 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
1329 } else {
1330 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
1334 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1336 struct sipe_group *group;
1337 GSList *entry;
1338 if (sip == NULL) {
1339 return NULL;
1342 entry = sip->groups;
1343 while (entry) {
1344 group = entry->data;
1345 if (group->id == id) {
1346 return group;
1348 entry = entry->next;
1350 return NULL;
1353 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1355 struct sipe_group *group;
1356 GSList *entry;
1357 if (!sip || !name) {
1358 return NULL;
1361 entry = sip->groups;
1362 while (entry) {
1363 group = entry->data;
1364 if (sipe_strequal(group->name, name)) {
1365 return group;
1367 entry = entry->next;
1369 return NULL;
1372 static void
1373 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1375 gchar *body;
1376 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
1377 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1378 send_soap_request(sip, body);
1379 g_free(body);
1380 g_free(group->name);
1381 group->name = g_strdup(name);
1385 * Only appends if no such value already stored.
1386 * Like Set in Java.
1388 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1389 GSList * res = list;
1390 if (!g_slist_find_custom(list, data, func)) {
1391 res = g_slist_insert_sorted(list, data, func);
1393 return res;
1396 static int
1397 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1398 return group1->id - group2->id;
1402 * Returns string like "2 4 7 8" - group ids buddy belong to.
1404 static gchar *
1405 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1406 int i = 0;
1407 gchar *res;
1408 //creating array from GList, converting int to gchar*
1409 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1410 GSList *entry = buddy->groups;
1412 if (!ids_arr) return NULL;
1414 while (entry) {
1415 struct sipe_group * group = entry->data;
1416 ids_arr[i] = g_strdup_printf("%d", group->id);
1417 entry = entry->next;
1418 i++;
1420 ids_arr[i] = NULL;
1421 res = g_strjoinv(" ", ids_arr);
1422 g_strfreev(ids_arr);
1423 return res;
1427 * Sends buddy update to server
1429 static void
1430 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1432 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1433 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1435 if (buddy && purple_buddy) {
1436 const char *alias = purple_buddy_get_alias(purple_buddy);
1437 gchar *groups = sipe_get_buddy_groups_string(buddy);
1438 if (groups) {
1439 gchar *body;
1440 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
1442 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1443 alias, groups, "true", buddy->name, sip->contacts_delta++
1445 send_soap_request(sip, body);
1446 g_free(groups);
1447 g_free(body);
1452 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1454 if (msg->response == 200) {
1455 struct sipe_group *group;
1456 struct group_user_context *ctx = trans->payload->data;
1457 sipe_xml *xml;
1458 const sipe_xml *node;
1459 char *group_id;
1460 struct sipe_buddy *buddy;
1462 xml = sipe_xml_parse(msg->body, msg->bodylen);
1463 if (!xml) {
1464 return FALSE;
1467 node = sipe_xml_child(xml, "Body/addGroup/groupID");
1468 if (!node) {
1469 sipe_xml_free(xml);
1470 return FALSE;
1473 group_id = sipe_xml_data(node);
1474 if (!group_id) {
1475 sipe_xml_free(xml);
1476 return FALSE;
1479 group = g_new0(struct sipe_group, 1);
1480 group->id = (int)g_ascii_strtod(group_id, NULL);
1481 g_free(group_id);
1482 group->name = g_strdup(ctx->group_name);
1484 sipe_group_add(sip, group);
1486 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1487 if (buddy) {
1488 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1491 sipe_group_set_user(sip, ctx->user_name);
1493 sipe_xml_free(xml);
1494 return TRUE;
1496 return FALSE;
1499 static void sipe_group_context_destroy(gpointer data)
1501 struct group_user_context *ctx = data;
1502 g_free(ctx->group_name);
1503 g_free(ctx->user_name);
1504 g_free(ctx);
1507 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1509 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1510 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1511 gchar *body;
1512 ctx->group_name = g_strdup(name);
1513 ctx->user_name = g_strdup(who);
1514 payload->destroy = sipe_group_context_destroy;
1515 payload->data = ctx;
1517 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1518 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1519 g_free(body);
1523 * Data structure for scheduled actions
1526 struct scheduled_action {
1528 * Name of action.
1529 * Format is <Event>[<Data>...]
1530 * Example: <presence><sip:user@domain.com> or <registration>
1532 gchar *name;
1533 guint timeout_handler;
1534 gboolean repetitive;
1535 Action action;
1536 GDestroyNotify destroy;
1537 struct sipe_account_data *sip;
1538 void *payload;
1542 * A timer callback
1543 * Should return FALSE if repetitive action is not needed
1545 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1547 gboolean ret;
1548 SIPE_DEBUG_INFO_NOFORMAT("sipe_scheduled_exec: executing");
1549 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1550 SIPE_DEBUG_INFO("sip->timeouts count:%d after removal", g_slist_length(sched_action->sip->timeouts));
1551 (sched_action->action)(sched_action->sip, sched_action->payload);
1552 ret = sched_action->repetitive;
1553 if (sched_action->destroy) {
1554 (*sched_action->destroy)(sched_action->payload);
1556 g_free(sched_action->name);
1557 g_free(sched_action);
1558 return ret;
1562 * Kills action timer effectively cancelling
1563 * scheduled action
1565 * @param name of action
1567 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1569 GSList *entry;
1571 if (!sip->timeouts || !name) return;
1573 entry = sip->timeouts;
1574 while (entry) {
1575 struct scheduled_action *sched_action = entry->data;
1576 if(sipe_strequal(sched_action->name, name)) {
1577 GSList *to_delete = entry;
1578 entry = entry->next;
1579 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1580 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
1581 purple_timeout_remove(sched_action->timeout_handler);
1582 if (sched_action->destroy) {
1583 (*sched_action->destroy)(sched_action->payload);
1585 g_free(sched_action->name);
1586 g_free(sched_action);
1587 } else {
1588 entry = entry->next;
1593 static void
1594 sipe_schedule_action0(const gchar *name,
1595 int timeout,
1596 gboolean isSeconds,
1597 Action action,
1598 GDestroyNotify destroy,
1599 struct sipe_account_data *sip,
1600 void *payload)
1602 struct scheduled_action *sched_action;
1604 /* Make sure each action only exists once */
1605 sipe_cancel_scheduled_action(sip, name);
1607 SIPE_DEBUG_INFO("scheduling action %s timeout:%d(%s)", name, timeout, isSeconds ? "sec" : "msec");
1608 sched_action = g_new0(struct scheduled_action, 1);
1609 sched_action->repetitive = FALSE;
1610 sched_action->name = g_strdup(name);
1611 sched_action->action = action;
1612 sched_action->destroy = destroy;
1613 sched_action->sip = sip;
1614 sched_action->payload = payload;
1615 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1616 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1617 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1618 SIPE_DEBUG_INFO("sip->timeouts count:%d after addition", g_slist_length(sip->timeouts));
1621 void
1622 sipe_schedule_action(const gchar *name,
1623 int timeout,
1624 Action action,
1625 GDestroyNotify destroy,
1626 struct sipe_account_data *sip,
1627 void *payload)
1629 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1633 * Same as sipe_schedule_action() but timeout is in milliseconds.
1635 static void
1636 sipe_schedule_action_msec(const gchar *name,
1637 int timeout,
1638 Action action,
1639 GDestroyNotify destroy,
1640 struct sipe_account_data *sip,
1641 void *payload)
1643 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1646 static void
1647 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1648 time_t calculate_from);
1650 static int
1651 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1653 static const char*
1654 sipe_get_status_by_availability(int avail,
1655 char** activity);
1657 static void
1658 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1659 const char *status_id,
1660 const char *message,
1661 time_t do_not_publish[]);
1663 static void
1664 sipe_apply_calendar_status(struct sipe_account_data *sip,
1665 struct sipe_buddy *sbuddy,
1666 const char *status_id)
1668 time_t cal_avail_since;
1669 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1670 int avail;
1671 gchar *self_uri;
1673 if (!sbuddy) return;
1675 if (cal_status < SIPE_CAL_NO_DATA) {
1676 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
1677 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1680 /* scheduled Cal update call */
1681 if (!status_id) {
1682 status_id = sbuddy->last_non_cal_status_id;
1683 g_free(sbuddy->activity);
1684 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1687 if (!status_id) {
1688 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
1689 sbuddy->name ? sbuddy->name : "" );
1690 return;
1693 /* adjust to calendar status */
1694 if (cal_status != SIPE_CAL_NO_DATA) {
1695 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1697 if (cal_status == SIPE_CAL_BUSY
1698 && cal_avail_since > sbuddy->user_avail_since
1699 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1701 status_id = SIPE_STATUS_ID_BUSY;
1702 g_free(sbuddy->activity);
1703 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1705 avail = sipe_get_availability_by_status(status_id, NULL);
1707 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1708 if (cal_avail_since > sbuddy->activity_since) {
1709 if (cal_status == SIPE_CAL_OOF
1710 && avail >= 15000) /* 12000 in 2007 */
1712 g_free(sbuddy->activity);
1713 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1718 /* then set status_id actually */
1719 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
1720 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1722 /* set our account state to the one in roaming (including calendar info) */
1723 self_uri = sip_uri_self(sip);
1724 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1725 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1726 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1729 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
1730 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1732 g_free(self_uri);
1735 static void
1736 sipe_got_user_status(struct sipe_account_data *sip,
1737 const char* uri,
1738 const char *status_id)
1740 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1742 if (!sbuddy) return;
1744 /* Check if on 2005 system contact's calendar,
1745 * then set/preserve it.
1747 if (!sip->ocs2007) {
1748 sipe_apply_calendar_status(sip, sbuddy, status_id);
1749 } else {
1750 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1754 static void
1755 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1756 struct sipe_buddy *sbuddy,
1757 struct sipe_account_data *sip)
1759 sipe_apply_calendar_status(sip, sbuddy, NULL);
1763 * Updates contact's status
1764 * based on their calendar information.
1766 * Applicability: 2005 systems
1768 static void
1769 update_calendar_status(struct sipe_account_data *sip)
1771 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
1772 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1774 /* repeat scheduling */
1775 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1779 * Schedules process of contacts' status update
1780 * based on their calendar information.
1781 * Should be scheduled to the beginning of every
1782 * 15 min interval, like:
1783 * 13:00, 13:15, 13:30, 13:45, etc.
1785 * Applicability: 2005 systems
1787 static void
1788 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1789 time_t calculate_from)
1791 int interval = 15*60;
1792 /** start of the beginning of closest 15 min interval. */
1793 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1795 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
1796 asctime(localtime(&calculate_from)));
1797 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
1798 asctime(localtime(&next_start)));
1800 sipe_schedule_action("<+2005-cal-status>",
1801 (int)(next_start - time(NULL)),
1802 (Action)update_calendar_status,
1803 NULL,
1804 sip,
1805 NULL);
1809 * Schedules process of self status publish
1810 * based on own calendar information.
1811 * Should be scheduled to the beginning of every
1812 * 15 min interval, like:
1813 * 13:00, 13:15, 13:30, 13:45, etc.
1815 * Applicability: 2007+ systems
1817 static void
1818 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1819 time_t calculate_from)
1821 int interval = 5*60;
1822 /** start of the beginning of closest 5 min interval. */
1823 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1825 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1826 asctime(localtime(&calculate_from)));
1827 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1828 asctime(localtime(&next_start)));
1830 sipe_schedule_action("<+2007-cal-status>",
1831 (int)(next_start - time(NULL)),
1832 (Action)publish_calendar_status_self,
1833 NULL,
1834 sip,
1835 NULL);
1838 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1840 /** Should be g_free()'d
1842 static gchar *
1843 sipe_get_subscription_key(const gchar *event,
1844 const gchar *with)
1846 gchar *key = NULL;
1848 if (is_empty(event)) return NULL;
1850 if (event && sipe_strcase_equal(event, "presence")) {
1851 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1852 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1854 /* @TODO drop participated buddies' just_added flag */
1855 } else if (event) {
1856 /* Subscription is identified by <event> key */
1857 key = g_strdup_printf("<%s>", event);
1860 return key;
1863 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1864 SIPE_UNUSED_PARAMETER struct transaction *trans)
1866 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1867 const gchar *event = sipmsg_find_header(msg, "Event");
1868 gchar *key;
1870 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1871 if (!event) {
1872 struct sipmsg *request_msg = trans->msg;
1873 event = sipmsg_find_header(request_msg, "Event");
1876 key = sipe_get_subscription_key(event, with);
1878 /* 200 OK; 481 Call Leg Does Not Exist */
1879 if (key && (msg->response == 200 || msg->response == 481)) {
1880 if (g_hash_table_lookup(sip->subscriptions, key)) {
1881 g_hash_table_remove(sip->subscriptions, key);
1882 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
1886 /* create/store subscription dialog if not yet */
1887 if (msg->response == 200) {
1888 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1889 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1891 if (key) {
1892 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1893 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1895 subscription->dialog.callid = g_strdup(callid);
1896 subscription->dialog.cseq = atoi(cseq);
1897 subscription->dialog.with = g_strdup(with);
1898 subscription->event = g_strdup(event);
1899 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1901 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key);
1904 g_free(cseq);
1907 g_free(key);
1908 g_free(with);
1910 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1912 process_incoming_notify(sip, msg, FALSE, FALSE);
1914 return TRUE;
1917 static void sipe_subscribe_resource_uri(const char *name,
1918 SIPE_UNUSED_PARAMETER gpointer value,
1919 gchar **resources_uri)
1921 gchar *tmp = *resources_uri;
1922 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1923 g_free(tmp);
1926 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1928 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1929 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1930 gchar *tmp = *resources_uri;
1932 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1934 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1935 g_free(tmp);
1939 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1940 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1941 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1942 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1943 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1946 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1948 gchar *key;
1949 gchar *contact = get_contact(sip);
1950 gchar *request;
1951 gchar *content;
1952 gchar *require = "";
1953 gchar *accept = "";
1954 gchar *autoextend = "";
1955 gchar *content_type;
1956 struct sip_dialog *dialog;
1958 if (sip->ocs2007) {
1959 require = ", categoryList";
1960 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1961 content_type = "application/msrtc-adrl-categorylist+xml";
1962 content = g_strdup_printf(
1963 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1964 "<action name=\"subscribe\" id=\"63792024\">\n"
1965 "<adhocList>\n%s</adhocList>\n"
1966 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1967 "<category name=\"calendarData\"/>\n"
1968 "<category name=\"contactCard\"/>\n"
1969 "<category name=\"note\"/>\n"
1970 "<category name=\"state\"/>\n"
1971 "</categoryList>\n"
1972 "</action>\n"
1973 "</batchSub>", sip->username, resources_uri);
1974 } else {
1975 autoextend = "Supported: com.microsoft.autoextend\r\n";
1976 content_type = "application/adrl+xml";
1977 content = g_strdup_printf(
1978 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1979 "<create xmlns=\"\">\n%s</create>\n"
1980 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1982 g_free(resources_uri);
1984 request = g_strdup_printf(
1985 "Require: adhoclist%s\r\n"
1986 "Supported: eventlist\r\n"
1987 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1988 "Supported: ms-piggyback-first-notify\r\n"
1989 "%sSupported: ms-benotify\r\n"
1990 "Proxy-Require: ms-benotify\r\n"
1991 "Event: presence\r\n"
1992 "Content-Type: %s\r\n"
1993 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1994 g_free(contact);
1996 /* subscribe to buddy presence */
1997 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1998 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1999 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2000 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2002 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2004 g_free(content);
2005 g_free(to);
2006 g_free(request);
2007 g_free(key);
2010 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
2011 SIPE_UNUSED_PARAMETER void *unused)
2013 gchar *to = sip_uri_self(sip);
2014 gchar *resources_uri = g_strdup("");
2015 if (sip->ocs2007) {
2016 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
2017 } else {
2018 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
2021 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
2024 struct presence_batched_routed {
2025 gchar *host;
2026 GSList *buddies;
2029 static void sipe_subscribe_presence_batched_routed_free(void *payload)
2031 struct presence_batched_routed *data = payload;
2032 GSList *buddies = data->buddies;
2033 while (buddies) {
2034 g_free(buddies->data);
2035 buddies = buddies->next;
2037 g_slist_free(data->buddies);
2038 g_free(data->host);
2039 g_free(payload);
2042 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
2044 struct presence_batched_routed *data = payload;
2045 GSList *buddies = data->buddies;
2046 gchar *resources_uri = g_strdup("");
2047 while (buddies) {
2048 gchar *tmp = resources_uri;
2049 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2050 g_free(tmp);
2051 buddies = buddies->next;
2053 sipe_subscribe_presence_batched_to(sip, resources_uri,
2054 g_strdup(data->host));
2058 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2059 * The user sends a single SUBSCRIBE request to the subscribed contact.
2060 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2064 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2067 gchar *key;
2068 gchar *to = sip_uri((char *)buddy_name);
2069 gchar *tmp = get_contact(sip);
2070 gchar *request;
2071 gchar *content = NULL;
2072 gchar *autoextend = "";
2073 gchar *content_type = "";
2074 struct sip_dialog *dialog;
2075 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2076 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2078 if (sbuddy) sbuddy->just_added = FALSE;
2080 if (sip->ocs2007) {
2081 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2082 } else {
2083 autoextend = "Supported: com.microsoft.autoextend\r\n";
2086 request = g_strdup_printf(
2087 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2088 "Supported: ms-piggyback-first-notify\r\n"
2089 "%s%sSupported: ms-benotify\r\n"
2090 "Proxy-Require: ms-benotify\r\n"
2091 "Event: presence\r\n"
2092 "Contact: %s\r\n", autoextend, content_type, tmp);
2094 if (sip->ocs2007) {
2095 content = g_strdup_printf(
2096 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2097 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2098 "<resource uri=\"%s\"%s\n"
2099 "</adhocList>\n"
2100 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2101 "<category name=\"calendarData\"/>\n"
2102 "<category name=\"contactCard\"/>\n"
2103 "<category name=\"note\"/>\n"
2104 "<category name=\"state\"/>\n"
2105 "</categoryList>\n"
2106 "</action>\n"
2107 "</batchSub>", sip->username, to, context);
2110 g_free(tmp);
2112 /* subscribe to buddy presence */
2113 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2114 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2115 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2116 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2118 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2120 g_free(content);
2121 g_free(to);
2122 g_free(request);
2123 g_free(key);
2126 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2128 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
2130 if (!purple_status_is_active(status))
2131 return;
2133 if (account->gc) {
2134 struct sipe_account_data *sip = account->gc->proto_data;
2136 if (sip) {
2137 gchar *action_name;
2138 gchar *tmp;
2139 time_t now = time(NULL);
2140 const char *status_id = purple_status_get_id(status);
2141 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2142 sipe_activity activity = sipe_get_activity_by_token(status_id);
2143 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2145 /* when other point of presence clears note, but we are keeping
2146 * state if OOF note.
2148 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2149 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
2150 do_not_publish = FALSE;
2153 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
2154 status_id, (int)sip->do_not_publish[activity], (int)now);
2156 sip->do_not_publish[activity] = 0;
2157 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
2158 status_id, (int)sip->do_not_publish[activity]);
2160 if (do_not_publish)
2162 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
2163 return;
2166 g_free(sip->status);
2167 sip->status = g_strdup(status_id);
2169 /* hack to escape apostrof before comparison */
2170 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
2172 /* this will preserve OOF flag as well */
2173 if (!sipe_strequal(tmp, sip->note)) {
2174 sip->is_oof_note = FALSE;
2175 g_free(sip->note);
2176 sip->note = g_strdup(note);
2177 sip->note_since = time(NULL);
2179 g_free(tmp);
2181 /* schedule 2 sec to capture idle flag */
2182 action_name = g_strdup_printf("<%s>", "+set-status");
2183 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2184 g_free(action_name);
2188 static void
2189 sipe_set_idle(PurpleConnection * gc,
2190 int interval)
2192 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
2194 if (gc) {
2195 struct sipe_account_data *sip = gc->proto_data;
2197 if (sip) {
2198 sip->idle_switch = time(NULL);
2199 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2204 static void
2205 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2206 SIPE_UNUSED_PARAMETER const char *alias)
2208 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2209 sipe_group_set_user(sip, name);
2212 static void
2213 sipe_group_buddy(PurpleConnection *gc,
2214 const char *who,
2215 const char *old_group_name,
2216 const char *new_group_name)
2218 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2219 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2220 struct sipe_group * old_group = NULL;
2221 struct sipe_group * new_group;
2223 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
2224 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2226 if(!buddy) { // buddy not in roaming list
2227 return;
2230 if (old_group_name) {
2231 old_group = sipe_group_find_by_name(sip, old_group_name);
2233 new_group = sipe_group_find_by_name(sip, new_group_name);
2235 if (old_group) {
2236 buddy->groups = g_slist_remove(buddy->groups, old_group);
2237 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
2240 if (!new_group) {
2241 sipe_group_create(sip, new_group_name, who);
2242 } else {
2243 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2244 sipe_group_set_user(sip, who);
2248 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2250 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2252 /* libpurple can call us with undefined buddy or group */
2253 if (buddy && group) {
2254 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2256 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2257 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2258 purple_blist_rename_buddy(buddy, buddy_name);
2259 g_free(buddy_name);
2261 /* Prepend sip: if needed */
2262 if (!g_str_has_prefix(buddy->name, "sip:")) {
2263 gchar *buf = sip_uri_from_name(buddy->name);
2264 purple_blist_rename_buddy(buddy, buf);
2265 g_free(buf);
2268 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2269 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2270 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
2271 b->name = g_strdup(buddy->name);
2272 b->just_added = TRUE;
2273 g_hash_table_insert(sip->buddies, b->name, b);
2274 sipe_group_buddy(gc, b->name, NULL, group->name);
2275 /* @TODO should go to callback */
2276 sipe_subscribe_presence_single(sip, b->name);
2277 } else {
2278 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
2283 static void sipe_free_buddy(struct sipe_buddy *buddy)
2285 #ifndef _WIN32
2287 * We are calling g_hash_table_foreach_steal(). That means that no
2288 * key/value deallocation functions are called. Therefore the glib
2289 * hash code does not touch the key (buddy->name) or value (buddy)
2290 * of the to-be-deleted hash node at all. It follows that we
2292 * - MUST free the memory for the key ourselves and
2293 * - ARE allowed to do it in this function
2295 * Conclusion: glib must be broken on the Windows platform if sipe
2296 * crashes with SIGTRAP when closing. You'll have to live
2297 * with the memory leak until this is fixed.
2299 g_free(buddy->name);
2300 #endif
2301 g_free(buddy->activity);
2302 g_free(buddy->meeting_subject);
2303 g_free(buddy->meeting_location);
2304 g_free(buddy->note);
2306 g_free(buddy->cal_start_time);
2307 g_free(buddy->cal_free_busy_base64);
2308 g_free(buddy->cal_free_busy);
2309 g_free(buddy->last_non_cal_activity);
2311 sipe_cal_free_working_hours(buddy->cal_working_hours);
2313 g_free(buddy->device_name);
2314 g_slist_free(buddy->groups);
2315 g_free(buddy);
2319 * Unassociates buddy from group first.
2320 * Then see if no groups left, removes buddy completely.
2321 * Otherwise updates buddy groups on server.
2323 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2325 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2326 struct sipe_buddy *b;
2327 struct sipe_group *g = NULL;
2329 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2330 if (!buddy) return;
2332 b = g_hash_table_lookup(sip->buddies, buddy->name);
2333 if (!b) return;
2335 if (group) {
2336 g = sipe_group_find_by_name(sip, group->name);
2339 if (g) {
2340 b->groups = g_slist_remove(b->groups, g);
2341 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
2344 if (g_slist_length(b->groups) < 1) {
2345 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2346 sipe_cancel_scheduled_action(sip, action_name);
2347 g_free(action_name);
2349 g_hash_table_remove(sip->buddies, buddy->name);
2351 if (b->name) {
2352 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2353 send_soap_request(sip, body);
2354 g_free(body);
2357 sipe_free_buddy(b);
2358 } else {
2359 //updates groups on server
2360 sipe_group_set_user(sip, b->name);
2365 static void
2366 sipe_rename_group(PurpleConnection *gc,
2367 const char *old_name,
2368 PurpleGroup *group,
2369 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2371 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2372 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2373 if (s_group) {
2374 sipe_group_rename(sip, s_group, group->name);
2375 } else {
2376 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
2380 static void
2381 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2383 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2384 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2385 if (s_group) {
2386 gchar *body;
2387 SIPE_DEBUG_INFO("Deleting group %s", group->name);
2388 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2389 send_soap_request(sip, body);
2390 g_free(body);
2392 sip->groups = g_slist_remove(sip->groups, s_group);
2393 g_free(s_group->name);
2394 g_free(s_group);
2395 } else {
2396 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
2400 /** All statuses need message attribute to pass Note */
2401 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2403 PurpleStatusType *type;
2404 GList *types = NULL;
2406 /* Macros to reduce code repetition.
2407 Translators: noun */
2408 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2409 prim, id, name, \
2410 TRUE, user, FALSE, \
2411 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2412 NULL); \
2413 types = g_list_append(types, type);
2415 /* Online */
2416 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2417 NULL,
2418 NULL,
2419 TRUE);
2421 /* Busy */
2422 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2423 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2424 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2425 TRUE);
2427 /* Do Not Disturb */
2428 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2429 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2430 NULL,
2431 TRUE);
2433 /* Away */
2434 /* Goes first in the list as
2435 * purple picks the first status with the AWAY type
2436 * for idle.
2438 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2439 NULL,
2440 NULL,
2441 TRUE);
2443 /* Be Right Back */
2444 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2445 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2446 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2447 TRUE);
2449 /* Appear Offline */
2450 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2451 NULL,
2452 NULL,
2453 TRUE);
2455 /* Offline */
2456 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2457 NULL,
2458 NULL,
2459 TRUE);
2460 types = g_list_append(types, type);
2462 return types;
2466 * A callback for g_hash_table_foreach
2468 static void
2469 sipe_buddy_subscribe_cb(char *buddy_name,
2470 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2471 struct sipe_account_data *sip)
2473 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2474 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2475 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2476 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2478 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2479 g_free(action_name);
2483 * Removes entries from purple buddy list
2484 * that does not correspond ones in the roaming contact list.
2486 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2487 GSList *buddies = purple_find_buddies(sip->account, NULL);
2488 GSList *entry = buddies;
2489 struct sipe_buddy *buddy;
2490 PurpleBuddy *b;
2491 PurpleGroup *g;
2493 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
2494 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sip->buddies));
2495 while (entry) {
2496 b = entry->data;
2497 g = purple_buddy_get_group(b);
2498 buddy = g_hash_table_lookup(sip->buddies, b->name);
2499 if(buddy) {
2500 gboolean in_sipe_groups = FALSE;
2501 GSList *entry2 = buddy->groups;
2502 while (entry2) {
2503 struct sipe_group *group = entry2->data;
2504 if (sipe_strequal(group->name, g->name)) {
2505 in_sipe_groups = TRUE;
2506 break;
2508 entry2 = entry2->next;
2510 if(!in_sipe_groups) {
2511 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
2512 purple_blist_remove_buddy(b);
2514 } else {
2515 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
2516 purple_blist_remove_buddy(b);
2518 entry = entry->next;
2520 g_slist_free(buddies);
2523 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2525 int len = msg->bodylen;
2527 const gchar *tmp = sipmsg_find_header(msg, "Event");
2528 const sipe_xml *item;
2529 sipe_xml *isc;
2530 const gchar *contacts_delta;
2531 const sipe_xml *group_node;
2532 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2533 return FALSE;
2536 /* Convert the contact from XML to Purple Buddies */
2537 isc = sipe_xml_parse(msg->body, len);
2538 if (!isc) {
2539 return FALSE;
2542 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
2543 if (contacts_delta) {
2544 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2547 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
2549 /* Parse groups */
2550 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
2551 struct sipe_group * group = g_new0(struct sipe_group, 1);
2552 const char *name = sipe_xml_attribute(group_node, "name");
2554 if (g_str_has_prefix(name, "~")) {
2555 name = _("Other Contacts");
2557 group->name = g_strdup(name);
2558 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
2560 sipe_group_add(sip, group);
2563 // Make sure we have at least one group
2564 if (g_slist_length(sip->groups) == 0) {
2565 struct sipe_group * group = g_new0(struct sipe_group, 1);
2566 PurpleGroup *purple_group;
2567 group->name = g_strdup(_("Other Contacts"));
2568 group->id = 1;
2569 purple_group = purple_group_new(group->name);
2570 purple_blist_add_group(purple_group, NULL);
2571 sip->groups = g_slist_append(sip->groups, group);
2574 /* Parse contacts */
2575 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
2576 const gchar *uri = sipe_xml_attribute(item, "uri");
2577 const gchar *name = sipe_xml_attribute(item, "name");
2578 gchar *buddy_name;
2579 struct sipe_buddy *buddy = NULL;
2580 gchar *tmp;
2581 gchar **item_groups;
2582 int i = 0;
2584 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2585 tmp = sip_uri_from_name(uri);
2586 buddy_name = g_ascii_strdown(tmp, -1);
2587 g_free(tmp);
2589 /* assign to group Other Contacts if nothing else received */
2590 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
2591 if(is_empty(tmp)) {
2592 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2593 g_free(tmp);
2594 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2596 item_groups = g_strsplit(tmp, " ", 0);
2597 g_free(tmp);
2599 while (item_groups[i]) {
2600 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2602 // If couldn't find the right group for this contact, just put them in the first group we have
2603 if (group == NULL && g_slist_length(sip->groups) > 0) {
2604 group = sip->groups->data;
2607 if (group != NULL) {
2608 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2609 if (!b){
2610 b = purple_buddy_new(sip->account, buddy_name, uri);
2611 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2613 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
2616 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2617 if (name != NULL && strlen(name) != 0) {
2618 purple_blist_alias_buddy(b, name);
2620 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
2624 if (!buddy) {
2625 buddy = g_new0(struct sipe_buddy, 1);
2626 buddy->name = g_strdup(b->name);
2627 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2630 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2632 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
2633 } else {
2634 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2635 name);
2638 i++;
2639 } // while, contact groups
2640 g_strfreev(item_groups);
2641 g_free(buddy_name);
2643 } // for, contacts
2645 sipe_cleanup_local_blist(sip);
2647 /* Add self-contact if not there yet. 2005 systems. */
2648 /* This will resemble subscription to roaming_self in 2007 systems */
2649 if (!sip->ocs2007) {
2650 gchar *self_uri = sip_uri_self(sip);
2651 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2653 if (!buddy) {
2654 buddy = g_new0(struct sipe_buddy, 1);
2655 buddy->name = g_strdup(self_uri);
2656 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2658 g_free(self_uri);
2661 sipe_xml_free(isc);
2663 /* subscribe to buddies */
2664 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2665 if (sip->batched_support) {
2666 sipe_subscribe_presence_batched(sip, NULL);
2667 } else {
2668 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2670 sip->subscribed_buddies = TRUE;
2672 /* for 2005 systems schedule contacts' status update
2673 * based on their calendar information
2675 if (!sip->ocs2007) {
2676 sipe_sched_calendar_status_update(sip, time(NULL));
2679 return 0;
2683 * Subscribe roaming contacts
2685 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2687 gchar *to = sip_uri_self(sip);
2688 gchar *tmp = get_contact(sip);
2689 gchar *hdr = g_strdup_printf(
2690 "Event: vnd-microsoft-roaming-contacts\r\n"
2691 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2692 "Supported: com.microsoft.autoextend\r\n"
2693 "Supported: ms-benotify\r\n"
2694 "Proxy-Require: ms-benotify\r\n"
2695 "Supported: ms-piggyback-first-notify\r\n"
2696 "Contact: %s\r\n", tmp);
2697 g_free(tmp);
2699 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2700 g_free(to);
2701 g_free(hdr);
2704 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2705 SIPE_UNUSED_PARAMETER void *unused)
2707 gchar *key;
2708 struct sip_dialog *dialog;
2709 gchar *to = sip_uri_self(sip);
2710 gchar *tmp = get_contact(sip);
2711 gchar *hdr = g_strdup_printf(
2712 "Event: presence.wpending\r\n"
2713 "Accept: text/xml+msrtc.wpending\r\n"
2714 "Supported: com.microsoft.autoextend\r\n"
2715 "Supported: ms-benotify\r\n"
2716 "Proxy-Require: ms-benotify\r\n"
2717 "Supported: ms-piggyback-first-notify\r\n"
2718 "Contact: %s\r\n", tmp);
2719 g_free(tmp);
2721 /* Subscription is identified by <event> key */
2722 key = g_strdup_printf("<%s>", "presence.wpending");
2723 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2724 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2726 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2728 g_free(to);
2729 g_free(hdr);
2730 g_free(key);
2734 * Fires on deregistration event initiated by server.
2735 * [MS-SIPREGE] SIP extension.
2738 // 2007 Example
2740 // Content-Type: text/registration-event
2741 // subscription-state: terminated;expires=0
2742 // ms-diagnostics-public: 4141;reason="User disabled"
2744 // deregistered;event=rejected
2746 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2748 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2749 gchar *event = NULL;
2750 gchar *reason = NULL;
2751 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2752 gchar *warning;
2754 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2755 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2757 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2758 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2759 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2760 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2761 } else {
2762 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2763 return;
2766 if (diagnostics != NULL) {
2767 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2768 } else { // for LCS2005
2769 int error_id = 0;
2770 if (event && sipe_strcase_equal(event, "unregistered")) {
2771 error_id = 4140; // [MS-SIPREGE]
2772 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2773 reason = g_strdup(_("you are already signed in at another location"));
2774 } else if (event && sipe_strcase_equal(event, "rejected")) {
2775 error_id = 4141;
2776 reason = g_strdup(_("user disabled")); // [MS-OCER]
2777 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2778 error_id = 4142;
2779 reason = g_strdup(_("user moved")); // [MS-OCER]
2782 g_free(event);
2783 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2784 g_free(reason);
2786 sip->gc->wants_to_die = TRUE;
2787 purple_connection_error(sip->gc, warning);
2788 g_free(warning);
2792 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2794 sipe_xml *xn_provision_group_list;
2795 const sipe_xml *node;
2797 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
2799 /* provisionGroup */
2800 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
2801 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
2802 g_free(sip->focus_factory_uri);
2803 sip->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
2804 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2805 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2806 break;
2809 sipe_xml_free(xn_provision_group_list);
2812 /** for 2005 system */
2813 static void
2814 sipe_process_provisioning(struct sipe_account_data *sip,
2815 struct sipmsg *msg)
2817 sipe_xml *xn_provision;
2818 const sipe_xml *node;
2820 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
2821 if ((node = sipe_xml_child(xn_provision, "user"))) {
2822 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
2823 if ((node = sipe_xml_child(node, "line"))) {
2824 const gchar *line_uri = sipe_xml_attribute(node, "uri");
2825 const gchar *server = sipe_xml_attribute(node, "server");
2826 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2827 sip_csta_open(sip, line_uri, server);
2830 sipe_xml_free(xn_provision);
2833 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2835 const gchar *contacts_delta;
2836 sipe_xml *xml;
2838 xml = sipe_xml_parse(msg->body, msg->bodylen);
2839 if (!xml)
2841 return;
2844 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
2845 if (contacts_delta)
2847 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2850 sipe_xml_free(xml);
2853 static void
2854 free_container_member(struct sipe_container_member *member)
2856 if (!member) return;
2858 g_free(member->type);
2859 g_free(member->value);
2860 g_free(member);
2863 static void
2864 free_container(struct sipe_container *container)
2866 GSList *entry;
2868 if (!container) return;
2870 entry = container->members;
2871 while (entry) {
2872 void *data = entry->data;
2873 entry = g_slist_remove(entry, data);
2874 free_container_member((struct sipe_container_member *)data);
2876 g_free(container);
2879 static void
2880 sipe_send_container_members_prepare(struct sipe_account_data *sip,
2881 const guint container_id,
2882 const guint container_version,
2883 const gchar *action,
2884 const gchar *type,
2885 const gchar *value,
2886 char **container_xmls)
2888 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2889 gchar *body;
2891 if (!container_xmls) return;
2893 body = g_strdup_printf(
2894 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
2895 container_id,
2896 container_version,
2897 action,
2898 type,
2899 value_str);
2900 g_free(value_str);
2902 if ((*container_xmls) == NULL) {
2903 *container_xmls = body;
2904 } else {
2905 char *tmp = *container_xmls;
2907 *container_xmls = g_strconcat(*container_xmls, body, NULL);
2908 g_free(tmp);
2909 g_free(body);
2913 static void
2914 sipe_send_set_container_members(struct sipe_account_data *sip,
2915 char *container_xmls)
2917 gchar *self;
2918 gchar *contact;
2919 gchar *hdr;
2920 gchar *body;
2922 if (!container_xmls) return;
2924 self = sip_uri_self(sip);
2925 body = g_strdup_printf(
2926 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2927 "%s"
2928 "</setContainerMembers>",
2929 container_xmls);
2931 contact = get_contact(sip);
2932 hdr = g_strdup_printf("Contact: %s\r\n"
2933 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2934 g_free(contact);
2936 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2938 g_free(hdr);
2939 g_free(body);
2940 g_free(self);
2944 * Finds locally stored MS-PRES container member
2946 static struct sipe_container_member *
2947 sipe_find_container_member(struct sipe_container *container,
2948 const gchar *type,
2949 const gchar *value)
2951 struct sipe_container_member *member;
2952 GSList *entry;
2954 if (container == NULL || type == NULL) {
2955 return NULL;
2958 entry = container->members;
2959 while (entry) {
2960 member = entry->data;
2961 if (sipe_strcase_equal(member->type, type) &&
2962 sipe_strcase_equal(member->value, value))
2964 return member;
2966 entry = entry->next;
2968 return NULL;
2972 * Finds locally stored MS-PRES container by id
2974 static struct sipe_container *
2975 sipe_find_container(struct sipe_account_data *sip,
2976 guint id)
2978 struct sipe_container *container;
2979 GSList *entry;
2981 if (sip == NULL) {
2982 return NULL;
2985 entry = sip->containers;
2986 while (entry) {
2987 container = entry->data;
2988 if (id == container->id) {
2989 return container;
2991 entry = entry->next;
2993 return NULL;
2996 /**
2997 * Returns pointer to domain part in provided Email URL
2999 * @param email an email URL. Example: first.last@hq.company.com
3000 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
3002 * Doesn't allocate memory
3004 static const char *
3005 sipe_get_domain(const char *email)
3007 char *tmp;
3009 if (!email) return NULL;
3011 tmp = strstr(email, "@");
3013 if (tmp && ((tmp+1) < (email + strlen(email)))) {
3014 return tmp+1;
3015 } else {
3016 return NULL;
3021 /* @TODO: replace with binary search for faster access? */
3022 /** source: http://support.microsoft.com/kb/897567 */
3023 static const char * const public_domains [] = {
3024 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
3025 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
3026 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
3027 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
3028 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
3029 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
3030 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
3031 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
3032 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
3033 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
3034 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
3035 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
3036 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
3037 "yahoo.com",
3038 NULL};
3040 static gboolean
3041 sipe_is_public_domain(const char *domain)
3043 int i = 0;
3044 while (public_domains[i]) {
3045 if (sipe_strcase_equal(public_domains[i], domain)) {
3046 return TRUE;
3048 i++;
3050 return FALSE;
3054 * Access Levels
3055 * 32000 - Blocked
3056 * 400 - Personal
3057 * 300 - Team
3058 * 200 - Company
3059 * 100 - Public
3061 static const char *
3062 sipe_get_access_level_name(int container_id)
3064 switch(container_id) {
3065 case 32000: return _("Blocked");
3066 case 400: return _("Personal");
3067 case 300: return _("Team");
3068 case 200: return _("Company");
3069 case 100: return _("Public");
3071 return _("Unknown");
3074 static const guint containers[] = {32000, 400, 300, 200, 100};
3075 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
3077 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
3078 static int
3079 sipe_find_access_level(struct sipe_account_data *sip,
3080 const gchar *type,
3081 const gchar *value)
3083 unsigned int i = 0;
3085 for (i = 0; i < CONTAINERS_LEN; i++) {
3086 struct sipe_container_member *member;
3087 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3088 if (!container) continue;
3090 if (sipe_strequal("user", type)) {
3091 const char *domain;
3092 const char *no_sip_uri = sipe_get_no_sip_uri(value);
3094 member = sipe_find_container_member(container, "user", no_sip_uri);
3095 if (member) return containers[i];
3097 domain = sipe_get_domain(no_sip_uri);
3098 member = sipe_find_container_member(container, "domain", domain);
3099 if (member) {
3100 return containers[i];
3103 member = sipe_find_container_member(container, "sameEnterprise", NULL);
3104 if (member &&
3105 sipe_strcase_equal(sip->sipdomain, domain))
3107 return containers[i];
3110 member = sipe_find_container_member(container, "publicCloud", NULL);
3111 if (member && sipe_is_public_domain(domain))
3113 return containers[i];
3116 member = sipe_find_container_member(container, "everyone", NULL);
3117 if (member)
3119 return containers[i];
3121 } else {
3122 member = sipe_find_container_member(container, type, value);
3123 if (member) {
3124 return containers[i];
3129 return -1;
3133 * @param container_id a new access level. If -1 then current access level
3134 * is just removed (I.e. the member is removed from all containers).
3135 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
3136 * @param value a value for member. E.g. SIP URI for "user" member type.
3138 static void
3139 sipe_change_access_level(struct sipe_account_data *sip,
3140 const int container_id,
3141 const gchar *type,
3142 const gchar *value)
3144 unsigned int i;
3145 int current_container_id = -1;
3146 char *container_xmls = NULL;
3148 /* for each container: find/delete */
3149 for (i = 0; i < CONTAINERS_LEN; i++) {
3150 struct sipe_container_member *member;
3151 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3153 if (!container) continue;
3155 member = sipe_find_container_member(container, type, value);
3156 if (member) {
3157 current_container_id = containers[i];
3158 /* delete/publish current access level */
3159 if (container_id < 0 || container_id != current_container_id) {
3160 sipe_send_container_members_prepare(
3161 sip, current_container_id, container->version, "remove", type, value, &container_xmls);
3162 /* remove member from our cache, to be able to recalculate AL below */
3163 container->members = g_slist_remove(container->members, member);
3164 current_container_id = -1;
3169 /* recalculate AL below */
3170 current_container_id = sipe_find_access_level(sip, type, value);
3172 /* assign/publish new access level */
3173 if (container_id != current_container_id && container_id >= 0) {
3174 struct sipe_container *container = sipe_find_container(sip, container_id);
3175 guint version = container ? container->version : 0;
3177 sipe_send_container_members_prepare(sip, container_id, version, "add", type, value, &container_xmls);
3180 if (container_xmls) {
3181 sipe_send_set_container_members(sip, container_xmls);
3183 g_free(container_xmls);
3186 static void
3187 free_publication(struct sipe_publication *publication)
3189 g_free(publication->category);
3190 g_free(publication->cal_event_hash);
3191 g_free(publication->note);
3193 g_free(publication->working_hours_xml_str);
3194 g_free(publication->fb_start_str);
3195 g_free(publication->free_busy_base64);
3197 g_free(publication);
3200 /* key is <category><instance><container> */
3201 static gboolean
3202 sipe_is_our_publication(struct sipe_account_data *sip,
3203 const gchar *key)
3205 GSList *entry;
3207 /* filling keys for our publications if not yet cached */
3208 if (!sip->our_publication_keys) {
3209 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
3210 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
3211 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
3212 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
3213 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
3214 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
3215 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
3217 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
3218 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
3219 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
3220 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
3221 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
3222 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
3223 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
3224 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
3225 SIPE_DEBUG_INFO("\tNote : %u", 0);
3226 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
3228 /* device */
3229 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3230 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
3232 /* state:machineState */
3233 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3234 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
3235 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3236 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3238 /* state:userState */
3239 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3240 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3241 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3242 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3244 /* state:calendarState */
3245 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3246 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3247 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3248 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3250 /* state:calendarState OOF */
3251 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3252 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3253 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3254 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3256 /* note */
3257 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3258 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3259 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3260 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3261 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3262 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3264 /* note OOF */
3265 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3266 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3267 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3268 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3269 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3270 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3272 /* calendarData:WorkingHours */
3273 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3274 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3275 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3276 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3277 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3278 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3279 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3280 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3281 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3282 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3283 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3284 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3286 /* calendarData:FreeBusy */
3287 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3288 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3289 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3290 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3291 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3292 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3293 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3294 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3295 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3296 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3297 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3298 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3300 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3301 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3304 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3306 entry = sip->our_publication_keys;
3307 while (entry) {
3308 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3309 if (sipe_strequal(entry->data, key)) {
3310 return TRUE;
3312 entry = entry->next;
3314 return FALSE;
3317 /** Property names to store in blist.xml */
3318 #define ALIAS_PROP "alias"
3319 #define EMAIL_PROP "email"
3320 #define PHONE_PROP "phone"
3321 #define PHONE_DISPLAY_PROP "phone-display"
3322 #define PHONE_MOBILE_PROP "phone-mobile"
3323 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3324 #define PHONE_HOME_PROP "phone-home"
3325 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3326 #define PHONE_OTHER_PROP "phone-other"
3327 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3328 #define PHONE_CUSTOM1_PROP "phone-custom1"
3329 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3330 #define SITE_PROP "site"
3331 #define COMPANY_PROP "company"
3332 #define DEPARTMENT_PROP "department"
3333 #define TITLE_PROP "title"
3334 #define OFFICE_PROP "office"
3335 /** implies work address */
3336 #define ADDRESS_STREET_PROP "address-street"
3337 #define ADDRESS_CITY_PROP "address-city"
3338 #define ADDRESS_STATE_PROP "address-state"
3339 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3340 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3343 * Tries to figure out user first and last name
3344 * based on Display Name and email properties.
3346 * Allocates memory - must be g_free()'d
3348 * Examples to parse:
3349 * First Last
3350 * First Last - Company Name
3351 * Last, First
3352 * Last, First M.
3353 * Last, First (C)(STP) (Company)
3354 * first.last@company.com (preprocessed as "first last")
3355 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3357 * Unusable examples:
3358 * user@company.com (preprocessed as "user")
3359 * first.m.last@company.com (preprocessed as "first m last")
3360 * user.company.com@reuters.net (preprocessed as "user company com")
3362 static void
3363 sipe_get_first_last_names(struct sipe_account_data *sip,
3364 const char *uri,
3365 char **first_name,
3366 char **last_name)
3368 PurpleBuddy *p_buddy;
3369 char *display_name;
3370 const char *email;
3371 const char *first, *last;
3372 char *tmp;
3373 char **parts;
3374 gboolean has_comma = FALSE;
3376 if (!sip || !uri) return;
3378 p_buddy = purple_find_buddy(sip->account, uri);
3380 if (!p_buddy) return;
3382 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3383 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3385 if (!display_name && !email) return;
3387 /* if no display name, make "first last anything_else" out of email */
3388 if (email && !display_name) {
3389 display_name = g_strndup(email, strstr(email, "@") - email);
3390 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
3391 g_free(tmp);
3394 if (display_name) {
3395 has_comma = (strstr(display_name, ",") != NULL);
3396 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
3397 g_free(tmp);
3398 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
3399 g_free(tmp);
3402 parts = g_strsplit(display_name, " ", 0);
3404 if (!parts[0] || !parts[1]) {
3405 g_free(display_name);
3406 g_strfreev(parts);
3407 return;
3410 if (has_comma) {
3411 last = parts[0];
3412 first = parts[1];
3413 } else {
3414 first = parts[0];
3415 last = parts[1];
3418 if (first_name) {
3419 *first_name = g_strstrip(g_strdup(first));
3422 if (last_name) {
3423 *last_name = g_strstrip(g_strdup(last));
3426 g_free(display_name);
3427 g_strfreev(parts);
3431 * Update user information
3433 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3434 * @param property_name
3435 * @param property_value may be modified to strip white space
3437 static void
3438 sipe_update_user_info(struct sipe_account_data *sip,
3439 const char *uri,
3440 const char *property_name,
3441 char *property_value)
3443 GSList *buddies, *entry;
3445 if (!property_name || strlen(property_name) == 0) return;
3447 if (property_value)
3448 property_value = g_strstrip(property_value);
3450 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3451 while (entry) {
3452 const char *prop_str;
3453 const char *server_alias;
3454 PurpleBuddy *p_buddy = entry->data;
3456 /* for Display Name */
3457 if (sipe_strequal(property_name, ALIAS_PROP)) {
3458 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3459 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
3460 purple_blist_alias_buddy(p_buddy, property_value);
3463 server_alias = purple_buddy_get_server_alias(p_buddy);
3464 if (!is_empty(property_value) &&
3465 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3467 purple_blist_server_alias_buddy(p_buddy, property_value);
3470 /* for other properties */
3471 else {
3472 if (!is_empty(property_value)) {
3473 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3474 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3475 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3480 entry = entry->next;
3482 g_slist_free(buddies);
3486 * Update user phone
3487 * Suitable for both 2005 and 2007 systems.
3489 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3490 * @param phone_type
3491 * @param phone may be modified to strip white space
3492 * @param phone_display_string may be modified to strip white space
3494 static void
3495 sipe_update_user_phone(struct sipe_account_data *sip,
3496 const char *uri,
3497 const gchar *phone_type,
3498 gchar *phone,
3499 gchar *phone_display_string)
3501 const char *phone_node = PHONE_PROP; /* work phone by default */
3502 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3504 if(!phone || strlen(phone) == 0) return;
3506 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3507 phone_node = PHONE_MOBILE_PROP;
3508 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3509 } else if (sipe_strequal(phone_type, "home")) {
3510 phone_node = PHONE_HOME_PROP;
3511 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3512 } else if (sipe_strequal(phone_type, "other")) {
3513 phone_node = PHONE_OTHER_PROP;
3514 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3515 } else if (sipe_strequal(phone_type, "custom1")) {
3516 phone_node = PHONE_CUSTOM1_PROP;
3517 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3520 sipe_update_user_info(sip, uri, phone_node, phone);
3521 if (phone_display_string) {
3522 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3526 static void
3527 sipe_update_calendar(struct sipe_account_data *sip)
3529 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3531 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: started.");
3533 if (sipe_strequal(calendar, "EXCH")) {
3534 sipe_ews_update_calendar(sip);
3537 /* schedule repeat */
3538 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3540 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: finished.");
3544 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3545 * by using standard Purple's means of signals and saved statuses.
3547 * Thus all UI elements get updated: Status Button with Note, docklet.
3548 * This is ablolutely important as both our status and note can come
3549 * inbound (roaming) or be updated programmatically (e.g. based on our
3550 * calendar data).
3552 static void
3553 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3554 const char *status_id,
3555 const char *message,
3556 time_t do_not_publish[])
3558 PurpleStatus *status = purple_account_get_active_status(account);
3559 gboolean changed = TRUE;
3561 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3562 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3564 changed = FALSE;
3567 if (purple_savedstatus_is_idleaway()) {
3568 changed = FALSE;
3571 if (changed) {
3572 PurpleSavedStatus *saved_status;
3573 const PurpleStatusType *acct_status_type =
3574 purple_status_type_find_with_id(account->status_types, status_id);
3575 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3576 sipe_activity activity = sipe_get_activity_by_token(status_id);
3578 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3579 if (saved_status) {
3580 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3583 /* If this type+message is unique then create a new transient saved status
3584 * Ref: gtkstatusbox.c
3586 if (!saved_status) {
3587 GList *tmp;
3588 GList *active_accts = purple_accounts_get_all_active();
3590 saved_status = purple_savedstatus_new(NULL, primitive);
3591 purple_savedstatus_set_message(saved_status, message);
3593 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3594 purple_savedstatus_set_substatus(saved_status,
3595 (PurpleAccount *)tmp->data, acct_status_type, message);
3597 g_list_free(active_accts);
3600 do_not_publish[activity] = time(NULL);
3601 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3602 status_id, (int)do_not_publish[activity]);
3604 /* Set the status for each account */
3605 purple_savedstatus_activate(saved_status);
3609 struct hash_table_delete_payload {
3610 GHashTable *hash_table;
3611 guint container;
3614 static void
3615 sipe_remove_category_container_publications_cb(const char *name,
3616 struct sipe_publication *publication,
3617 struct hash_table_delete_payload *payload)
3619 if (publication->container == payload->container) {
3620 g_hash_table_remove(payload->hash_table, name);
3623 static void
3624 sipe_remove_category_container_publications(GHashTable *our_publications,
3625 const char *category,
3626 guint container)
3628 struct hash_table_delete_payload payload;
3629 payload.hash_table = g_hash_table_lookup(our_publications, category);
3631 if (!payload.hash_table) return;
3633 payload.container = container;
3634 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3637 static void
3638 send_publish_category_initial(struct sipe_account_data *sip);
3641 * When we receive some self (BE) NOTIFY with a new subscriber
3642 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3645 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3647 gchar *contact;
3648 gchar *to;
3649 sipe_xml *xml;
3650 const sipe_xml *node;
3651 const sipe_xml *node2;
3652 char *display_name = NULL;
3653 char *uri;
3654 GSList *category_names = NULL;
3655 int aggreg_avail = 0;
3656 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3657 gboolean do_update_status = FALSE;
3658 gboolean has_note_cleaned = FALSE;
3660 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3662 xml = sipe_xml_parse(msg->body, msg->bodylen);
3663 if (!xml) return;
3665 contact = get_contact(sip);
3666 to = sip_uri_self(sip);
3669 /* categories */
3670 /* set list of categories participating in this XML */
3671 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3672 const gchar *name = sipe_xml_attribute(node, "name");
3673 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3675 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3676 category_names ? (int) g_slist_length(category_names) : -1);
3677 /* drop category information */
3678 if (category_names) {
3679 GSList *entry = category_names;
3680 while (entry) {
3681 GHashTable *cat_publications;
3682 const gchar *category = entry->data;
3683 entry = entry->next;
3684 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
3685 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3686 if (cat_publications) {
3687 g_hash_table_remove(sip->our_publications, category);
3688 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
3692 g_slist_free(category_names);
3693 /* filling our categories reflected in roaming data */
3694 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3695 const char *tmp;
3696 const gchar *name = sipe_xml_attribute(node, "name");
3697 guint container = sipe_xml_int_attribute(node, "container", -1);
3698 guint instance = sipe_xml_int_attribute(node, "instance", -1);
3699 guint version = sipe_xml_int_attribute(node, "version", 0);
3700 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
3701 sipe_utils_str_to_time(tmp) : 0;
3702 gchar *key;
3703 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3705 /* Ex. clear note: <category name="note"/> */
3706 if (container == (guint)-1) {
3707 g_free(sip->note);
3708 sip->note = NULL;
3709 do_update_status = TRUE;
3710 continue;
3713 /* Ex. clear note: <category name="note" container="200"/> */
3714 if (instance == (guint)-1) {
3715 if (container == 200) {
3716 g_free(sip->note);
3717 sip->note = NULL;
3718 do_update_status = TRUE;
3720 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
3721 sipe_remove_category_container_publications(
3722 sip->our_publications, name, container);
3723 continue;
3726 /* key is <category><instance><container> */
3727 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3728 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
3730 /* capture all userState publication for later clean up if required */
3731 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3732 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3734 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
3735 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3736 publication->category = g_strdup(name);
3737 publication->instance = instance;
3738 publication->container = container;
3739 publication->version = version;
3741 if (!sip->user_state_publications) {
3742 sip->user_state_publications = g_hash_table_new_full(
3743 g_str_hash, g_str_equal,
3744 g_free, (GDestroyNotify)free_publication);
3746 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3747 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3748 key, version);
3752 if (sipe_is_our_publication(sip, key)) {
3753 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3755 publication->category = g_strdup(name);
3756 publication->instance = instance;
3757 publication->container = container;
3758 publication->version = version;
3760 /* filling publication->availability */
3761 if (sipe_strequal(name, "state")) {
3762 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3763 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3765 if (xn_avail) {
3766 gchar *avail_str = sipe_xml_data(xn_avail);
3767 if (avail_str) {
3768 publication->availability = atoi(avail_str);
3770 g_free(avail_str);
3772 /* for calendarState */
3773 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
3774 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3775 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3777 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
3778 if (xn_activity) {
3779 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
3780 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3782 event->is_meeting = TRUE;
3785 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
3786 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
3788 publication->cal_event_hash = sipe_cal_event_hash(event);
3789 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3790 publication->cal_event_hash);
3791 sipe_cal_event_free(event);
3794 /* filling publication->note */
3795 if (sipe_strequal(name, "note")) {
3796 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
3798 if (!has_note_cleaned) {
3799 has_note_cleaned = TRUE;
3801 g_free(sip->note);
3802 sip->note = NULL;
3803 sip->note_since = publish_time;
3805 do_update_status = TRUE;
3808 g_free(publication->note);
3809 publication->note = NULL;
3810 if (xn_body) {
3811 char *tmp;
3813 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
3814 g_free(tmp);
3815 if (publish_time >= sip->note_since) {
3816 g_free(sip->note);
3817 sip->note = g_strdup(publication->note);
3818 sip->note_since = publish_time;
3819 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
3821 do_update_status = TRUE;
3826 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3827 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3828 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
3829 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
3830 if (xn_free_busy) {
3831 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3832 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
3834 if (xn_working_hours) {
3835 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
3839 if (!cat_publications) {
3840 cat_publications = g_hash_table_new_full(
3841 g_str_hash, g_str_equal,
3842 g_free, (GDestroyNotify)free_publication);
3843 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3844 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3846 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3847 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3849 g_free(key);
3851 /* aggregateState (not an our publication) from 2-nd container */
3852 if (sipe_strequal(name, "state") && container == 2) {
3853 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3855 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
3856 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3857 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3859 if (xn_avail) {
3860 gchar *avail_str = sipe_xml_data(xn_avail);
3861 if (avail_str) {
3862 aggreg_avail = atoi(avail_str);
3864 g_free(avail_str);
3867 if (xn_activity) {
3868 const char *activity_token = sipe_xml_attribute(xn_activity, "token");
3870 aggreg_activity = sipe_get_activity_by_token(activity_token);
3873 do_update_status = TRUE;
3877 /* userProperties published by server from AD */
3878 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3879 const sipe_xml *line;
3880 /* line, for Remote Call Control (RCC) */
3881 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
3882 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
3883 const gchar *line_type = sipe_xml_attribute(line, "lineType");
3884 gchar *line_uri;
3886 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3888 line_uri = sipe_xml_data(line);
3889 if (line_uri) {
3890 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
3891 sip_csta_open(sip, line_uri, line_server);
3893 g_free(line_uri);
3895 break;
3899 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
3900 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3902 /* containers */
3903 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
3904 guint id = sipe_xml_int_attribute(node, "id", 0);
3905 struct sipe_container *container = sipe_find_container(sip, id);
3907 if (container) {
3908 sip->containers = g_slist_remove(sip->containers, container);
3909 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
3910 free_container(container);
3912 container = g_new0(struct sipe_container, 1);
3913 container->id = id;
3914 container->version = sipe_xml_int_attribute(node, "version", 0);
3915 sip->containers = g_slist_append(sip->containers, container);
3916 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
3918 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
3919 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3920 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
3921 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
3922 container->members = g_slist_append(container->members, member);
3923 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
3924 member->type, member->value ? member->value : "");
3928 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
3929 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
3930 char *container_xmls = NULL;
3931 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3932 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3934 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
3935 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
3936 /* initial set-up to let counterparties see your status */
3937 if (sameEnterpriseAL < 0) {
3938 struct sipe_container *container = sipe_find_container(sip, 200);
3939 guint version = container ? container->version : 0;
3940 sipe_send_container_members_prepare(sip, 200, version, "add", "sameEnterprise", NULL, &container_xmls);
3942 if (federatedAL < 0) {
3943 struct sipe_container *container = sipe_find_container(sip, 100);
3944 guint version = container ? container->version : 0;
3945 sipe_send_container_members_prepare(sip, 100, version, "add", "federated", NULL, &container_xmls);
3947 sip->access_level_set = TRUE;
3949 if (container_xmls) {
3950 sipe_send_set_container_members(sip, container_xmls);
3952 g_free(container_xmls);
3955 /* subscribers */
3956 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
3957 const char *user;
3958 const char *acknowledged;
3959 gchar *hdr;
3960 gchar *body;
3962 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
3963 if (!user) continue;
3964 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
3965 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
3966 uri = sip_uri_from_name(user);
3968 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3970 acknowledged= sipe_xml_attribute(node, "acknowledged");
3971 if(sipe_strcase_equal(acknowledged,"false")){
3972 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
3973 if (!purple_find_buddy(sip->account, uri)) {
3974 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3977 hdr = g_strdup_printf(
3978 "Contact: %s\r\n"
3979 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3981 body = g_strdup_printf(
3982 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3983 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3984 "</setSubscribers>", user);
3986 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3987 g_free(body);
3988 g_free(hdr);
3990 g_free(display_name);
3991 g_free(uri);
3994 g_free(contact);
3995 sipe_xml_free(xml);
3997 /* Publish initial state if not yet.
3998 * Assuming this happens on initial responce to subscription to roaming-self
3999 * so we've already updated our roaming data in full.
4000 * Only for 2007+
4002 if (!sip->initial_state_published) {
4003 send_publish_category_initial(sip);
4004 sip->initial_state_published = TRUE;
4005 /* dalayed run */
4006 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
4007 do_update_status = FALSE;
4008 } else if (aggreg_avail) {
4010 g_free(sip->status);
4011 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
4012 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
4013 } else {
4014 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
4018 if (do_update_status) {
4019 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
4020 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
4023 g_free(to);
4026 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
4028 gchar *to = sip_uri_self(sip);
4029 gchar *tmp = get_contact(sip);
4030 gchar *hdr = g_strdup_printf(
4031 "Event: vnd-microsoft-roaming-ACL\r\n"
4032 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
4033 "Supported: com.microsoft.autoextend\r\n"
4034 "Supported: ms-benotify\r\n"
4035 "Proxy-Require: ms-benotify\r\n"
4036 "Supported: ms-piggyback-first-notify\r\n"
4037 "Contact: %s\r\n", tmp);
4038 g_free(tmp);
4040 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
4041 g_free(to);
4042 g_free(hdr);
4046 * To request for presence information about the user, access level settings that have already been configured by the user
4047 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
4048 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
4051 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
4053 gchar *to = sip_uri_self(sip);
4054 gchar *tmp = get_contact(sip);
4055 gchar *hdr = g_strdup_printf(
4056 "Event: vnd-microsoft-roaming-self\r\n"
4057 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
4058 "Supported: ms-benotify\r\n"
4059 "Proxy-Require: ms-benotify\r\n"
4060 "Supported: ms-piggyback-first-notify\r\n"
4061 "Contact: %s\r\n"
4062 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
4064 gchar *body=g_strdup(
4065 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
4066 "<roaming type=\"categories\"/>"
4067 "<roaming type=\"containers\"/>"
4068 "<roaming type=\"subscribers\"/></roamingList>");
4070 g_free(tmp);
4071 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4072 g_free(body);
4073 g_free(to);
4074 g_free(hdr);
4078 * For 2005 version
4080 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
4082 gchar *to = sip_uri_self(sip);
4083 gchar *tmp = get_contact(sip);
4084 gchar *hdr = g_strdup_printf(
4085 "Event: vnd-microsoft-provisioning\r\n"
4086 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
4087 "Supported: com.microsoft.autoextend\r\n"
4088 "Supported: ms-benotify\r\n"
4089 "Proxy-Require: ms-benotify\r\n"
4090 "Supported: ms-piggyback-first-notify\r\n"
4091 "Expires: 0\r\n"
4092 "Contact: %s\r\n", tmp);
4094 g_free(tmp);
4095 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
4096 g_free(to);
4097 g_free(hdr);
4100 /** Subscription for provisioning information to help with initial
4101 * configuration. This subscription is a one-time query (denoted by the Expires header,
4102 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
4103 * configuration, meeting policies, and policy settings that Communicator must enforce.
4104 * TODO: for what we need this information.
4107 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
4109 gchar *to = sip_uri_self(sip);
4110 gchar *tmp = get_contact(sip);
4111 gchar *hdr = g_strdup_printf(
4112 "Event: vnd-microsoft-provisioning-v2\r\n"
4113 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
4114 "Supported: com.microsoft.autoextend\r\n"
4115 "Supported: ms-benotify\r\n"
4116 "Proxy-Require: ms-benotify\r\n"
4117 "Supported: ms-piggyback-first-notify\r\n"
4118 "Expires: 0\r\n"
4119 "Contact: %s\r\n"
4120 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
4121 gchar *body = g_strdup(
4122 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
4123 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
4124 "<provisioningGroup name=\"ucPolicy\"/>"
4125 "</provisioningGroupList>");
4127 g_free(tmp);
4128 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4129 g_free(body);
4130 g_free(to);
4131 g_free(hdr);
4134 static void
4135 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
4136 gpointer value, gpointer user_data)
4138 struct sip_subscription *subscription = value;
4139 struct sip_dialog *dialog = &subscription->dialog;
4140 struct sipe_account_data *sip = user_data;
4141 gchar *tmp = get_contact(sip);
4142 gchar *hdr = g_strdup_printf(
4143 "Event: %s\r\n"
4144 "Expires: 0\r\n"
4145 "Contact: %s\r\n", subscription->event, tmp);
4146 g_free(tmp);
4148 /* Rate limit to max. 25 requests per seconds */
4149 g_usleep(1000000 / 25);
4151 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
4152 g_free(hdr);
4155 /* IM Session (INVITE and MESSAGE methods) */
4157 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
4158 static gchar *
4159 get_end_points (struct sipe_account_data *sip,
4160 struct sip_session *session)
4162 gchar *res;
4164 if (session == NULL) {
4165 return NULL;
4168 res = g_strdup_printf("<sip:%s>", sip->username);
4170 SIPE_DIALOG_FOREACH {
4171 gchar *tmp = res;
4172 res = g_strdup_printf("%s, <%s>", res, dialog->with);
4173 g_free(tmp);
4175 if (dialog->theirepid) {
4176 tmp = res;
4177 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
4178 g_free(tmp);
4180 } SIPE_DIALOG_FOREACH_END;
4182 return res;
4185 static gboolean
4186 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
4187 struct sipmsg *msg,
4188 SIPE_UNUSED_PARAMETER struct transaction *trans)
4190 gboolean ret = TRUE;
4192 if (msg->response != 200) {
4193 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
4194 return FALSE;
4197 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
4199 return ret;
4203 * Asks UA/proxy about its capabilities.
4205 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
4207 gchar *to = sip_uri(who);
4208 gchar *contact = get_contact(sip);
4209 gchar *request = g_strdup_printf(
4210 "Accept: application/sdp\r\n"
4211 "Contact: %s\r\n", contact);
4212 g_free(contact);
4214 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
4216 g_free(to);
4217 g_free(request);
4220 static void
4221 sipe_notify_user(struct sipe_account_data *sip,
4222 struct sip_session *session,
4223 PurpleMessageFlags flags,
4224 const gchar *message)
4226 PurpleConversation *conv;
4228 if (!session->conv) {
4229 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
4230 } else {
4231 conv = session->conv;
4233 purple_conversation_write(conv, NULL, message, flags, time(NULL));
4236 void
4237 sipe_present_info(struct sipe_account_data *sip,
4238 struct sip_session *session,
4239 const gchar *message)
4241 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4244 static void
4245 sipe_present_err(struct sipe_account_data *sip,
4246 struct sip_session *session,
4247 const gchar *message)
4249 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4252 void
4253 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4254 struct sip_session *session,
4255 int sip_error,
4256 int sip_warning,
4257 const gchar *who,
4258 const gchar *message)
4260 char *msg, *msg_tmp, *msg_tmp2;
4261 const char *label;
4263 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
4264 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4265 g_free(msg_tmp);
4266 /* Service unavailable; Server Internal Error; Server Time-out */
4267 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4268 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4269 g_free(msg);
4270 msg = NULL;
4271 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4272 label = _("This message was not delivered to %s because the service is not available");
4273 } else if (sip_error == 486) { /* Busy Here */
4274 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4275 } else if (sip_error == 415) { /* Unsupported media type */
4276 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4277 } else {
4278 label = _("This message was not delivered to %s because one or more recipients are offline");
4281 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4282 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4283 msg ? ":" : "",
4284 msg ? msg : "");
4285 sipe_present_err(sip, session, msg_tmp);
4286 g_free(msg_tmp2);
4287 g_free(msg_tmp);
4288 g_free(msg);
4292 static gboolean
4293 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4294 SIPE_UNUSED_PARAMETER struct transaction *trans)
4296 gboolean ret = TRUE;
4297 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4298 struct sip_session *session = sipe_session_find_im(sip, with);
4299 struct sip_dialog *dialog;
4300 gchar *cseq;
4301 char *key;
4302 struct queued_message *message;
4304 if (!session) {
4305 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4306 g_free(with);
4307 return FALSE;
4310 dialog = sipe_dialog_find(session, with);
4311 if (!dialog) {
4312 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4313 g_free(with);
4314 return FALSE;
4317 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4318 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4319 g_free(cseq);
4320 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4322 if (msg->response >= 400) {
4323 PurpleBuddy *pbuddy;
4324 const char *alias = with;
4325 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4326 int warning = -1;
4328 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4330 if (warn_hdr) {
4331 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4332 if (parts[0]) {
4333 warning = atoi(parts[0]);
4335 g_strfreev(parts);
4338 /* cancel file transfer as rejected by server */
4339 if (msg->response == 606 && /* Not acceptable all. */
4340 warning == 309 && /* Message contents not allowed by policy */
4341 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4343 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4344 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4345 sipe_utils_nameval_free(parsed_body);
4348 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4349 alias = purple_buddy_get_alias(pbuddy);
4352 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4354 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4355 if (msg->response == 408 || /* Request timeout */
4356 msg->response == 480 || /* Temporarily Unavailable */
4357 msg->response == 481) { /* Call/Transaction Does Not Exist */
4358 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4359 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4362 ret = FALSE;
4363 } else {
4364 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4365 if (message_id) {
4366 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4367 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4368 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4371 g_hash_table_remove(session->unconfirmed_messages, key);
4372 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4373 key, g_hash_table_size(session->unconfirmed_messages));
4376 g_free(key);
4377 g_free(with);
4379 if (ret) sipe_im_process_queue(sip, session);
4380 return ret;
4383 static gboolean
4384 sipe_is_election_finished(struct sip_session *session);
4386 static void
4387 sipe_election_result(struct sipe_account_data *sip,
4388 void *sess);
4390 static gboolean
4391 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4392 SIPE_UNUSED_PARAMETER struct transaction *trans)
4394 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4395 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4396 struct sip_dialog *dialog;
4397 struct sip_session *session;
4399 session = sipe_session_find_chat_by_callid(sip, callid);
4400 if (!session) {
4401 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
4402 return FALSE;
4405 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4406 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
4407 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
4408 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
4410 if (xn_request_rm_response) {
4411 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
4412 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
4414 dialog = sipe_dialog_find(session, with);
4415 if (!dialog) {
4416 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
4417 sipe_xml_free(xn_action);
4418 return FALSE;
4421 if (allow && !g_strcasecmp(allow, "true")) {
4422 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
4423 dialog->election_vote = 1;
4424 } else if (allow && !g_strcasecmp(allow, "false")) {
4425 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
4426 dialog->election_vote = -1;
4429 if (sipe_is_election_finished(session)) {
4430 sipe_election_result(sip, session);
4433 } else if (xn_set_rm_response) {
4436 sipe_xml_free(xn_action);
4440 return TRUE;
4443 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4445 gchar *hdr;
4446 gchar *tmp;
4447 char *msgtext = NULL;
4448 const gchar *msgr = "";
4449 gchar *tmp2 = NULL;
4451 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4452 char *msgformat;
4453 gchar *msgr_value;
4455 sipe_parse_html(msg, &msgformat, &msgtext);
4456 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
4458 msgr_value = sipmsg_get_msgr_string(msgformat);
4459 g_free(msgformat);
4460 if (msgr_value) {
4461 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4462 g_free(msgr_value);
4464 } else {
4465 msgtext = g_strdup(msg);
4468 tmp = get_contact(sip);
4469 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4470 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4471 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4472 if (content_type == NULL)
4473 content_type = "text/plain";
4475 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4476 g_free(tmp);
4477 g_free(tmp2);
4479 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4480 g_free(msgtext);
4481 g_free(hdr);
4485 void
4486 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4488 GSList *entry2 = session->outgoing_message_queue;
4489 while (entry2) {
4490 struct queued_message *msg = entry2->data;
4492 /* for multiparty chat or conference */
4493 if (session->is_multiparty || session->focus_uri) {
4494 gchar *who = sip_uri_self(sip);
4495 serv_got_chat_in(sip->gc, session->chat_id, who,
4496 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4497 g_free(who);
4500 SIPE_DIALOG_FOREACH {
4501 char *key;
4502 struct queued_message *message;
4504 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4506 message = g_new0(struct queued_message,1);
4507 message->body = g_strdup(msg->body);
4508 if (msg->content_type != NULL)
4509 message->content_type = g_strdup(msg->content_type);
4511 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4512 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4513 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4514 key, g_hash_table_size(session->unconfirmed_messages));
4515 g_free(key);
4517 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4518 } SIPE_DIALOG_FOREACH_END;
4520 entry2 = sipe_session_dequeue_message(session);
4524 static void
4525 sipe_refer_notify(struct sipe_account_data *sip,
4526 struct sip_session *session,
4527 const gchar *who,
4528 int status,
4529 const gchar *desc)
4531 gchar *hdr;
4532 gchar *body;
4533 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4535 hdr = g_strdup_printf(
4536 "Event: refer\r\n"
4537 "Subscription-State: %s\r\n"
4538 "Content-Type: message/sipfrag\r\n",
4539 status >= 200 ? "terminated" : "active");
4541 body = g_strdup_printf(
4542 "SIP/2.0 %d %s\r\n",
4543 status, desc);
4545 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4547 g_free(hdr);
4548 g_free(body);
4551 static gboolean
4552 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4554 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4555 struct sip_session *session;
4556 struct sip_dialog *dialog;
4557 char *cseq;
4558 char *key;
4559 struct queued_message *message;
4560 struct sipmsg *request_msg = trans->msg;
4562 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4563 gchar *referred_by;
4565 session = sipe_session_find_chat_by_callid(sip, callid);
4566 if (!session) {
4567 session = sipe_session_find_im(sip, with);
4569 if (!session) {
4570 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4571 g_free(with);
4572 return FALSE;
4575 dialog = sipe_dialog_find(session, with);
4576 if (!dialog) {
4577 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4578 g_free(with);
4579 return FALSE;
4582 sipe_dialog_parse(dialog, msg, TRUE);
4584 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4585 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4586 g_free(cseq);
4587 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4589 if (msg->response != 200) {
4590 PurpleBuddy *pbuddy;
4591 const char *alias = with;
4592 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4593 int warning = -1;
4595 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4597 if (warn_hdr) {
4598 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4599 if (parts[0]) {
4600 warning = atoi(parts[0]);
4602 g_strfreev(parts);
4605 /* cancel file transfer as rejected by server */
4606 if (msg->response == 606 && /* Not acceptable all. */
4607 warning == 309 && /* Message contents not allowed by policy */
4608 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4610 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4611 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4612 sipe_utils_nameval_free(parsed_body);
4615 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4616 alias = purple_buddy_get_alias(pbuddy);
4619 if (message) {
4620 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4621 } else {
4622 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4623 sipe_present_err(sip, session, tmp_msg);
4624 g_free(tmp_msg);
4627 sipe_dialog_remove(session, with);
4629 g_free(key);
4630 g_free(with);
4631 return FALSE;
4634 dialog->cseq = 0;
4635 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4636 dialog->outgoing_invite = NULL;
4637 dialog->is_established = TRUE;
4639 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4640 if (referred_by) {
4641 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4642 g_free(referred_by);
4645 /* add user to chat if it is a multiparty session */
4646 if (session->is_multiparty) {
4647 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4648 with, NULL,
4649 PURPLE_CBFLAGS_NONE, TRUE);
4652 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4653 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4654 sipe_session_dequeue_message(session);
4657 sipe_im_process_queue(sip, session);
4659 g_hash_table_remove(session->unconfirmed_messages, key);
4660 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4661 key, g_hash_table_size(session->unconfirmed_messages));
4663 g_free(key);
4664 g_free(with);
4665 return TRUE;
4669 void
4670 sipe_invite(struct sipe_account_data *sip,
4671 struct sip_session *session,
4672 const gchar *who,
4673 const gchar *msg_body,
4674 const gchar *msg_content_type,
4675 const gchar *referred_by,
4676 const gboolean is_triggered)
4678 gchar *hdr;
4679 gchar *to;
4680 gchar *contact;
4681 gchar *body;
4682 gchar *self;
4683 char *ms_text_format = NULL;
4684 gchar *roster_manager;
4685 gchar *end_points;
4686 gchar *referred_by_str;
4687 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4689 if (dialog && dialog->is_established) {
4690 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
4691 return;
4694 if (!dialog) {
4695 dialog = sipe_dialog_add(session);
4696 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4697 dialog->with = g_strdup(who);
4700 if (!(dialog->ourtag)) {
4701 dialog->ourtag = gentag();
4704 to = sip_uri(who);
4706 if (msg_body) {
4707 char *msgtext = NULL;
4708 char *base64_msg;
4709 const gchar *msgr = "";
4710 char *key;
4711 struct queued_message *message;
4712 gchar *tmp = NULL;
4714 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4715 char *msgformat;
4716 gchar *msgr_value;
4718 sipe_parse_html(msg_body, &msgformat, &msgtext);
4719 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
4721 msgr_value = sipmsg_get_msgr_string(msgformat);
4722 g_free(msgformat);
4723 if (msgr_value) {
4724 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4725 g_free(msgr_value);
4727 } else {
4728 msgtext = g_strdup(msg_body);
4731 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4732 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4733 msg_content_type ? msg_content_type : "text/plain",
4734 msgr,
4735 base64_msg);
4736 g_free(msgtext);
4737 g_free(tmp);
4738 g_free(base64_msg);
4740 message = g_new0(struct queued_message,1);
4741 message->body = g_strdup(msg_body);
4742 if (msg_content_type != NULL)
4743 message->content_type = g_strdup(msg_content_type);
4745 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4746 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4747 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4748 key, g_hash_table_size(session->unconfirmed_messages));
4749 g_free(key);
4752 contact = get_contact(sip);
4753 end_points = get_end_points(sip, session);
4754 self = sip_uri_self(sip);
4755 roster_manager = g_strdup_printf(
4756 "Roster-Manager: %s\r\n"
4757 "EndPoints: %s\r\n",
4758 self,
4759 end_points);
4760 referred_by_str = referred_by ?
4761 g_strdup_printf(
4762 "Referred-By: %s\r\n",
4763 referred_by)
4764 : g_strdup("");
4765 hdr = g_strdup_printf(
4766 "Supported: ms-sender\r\n"
4767 "%s"
4768 "%s"
4769 "%s"
4770 "%s"
4771 "Contact: %s\r\n%s"
4772 "Content-Type: application/sdp\r\n",
4773 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4774 referred_by_str,
4775 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4776 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4777 contact,
4778 ms_text_format ? ms_text_format : "");
4779 g_free(ms_text_format);
4780 g_free(self);
4782 body = g_strdup_printf(
4783 "v=0\r\n"
4784 "o=- 0 0 IN IP4 %s\r\n"
4785 "s=session\r\n"
4786 "c=IN IP4 %s\r\n"
4787 "t=0 0\r\n"
4788 "m=%s %d sip null\r\n"
4789 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4790 sipe_backend_network_ip_address(),
4791 sipe_backend_network_ip_address(),
4792 sip->ocs2007 ? "message" : "x-ms-message",
4793 sip->realport);
4795 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4796 to, to, hdr, body, dialog, process_invite_response);
4798 g_free(to);
4799 g_free(roster_manager);
4800 g_free(end_points);
4801 g_free(referred_by_str);
4802 g_free(body);
4803 g_free(hdr);
4804 g_free(contact);
4807 static void
4808 sipe_refer(struct sipe_account_data *sip,
4809 struct sip_session *session,
4810 const gchar *who)
4812 gchar *hdr;
4813 gchar *contact;
4814 gchar *epid = get_epid(sip);
4815 struct sip_dialog *dialog = sipe_dialog_find(session,
4816 session->roster_manager);
4817 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4819 contact = get_contact(sip);
4820 hdr = g_strdup_printf(
4821 "Contact: %s\r\n"
4822 "Refer-to: <%s>\r\n"
4823 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4824 "Require: com.microsoft.rtc-multiparty\r\n",
4825 contact,
4826 who,
4827 sip->username,
4828 ourtag ? ";tag=" : "",
4829 ourtag ? ourtag : "",
4830 epid);
4831 g_free(epid);
4833 send_sip_request(sip->gc, "REFER",
4834 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4836 g_free(hdr);
4837 g_free(contact);
4840 static void
4841 sipe_send_election_request_rm(struct sipe_account_data *sip,
4842 struct sip_dialog *dialog,
4843 int bid)
4845 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4847 gchar *body = g_strdup_printf(
4848 "<?xml version=\"1.0\"?>\r\n"
4849 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4850 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4851 sip->username, bid);
4853 send_sip_request(sip->gc, "INFO",
4854 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4856 g_free(body);
4859 static void
4860 sipe_send_election_set_rm(struct sipe_account_data *sip,
4861 struct sip_dialog *dialog)
4863 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4865 gchar *body = g_strdup_printf(
4866 "<?xml version=\"1.0\"?>\r\n"
4867 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4868 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4869 sip->username);
4871 send_sip_request(sip->gc, "INFO",
4872 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4874 g_free(body);
4877 static void
4878 sipe_session_close(struct sipe_account_data *sip,
4879 struct sip_session * session)
4881 if (session && session->focus_uri) {
4882 sipe_conf_immcu_closed(sip, session);
4883 conf_session_close(sip, session);
4886 if (session) {
4887 SIPE_DIALOG_FOREACH {
4888 /* @TODO slow down BYE message sending rate */
4889 /* @see single subscription code */
4890 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4891 } SIPE_DIALOG_FOREACH_END;
4893 sipe_session_remove(sip, session);
4897 static void
4898 sipe_session_close_all(struct sipe_account_data *sip)
4900 GSList *entry;
4901 while ((entry = sip->sessions) != NULL) {
4902 sipe_session_close(sip, entry->data);
4906 static void
4907 sipe_convo_closed(PurpleConnection * gc, const char *who)
4909 struct sipe_account_data *sip = gc->proto_data;
4911 SIPE_DEBUG_INFO("conversation with %s closed", who);
4912 sipe_session_close(sip, sipe_session_find_im(sip, who));
4915 static void
4916 sipe_chat_invite(PurpleConnection *gc, int id,
4917 SIPE_UNUSED_PARAMETER const char *message,
4918 const char *name)
4920 sipe_chat_create(gc->proto_data, id, name);
4923 static void
4924 sipe_chat_leave (PurpleConnection *gc, int id)
4926 struct sipe_account_data *sip = gc->proto_data;
4927 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4929 sipe_session_close(sip, session);
4932 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4933 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4935 struct sipe_account_data *sip = gc->proto_data;
4936 struct sip_session *session;
4937 struct sip_dialog *dialog;
4938 gchar *uri = sip_uri(who);
4940 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
4942 session = sipe_session_find_or_add_im(sip, uri);
4943 dialog = sipe_dialog_find(session, uri);
4945 // Queue the message
4946 sipe_session_enqueue_message(session, what, NULL);
4948 if (dialog && !dialog->outgoing_invite) {
4949 sipe_im_process_queue(sip, session);
4950 } else if (!dialog || !dialog->outgoing_invite) {
4951 // Need to send the INVITE to get the outgoing dialog setup
4952 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4955 g_free(uri);
4956 return 1;
4959 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4960 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4962 struct sipe_account_data *sip = gc->proto_data;
4963 struct sip_session *session;
4965 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
4967 session = sipe_session_find_chat_by_id(sip, id);
4969 // Queue the message
4970 if (session && session->dialogs) {
4971 sipe_session_enqueue_message(session,what,NULL);
4972 sipe_im_process_queue(sip, session);
4973 } else if (sip) {
4974 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4975 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4977 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
4978 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
4980 if (sip->ocs2007) {
4981 struct sip_session *session = sipe_session_add_chat(sip);
4983 session->is_multiparty = FALSE;
4984 session->focus_uri = g_strdup(proto_chat_id);
4985 sipe_session_enqueue_message(session, what, NULL);
4986 sipe_invite_conf_focus(sip, session);
4990 return 1;
4993 /* End IM Session (INVITE and MESSAGE methods) */
4995 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4997 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4998 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4999 gchar *from;
5000 struct sip_session *session;
5002 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
5004 /* Call Control protocol */
5005 if (g_str_has_prefix(contenttype, "application/csta+xml"))
5007 process_incoming_info_csta(sip, msg);
5008 return;
5011 from = parse_from(sipmsg_find_header(msg, "From"));
5012 session = sipe_session_find_chat_by_callid(sip, callid);
5013 if (!session) {
5014 session = sipe_session_find_im(sip, from);
5016 if (!session) {
5017 g_free(from);
5018 return;
5021 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
5023 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
5024 const sipe_xml *xn_request_rm = sipe_xml_child(xn_action, "RequestRM");
5025 const sipe_xml *xn_set_rm = sipe_xml_child(xn_action, "SetRM");
5027 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
5029 if (xn_request_rm) {
5030 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
5031 int bid = sipe_xml_int_attribute(xn_request_rm, "bid", 0);
5032 gchar *body = g_strdup_printf(
5033 "<?xml version=\"1.0\"?>\r\n"
5034 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5035 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
5036 sip->username,
5037 session->bid < bid ? "true" : "false");
5038 send_sip_response(sip->gc, msg, 200, "OK", body);
5039 g_free(body);
5040 } else if (xn_set_rm) {
5041 gchar *body;
5042 const char *rm = sipe_xml_attribute(xn_set_rm, "uri");
5043 g_free(session->roster_manager);
5044 session->roster_manager = g_strdup(rm);
5046 body = g_strdup_printf(
5047 "<?xml version=\"1.0\"?>\r\n"
5048 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5049 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
5050 sip->username);
5051 send_sip_response(sip->gc, msg, 200, "OK", body);
5052 g_free(body);
5054 sipe_xml_free(xn_action);
5057 else
5059 /* looks like purple lacks typing notification for chat */
5060 if (!session->is_multiparty && !session->focus_uri) {
5061 sipe_xml *xn_keyboard_activity = sipe_xml_parse(msg->body, msg->bodylen);
5062 const char *status = sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity, "status"),
5063 "status");
5064 if (sipe_strequal(status, "type")) {
5065 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
5066 } else if (sipe_strequal(status, "idle")) {
5067 serv_got_typing_stopped(sip->gc, from);
5069 sipe_xml_free(xn_keyboard_activity);
5072 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5074 g_free(from);
5077 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
5079 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5080 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5081 struct sip_session *session;
5082 struct sip_dialog *dialog;
5084 /* collect dialog identification
5085 * we need callid, ourtag and theirtag to unambiguously identify dialog
5087 /* take data before 'msg' will be modified by send_sip_response */
5088 dialog = g_new0(struct sip_dialog, 1);
5089 dialog->callid = g_strdup(callid);
5090 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
5091 dialog->with = g_strdup(from);
5092 sipe_dialog_parse(dialog, msg, FALSE);
5094 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5096 session = sipe_session_find_chat_by_callid(sip, callid);
5097 if (!session) {
5098 session = sipe_session_find_im(sip, from);
5100 if (!session) {
5101 sipe_dialog_free(dialog);
5102 g_free(from);
5103 return;
5106 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
5107 g_free(session->roster_manager);
5108 session->roster_manager = NULL;
5111 /* This what BYE is essentially for - terminating dialog */
5112 sipe_dialog_remove_3(session, dialog);
5113 sipe_dialog_free(dialog);
5114 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
5115 sipe_conf_immcu_closed(sip, session);
5116 } else if (session->is_multiparty) {
5117 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
5120 g_free(from);
5123 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
5125 gchar *self = sip_uri_self(sip);
5126 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5127 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5128 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
5129 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
5130 struct sip_session *session;
5131 struct sip_dialog *dialog;
5133 session = sipe_session_find_chat_by_callid(sip, callid);
5134 dialog = sipe_dialog_find(session, from);
5136 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
5137 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
5138 } else {
5139 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
5141 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
5144 g_free(self);
5145 g_free(from);
5146 g_free(refer_to);
5147 g_free(referred_by);
5150 static unsigned int
5151 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
5153 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
5154 struct sip_session *session;
5155 struct sip_dialog *dialog;
5157 if (state == PURPLE_NOT_TYPING)
5158 return 0;
5160 session = sipe_session_find_im(sip, who);
5161 dialog = sipe_dialog_find(session, who);
5163 if (session && dialog && dialog->is_established) {
5164 send_sip_request(gc, "INFO", who, who,
5165 "Content-Type: application/xml\r\n",
5166 SIPE_SEND_TYPING, dialog, NULL);
5168 return SIPE_TYPING_SEND_TIMEOUT;
5171 static gboolean resend_timeout(struct sipe_account_data *sip)
5173 GSList *tmp = sip->transactions;
5174 time_t currtime = time(NULL);
5175 while (tmp) {
5176 struct transaction *trans = tmp->data;
5177 tmp = tmp->next;
5178 SIPE_DEBUG_INFO("have open transaction age: %ld", (long int)currtime-trans->time);
5179 if ((currtime - trans->time > 5) && trans->retries >= 1) {
5180 /* TODO 408 */
5181 } else {
5182 if ((currtime - trans->time > 2) && trans->retries == 0) {
5183 trans->retries++;
5184 sendout_sipmsg(sip, trans->msg);
5188 return TRUE;
5191 static void do_reauthenticate_cb(struct sipe_account_data *sip,
5192 SIPE_UNUSED_PARAMETER void *unused)
5194 /* register again when security token expires */
5195 /* we have to start a new authentication as the security token
5196 * is almost expired by sending a not signed REGISTER message */
5197 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
5198 sipe_auth_free(&sip->registrar);
5199 sipe_auth_free(&sip->proxy);
5200 sip->registerstatus = 0;
5201 do_register(sip);
5202 sip->reauthenticate_set = FALSE;
5205 static gboolean
5206 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
5207 struct sipmsg *msg,
5208 GSList *parsed_body)
5210 gboolean found = FALSE;
5212 if (parsed_body) {
5213 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
5215 if (sipe_strequal(invitation_command, "INVITE")) {
5216 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
5217 found = TRUE;
5218 } else if (sipe_strequal(invitation_command, "CANCEL")) {
5219 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
5220 found = TRUE;
5221 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
5222 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
5223 found = TRUE;
5226 return found;
5229 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
5231 gchar *from;
5232 const gchar *contenttype;
5233 gboolean found = FALSE;
5235 from = parse_from(sipmsg_find_header(msg, "From"));
5237 if (!from) return;
5239 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
5241 contenttype = sipmsg_find_header(msg, "Content-Type");
5242 if (g_str_has_prefix(contenttype, "text/plain")
5243 || g_str_has_prefix(contenttype, "text/html")
5244 || g_str_has_prefix(contenttype, "multipart/related")
5245 || g_str_has_prefix(contenttype, "multipart/alternative"))
5247 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5248 gchar *html = get_html_message(contenttype, msg->body);
5250 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5251 if (!session) {
5252 session = sipe_session_find_im(sip, from);
5255 if (session && session->focus_uri) { /* a conference */
5256 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5257 gchar *sender = parse_from(tmp);
5258 g_free(tmp);
5259 serv_got_chat_in(sip->gc, session->chat_id, sender,
5260 PURPLE_MESSAGE_RECV, html, time(NULL));
5261 g_free(sender);
5262 } else if (session && session->is_multiparty) { /* a multiparty chat */
5263 serv_got_chat_in(sip->gc, session->chat_id, from,
5264 PURPLE_MESSAGE_RECV, html, time(NULL));
5265 } else {
5266 serv_got_im(sip->gc, from, html, 0, time(NULL));
5268 g_free(html);
5269 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5270 found = TRUE;
5272 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5273 sipe_xml *isc = sipe_xml_parse(msg->body, msg->bodylen);
5274 const sipe_xml *state;
5275 gchar *statedata;
5277 if (!isc) {
5278 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
5279 g_free(from);
5280 return;
5283 state = sipe_xml_child(isc, "state");
5285 if (!state) {
5286 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
5287 sipe_xml_free(isc);
5288 g_free(from);
5289 return;
5292 statedata = sipe_xml_data(state);
5293 if (statedata) {
5294 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5295 else serv_got_typing_stopped(sip->gc, from);
5297 g_free(statedata);
5299 sipe_xml_free(isc);
5300 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5301 found = TRUE;
5302 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5303 GSList *body = sipe_ft_parse_msg_body(msg->body);
5304 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5305 sipe_utils_nameval_free(body);
5306 if (found) {
5307 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5310 if (!found) {
5311 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5312 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5313 if (!session) {
5314 session = sipe_session_find_im(sip, from);
5316 if (session) {
5317 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5318 from);
5319 sipe_present_err(sip, session, errmsg);
5320 g_free(errmsg);
5323 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
5324 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5326 g_free(from);
5329 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5331 gchar *body;
5332 gchar *newTag;
5333 const gchar *oldHeader;
5334 gchar *newHeader;
5335 gboolean is_multiparty = FALSE;
5336 gboolean is_triggered = FALSE;
5337 gboolean was_multiparty = TRUE;
5338 gboolean just_joined = FALSE;
5339 gchar *from;
5340 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5341 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5342 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5343 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5344 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5345 GSList *end_points = NULL;
5346 char *tmp = NULL;
5347 struct sip_session *session;
5348 const gchar *ms_text_format;
5350 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
5351 g_free(tmp);
5353 /* Invitation to join conference */
5354 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5355 process_incoming_invite_conf(sip, msg);
5356 return;
5359 /* Only accept text invitations */
5360 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5361 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5362 return;
5365 // TODO There *must* be a better way to clean up the To header to add a tag...
5366 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5367 oldHeader = sipmsg_find_header(msg, "To");
5368 newTag = gentag();
5369 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5370 sipmsg_remove_header_now(msg, "To");
5371 sipmsg_add_header_now(msg, "To", newHeader);
5372 g_free(newHeader);
5374 if (end_points_hdr) {
5375 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5377 if (g_slist_length(end_points) > 2) {
5378 is_multiparty = TRUE;
5381 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5382 is_triggered = TRUE;
5383 is_multiparty = TRUE;
5386 session = sipe_session_find_chat_by_callid(sip, callid);
5387 /* Convert to multiparty */
5388 if (session && is_multiparty && !session->is_multiparty) {
5389 g_free(session->with);
5390 session->with = NULL;
5391 was_multiparty = FALSE;
5392 session->is_multiparty = TRUE;
5393 session->chat_id = rand();
5396 if (!session && is_multiparty) {
5397 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5399 /* IM session */
5400 from = parse_from(sipmsg_find_header(msg, "From"));
5401 if (!session) {
5402 session = sipe_session_find_or_add_im(sip, from);
5405 if (session) {
5406 g_free(session->callid);
5407 session->callid = g_strdup(callid);
5409 session->is_multiparty = is_multiparty;
5410 if (roster_manager) {
5411 session->roster_manager = g_strdup(roster_manager);
5415 if (is_multiparty && end_points) {
5416 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5417 GSList *entry = end_points;
5418 while (entry) {
5419 struct sip_dialog *dialog;
5420 struct sipendpoint *end_point = entry->data;
5421 entry = entry->next;
5423 if (!g_strcasecmp(from, end_point->contact) ||
5424 !g_strcasecmp(to, end_point->contact))
5425 continue;
5427 dialog = sipe_dialog_find(session, end_point->contact);
5428 if (dialog) {
5429 g_free(dialog->theirepid);
5430 dialog->theirepid = end_point->epid;
5431 end_point->epid = NULL;
5432 } else {
5433 dialog = sipe_dialog_add(session);
5435 dialog->callid = g_strdup(session->callid);
5436 dialog->with = end_point->contact;
5437 end_point->contact = NULL;
5438 dialog->theirepid = end_point->epid;
5439 end_point->epid = NULL;
5441 just_joined = TRUE;
5443 /* send triggered INVITE */
5444 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5447 g_free(to);
5450 if (end_points) {
5451 GSList *entry = end_points;
5452 while (entry) {
5453 struct sipendpoint *end_point = entry->data;
5454 entry = entry->next;
5455 g_free(end_point->contact);
5456 g_free(end_point->epid);
5457 g_free(end_point);
5459 g_slist_free(end_points);
5462 if (session) {
5463 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5464 if (dialog) {
5465 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5466 sipe_dialog_parse_routes(dialog, msg, FALSE);
5467 } else {
5468 dialog = sipe_dialog_add(session);
5470 dialog->callid = g_strdup(session->callid);
5471 dialog->with = g_strdup(from);
5472 sipe_dialog_parse(dialog, msg, FALSE);
5474 if (!dialog->ourtag) {
5475 dialog->ourtag = newTag;
5476 newTag = NULL;
5479 just_joined = TRUE;
5481 } else {
5482 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5484 g_free(newTag);
5486 if (is_multiparty && !session->conv) {
5487 gchar *chat_title = sipe_chat_get_name(callid);
5488 gchar *self = sip_uri_self(sip);
5489 /* create prpl chat */
5490 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5491 session->chat_title = g_strdup(chat_title);
5492 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5493 /* add self */
5494 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5495 self, NULL,
5496 PURPLE_CBFLAGS_NONE, FALSE);
5497 g_free(chat_title);
5498 g_free(self);
5501 if (is_multiparty && !was_multiparty) {
5502 /* add current IM counterparty to chat */
5503 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5504 sipe_dialog_first(session)->with, NULL,
5505 PURPLE_CBFLAGS_NONE, FALSE);
5508 /* add inviting party to chat */
5509 if (just_joined && session->conv) {
5510 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5511 from, NULL,
5512 PURPLE_CBFLAGS_NONE, TRUE);
5515 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5517 /* This used only in 2005 official client, not 2007 or Reuters.
5518 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5519 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5521 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5522 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5523 if (is_multiparty ||
5524 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5526 if (ms_text_format) {
5527 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5529 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5530 if (tmp) {
5531 gsize len;
5532 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5534 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5536 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5537 sipe_utils_nameval_free(parsed_body);
5538 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5540 g_free(tmp);
5542 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5544 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5545 gchar *html = get_html_message(ms_text_format, NULL);
5546 if (html) {
5547 if (is_multiparty) {
5548 serv_got_chat_in(sip->gc, session->chat_id, from,
5549 PURPLE_MESSAGE_RECV, html, time(NULL));
5550 } else {
5551 serv_got_im(sip->gc, from, html, 0, time(NULL));
5553 g_free(html);
5554 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5560 g_free(from);
5562 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5563 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5564 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5566 body = g_strdup_printf(
5567 "v=0\r\n"
5568 "o=- 0 0 IN IP4 %s\r\n"
5569 "s=session\r\n"
5570 "c=IN IP4 %s\r\n"
5571 "t=0 0\r\n"
5572 "m=%s %d sip sip:%s\r\n"
5573 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5574 sipe_backend_network_ip_address(),
5575 sipe_backend_network_ip_address(),
5576 sip->ocs2007 ? "message" : "x-ms-message",
5577 sip->realport,
5578 sip->username);
5579 send_sip_response(sip->gc, msg, 200, "OK", body);
5580 g_free(body);
5583 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5585 gchar *body;
5587 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5588 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5589 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5591 body = g_strdup_printf(
5592 "v=0\r\n"
5593 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5594 "s=session\r\n"
5595 "c=IN IP4 0.0.0.0\r\n"
5596 "t=0 0\r\n"
5597 "m=%s %d sip sip:%s\r\n"
5598 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5599 sip->ocs2007 ? "message" : "x-ms-message",
5600 sip->realport,
5601 sip->username);
5602 send_sip_response(sip->gc, msg, 200, "OK", body);
5603 g_free(body);
5606 static const char*
5607 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5609 const char *res = "NTLM";
5610 #ifdef HAVE_LIBKRB5
5611 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5612 res = "Kerberos";
5614 #else
5615 (void) sip; /* make compiler happy */
5616 #endif
5617 return res;
5620 static void sipe_connection_cleanup(struct sipe_account_data *);
5621 static void create_connection(struct sipe_account_data *, gchar *, int);
5623 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5624 SIPE_UNUSED_PARAMETER struct transaction *trans)
5626 gchar *tmp;
5627 const gchar *expires_header;
5628 int expires, i;
5629 GSList *hdr = msg->headers;
5630 struct sipnameval *elem;
5632 expires_header = sipmsg_find_header(msg, "Expires");
5633 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5634 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
5636 switch (msg->response) {
5637 case 200:
5638 if (expires == 0) {
5639 sip->registerstatus = 0;
5640 } else {
5641 const gchar *contact_hdr;
5642 gchar *gruu = NULL;
5643 gchar *epid;
5644 gchar *uuid;
5645 gchar *timeout;
5646 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5647 const char *auth_scheme;
5649 if (!sip->reregister_set) {
5650 gchar *action_name = g_strdup_printf("<%s>", "registration");
5651 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5652 g_free(action_name);
5653 sip->reregister_set = TRUE;
5656 sip->registerstatus = 3;
5658 if (server_hdr && !sip->server_version) {
5659 sip->server_version = g_strdup(server_hdr);
5660 g_free(default_ua);
5661 default_ua = NULL;
5664 auth_scheme = sipe_get_auth_scheme_name(sip);
5665 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5667 if (tmp) {
5668 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
5669 fill_auth(tmp, &sip->registrar);
5672 if (!sip->reauthenticate_set) {
5673 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5674 guint reauth_timeout;
5675 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5676 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5677 reauth_timeout = sip->registrar.expires - 300;
5678 } else {
5679 /* NTLM: we have to reauthenticate as our security token expires
5680 after eight hours (be five minutes early) */
5681 reauth_timeout = (8 * 3600) - 300;
5683 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5684 g_free(action_name);
5685 sip->reauthenticate_set = TRUE;
5688 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5690 epid = get_epid(sip);
5691 uuid = generateUUIDfromEPID(epid);
5692 g_free(epid);
5694 // There can be multiple Contact headers (one per location where the user is logged in) so
5695 // make sure to only get the one for this uuid
5696 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5697 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5698 if (valid_contact) {
5699 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5700 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5701 g_free(valid_contact);
5702 break;
5703 } else {
5704 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5707 g_free(uuid);
5709 g_free(sip->contact);
5710 if(gruu) {
5711 sip->contact = g_strdup_printf("<%s>", gruu);
5712 g_free(gruu);
5713 } else {
5714 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5715 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, sipe_backend_network_ip_address(), TRANSPORT_DESCRIPTOR);
5717 sip->ocs2007 = FALSE;
5718 sip->batched_support = FALSE;
5720 while(hdr)
5722 elem = hdr->data;
5723 if (sipe_strcase_equal(elem->name, "Supported")) {
5724 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5725 /* We interpret this as OCS2007+ indicator */
5726 sip->ocs2007 = TRUE;
5727 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5729 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5730 sip->batched_support = TRUE;
5731 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5734 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5735 gchar **caps = g_strsplit(elem->value,",",0);
5736 i = 0;
5737 while (caps[i]) {
5738 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5739 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5740 i++;
5742 g_strfreev(caps);
5744 hdr = g_slist_next(hdr);
5747 /* rejoin open chats to be able to use them by continue to send messages */
5748 purple_conversation_foreach(sipe_rejoin_chat);
5750 /* subscriptions */
5751 if (!sip->subscribed) { //do it just once, not every re-register
5753 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5754 (GCompareFunc)g_ascii_strcasecmp)) {
5755 sipe_subscribe_roaming_contacts(sip);
5758 /* For 2007+ it does not make sence to subscribe to:
5759 * vnd-microsoft-roaming-ACL
5760 * vnd-microsoft-provisioning (not v2)
5761 * presence.wpending
5762 * These are for backward compatibility.
5764 if (sip->ocs2007)
5766 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5767 (GCompareFunc)g_ascii_strcasecmp)) {
5768 sipe_subscribe_roaming_self(sip);
5770 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5771 (GCompareFunc)g_ascii_strcasecmp)) {
5772 sipe_subscribe_roaming_provisioning_v2(sip);
5775 /* For 2005- servers */
5776 else
5778 //sipe_options_request(sip, sip->sipdomain);
5780 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5781 (GCompareFunc)g_ascii_strcasecmp)) {
5782 sipe_subscribe_roaming_acl(sip);
5784 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5785 (GCompareFunc)g_ascii_strcasecmp)) {
5786 sipe_subscribe_roaming_provisioning(sip);
5788 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5789 (GCompareFunc)g_ascii_strcasecmp)) {
5790 sipe_subscribe_presence_wpending(sip, msg);
5793 /* For 2007+ we publish our initial statuses and calendar data only after
5794 * received our existing publications in sipe_process_roaming_self()
5795 * Only in this case we know versions of current publications made
5796 * on our behalf.
5798 /* For 2005- we publish our initial statuses only after
5799 * received our existing UserInfo data in response to
5800 * self subscription.
5801 * Only in this case we won't override existing UserInfo data
5802 * set earlier or by other client on our behalf.
5806 sip->subscribed = TRUE;
5809 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5810 "timeout=", ";", NULL);
5811 if (timeout != NULL) {
5812 sscanf(timeout, "%u", &sip->keepalive_timeout);
5813 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5814 sip->keepalive_timeout);
5815 g_free(timeout);
5818 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5820 break;
5821 case 301:
5823 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5825 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5826 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5827 gchar **tmp;
5828 gchar *hostname;
5829 int port = 0;
5830 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5831 int i = 1;
5833 tmp = g_strsplit(parts[0], ":", 0);
5834 hostname = g_strdup(tmp[0]);
5835 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5836 g_strfreev(tmp);
5838 while (parts[i]) {
5839 tmp = g_strsplit(parts[i], "=", 0);
5840 if (tmp[1]) {
5841 if (g_strcasecmp("transport", tmp[0]) == 0) {
5842 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5843 transport = SIPE_TRANSPORT_TCP;
5844 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5845 transport = SIPE_TRANSPORT_UDP;
5849 g_strfreev(tmp);
5850 i++;
5852 g_strfreev(parts);
5854 /* Close old connection */
5855 sipe_connection_cleanup(sip);
5857 /* Create new connection */
5858 sip->transport = transport;
5859 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5860 hostname, port, TRANSPORT_DESCRIPTOR);
5861 create_connection(sip, hostname, port);
5863 g_free(redirect);
5865 break;
5866 case 401:
5867 if (sip->registerstatus != 2) {
5868 const char *auth_scheme;
5869 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5870 if (sip->registrar.retries > 3) {
5871 sip->gc->wants_to_die = TRUE;
5872 purple_connection_error(sip->gc, _("Authentication failed"));
5873 return TRUE;
5876 auth_scheme = sipe_get_auth_scheme_name(sip);
5877 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5879 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
5880 if (!tmp) {
5881 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5882 sip->gc->wants_to_die = TRUE;
5883 purple_connection_error(sip->gc, tmp2);
5884 g_free(tmp2);
5885 return TRUE;
5887 fill_auth(tmp, &sip->registrar);
5888 sip->registerstatus = 2;
5889 if (sip->account->disconnecting) {
5890 do_register_exp(sip, 0);
5891 } else {
5892 do_register(sip);
5895 break;
5896 case 403:
5898 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5899 gchar **reason = NULL;
5900 gchar *warning;
5901 if (diagnostics != NULL) {
5902 /* Example header:
5903 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5905 reason = g_strsplit(diagnostics, "\"", 0);
5907 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5908 (reason && reason[1]) ? reason[1] : _("no reason given"));
5909 g_strfreev(reason);
5911 sip->gc->wants_to_die = TRUE;
5912 purple_connection_error(sip->gc, warning);
5913 g_free(warning);
5914 return TRUE;
5916 break;
5917 case 404:
5919 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5920 gchar *reason = NULL;
5921 gchar *warning;
5922 if (diagnostics != NULL) {
5923 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5925 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5926 diagnostics ? (reason ? reason : _("no reason given")) :
5927 _("SIP is either not enabled for the destination URI or it does not exist"));
5928 g_free(reason);
5930 sip->gc->wants_to_die = TRUE;
5931 purple_connection_error(sip->gc, warning);
5932 g_free(warning);
5933 return TRUE;
5935 break;
5936 case 503:
5937 case 504: /* Server time-out */
5939 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5940 gchar *reason = NULL;
5941 gchar *warning;
5942 if (diagnostics != NULL) {
5943 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5945 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5946 g_free(reason);
5948 sip->gc->wants_to_die = TRUE;
5949 purple_connection_error(sip->gc, warning);
5950 g_free(warning);
5951 return TRUE;
5953 break;
5955 return TRUE;
5959 * Returns 2005-style activity and Availability.
5961 * @param status Sipe statis id.
5963 static void
5964 sipe_get_act_avail_by_status_2005(const char *status,
5965 int *activity,
5966 int *availability)
5968 int avail = 300; /* online */
5969 int act = 400; /* Available */
5971 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5972 act = 100;
5973 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5974 // act = 150;
5975 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5976 act = 300;
5977 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5978 act = 400;
5979 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5980 // act = 500;
5981 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5982 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5983 act = 600;
5984 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5985 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5986 avail = 0; /* offline */
5987 act = 100;
5988 } else {
5989 act = 400; /* Available */
5992 if (activity) *activity = act;
5993 if (availability) *availability = avail;
5997 * [MS-SIP] 2.2.1
5999 * @param activity 2005 aggregated activity. Ex.: 600
6000 * @param availablity 2005 aggregated availablity. Ex.: 300
6002 static const char *
6003 sipe_get_status_by_act_avail_2005(const int activity,
6004 const int availablity,
6005 char **activity_desc)
6007 const char *status_id = NULL;
6008 const char *act = NULL;
6010 if (activity < 150) {
6011 status_id = SIPE_STATUS_ID_AWAY;
6012 } else if (activity < 200) {
6013 //status_id = SIPE_STATUS_ID_LUNCH;
6014 status_id = SIPE_STATUS_ID_AWAY;
6015 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
6016 } else if (activity < 300) {
6017 //status_id = SIPE_STATUS_ID_IDLE;
6018 status_id = SIPE_STATUS_ID_AWAY;
6019 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6020 } else if (activity < 400) {
6021 status_id = SIPE_STATUS_ID_BRB;
6022 } else if (activity < 500) {
6023 status_id = SIPE_STATUS_ID_AVAILABLE;
6024 } else if (activity < 600) {
6025 //status_id = SIPE_STATUS_ID_ON_PHONE;
6026 status_id = SIPE_STATUS_ID_BUSY;
6027 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
6028 } else if (activity < 700) {
6029 status_id = SIPE_STATUS_ID_BUSY;
6030 } else if (activity < 800) {
6031 status_id = SIPE_STATUS_ID_AWAY;
6032 } else {
6033 status_id = SIPE_STATUS_ID_AVAILABLE;
6036 if (availablity < 100)
6037 status_id = SIPE_STATUS_ID_OFFLINE;
6039 if (activity_desc && act) {
6040 g_free(*activity_desc);
6041 *activity_desc = g_strdup(act);
6044 return status_id;
6048 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
6050 static const char*
6051 sipe_get_status_by_availability(int avail,
6052 char** activity_desc)
6054 const char *status;
6055 const char *act = NULL;
6057 if (avail < 3000) {
6058 status = SIPE_STATUS_ID_OFFLINE;
6059 } else if (avail < 4500) {
6060 status = SIPE_STATUS_ID_AVAILABLE;
6061 } else if (avail < 6000) {
6062 //status = SIPE_STATUS_ID_IDLE;
6063 status = SIPE_STATUS_ID_AVAILABLE;
6064 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6065 } else if (avail < 7500) {
6066 status = SIPE_STATUS_ID_BUSY;
6067 } else if (avail < 9000) {
6068 //status = SIPE_STATUS_ID_BUSYIDLE;
6069 status = SIPE_STATUS_ID_BUSY;
6070 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
6071 } else if (avail < 12000) {
6072 status = SIPE_STATUS_ID_DND;
6073 } else if (avail < 15000) {
6074 status = SIPE_STATUS_ID_BRB;
6075 } else if (avail < 18000) {
6076 status = SIPE_STATUS_ID_AWAY;
6077 } else {
6078 status = SIPE_STATUS_ID_OFFLINE;
6081 if (activity_desc && act) {
6082 g_free(*activity_desc);
6083 *activity_desc = g_strdup(act);
6086 return status;
6090 * Returns 2007-style availability value
6092 * @param sipe_status_id (in)
6093 * @param activity_token (out) Must be g_free()'d after use if consumed.
6095 static int
6096 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
6098 int availability;
6099 sipe_activity activity = SIPE_ACTIVITY_UNSET;
6101 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
6102 availability = 15500;
6103 if (!activity_token || !(*activity_token)) {
6104 activity = SIPE_ACTIVITY_AWAY;
6106 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
6107 availability = 12500;
6108 activity = SIPE_ACTIVITY_BRB;
6109 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
6110 availability = 9500;
6111 activity = SIPE_ACTIVITY_DND;
6112 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
6113 availability = 6500;
6114 if (!activity_token || !(*activity_token)) {
6115 activity = SIPE_ACTIVITY_BUSY;
6117 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
6118 availability = 3500;
6119 activity = SIPE_ACTIVITY_ONLINE;
6120 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
6121 availability = 0;
6122 } else {
6123 // Offline or invisible
6124 availability = 18500;
6125 activity = SIPE_ACTIVITY_OFFLINE;
6128 if (activity_token) {
6129 *activity_token = g_strdup(sipe_activity_map[activity].token);
6131 return availability;
6134 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
6136 const char *uri;
6137 sipe_xml *xn_categories;
6138 const sipe_xml *xn_category;
6139 const char *status = NULL;
6140 gboolean do_update_status = FALSE;
6141 gboolean has_note_cleaned = FALSE;
6142 gboolean has_free_busy_cleaned = FALSE;
6144 xn_categories = sipe_xml_parse(data, len);
6145 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
6147 for (xn_category = sipe_xml_child(xn_categories, "category");
6148 xn_category ;
6149 xn_category = sipe_xml_twin(xn_category) )
6151 const sipe_xml *xn_node;
6152 const char *tmp;
6153 const char *attrVar = sipe_xml_attribute(xn_category, "name");
6154 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
6155 sipe_utils_str_to_time(tmp) : 0;
6157 /* contactCard */
6158 if (sipe_strequal(attrVar, "contactCard"))
6160 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
6162 if (card) {
6163 const sipe_xml *node;
6164 /* identity - Display Name and email */
6165 node = sipe_xml_child(card, "identity");
6166 if (node) {
6167 char* display_name = sipe_xml_data(
6168 sipe_xml_child(node, "name/displayName"));
6169 char* email = sipe_xml_data(
6170 sipe_xml_child(node, "email"));
6172 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6173 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6175 g_free(display_name);
6176 g_free(email);
6178 /* company */
6179 node = sipe_xml_child(card, "company");
6180 if (node) {
6181 char* company = sipe_xml_data(node);
6182 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
6183 g_free(company);
6185 /* department */
6186 node = sipe_xml_child(card, "department");
6187 if (node) {
6188 char* department = sipe_xml_data(node);
6189 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
6190 g_free(department);
6192 /* title */
6193 node = sipe_xml_child(card, "title");
6194 if (node) {
6195 char* title = sipe_xml_data(node);
6196 sipe_update_user_info(sip, uri, TITLE_PROP, title);
6197 g_free(title);
6199 /* office */
6200 node = sipe_xml_child(card, "office");
6201 if (node) {
6202 char* office = sipe_xml_data(node);
6203 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
6204 g_free(office);
6206 /* site (url) */
6207 node = sipe_xml_child(card, "url");
6208 if (node) {
6209 char* site = sipe_xml_data(node);
6210 sipe_update_user_info(sip, uri, SITE_PROP, site);
6211 g_free(site);
6213 /* phone */
6214 for (node = sipe_xml_child(card, "phone");
6215 node;
6216 node = sipe_xml_twin(node))
6218 const char *phone_type = sipe_xml_attribute(node, "type");
6219 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
6220 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
6222 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
6224 g_free(phone);
6225 g_free(phone_display_string);
6227 /* address */
6228 for (node = sipe_xml_child(card, "address");
6229 node;
6230 node = sipe_xml_twin(node))
6232 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
6233 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
6234 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
6235 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
6236 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
6237 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
6239 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
6240 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
6241 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
6242 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
6243 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
6245 g_free(street);
6246 g_free(city);
6247 g_free(state);
6248 g_free(zipcode);
6249 g_free(country_code);
6251 break;
6256 /* note */
6257 else if (sipe_strequal(attrVar, "note"))
6259 if (uri) {
6260 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6262 if (!has_note_cleaned) {
6263 has_note_cleaned = TRUE;
6265 g_free(sbuddy->note);
6266 sbuddy->note = NULL;
6267 sbuddy->is_oof_note = FALSE;
6268 sbuddy->note_since = publish_time;
6270 do_update_status = TRUE;
6272 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6273 /* clean up in case no 'note' element is supplied
6274 * which indicate note removal in client
6276 g_free(sbuddy->note);
6277 sbuddy->note = NULL;
6278 sbuddy->is_oof_note = FALSE;
6279 sbuddy->note_since = publish_time;
6281 xn_node = sipe_xml_child(xn_category, "note/body");
6282 if (xn_node) {
6283 char *tmp;
6284 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6285 g_free(tmp);
6286 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6287 sbuddy->note_since = publish_time;
6289 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6290 uri, sbuddy->note ? sbuddy->note : "");
6292 /* to trigger UI refresh in case no status info is supplied in this update */
6293 do_update_status = TRUE;
6297 /* state */
6298 else if(sipe_strequal(attrVar, "state"))
6300 char *tmp;
6301 int availability;
6302 const sipe_xml *xn_availability;
6303 const sipe_xml *xn_activity;
6304 const sipe_xml *xn_meeting_subject;
6305 const sipe_xml *xn_meeting_location;
6306 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6308 xn_node = sipe_xml_child(xn_category, "state");
6309 if (!xn_node) continue;
6310 xn_availability = sipe_xml_child(xn_node, "availability");
6311 if (!xn_availability) continue;
6312 xn_activity = sipe_xml_child(xn_node, "activity");
6313 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6314 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6316 tmp = sipe_xml_data(xn_availability);
6317 availability = atoi(tmp);
6318 g_free(tmp);
6320 /* activity, meeting_subject, meeting_location */
6321 if (sbuddy) {
6322 char *tmp = NULL;
6324 /* activity */
6325 g_free(sbuddy->activity);
6326 sbuddy->activity = NULL;
6327 if (xn_activity) {
6328 const char *token = sipe_xml_attribute(xn_activity, "token");
6329 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6331 /* from token */
6332 if (!is_empty(token)) {
6333 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6335 /* from custom element */
6336 if (xn_custom) {
6337 char *custom = sipe_xml_data(xn_custom);
6339 if (!is_empty(custom)) {
6340 sbuddy->activity = custom;
6341 custom = NULL;
6343 g_free(custom);
6346 /* meeting_subject */
6347 g_free(sbuddy->meeting_subject);
6348 sbuddy->meeting_subject = NULL;
6349 if (xn_meeting_subject) {
6350 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6352 if (!is_empty(meeting_subject)) {
6353 sbuddy->meeting_subject = meeting_subject;
6354 meeting_subject = NULL;
6356 g_free(meeting_subject);
6358 /* meeting_location */
6359 g_free(sbuddy->meeting_location);
6360 sbuddy->meeting_location = NULL;
6361 if (xn_meeting_location) {
6362 char *meeting_location = sipe_xml_data(xn_meeting_location);
6364 if (!is_empty(meeting_location)) {
6365 sbuddy->meeting_location = meeting_location;
6366 meeting_location = NULL;
6368 g_free(meeting_location);
6371 status = sipe_get_status_by_availability(availability, &tmp);
6372 if (sbuddy->activity && tmp) {
6373 char *tmp2 = sbuddy->activity;
6375 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6376 g_free(tmp);
6377 g_free(tmp2);
6378 } else if (tmp) {
6379 sbuddy->activity = tmp;
6383 do_update_status = TRUE;
6385 /* calendarData */
6386 else if(sipe_strequal(attrVar, "calendarData"))
6388 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6389 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6390 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6392 if (sbuddy && xn_free_busy) {
6393 if (!has_free_busy_cleaned) {
6394 has_free_busy_cleaned = TRUE;
6396 g_free(sbuddy->cal_start_time);
6397 sbuddy->cal_start_time = NULL;
6399 g_free(sbuddy->cal_free_busy_base64);
6400 sbuddy->cal_free_busy_base64 = NULL;
6402 g_free(sbuddy->cal_free_busy);
6403 sbuddy->cal_free_busy = NULL;
6405 sbuddy->cal_free_busy_published = publish_time;
6408 if (publish_time >= sbuddy->cal_free_busy_published) {
6409 g_free(sbuddy->cal_start_time);
6410 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6412 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6413 15 : 0;
6415 g_free(sbuddy->cal_free_busy_base64);
6416 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6418 g_free(sbuddy->cal_free_busy);
6419 sbuddy->cal_free_busy = NULL;
6421 sbuddy->cal_free_busy_published = publish_time;
6423 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: startTime=%s granularity=%d cal_free_busy_base64=\n%s", sbuddy->cal_start_time, sbuddy->cal_granularity, sbuddy->cal_free_busy_base64);
6427 if (sbuddy && xn_working_hours) {
6428 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6433 if (do_update_status) {
6434 if (!status) { /* no status category in this update, using contact's current status */
6435 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6436 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6437 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6438 status = purple_status_get_id(pstatus);
6441 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
6442 sipe_got_user_status(sip, uri, status);
6445 sipe_xml_free(xn_categories);
6448 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6450 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6451 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
6452 payload->host = g_strdup(host);
6453 payload->buddies = server;
6454 sipe_subscribe_presence_batched_routed(sip, payload);
6455 sipe_subscribe_presence_batched_routed_free(payload);
6458 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6460 sipe_xml *xn_list;
6461 const sipe_xml *xn_resource;
6462 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6463 g_free, NULL);
6464 GSList *server;
6465 gchar *host;
6467 xn_list = sipe_xml_parse(data, len);
6469 for (xn_resource = sipe_xml_child(xn_list, "resource");
6470 xn_resource;
6471 xn_resource = sipe_xml_twin(xn_resource) )
6473 const char *uri, *state;
6474 const sipe_xml *xn_instance;
6476 xn_instance = sipe_xml_child(xn_resource, "instance");
6477 if (!xn_instance) continue;
6479 uri = sipe_xml_attribute(xn_resource, "uri");
6480 state = sipe_xml_attribute(xn_instance, "state");
6481 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
6483 if (strstr(state, "resubscribe")) {
6484 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
6486 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6487 gchar *user = g_strdup(uri);
6488 host = g_strdup(poolFqdn);
6489 server = g_hash_table_lookup(servers, host);
6490 server = g_slist_append(server, user);
6491 g_hash_table_insert(servers, host, server);
6492 } else {
6493 sipe_subscribe_presence_single(sip, (void *) uri);
6498 /* Send out any deferred poolFqdn subscriptions */
6499 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6500 g_hash_table_destroy(servers);
6502 sipe_xml_free(xn_list);
6505 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6507 gchar *uri;
6508 gchar *getbasic;
6509 gchar *activity = NULL;
6510 sipe_xml *pidf;
6511 const sipe_xml *basicstatus = NULL, *tuple, *status;
6512 gboolean isonline = FALSE;
6513 const sipe_xml *display_name_node;
6515 pidf = sipe_xml_parse(data, len);
6516 if (!pidf) {
6517 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
6518 return;
6521 if ((tuple = sipe_xml_child(pidf, "tuple")))
6523 if ((status = sipe_xml_child(tuple, "status"))) {
6524 basicstatus = sipe_xml_child(status, "basic");
6528 if (!basicstatus) {
6529 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6530 sipe_xml_free(pidf);
6531 return;
6534 getbasic = sipe_xml_data(basicstatus);
6535 if (!getbasic) {
6536 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6537 sipe_xml_free(pidf);
6538 return;
6541 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
6542 if (strstr(getbasic, "open")) {
6543 isonline = TRUE;
6545 g_free(getbasic);
6547 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6549 display_name_node = sipe_xml_child(pidf, "display-name");
6550 if (display_name_node) {
6551 char * display_name = sipe_xml_data(display_name_node);
6553 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6554 g_free(display_name);
6557 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
6558 if ((status = sipe_xml_child(tuple, "status"))) {
6559 if ((basicstatus = sipe_xml_child(status, "activities"))) {
6560 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
6561 activity = sipe_xml_data(basicstatus);
6562 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
6568 if (isonline) {
6569 const gchar * status_id = NULL;
6570 if (activity) {
6571 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6572 status_id = SIPE_STATUS_ID_BUSY;
6573 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6574 status_id = SIPE_STATUS_ID_AWAY;
6578 if (!status_id) {
6579 status_id = SIPE_STATUS_ID_AVAILABLE;
6582 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
6583 sipe_got_user_status(sip, uri, status_id);
6584 } else {
6585 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6588 g_free(activity);
6589 g_free(uri);
6590 sipe_xml_free(pidf);
6593 /** 2005 */
6594 static void
6595 sipe_user_info_has_updated(struct sipe_account_data *sip,
6596 const sipe_xml *xn_userinfo)
6598 const sipe_xml *xn_states;
6600 g_free(sip->user_states);
6601 sip->user_states = NULL;
6602 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
6603 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
6605 /* this is a hack-around to remove added newline after inner element,
6606 * state in this case, where it shouldn't be.
6607 * After several use of sipe_xml_stringify, amount of added newlines
6608 * grows significantly.
6610 if (orig) {
6611 gchar c, *stripped = orig;
6612 while ((c = *orig++)) {
6613 if ((c != '\n') /* && (c != '\r') */) {
6614 *stripped++ = c;
6617 *stripped = '\0';
6621 /* Publish initial state if not yet.
6622 * Assuming this happens on initial responce to self subscription
6623 * so we've already updated our UserInfo.
6625 if (!sip->initial_state_published) {
6626 send_presence_soap(sip, FALSE);
6627 /* dalayed run */
6628 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6632 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6634 char *activity = NULL;
6635 const char *epid;
6636 const char *status_id = NULL;
6637 const char *name;
6638 char *uri;
6639 char *self_uri = sip_uri_self(sip);
6640 int avl;
6641 int act;
6642 const char *device_name = NULL;
6643 const char *cal_start_time = NULL;
6644 const char *cal_granularity = NULL;
6645 char *cal_free_busy_base64 = NULL;
6646 struct sipe_buddy *sbuddy;
6647 const sipe_xml *node;
6648 sipe_xml *xn_presentity;
6649 const sipe_xml *xn_availability;
6650 const sipe_xml *xn_activity;
6651 const sipe_xml *xn_display_name;
6652 const sipe_xml *xn_email;
6653 const sipe_xml *xn_phone_number;
6654 const sipe_xml *xn_userinfo;
6655 const sipe_xml *xn_note;
6656 const sipe_xml *xn_oof;
6657 const sipe_xml *xn_state;
6658 const sipe_xml *xn_contact;
6659 char *note;
6660 char *free_activity;
6661 int user_avail;
6662 const char *user_avail_nil;
6663 int res_avail;
6664 time_t user_avail_since = 0;
6665 time_t activity_since = 0;
6667 /* fix for Reuters environment on Linux */
6668 if (data && strstr(data, "encoding=\"utf-16\"")) {
6669 char *tmp_data;
6670 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6671 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
6672 g_free(tmp_data);
6673 } else {
6674 xn_presentity = sipe_xml_parse(data, len);
6677 xn_availability = sipe_xml_child(xn_presentity, "availability");
6678 xn_activity = sipe_xml_child(xn_presentity, "activity");
6679 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
6680 xn_email = sipe_xml_child(xn_presentity, "email");
6681 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
6682 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
6683 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
6684 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
6685 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
6686 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
6687 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
6688 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
6689 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
6690 note = xn_note ? sipe_xml_data(xn_note) : NULL;
6692 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6693 user_avail = 0;
6694 user_avail_since = 0;
6697 free_activity = NULL;
6699 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
6700 uri = sip_uri_from_name(name);
6701 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
6702 epid = sipe_xml_attribute(xn_availability, "epid");
6703 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
6705 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6706 res_avail = sipe_get_availability_by_status(status_id, NULL);
6707 if (user_avail > res_avail) {
6708 res_avail = user_avail;
6709 status_id = sipe_get_status_by_availability(user_avail, NULL);
6712 if (xn_display_name) {
6713 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
6714 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
6715 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
6716 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
6717 char *tel_uri = sip_to_tel_uri(phone_number);
6719 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6720 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6721 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6722 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6724 g_free(tel_uri);
6725 g_free(phone_label);
6726 g_free(phone_number);
6727 g_free(email);
6728 g_free(display_name);
6731 if (xn_contact) {
6732 /* tel */
6733 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
6735 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6736 const char *phone_type = sipe_xml_attribute(node, "type");
6737 char* phone = sipe_xml_data(node);
6739 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6741 g_free(phone);
6745 /* devicePresence */
6746 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
6747 const sipe_xml *xn_device_name;
6748 const sipe_xml *xn_calendar_info;
6749 const sipe_xml *xn_state;
6750 char *state;
6752 /* deviceName */
6753 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
6754 xn_device_name = sipe_xml_child(node, "deviceName");
6755 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
6758 /* calendarInfo */
6759 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
6760 if (xn_calendar_info) {
6761 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
6763 if (cal_start_time) {
6764 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6765 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6767 if (cal_start_time_t_tmp > cal_start_time_t) {
6768 cal_start_time = cal_start_time_tmp;
6769 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6770 g_free(cal_free_busy_base64);
6771 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6773 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
6775 } else {
6776 cal_start_time = cal_start_time_tmp;
6777 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6778 g_free(cal_free_busy_base64);
6779 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6781 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
6785 /* state */
6786 xn_state = sipe_xml_child(node, "states/state");
6787 if (xn_state) {
6788 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
6789 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
6791 state = sipe_xml_data(xn_state);
6792 if (dev_avail_since > user_avail_since &&
6793 dev_avail >= res_avail)
6795 res_avail = dev_avail;
6796 if (!is_empty(state))
6798 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6799 g_free(activity);
6800 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6801 } else if (sipe_strequal(state, "presenting")) {
6802 g_free(activity);
6803 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6804 } else {
6805 activity = state;
6806 state = NULL;
6808 activity_since = dev_avail_since;
6810 status_id = sipe_get_status_by_availability(res_avail, &activity);
6812 g_free(state);
6816 /* oof */
6817 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6818 g_free(activity);
6819 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6820 activity_since = 0;
6823 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6824 if (sbuddy)
6826 g_free(sbuddy->activity);
6827 sbuddy->activity = activity;
6828 activity = NULL;
6830 sbuddy->activity_since = activity_since;
6832 sbuddy->user_avail = user_avail;
6833 sbuddy->user_avail_since = user_avail_since;
6835 g_free(sbuddy->note);
6836 sbuddy->note = NULL;
6837 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6839 sbuddy->is_oof_note = (xn_oof != NULL);
6841 g_free(sbuddy->device_name);
6842 sbuddy->device_name = NULL;
6843 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6845 if (!is_empty(cal_free_busy_base64)) {
6846 g_free(sbuddy->cal_start_time);
6847 sbuddy->cal_start_time = g_strdup(cal_start_time);
6849 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6851 g_free(sbuddy->cal_free_busy_base64);
6852 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6853 cal_free_busy_base64 = NULL;
6855 g_free(sbuddy->cal_free_busy);
6856 sbuddy->cal_free_busy = NULL;
6859 sbuddy->last_non_cal_status_id = status_id;
6860 g_free(sbuddy->last_non_cal_activity);
6861 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6863 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6864 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6866 sip->is_oof_note = sbuddy->is_oof_note;
6868 g_free(sip->note);
6869 sip->note = g_strdup(sbuddy->note);
6871 sip->note_since = time(NULL);
6874 g_free(sip->status);
6875 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6878 g_free(cal_free_busy_base64);
6879 g_free(activity);
6881 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
6882 sipe_got_user_status(sip, uri, status_id);
6884 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6885 sipe_user_info_has_updated(sip, xn_userinfo);
6888 g_free(note);
6889 sipe_xml_free(xn_presentity);
6890 g_free(uri);
6891 g_free(self_uri);
6894 static void sipe_presence_mime_cb(gpointer user_data,
6895 const gchar *type,
6896 const gchar *body,
6897 gsize length)
6899 if (strstr(type,"application/rlmi+xml")) {
6900 process_incoming_notify_rlmi_resub(user_data, body, length);
6901 } else if (strstr(type, "text/xml+msrtc.pidf")) {
6902 process_incoming_notify_msrtc(user_data, body, length);
6903 } else {
6904 process_incoming_notify_rlmi(user_data, body, length);
6908 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6910 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6912 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
6914 if (ctype &&
6915 (strstr(ctype, "application/rlmi+xml") ||
6916 strstr(ctype, "application/msrtc-event-categories+xml")))
6918 if (strstr(ctype, "multipart"))
6920 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sip);
6922 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6924 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6926 else if(strstr(ctype, "application/rlmi+xml"))
6928 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6931 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6933 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6935 else
6937 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6941 static void sipe_presence_timeout_mime_cb(gpointer user_data,
6942 SIPE_UNUSED_PARAMETER const gchar *type,
6943 const gchar *body,
6944 gsize length)
6946 GSList **buddies = user_data;
6947 sipe_xml *xml = sipe_xml_parse(body, length);
6949 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
6950 const gchar *uri = sipe_xml_attribute(xml, "uri");
6951 const sipe_xml *xn_category;
6954 * automaton: presence is never expected to change
6956 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
6958 for (xn_category = sipe_xml_child(xml, "category");
6959 xn_category;
6960 xn_category = sipe_xml_twin(xn_category)) {
6961 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
6962 "contactCard")) {
6963 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
6964 if (node) {
6965 char *boolean = sipe_xml_data(node);
6966 if (sipe_strequal(boolean, "true")) {
6967 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
6968 uri);
6969 uri = NULL;
6971 g_free(boolean);
6973 break;
6977 if (uri) {
6978 *buddies = g_slist_append(*buddies, sip_uri(uri));
6982 sipe_xml_free(xml);
6985 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6987 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6988 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6990 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
6992 if (ctype &&
6993 strstr(ctype, "multipart") &&
6994 (strstr(ctype, "application/rlmi+xml") ||
6995 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6996 GSList *buddies = NULL;
6998 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
7000 if (buddies) {
7001 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
7002 payload->host = g_strdup(who);
7003 payload->buddies = buddies;
7004 sipe_schedule_action(action_name, timeout,
7005 sipe_subscribe_presence_batched_routed,
7006 sipe_subscribe_presence_batched_routed_free,
7007 sip, payload);
7008 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
7011 } else {
7012 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7013 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
7015 g_free(action_name);
7019 * Dispatcher for all incoming subscription information
7020 * whether it comes from NOTIFY, BENOTIFY requests or
7021 * piggy-backed to subscription's OK responce.
7023 * @param request whether initiated from BE/NOTIFY request or OK-response message.
7024 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
7026 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
7028 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
7029 const gchar *event = sipmsg_find_header(msg, "Event");
7030 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
7031 char *tmp;
7033 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
7034 event ? event : "",
7035 tmp = fix_newlines(msg->body));
7036 g_free(tmp);
7037 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
7039 /* implicit subscriptions */
7040 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
7041 sipe_process_imdn(sip, msg);
7044 if (event) {
7045 /* for one off subscriptions (send with Expire: 0) */
7046 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
7048 sipe_process_provisioning_v2(sip, msg);
7050 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
7052 sipe_process_provisioning(sip, msg);
7054 else if (sipe_strcase_equal(event, "presence"))
7056 sipe_process_presence(sip, msg);
7058 else if (sipe_strcase_equal(event, "registration-notify"))
7060 sipe_process_registration_notify(sip, msg);
7063 if (!subscription_state || strstr(subscription_state, "active"))
7065 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
7067 sipe_process_roaming_contacts(sip, msg);
7069 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
7071 sipe_process_roaming_self(sip, msg);
7073 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
7075 sipe_process_roaming_acl(sip, msg);
7077 else if (sipe_strcase_equal(event, "presence.wpending"))
7079 sipe_process_presence_wpending(sip, msg);
7081 else if (sipe_strcase_equal(event, "conference"))
7083 sipe_process_conference(sip, msg);
7088 /* The server sends status 'terminated' */
7089 if (subscription_state && strstr(subscription_state, "terminated") ) {
7090 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
7091 gchar *key = sipe_get_subscription_key(event, who);
7093 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
7094 g_free(who);
7096 if (g_hash_table_lookup(sip->subscriptions, key)) {
7097 g_hash_table_remove(sip->subscriptions, key);
7098 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
7101 g_free(key);
7104 if (!request && event) {
7105 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
7106 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
7107 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
7109 if (timeout) {
7110 /* 2 min ahead of expiration */
7111 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
7113 if (sipe_strcase_equal(event, "presence.wpending") &&
7114 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
7116 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
7117 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
7118 g_free(action_name);
7120 else if (sipe_strcase_equal(event, "presence") &&
7121 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
7123 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
7124 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7126 if (sip->batched_support) {
7127 sipe_process_presence_timeout(sip, msg, who, timeout);
7129 else {
7130 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7131 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
7133 g_free(action_name);
7134 g_free(who);
7139 /* The client responses on received a NOTIFY message */
7140 if (request && !benotify)
7142 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7147 * Whether user manually changed status or
7148 * it was changed automatically due to user
7149 * became inactive/active again
7151 static gboolean
7152 sipe_is_user_state(struct sipe_account_data *sip)
7154 gboolean res;
7155 time_t now = time(NULL);
7157 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
7158 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
7160 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
7162 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
7163 return res;
7166 static void
7167 send_presence_soap0(struct sipe_account_data *sip,
7168 gboolean do_publish_calendar,
7169 gboolean do_reset_status)
7171 struct sipe_ews* ews = sip->ews;
7172 int availability = 0;
7173 int activity = 0;
7174 gchar *body;
7175 gchar *tmp;
7176 gchar *tmp2 = NULL;
7177 gchar *res_note = NULL;
7178 gchar *res_oof = NULL;
7179 const gchar *note_pub = NULL;
7180 gchar *states = NULL;
7181 gchar *calendar_data = NULL;
7182 gchar *epid = get_epid(sip);
7183 time_t now = time(NULL);
7184 gchar *since_time_str = sipe_utils_time_to_str(now);
7185 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
7186 const char *user_input;
7187 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
7189 if (oof_note && sip->note) {
7190 SIPE_DEBUG_INFO("ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
7191 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
7194 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
7196 if (!sip->initial_state_published ||
7197 do_reset_status)
7199 g_free(sip->status);
7200 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
7203 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
7205 /* Note */
7206 if (pub_oof) {
7207 note_pub = oof_note;
7208 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
7209 ews->published = TRUE;
7210 } else if (sip->note) {
7211 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
7212 g_free(sip->note);
7213 sip->note = NULL;
7214 sip->is_oof_note = FALSE;
7215 sip->note_since = 0;
7216 } else {
7217 note_pub = sip->note;
7218 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
7222 if (note_pub)
7224 /* to protocol internal plain text format */
7225 tmp = sipe_backend_markup_strip_html(note_pub);
7226 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
7227 g_free(tmp);
7230 /* User State */
7231 if (!do_reset_status) {
7232 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
7234 gchar *activity_token = NULL;
7235 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
7237 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
7238 avail_2007,
7239 since_time_str,
7240 epid,
7241 activity_token);
7242 g_free(activity_token);
7244 else /* preserve existing publication */
7246 if (sip->user_states) {
7247 states = g_strdup(sip->user_states);
7250 } else {
7251 /* do nothing - then User state will be erased */
7253 sip->initial_state_published = TRUE;
7255 /* CalendarInfo */
7256 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
7258 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7259 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7260 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
7261 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
7262 fb_start_str,
7263 free_busy_base64);
7264 g_free(fb_start_str);
7265 g_free(free_busy_base64);
7268 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
7270 /* forming resulting XML */
7271 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7272 sip->username,
7273 availability,
7274 activity,
7275 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7276 res_note ? res_note : "",
7277 res_oof ? res_oof : "",
7278 states ? states : "",
7279 calendar_data ? calendar_data : "",
7280 epid,
7281 since_time_str,
7282 since_time_str,
7283 user_input);
7284 g_free(tmp);
7285 g_free(tmp2);
7286 g_free(res_note);
7287 g_free(states);
7288 g_free(calendar_data);
7290 send_soap_request(sip, body);
7292 g_free(body);
7293 g_free(since_time_str);
7294 g_free(epid);
7297 void
7298 send_presence_soap(struct sipe_account_data *sip,
7299 gboolean do_publish_calendar)
7301 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7305 static gboolean
7306 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7307 struct sipmsg *msg,
7308 struct transaction *trans)
7310 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7312 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7313 sipe_xml *xml;
7314 const sipe_xml *node;
7315 gchar *fault_code;
7316 GHashTable *faults;
7317 int index_our;
7318 gboolean has_device_publication = FALSE;
7320 xml = sipe_xml_parse(msg->body, msg->bodylen);
7322 /* test if version mismatch fault */
7323 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
7324 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7325 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
7326 g_free(fault_code);
7327 sipe_xml_free(xml);
7328 return TRUE;
7330 g_free(fault_code);
7332 /* accumulating information about faulty versions */
7333 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7334 for (node = sipe_xml_child(xml, "details/operation");
7335 node;
7336 node = sipe_xml_twin(node))
7338 const gchar *index = sipe_xml_attribute(node, "index");
7339 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
7341 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7342 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
7344 sipe_xml_free(xml);
7346 /* here we are parsing own request to figure out what publication
7347 * referensed here only by index went wrong
7349 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
7351 /* publication */
7352 for (node = sipe_xml_child(xml, "publications/publication"),
7353 index_our = 1; /* starts with 1 - our first publication */
7354 node;
7355 node = sipe_xml_twin(node), index_our++)
7357 gchar *idx = g_strdup_printf("%d", index_our);
7358 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7359 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
7360 g_free(idx);
7362 if (sipe_strequal("device", categoryName)) {
7363 has_device_publication = TRUE;
7366 if (curVersion) { /* fault exist on this index */
7367 const gchar *container = sipe_xml_attribute(node, "container");
7368 const gchar *instance = sipe_xml_attribute(node, "instance");
7369 /* key is <category><instance><container> */
7370 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7371 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
7373 if (category) {
7374 struct sipe_publication *publication =
7375 g_hash_table_lookup(category, key);
7377 SIPE_DEBUG_INFO("key is %s", key);
7379 if (publication) {
7380 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7381 key, curVersion, publication->version);
7382 /* updating publication's version to the correct one */
7383 publication->version = atoi(curVersion);
7385 } else {
7386 /* We somehow lost this category from our publications... */
7387 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
7388 publication->category = g_strdup(categoryName);
7389 publication->instance = atoi(instance);
7390 publication->container = atoi(container);
7391 publication->version = atoi(curVersion);
7392 category = g_hash_table_new_full(g_str_hash, g_str_equal,
7393 g_free, (GDestroyNotify)free_publication);
7394 g_hash_table_insert(category, g_strdup(key), publication);
7395 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
7396 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
7398 g_free(key);
7401 sipe_xml_free(xml);
7402 g_hash_table_destroy(faults);
7404 /* rebublishing with right versions */
7405 if (has_device_publication) {
7406 send_publish_category_initial(sip);
7407 } else {
7408 send_presence_status(sip);
7411 return TRUE;
7415 * Returns 'device' XML part for publication.
7416 * Must be g_free'd after use.
7418 static gchar *
7419 sipe_publish_get_category_device(struct sipe_account_data *sip)
7421 gchar *uri;
7422 gchar *doc;
7423 gchar *epid = get_epid(sip);
7424 gchar *uuid = generateUUIDfromEPID(epid);
7425 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7426 /* key is <category><instance><container> */
7427 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7428 struct sipe_publication *publication =
7429 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7431 g_free(key);
7432 g_free(epid);
7434 uri = sip_uri_self(sip);
7435 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7436 device_instance,
7437 publication ? publication->version : 0,
7438 uuid,
7439 uri,
7440 "00:00:00+01:00", /* @TODO make timezone real*/
7441 g_get_host_name()
7444 g_free(uri);
7445 g_free(uuid);
7447 return doc;
7451 * A service method - use
7452 * - send_publish_get_category_state_machine and
7453 * - send_publish_get_category_state_user instead.
7454 * Must be g_free'd after use.
7456 static gchar *
7457 sipe_publish_get_category_state(struct sipe_account_data *sip,
7458 gboolean is_user_state)
7460 int availability = sipe_get_availability_by_status(sip->status, NULL);
7461 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7462 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7463 /* key is <category><instance><container> */
7464 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7465 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7466 struct sipe_publication *publication_2 =
7467 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7468 struct sipe_publication *publication_3 =
7469 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7471 g_free(key_2);
7472 g_free(key_3);
7474 if (publication_2 && (publication_2->availability == availability))
7476 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7477 return NULL; /* nothing to update */
7480 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7481 instance,
7482 publication_2 ? publication_2->version : 0,
7483 availability,
7484 instance,
7485 publication_3 ? publication_3->version : 0,
7486 availability);
7490 * Only Busy and OOF calendar event are published.
7491 * Different instances are used for that.
7493 * Must be g_free'd after use.
7495 static gchar *
7496 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7497 struct sipe_cal_event *event,
7498 const char *uri,
7499 int cal_satus)
7501 gchar *start_time_str;
7502 int availability = 0;
7503 gchar *res;
7504 gchar *tmp = NULL;
7505 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7506 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7507 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7509 /* key is <category><instance><container> */
7510 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7511 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7512 struct sipe_publication *publication_2 =
7513 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7514 struct sipe_publication *publication_3 =
7515 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7517 g_free(key_2);
7518 g_free(key_3);
7520 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7521 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7522 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
7523 return NULL;
7526 if (event &&
7527 publication_3 &&
7528 (publication_3->availability == availability) &&
7529 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7531 g_free(tmp);
7532 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7533 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
7534 return NULL; /* nothing to update */
7536 g_free(tmp);
7538 if (event &&
7539 (event->cal_status == SIPE_CAL_BUSY ||
7540 event->cal_status == SIPE_CAL_OOF))
7542 gchar *availability_xml_str = NULL;
7543 gchar *activity_xml_str = NULL;
7545 if (event->cal_status == SIPE_CAL_BUSY) {
7546 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7549 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7550 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7551 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7552 "minAvailability=\"6500\"",
7553 "maxAvailability=\"8999\"");
7554 } else if (event->cal_status == SIPE_CAL_OOF) {
7555 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7556 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7557 "minAvailability=\"12000\"",
7558 "");
7560 start_time_str = sipe_utils_time_to_str(event->start_time);
7562 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7563 instance,
7564 publication_2 ? publication_2->version : 0,
7565 uri,
7566 start_time_str,
7567 availability_xml_str ? availability_xml_str : "",
7568 activity_xml_str ? activity_xml_str : "",
7569 event->subject ? event->subject : "",
7570 event->location ? event->location : "",
7572 instance,
7573 publication_3 ? publication_3->version : 0,
7574 uri,
7575 start_time_str,
7576 availability_xml_str ? availability_xml_str : "",
7577 activity_xml_str ? activity_xml_str : "",
7578 event->subject ? event->subject : "",
7579 event->location ? event->location : ""
7581 g_free(start_time_str);
7582 g_free(availability_xml_str);
7583 g_free(activity_xml_str);
7586 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7588 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7589 instance,
7590 publication_2 ? publication_2->version : 0,
7592 instance,
7593 publication_3 ? publication_3->version : 0
7597 return res;
7601 * Returns 'machineState' XML part for publication.
7602 * Must be g_free'd after use.
7604 static gchar *
7605 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7607 return sipe_publish_get_category_state(sip, FALSE);
7611 * Returns 'userState' XML part for publication.
7612 * Must be g_free'd after use.
7614 static gchar *
7615 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7617 return sipe_publish_get_category_state(sip, TRUE);
7621 * Returns 'note' XML part for publication.
7622 * Must be g_free'd after use.
7624 * Protocol format for Note is plain text.
7626 * @param note a note in Sipe internal HTML format
7627 * @param note_type either personal or OOF
7629 static gchar *
7630 sipe_publish_get_category_note(struct sipe_account_data *sip,
7631 const char *note, /* html */
7632 const char *note_type,
7633 time_t note_start,
7634 time_t note_end)
7636 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7637 /* key is <category><instance><container> */
7638 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7639 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7640 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7642 struct sipe_publication *publication_note_200 =
7643 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7644 struct sipe_publication *publication_note_300 =
7645 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7646 struct sipe_publication *publication_note_400 =
7647 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7649 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7650 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7651 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7652 char *res, *tmp1, *tmp2, *tmp3;
7653 char *start_time_attr;
7654 char *end_time_attr;
7656 g_free(tmp);
7657 tmp = NULL;
7658 g_free(key_note_200);
7659 g_free(key_note_300);
7660 g_free(key_note_400);
7662 /* we even need to republish empty note */
7663 if (sipe_strequal(n1, n2))
7665 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7666 g_free(n1);
7667 return NULL; /* nothing to update */
7670 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7671 g_free(tmp);
7672 tmp = NULL;
7673 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7674 g_free(tmp);
7676 if (n1) {
7677 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7678 instance,
7679 200,
7680 publication_note_200 ? publication_note_200->version : 0,
7681 note_type,
7682 start_time_attr ? start_time_attr : "",
7683 end_time_attr ? end_time_attr : "",
7684 n1);
7686 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7687 instance,
7688 300,
7689 publication_note_300 ? publication_note_300->version : 0,
7690 note_type,
7691 start_time_attr ? start_time_attr : "",
7692 end_time_attr ? end_time_attr : "",
7693 n1);
7695 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7696 instance,
7697 400,
7698 publication_note_400 ? publication_note_400->version : 0,
7699 note_type,
7700 start_time_attr ? start_time_attr : "",
7701 end_time_attr ? end_time_attr : "",
7702 n1);
7703 } else {
7704 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7705 "note",
7706 instance,
7707 200,
7708 publication_note_200 ? publication_note_200->version : 0,
7709 "static");
7710 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7711 "note",
7712 instance,
7713 300,
7714 publication_note_200 ? publication_note_200->version : 0,
7715 "static");
7716 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7717 "note",
7718 instance,
7719 400,
7720 publication_note_200 ? publication_note_200->version : 0,
7721 "static");
7723 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7725 g_free(start_time_attr);
7726 g_free(end_time_attr);
7727 g_free(tmp1);
7728 g_free(tmp2);
7729 g_free(tmp3);
7730 g_free(n1);
7732 return res;
7736 * Returns 'calendarData' XML part with WorkingHours for publication.
7737 * Must be g_free'd after use.
7739 static gchar *
7740 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7742 struct sipe_ews* ews = sip->ews;
7744 /* key is <category><instance><container> */
7745 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7746 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7747 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7748 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7749 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7750 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7752 struct sipe_publication *publication_cal_1 =
7753 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7754 struct sipe_publication *publication_cal_100 =
7755 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7756 struct sipe_publication *publication_cal_200 =
7757 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7758 struct sipe_publication *publication_cal_300 =
7759 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7760 struct sipe_publication *publication_cal_400 =
7761 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7762 struct sipe_publication *publication_cal_32000 =
7763 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7765 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7766 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7768 g_free(key_cal_1);
7769 g_free(key_cal_100);
7770 g_free(key_cal_200);
7771 g_free(key_cal_300);
7772 g_free(key_cal_400);
7773 g_free(key_cal_32000);
7775 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7776 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7777 return NULL;
7780 if (sipe_strequal(n1, n2))
7782 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7783 return NULL; /* nothing to update */
7786 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7787 /* 1 */
7788 publication_cal_1 ? publication_cal_1->version : 0,
7789 ews->email,
7790 ews->working_hours_xml_str,
7791 /* 100 - Public */
7792 publication_cal_100 ? publication_cal_100->version : 0,
7793 /* 200 - Company */
7794 publication_cal_200 ? publication_cal_200->version : 0,
7795 ews->email,
7796 ews->working_hours_xml_str,
7797 /* 300 - Team */
7798 publication_cal_300 ? publication_cal_300->version : 0,
7799 ews->email,
7800 ews->working_hours_xml_str,
7801 /* 400 - Personal */
7802 publication_cal_400 ? publication_cal_400->version : 0,
7803 ews->email,
7804 ews->working_hours_xml_str,
7805 /* 32000 - Blocked */
7806 publication_cal_32000 ? publication_cal_32000->version : 0
7811 * Returns 'calendarData' XML part with FreeBusy for publication.
7812 * Must be g_free'd after use.
7814 static gchar *
7815 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7817 struct sipe_ews* ews = sip->ews;
7818 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7819 char *fb_start_str;
7820 char *free_busy_base64;
7821 const char *st;
7822 const char *fb;
7823 char *res;
7825 /* key is <category><instance><container> */
7826 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7827 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7828 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7829 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7830 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7831 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7833 struct sipe_publication *publication_cal_1 =
7834 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7835 struct sipe_publication *publication_cal_100 =
7836 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7837 struct sipe_publication *publication_cal_200 =
7838 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7839 struct sipe_publication *publication_cal_300 =
7840 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7841 struct sipe_publication *publication_cal_400 =
7842 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7843 struct sipe_publication *publication_cal_32000 =
7844 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7846 g_free(key_cal_1);
7847 g_free(key_cal_100);
7848 g_free(key_cal_200);
7849 g_free(key_cal_300);
7850 g_free(key_cal_400);
7851 g_free(key_cal_32000);
7853 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7854 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7855 return NULL;
7858 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7859 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7861 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7862 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7864 /* we will rebuplish the same data to refresh publication time,
7865 * so if data from multiple sources, most recent will be choosen
7867 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7869 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7870 // g_free(fb_start_str);
7871 // g_free(free_busy_base64);
7872 // return NULL; /* nothing to update */
7875 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7876 /* 1 */
7877 cal_data_instance,
7878 publication_cal_1 ? publication_cal_1->version : 0,
7879 /* 100 - Public */
7880 cal_data_instance,
7881 publication_cal_100 ? publication_cal_100->version : 0,
7882 /* 200 - Company */
7883 cal_data_instance,
7884 publication_cal_200 ? publication_cal_200->version : 0,
7885 ews->email,
7886 fb_start_str,
7887 free_busy_base64,
7888 /* 300 - Team */
7889 cal_data_instance,
7890 publication_cal_300 ? publication_cal_300->version : 0,
7891 ews->email,
7892 fb_start_str,
7893 free_busy_base64,
7894 /* 400 - Personal */
7895 cal_data_instance,
7896 publication_cal_400 ? publication_cal_400->version : 0,
7897 ews->email,
7898 fb_start_str,
7899 free_busy_base64,
7900 /* 32000 - Blocked */
7901 cal_data_instance,
7902 publication_cal_32000 ? publication_cal_32000->version : 0
7905 g_free(fb_start_str);
7906 g_free(free_busy_base64);
7907 return res;
7910 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7912 gchar *uri;
7913 gchar *doc;
7914 gchar *tmp;
7915 gchar *hdr;
7917 uri = sip_uri_self(sip);
7918 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7919 uri,
7920 publications);
7922 tmp = get_contact(sip);
7923 hdr = g_strdup_printf("Contact: %s\r\n"
7924 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7926 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7928 g_free(tmp);
7929 g_free(hdr);
7930 g_free(uri);
7931 g_free(doc);
7934 static void
7935 send_publish_category_initial(struct sipe_account_data *sip)
7937 gchar *pub_device = sipe_publish_get_category_device(sip);
7938 gchar *pub_machine;
7939 gchar *publications;
7941 g_free(sip->status);
7942 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7944 pub_machine = sipe_publish_get_category_state_machine(sip);
7945 publications = g_strdup_printf("%s%s",
7946 pub_device,
7947 pub_machine ? pub_machine : "");
7948 g_free(pub_device);
7949 g_free(pub_machine);
7951 send_presence_publish(sip, publications);
7952 g_free(publications);
7955 static void
7956 send_presence_category_publish(struct sipe_account_data *sip)
7958 gchar *pub_state = sipe_is_user_state(sip) ?
7959 sipe_publish_get_category_state_user(sip) :
7960 sipe_publish_get_category_state_machine(sip);
7961 gchar *pub_note = sipe_publish_get_category_note(sip,
7962 sip->note,
7963 sip->is_oof_note ? "OOF" : "personal",
7966 gchar *publications;
7968 if (!pub_state && !pub_note) {
7969 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
7970 return;
7973 publications = g_strdup_printf("%s%s",
7974 pub_state ? pub_state : "",
7975 pub_note ? pub_note : "");
7977 g_free(pub_state);
7978 g_free(pub_note);
7980 send_presence_publish(sip, publications);
7981 g_free(publications);
7985 * Publishes self status
7986 * based on own calendar information.
7988 * For 2007+
7990 void
7991 publish_calendar_status_self(struct sipe_account_data *sip)
7993 struct sipe_cal_event* event = NULL;
7994 gchar *pub_cal_working_hours = NULL;
7995 gchar *pub_cal_free_busy = NULL;
7996 gchar *pub_calendar = NULL;
7997 gchar *pub_calendar2 = NULL;
7998 gchar *pub_oof_note = NULL;
7999 const gchar *oof_note;
8000 time_t oof_start = 0;
8001 time_t oof_end = 0;
8003 if (!sip->ews) {
8004 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
8005 return;
8008 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
8009 if (sip->ews->cal_events) {
8010 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
8013 if (!event) {
8014 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
8015 } else {
8016 char *desc = sipe_cal_event_describe(event);
8017 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
8018 g_free(desc);
8021 /* Logic
8022 if OOF
8023 OOF publish, Busy clean
8024 ilse if Busy
8025 OOF clean, Busy publish
8026 else
8027 OOF clean, Busy clean
8029 if (event && event->cal_status == SIPE_CAL_OOF) {
8030 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
8031 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8032 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
8033 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8034 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
8035 } else {
8036 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8037 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8040 oof_note = sipe_ews_get_oof_note(sip->ews);
8041 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
8042 oof_start = sip->ews->oof_start;
8043 oof_end = sip->ews->oof_end;
8045 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
8047 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
8048 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
8050 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
8051 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
8052 } else {
8053 gchar *publications = g_strdup_printf("%s%s%s%s%s",
8054 pub_cal_working_hours ? pub_cal_working_hours : "",
8055 pub_cal_free_busy ? pub_cal_free_busy : "",
8056 pub_calendar ? pub_calendar : "",
8057 pub_calendar2 ? pub_calendar2 : "",
8058 pub_oof_note ? pub_oof_note : "");
8060 send_presence_publish(sip, publications);
8061 g_free(publications);
8064 g_free(pub_cal_working_hours);
8065 g_free(pub_cal_free_busy);
8066 g_free(pub_calendar);
8067 g_free(pub_calendar2);
8068 g_free(pub_oof_note);
8070 /* repeat scheduling */
8071 sipe_sched_calendar_status_self_publish(sip, time(NULL));
8074 static void send_presence_status(struct sipe_account_data *sip)
8076 PurpleStatus * status = purple_account_get_active_status(sip->account);
8078 if (!status) return;
8080 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
8081 purple_status_get_id(status) ? purple_status_get_id(status) : "",
8082 sipe_is_user_state(sip) ? "USER" : "MACHINE");
8084 if (sip->ocs2007) {
8085 send_presence_category_publish(sip);
8086 } else {
8087 send_presence_soap(sip, FALSE);
8091 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
8093 gboolean found = FALSE;
8094 const char *method = msg->method ? msg->method : "NOT FOUND";
8095 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
8096 if (msg->response == 0) { /* request */
8097 if (sipe_strequal(method, "MESSAGE")) {
8098 process_incoming_message(sip, msg);
8099 found = TRUE;
8100 } else if (sipe_strequal(method, "NOTIFY")) {
8101 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
8102 process_incoming_notify(sip, msg, TRUE, FALSE);
8103 found = TRUE;
8104 } else if (sipe_strequal(method, "BENOTIFY")) {
8105 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
8106 process_incoming_notify(sip, msg, TRUE, TRUE);
8107 found = TRUE;
8108 } else if (sipe_strequal(method, "INVITE")) {
8109 process_incoming_invite(sip, msg);
8110 found = TRUE;
8111 } else if (sipe_strequal(method, "REFER")) {
8112 process_incoming_refer(sip, msg);
8113 found = TRUE;
8114 } else if (sipe_strequal(method, "OPTIONS")) {
8115 process_incoming_options(sip, msg);
8116 found = TRUE;
8117 } else if (sipe_strequal(method, "INFO")) {
8118 process_incoming_info(sip, msg);
8119 found = TRUE;
8120 } else if (sipe_strequal(method, "ACK")) {
8121 // ACK's don't need any response
8122 found = TRUE;
8123 } else if (sipe_strequal(method, "SUBSCRIBE")) {
8124 // LCS 2005 sends us these - just respond 200 OK
8125 found = TRUE;
8126 send_sip_response(sip->gc, msg, 200, "OK", NULL);
8127 } else if (sipe_strequal(method, "BYE")) {
8128 process_incoming_bye(sip, msg);
8129 found = TRUE;
8130 } else {
8131 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
8133 } else { /* response */
8134 struct transaction *trans = transactions_find(sip, msg);
8135 if (trans) {
8136 if (msg->response == 407) {
8137 gchar *resend, *auth;
8138 const gchar *ptmp;
8140 if (sip->proxy.retries > 30) return;
8141 sip->proxy.retries++;
8142 /* do proxy authentication */
8144 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
8146 fill_auth(ptmp, &sip->proxy);
8147 auth = auth_header(sip, &sip->proxy, trans->msg);
8148 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
8149 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
8150 g_free(auth);
8151 resend = sipmsg_to_string(trans->msg);
8152 /* resend request */
8153 sendout_pkt(sip->gc, resend);
8154 g_free(resend);
8155 } else {
8156 if (msg->response < 200) {
8157 /* ignore provisional response */
8158 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
8159 } else {
8160 sip->proxy.retries = 0;
8161 if (sipe_strequal(trans->msg->method, "REGISTER")) {
8162 if (msg->response == 401)
8164 sip->registrar.retries++;
8166 else
8168 sip->registrar.retries = 0;
8170 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
8171 } else {
8172 if (msg->response == 401) {
8173 gchar *resend, *auth, *ptmp;
8174 const char* auth_scheme;
8176 if (sip->registrar.retries > 4) return;
8177 sip->registrar.retries++;
8179 auth_scheme = sipe_get_auth_scheme_name(sip);
8180 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
8182 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
8183 if (!ptmp) {
8184 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
8185 sip->gc->wants_to_die = TRUE;
8186 purple_connection_error(sip->gc, tmp2);
8187 g_free(tmp2);
8188 return;
8191 fill_auth(ptmp, &sip->registrar);
8192 auth = auth_header(sip, &sip->registrar, trans->msg);
8193 sipmsg_remove_header_now(trans->msg, "Authorization");
8194 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
8195 g_free(auth);
8196 resend = sipmsg_to_string(trans->msg);
8197 /* resend request */
8198 sendout_pkt(sip->gc, resend);
8199 g_free(resend);
8203 if (trans->callback) {
8204 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
8205 /* call the callback to process response*/
8206 (trans->callback)(sip, msg, trans);
8209 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
8210 transactions_remove(sip, trans);
8214 found = TRUE;
8215 } else {
8216 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
8219 if (!found) {
8220 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
8224 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
8226 char *cur;
8227 char *dummy;
8228 char *tmp;
8229 struct sipmsg *msg;
8230 int restlen;
8231 cur = conn->inbuf;
8233 /* according to the RFC remove CRLF at the beginning */
8234 while (*cur == '\r' || *cur == '\n') {
8235 cur++;
8237 if (cur != conn->inbuf) {
8238 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
8239 conn->inbufused = strlen(conn->inbuf);
8242 /* Received a full Header? */
8243 sip->processing_input = TRUE;
8244 while (sip->processing_input &&
8245 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
8246 time_t currtime = time(NULL);
8247 cur += 2;
8248 cur[0] = '\0';
8249 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
8250 g_free(tmp);
8251 msg = sipmsg_parse_header(conn->inbuf);
8252 cur[0] = '\r';
8253 cur += 2;
8254 restlen = conn->inbufused - (cur - conn->inbuf);
8255 if (msg && restlen >= msg->bodylen) {
8256 dummy = g_malloc(msg->bodylen + 1);
8257 memcpy(dummy, cur, msg->bodylen);
8258 dummy[msg->bodylen] = '\0';
8259 msg->body = dummy;
8260 cur += msg->bodylen;
8261 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
8262 conn->inbufused = strlen(conn->inbuf);
8263 } else {
8264 if (msg){
8265 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen, msg->bodylen, (int)strlen(conn->inbuf));
8266 sipmsg_free(msg);
8268 return;
8271 /*if (msg->body) {
8272 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8275 // Verify the signature before processing it
8276 if (sip->registrar.gssapi_context) {
8277 struct sipmsg_breakdown msgbd;
8278 gchar *signature_input_str;
8279 gchar *rspauth;
8280 msgbd.msg = msg;
8281 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8282 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8284 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8286 if (rspauth != NULL) {
8287 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8288 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8289 process_input_message(sip, msg);
8290 } else {
8291 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8292 purple_connection_error(sip->gc, _("Invalid message signature received"));
8293 sip->gc->wants_to_die = TRUE;
8295 } else if (msg->response == 401) {
8296 purple_connection_error(sip->gc, _("Authentication failed"));
8297 sip->gc->wants_to_die = TRUE;
8299 g_free(signature_input_str);
8301 g_free(rspauth);
8302 sipmsg_breakdown_free(&msgbd);
8303 } else {
8304 process_input_message(sip, msg);
8307 sipmsg_free(msg);
8311 static void sipe_udp_process(gpointer data, gint source,
8312 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8314 PurpleConnection *gc = data;
8315 struct sipe_account_data *sip = gc->proto_data;
8316 int len;
8318 static char buffer[65536];
8319 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8320 time_t currtime = time(NULL);
8321 struct sipmsg *msg;
8322 buffer[len] = '\0';
8323 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), buffer);
8324 msg = sipmsg_parse_msg(buffer);
8325 if (msg) process_input_message(sip, msg);
8329 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8331 struct sipe_account_data *sip = gc->proto_data;
8332 PurpleSslConnection *gsc = sip->gsc;
8334 SIPE_DEBUG_ERROR("%s", debug);
8335 purple_connection_error(gc, msg);
8337 /* Invalidate this connection. Next send will open a new one */
8338 if (gsc) {
8339 connection_remove(sip, gsc->fd);
8340 purple_ssl_close(gsc);
8342 sip->gsc = NULL;
8343 sip->fd = -1;
8346 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8347 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8349 PurpleConnection *gc = data;
8350 struct sipe_account_data *sip;
8351 struct sip_connection *conn;
8352 int readlen, len;
8353 gboolean firstread = TRUE;
8355 /* NOTE: This check *IS* necessary */
8356 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8357 purple_ssl_close(gsc);
8358 return;
8361 sip = gc->proto_data;
8362 conn = connection_find(sip, gsc->fd);
8363 if (conn == NULL) {
8364 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
8365 gc->wants_to_die = TRUE;
8366 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8367 return;
8370 /* Read all available data from the SSL connection */
8371 do {
8372 /* Increase input buffer size as needed */
8373 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8374 conn->inbuflen += SIMPLE_BUF_INC;
8375 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8376 SIPE_DEBUG_INFO("sipe_input_cb_ssl: new input buffer length %d", conn->inbuflen);
8379 /* Try to read as much as there is space left in the buffer */
8380 readlen = conn->inbuflen - conn->inbufused - 1;
8381 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8383 if (len < 0 && errno == EAGAIN) {
8384 /* Try again later */
8385 return;
8386 } else if (len < 0) {
8387 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8388 return;
8389 } else if (firstread && (len == 0)) {
8390 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8391 return;
8394 conn->inbufused += len;
8395 firstread = FALSE;
8397 /* Equivalence indicates that there is possibly more data to read */
8398 } while (len == readlen);
8400 conn->inbuf[conn->inbufused] = '\0';
8401 process_input(sip, conn);
8405 static void sipe_input_cb(gpointer data, gint source,
8406 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8408 PurpleConnection *gc = data;
8409 struct sipe_account_data *sip = gc->proto_data;
8410 int len;
8411 struct sip_connection *conn = connection_find(sip, source);
8412 if (!conn) {
8413 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found!");
8414 return;
8417 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8418 conn->inbuflen += SIMPLE_BUF_INC;
8419 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8422 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8424 if (len < 0 && errno == EAGAIN)
8425 return;
8426 else if (len <= 0) {
8427 SIPE_DEBUG_INFO_NOFORMAT("sipe_input_cb: read error");
8428 connection_remove(sip, source);
8429 if (sip->fd == source) sip->fd = -1;
8430 return;
8433 conn->inbufused += len;
8434 conn->inbuf[conn->inbufused] = '\0';
8436 process_input(sip, conn);
8439 /* Callback for new connections on incoming TCP port */
8440 static void sipe_newconn_cb(gpointer data, gint source,
8441 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8443 PurpleConnection *gc = data;
8444 struct sipe_account_data *sip = gc->proto_data;
8445 struct sip_connection *conn;
8447 int newfd = accept(source, NULL, NULL);
8449 conn = connection_create(sip, newfd);
8451 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8454 static void login_cb(gpointer data, gint source,
8455 SIPE_UNUSED_PARAMETER const gchar *error_message)
8457 PurpleConnection *gc = data;
8458 struct sipe_account_data *sip;
8459 struct sip_connection *conn;
8461 if (!PURPLE_CONNECTION_IS_VALID(gc))
8463 if (source >= 0)
8464 close(source);
8465 return;
8468 if (source < 0) {
8469 purple_connection_error(gc, _("Could not connect"));
8470 return;
8473 sip = gc->proto_data;
8474 sip->fd = source;
8475 sip->last_keepalive = time(NULL);
8477 conn = connection_create(sip, source);
8479 do_register(sip);
8481 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8484 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8485 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8487 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8488 if (sip == NULL) return;
8490 do_register(sip);
8493 static guint sipe_ht_hash_nick(const char *nick)
8495 char *lc = g_utf8_strdown(nick, -1);
8496 guint bucket = g_str_hash(lc);
8497 g_free(lc);
8499 return bucket;
8502 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8504 char *nick1_norm = NULL;
8505 char *nick2_norm = NULL;
8506 gboolean equal;
8508 if (nick1 == NULL && nick2 == NULL) return TRUE;
8509 if (nick1 == NULL || nick2 == NULL ||
8510 !g_utf8_validate(nick1, -1, NULL) ||
8511 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
8513 nick1_norm = g_utf8_casefold(nick1, -1);
8514 nick2_norm = g_utf8_casefold(nick2, -1);
8515 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
8516 g_free(nick2_norm);
8517 g_free(nick1_norm);
8519 return equal;
8522 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8524 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8526 sip->listen_data = NULL;
8528 if (listenfd == -1) {
8529 purple_connection_error(sip->gc, _("Could not create listen socket"));
8530 return;
8533 sip->fd = listenfd;
8535 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8536 sip->listenfd = sip->fd;
8538 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8540 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8541 do_register(sip);
8544 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8545 SIPE_UNUSED_PARAMETER const char *error_message)
8547 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8549 sip->query_data = NULL;
8551 if (!hosts || !hosts->data) {
8552 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8553 return;
8556 hosts = g_slist_remove(hosts, hosts->data);
8557 g_free(sip->serveraddr);
8558 sip->serveraddr = hosts->data;
8559 hosts = g_slist_remove(hosts, hosts->data);
8560 while (hosts) {
8561 void *tmp = hosts->data;
8562 hosts = g_slist_remove(hosts, tmp);
8563 hosts = g_slist_remove(hosts, tmp);
8564 g_free(tmp);
8567 /* create socket for incoming connections */
8568 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8569 sipe_udp_host_resolved_listen_cb, sip);
8570 if (sip->listen_data == NULL) {
8571 purple_connection_error(sip->gc, _("Could not create listen socket"));
8572 return;
8576 static const struct sipe_service_data *current_service = NULL;
8578 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8579 PurpleSslErrorType error,
8580 gpointer data)
8582 PurpleConnection *gc = data;
8583 struct sipe_account_data *sip;
8585 /* If the connection is already disconnected, we don't need to do anything else */
8586 if (!PURPLE_CONNECTION_IS_VALID(gc))
8587 return;
8589 sip = gc->proto_data;
8590 current_service = sip->service_data;
8591 if (current_service) {
8592 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8593 current_service->transport ? current_service->transport : "NULL",
8594 current_service->service ? current_service->service : "NULL");
8597 sip->fd = -1;
8598 sip->gsc = NULL;
8600 switch(error) {
8601 case PURPLE_SSL_CONNECT_FAILED:
8602 purple_connection_error(gc, _("Connection failed"));
8603 break;
8604 case PURPLE_SSL_HANDSHAKE_FAILED:
8605 purple_connection_error(gc, _("SSL handshake failed"));
8606 break;
8607 case PURPLE_SSL_CERTIFICATE_INVALID:
8608 purple_connection_error(gc, _("SSL certificate invalid"));
8609 break;
8613 static void
8614 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8616 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8617 PurpleProxyConnectData *connect_data;
8619 sip->listen_data = NULL;
8621 sip->listenfd = listenfd;
8622 if (sip->listenfd == -1) {
8623 purple_connection_error(sip->gc, _("Could not create listen socket"));
8624 return;
8627 SIPE_DEBUG_INFO("listenfd: %d", sip->listenfd);
8628 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8629 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8630 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8631 sipe_newconn_cb, sip->gc);
8632 SIPE_DEBUG_INFO("connecting to %s port %d",
8633 sip->realhostname, sip->realport);
8634 /* open tcp connection to the server */
8635 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8636 sip->realport, login_cb, sip->gc);
8638 if (connect_data == NULL) {
8639 purple_connection_error(sip->gc, _("Could not create socket"));
8643 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8645 PurpleAccount *account = sip->account;
8646 PurpleConnection *gc = sip->gc;
8648 if (port == 0) {
8649 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8652 sip->realhostname = hostname;
8653 sip->realport = port;
8655 SIPE_DEBUG_INFO("create_connection - hostname: %s port: %d",
8656 hostname, port);
8658 /* TODO: is there a good default grow size? */
8659 if (sip->transport != SIPE_TRANSPORT_UDP)
8660 sip->txbuf = purple_circ_buffer_new(0);
8662 if (sip->transport == SIPE_TRANSPORT_TLS) {
8663 /* SSL case */
8664 if (!purple_ssl_is_supported()) {
8665 gc->wants_to_die = TRUE;
8666 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8667 return;
8670 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
8672 sip->gsc = purple_ssl_connect(account, hostname, port,
8673 login_cb_ssl, sipe_ssl_connect_failure, gc);
8674 if (sip->gsc == NULL) {
8675 purple_connection_error(gc, _("Could not create SSL context"));
8676 return;
8678 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8679 /* UDP case */
8680 SIPE_DEBUG_INFO_NOFORMAT("using UDP");
8682 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8683 if (sip->query_data == NULL) {
8684 purple_connection_error(gc, _("Could not resolve hostname"));
8686 } else {
8687 /* TCP case */
8688 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
8689 /* create socket for incoming connections */
8690 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8691 sipe_tcp_connect_listen_cb, sip);
8692 if (sip->listen_data == NULL) {
8693 purple_connection_error(gc, _("Could not create listen socket"));
8694 return;
8699 /* Service list for autodection */
8700 static const struct sipe_service_data service_autodetect[] = {
8701 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8702 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8703 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8704 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8705 { NULL, NULL, 0 }
8708 /* Service list for SSL/TLS */
8709 static const struct sipe_service_data service_tls[] = {
8710 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8711 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8712 { NULL, NULL, 0 }
8715 /* Service list for TCP */
8716 static const struct sipe_service_data service_tcp[] = {
8717 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8718 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8719 { NULL, NULL, 0 }
8722 /* Service list for UDP */
8723 static const struct sipe_service_data service_udp[] = {
8724 { "sip", "udp", SIPE_TRANSPORT_UDP },
8725 { NULL, NULL, 0 }
8728 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8729 static void resolve_next_service(struct sipe_account_data *sip,
8730 const struct sipe_service_data *start)
8732 if (start) {
8733 sip->service_data = start;
8734 } else {
8735 sip->service_data++;
8736 if (sip->service_data->service == NULL) {
8737 gchar *hostname;
8738 /* Try connecting to the SIP hostname directly */
8739 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8740 if (sip->auto_transport) {
8741 // If SSL is supported, default to using it; OCS servers aren't configured
8742 // by default to accept TCP
8743 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8744 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8745 SIPE_DEBUG_INFO_NOFORMAT("set transport type..");
8748 hostname = g_strdup(sip->sipdomain);
8749 create_connection(sip, hostname, 0);
8750 return;
8754 /* Try to resolve next service */
8755 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8756 sip->service_data->transport,
8757 sip->sipdomain,
8758 srvresolved, sip);
8761 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8763 struct sipe_account_data *sip = data;
8765 sip->srv_query_data = NULL;
8767 /* find the host to connect to */
8768 if (results) {
8769 gchar *hostname = g_strdup(resp->hostname);
8770 int port = resp->port;
8771 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8772 hostname, port);
8773 g_free(resp);
8775 sip->transport = sip->service_data->type;
8777 create_connection(sip, hostname, port);
8778 } else {
8779 resolve_next_service(sip, NULL);
8783 static void sipe_login(PurpleAccount *account)
8785 PurpleConnection *gc;
8786 struct sipe_account_data *sip;
8787 gchar **signinname_login, **userserver;
8788 const char *transport;
8789 const char *email;
8791 const char *username = purple_account_get_username(account);
8792 gc = purple_account_get_connection(account);
8794 SIPE_DEBUG_INFO("sipe_login: username '%s'", username);
8796 if (strpbrk(username, "\t\v\r\n") != NULL) {
8797 gc->wants_to_die = TRUE;
8798 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8799 return;
8802 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8803 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8804 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8805 sip->gc = gc;
8806 sip->account = account;
8807 sip->reregister_set = FALSE;
8808 sip->reauthenticate_set = FALSE;
8809 sip->subscribed = FALSE;
8810 sip->subscribed_buddies = FALSE;
8811 sip->initial_state_published = FALSE;
8813 /* username format: <username>,[<optional login>] */
8814 signinname_login = g_strsplit(username, ",", 2);
8815 SIPE_DEBUG_INFO("sipe_login: signinname[0] '%s'", signinname_login[0]);
8817 /* ensure that username format is name@domain */
8818 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8819 g_strfreev(signinname_login);
8820 gc->wants_to_die = TRUE;
8821 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8822 return;
8824 sip->username = g_strdup(signinname_login[0]);
8826 /* ensure that email format is name@domain if provided */
8827 email = purple_account_get_string(sip->account, "email", NULL);
8828 if (!is_empty(email) &&
8829 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8831 gc->wants_to_die = TRUE;
8832 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8833 return;
8835 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8837 /* login name specified? */
8838 if (signinname_login[1] && strlen(signinname_login[1])) {
8839 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8840 gboolean has_domain = domain_user[1] != NULL;
8841 SIPE_DEBUG_INFO("sipe_login: signinname[1] '%s'", signinname_login[1]);
8842 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8843 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8844 SIPE_DEBUG_INFO("sipe_login: auth domain '%s' user '%s'",
8845 sip->authdomain ? sip->authdomain : "", sip->authuser);
8846 g_strfreev(domain_user);
8849 userserver = g_strsplit(signinname_login[0], "@", 2);
8850 SIPE_DEBUG_INFO("sipe_login: user '%s' server '%s'", userserver[0], userserver[1]);
8851 purple_connection_set_display_name(gc, userserver[0]);
8852 sip->sipdomain = g_strdup(userserver[1]);
8853 g_strfreev(userserver);
8854 g_strfreev(signinname_login);
8856 if (strchr(sip->username, ' ') != NULL) {
8857 gc->wants_to_die = TRUE;
8858 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8859 return;
8862 sip->password = g_strdup(purple_connection_get_password(gc));
8864 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8865 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8866 g_free, (GDestroyNotify)g_hash_table_destroy);
8867 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8868 g_free, (GDestroyNotify)sipe_subscription_free);
8870 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8872 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8874 g_free(sip->status);
8875 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8877 sip->auto_transport = FALSE;
8878 transport = purple_account_get_string(account, "transport", "auto");
8879 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8880 if (userserver[0]) {
8881 /* Use user specified server[:port] */
8882 int port = 0;
8884 if (userserver[1])
8885 port = atoi(userserver[1]);
8887 SIPE_DEBUG_INFO("sipe_login: user specified SIP server %s:%d",
8888 userserver[0], port);
8890 if (sipe_strequal(transport, "auto")) {
8891 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8892 } else if (sipe_strequal(transport, "tls")) {
8893 sip->transport = SIPE_TRANSPORT_TLS;
8894 } else if (sipe_strequal(transport, "tcp")) {
8895 sip->transport = SIPE_TRANSPORT_TCP;
8896 } else {
8897 sip->transport = SIPE_TRANSPORT_UDP;
8900 create_connection(sip, g_strdup(userserver[0]), port);
8901 } else {
8902 /* Server auto-discovery */
8903 if (sipe_strequal(transport, "auto")) {
8904 sip->auto_transport = TRUE;
8905 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8906 current_service++;
8907 resolve_next_service(sip, current_service);
8908 } else {
8909 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8911 } else if (sipe_strequal(transport, "tls")) {
8912 resolve_next_service(sip, service_tls);
8913 } else if (sipe_strequal(transport, "tcp")) {
8914 resolve_next_service(sip, service_tcp);
8915 } else {
8916 resolve_next_service(sip, service_udp);
8919 g_strfreev(userserver);
8922 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8924 connection_free_all(sip);
8926 g_free(sip->epid);
8927 sip->epid = NULL;
8929 if (sip->query_data != NULL)
8930 purple_dnsquery_destroy(sip->query_data);
8931 sip->query_data = NULL;
8933 if (sip->srv_query_data != NULL)
8934 purple_srv_cancel(sip->srv_query_data);
8935 sip->srv_query_data = NULL;
8937 if (sip->listen_data != NULL)
8938 purple_network_listen_cancel(sip->listen_data);
8939 sip->listen_data = NULL;
8941 if (sip->gsc != NULL)
8942 purple_ssl_close(sip->gsc);
8943 sip->gsc = NULL;
8945 sipe_auth_free(&sip->registrar);
8946 sipe_auth_free(&sip->proxy);
8948 if (sip->txbuf)
8949 purple_circ_buffer_destroy(sip->txbuf);
8950 sip->txbuf = NULL;
8952 g_free(sip->realhostname);
8953 sip->realhostname = NULL;
8955 g_free(sip->server_version);
8956 sip->server_version = NULL;
8958 if (sip->listenpa)
8959 purple_input_remove(sip->listenpa);
8960 sip->listenpa = 0;
8961 if (sip->tx_handler)
8962 purple_input_remove(sip->tx_handler);
8963 sip->tx_handler = 0;
8964 if (sip->resendtimeout)
8965 purple_timeout_remove(sip->resendtimeout);
8966 sip->resendtimeout = 0;
8967 if (sip->timeouts) {
8968 GSList *entry = sip->timeouts;
8969 while (entry) {
8970 struct scheduled_action *sched_action = entry->data;
8971 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
8972 purple_timeout_remove(sched_action->timeout_handler);
8973 if (sched_action->destroy) {
8974 (*sched_action->destroy)(sched_action->payload);
8976 g_free(sched_action->name);
8977 g_free(sched_action);
8978 entry = entry->next;
8981 g_slist_free(sip->timeouts);
8983 if (sip->allow_events) {
8984 GSList *entry = sip->allow_events;
8985 while (entry) {
8986 g_free(entry->data);
8987 entry = entry->next;
8990 g_slist_free(sip->allow_events);
8992 if (sip->containers) {
8993 GSList *entry = sip->containers;
8994 while (entry) {
8995 free_container((struct sipe_container *)entry->data);
8996 entry = entry->next;
8999 g_slist_free(sip->containers);
9001 if (sip->contact)
9002 g_free(sip->contact);
9003 sip->contact = NULL;
9004 if (sip->regcallid)
9005 g_free(sip->regcallid);
9006 sip->regcallid = NULL;
9008 if (sip->serveraddr)
9009 g_free(sip->serveraddr);
9010 sip->serveraddr = NULL;
9012 if (sip->focus_factory_uri)
9013 g_free(sip->focus_factory_uri);
9014 sip->focus_factory_uri = NULL;
9016 sip->fd = -1;
9017 sip->processing_input = FALSE;
9019 if (sip->ews) {
9020 sipe_ews_free(sip->ews);
9022 sip->ews = NULL;
9026 * A callback for g_hash_table_foreach_remove
9028 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
9029 SIPE_UNUSED_PARAMETER gpointer user_data)
9031 sipe_free_buddy((struct sipe_buddy *) buddy);
9033 /* We must return TRUE as the key/value have already been deleted */
9034 return(TRUE);
9037 static void sipe_close(PurpleConnection *gc)
9039 struct sipe_account_data *sip = gc->proto_data;
9041 if (sip) {
9042 /* leave all conversations */
9043 sipe_session_close_all(sip);
9044 sipe_session_remove_all(sip);
9046 if (sip->csta) {
9047 sip_csta_close(sip);
9050 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
9051 /* unsubscribe all */
9052 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
9054 /* unregister */
9055 do_register_exp(sip, 0);
9058 sipe_connection_cleanup(sip);
9059 g_free(sip->sipdomain);
9060 g_free(sip->username);
9061 g_free(sip->email);
9062 g_free(sip->password);
9063 g_free(sip->authdomain);
9064 g_free(sip->authuser);
9065 g_free(sip->status);
9066 g_free(sip->note);
9067 g_free(sip->user_states);
9069 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
9070 g_hash_table_destroy(sip->buddies);
9071 g_hash_table_destroy(sip->our_publications);
9072 g_hash_table_destroy(sip->user_state_publications);
9073 g_hash_table_destroy(sip->subscriptions);
9074 g_hash_table_destroy(sip->filetransfers);
9076 if (sip->groups) {
9077 GSList *entry = sip->groups;
9078 while (entry) {
9079 struct sipe_group *group = entry->data;
9080 g_free(group->name);
9081 g_free(group);
9082 entry = entry->next;
9085 g_slist_free(sip->groups);
9087 if (sip->our_publication_keys) {
9088 GSList *entry = sip->our_publication_keys;
9089 while (entry) {
9090 g_free(entry->data);
9091 entry = entry->next;
9094 g_slist_free(sip->our_publication_keys);
9096 while (sip->transactions)
9097 transactions_remove(sip, sip->transactions->data);
9099 g_free(gc->proto_data);
9100 gc->proto_data = NULL;
9103 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
9104 SIPE_UNUSED_PARAMETER void *user_data)
9106 PurpleAccount *acct = purple_connection_get_account(gc);
9107 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
9108 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
9109 if (conv == NULL)
9110 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
9111 purple_conversation_present(conv);
9112 g_free(id);
9115 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
9116 SIPE_UNUSED_PARAMETER void *user_data)
9119 purple_blist_request_add_buddy(purple_connection_get_account(gc),
9120 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
9123 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
9124 SIPE_UNUSED_PARAMETER struct transaction *trans)
9126 PurpleNotifySearchResults *results;
9127 PurpleNotifySearchColumn *column;
9128 sipe_xml *searchResults;
9129 const sipe_xml *mrow;
9130 int match_count = 0;
9131 gboolean more = FALSE;
9132 gchar *secondary;
9134 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
9136 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
9137 if (!searchResults) {
9138 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
9139 return FALSE;
9142 results = purple_notify_searchresults_new();
9144 if (results == NULL) {
9145 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
9146 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
9148 sipe_xml_free(searchResults);
9149 return FALSE;
9152 column = purple_notify_searchresults_column_new(_("User name"));
9153 purple_notify_searchresults_column_add(results, column);
9155 column = purple_notify_searchresults_column_new(_("Name"));
9156 purple_notify_searchresults_column_add(results, column);
9158 column = purple_notify_searchresults_column_new(_("Company"));
9159 purple_notify_searchresults_column_add(results, column);
9161 column = purple_notify_searchresults_column_new(_("Country"));
9162 purple_notify_searchresults_column_add(results, column);
9164 column = purple_notify_searchresults_column_new(_("Email"));
9165 purple_notify_searchresults_column_add(results, column);
9167 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
9168 GList *row = NULL;
9170 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
9171 row = g_list_append(row, g_strdup(uri_parts[1]));
9172 g_strfreev(uri_parts);
9174 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
9175 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
9176 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
9177 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
9179 purple_notify_searchresults_row_add(results, row);
9180 match_count++;
9183 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
9184 char *data = sipe_xml_data(mrow);
9185 more = (g_strcasecmp(data, "true") == 0);
9186 g_free(data);
9189 secondary = g_strdup_printf(
9190 dngettext(PACKAGE_NAME,
9191 "Found %d contact%s:",
9192 "Found %d contacts%s:", match_count),
9193 match_count, more ? _(" (more matched your query)") : "");
9195 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
9196 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
9197 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
9199 g_free(secondary);
9200 sipe_xml_free(searchResults);
9201 return TRUE;
9204 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
9206 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
9207 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
9208 unsigned i = 0;
9210 if (!attrs) return;
9212 do {
9213 PurpleRequestField *field = entries->data;
9214 const char *id = purple_request_field_get_id(field);
9215 const char *value = purple_request_field_string_get_value(field);
9217 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
9219 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
9220 } while ((entries = g_list_next(entries)) != NULL);
9221 attrs[i] = NULL;
9223 if (i > 0) {
9224 struct sipe_account_data *sip = gc->proto_data;
9225 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9226 gchar *query = g_strjoinv(NULL, attrs);
9227 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
9228 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
9229 send_soap_request_with_cb(sip, domain_uri, body,
9230 (TransCallback) process_search_contact_response, NULL);
9231 g_free(domain_uri);
9232 g_free(body);
9233 g_free(query);
9236 g_strfreev(attrs);
9239 static void sipe_show_find_contact(PurplePluginAction *action)
9241 PurpleConnection *gc = (PurpleConnection *) action->context;
9242 PurpleRequestFields *fields;
9243 PurpleRequestFieldGroup *group;
9244 PurpleRequestField *field;
9246 fields = purple_request_fields_new();
9247 group = purple_request_field_group_new(NULL);
9248 purple_request_fields_add_group(fields, group);
9250 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
9251 purple_request_field_group_add_field(group, field);
9252 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
9253 purple_request_field_group_add_field(group, field);
9254 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
9255 purple_request_field_group_add_field(group, field);
9256 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
9257 purple_request_field_group_add_field(group, field);
9259 purple_request_fields(gc,
9260 _("Search"),
9261 _("Search for a contact"),
9262 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
9263 fields,
9264 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
9265 _("_Cancel"), NULL,
9266 purple_connection_get_account(gc), NULL, NULL, gc);
9269 static void sipe_show_about_plugin(PurplePluginAction *action)
9271 PurpleConnection *gc = (PurpleConnection *) action->context;
9272 char *tmp = g_strdup_printf(
9274 * Non-translatable parts, like markup, are hard-coded
9275 * into the format string. This requires more translatable
9276 * texts but it makes the translations less error prone.
9278 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
9279 "<br/>"
9280 /* 1 */ "%s:<br/>"
9281 "<li> - MS Office Communications Server 2007 R2</li><br/>"
9282 "<li> - MS Office Communications Server 2007</li><br/>"
9283 "<li> - MS Live Communications Server 2005</li><br/>"
9284 "<li> - MS Live Communications Server 2003</li><br/>"
9285 "<li> - Reuters Messaging</li><br/>"
9286 "<br/>"
9287 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
9288 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
9289 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
9290 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
9291 /* 8 */ "%s: GPLv2+<br/>"
9292 "<br/>"
9293 /* 9 */ "%s:<br/>"
9294 " - CERN<br/>"
9295 " - Reuters Messaging network<br/>"
9296 " - Deutsche Bank<br/>"
9297 " - Merrill Lynch<br/>"
9298 " - Wachovia<br/>"
9299 " - Intel<br/>"
9300 " - Nokia<br/>"
9301 " - HP<br/>"
9302 " - Symantec<br/>"
9303 " - Accenture<br/>"
9304 " - Capgemini<br/>"
9305 " - Siemens<br/>"
9306 " - Alcatel-Lucent<br/>"
9307 " - BT<br/>"
9308 "<br/>"
9309 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9310 "<br/>"
9311 /* 12 */ "<b>%s:</b><br/>"
9312 " - Anibal Avelar<br/>"
9313 " - Gabriel Burt<br/>"
9314 " - Stefan Becker<br/>"
9315 " - pier11<br/>"
9316 " - Jakub Adam<br/>"
9317 " - Tomáš Hrabčík<br/>"
9318 "<br/>"
9319 /* 13 */ "%s<br/>"
9321 /* The next 13 texts make up the SIPE about note text */
9322 /* About note, part 1/13: introduction */
9323 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9324 /* About note, part 2/13: home page URL (label) */
9325 _("Home"),
9326 /* About note, part 3/13: support forum URL (label) */
9327 _("Support"),
9328 /* About note, part 4/13: support forum name (hyperlink text) */
9329 _("Help Forum"),
9330 /* About note, part 5/13: bug tracker URL (label) */
9331 _("Report Problems"),
9332 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9333 _("Bug Tracker"),
9334 /* About note, part 7/13: translation service URL (label) */
9335 _("Translations"),
9336 /* About note, part 8/13: license type (label) */
9337 _("License"),
9338 /* About note, part 9/13: known users */
9339 _("We support users in such organizations as"),
9340 /* About note, part 10/13: translation request, text before Transifex.net URL */
9341 /* append a space if text is not empty */
9342 _("Please help us to translate SIPE to your native language here at "),
9343 /* About note, part 11/13: translation request, text after Transifex.net URL */
9344 /* start with a space if text is not empty */
9345 _(" using convenient web interface"),
9346 /* About note, part 12/13: author list (header) */
9347 _("Authors"),
9348 /* About note, part 13/13: Localization credit */
9349 /* PLEASE NOTE: do *NOT* simply translate the english original */
9350 /* but write something similar to the following sentence: */
9351 /* "Localization for <language name> (<language code>): <name>" */
9352 _("Original texts in English (en): SIPE developers")
9354 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9355 g_free(tmp);
9358 static void sipe_republish_calendar(PurplePluginAction *action)
9360 PurpleConnection *gc = (PurpleConnection *) action->context;
9361 struct sipe_account_data *sip = gc->proto_data;
9363 sipe_update_calendar(sip);
9366 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9367 gpointer value,
9368 GString* str)
9370 struct sipe_publication *publication = value;
9372 g_string_append_printf( str,
9373 SIPE_PUB_XML_PUBLICATION_CLEAR,
9374 publication->category,
9375 publication->instance,
9376 publication->container,
9377 publication->version,
9378 "static");
9381 static void sipe_reset_status(PurplePluginAction *action)
9383 PurpleConnection *gc = (PurpleConnection *) action->context;
9384 struct sipe_account_data *sip = gc->proto_data;
9386 if (sip->ocs2007) /* 2007+ */
9388 GString* str = g_string_new(NULL);
9389 gchar *publications;
9391 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9392 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
9393 return;
9396 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9397 publications = g_string_free(str, FALSE);
9399 send_presence_publish(sip, publications);
9400 g_free(publications);
9402 else /* 2005 */
9404 send_presence_soap0(sip, FALSE, TRUE);
9408 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9409 gpointer context)
9411 PurpleConnection *gc = (PurpleConnection *)context;
9412 struct sipe_account_data *sip = gc->proto_data;
9413 GList *menu = NULL;
9414 PurplePluginAction *act;
9415 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9417 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9418 menu = g_list_prepend(menu, act);
9420 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9421 menu = g_list_prepend(menu, act);
9423 if (sipe_strequal(calendar, "EXCH")) {
9424 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9425 menu = g_list_prepend(menu, act);
9428 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9429 menu = g_list_prepend(menu, act);
9431 menu = g_list_reverse(menu);
9433 return menu;
9436 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9440 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9442 return TRUE;
9446 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9448 return TRUE;
9452 static char *sipe_status_text(PurpleBuddy *buddy)
9454 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9455 const PurpleStatus *status = purple_presence_get_active_status(presence);
9456 const char *status_id = purple_status_get_id(status);
9457 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9458 struct sipe_buddy *sbuddy;
9459 char *text = NULL;
9461 if (!sip) return NULL; /* happens on pidgin exit */
9463 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9464 if (sbuddy) {
9465 const char *activity_str = sbuddy->activity ?
9466 sbuddy->activity :
9467 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9468 purple_status_get_name(status) : NULL;
9470 if (activity_str && sbuddy->note)
9472 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9474 else if (activity_str)
9476 text = g_strdup(activity_str);
9478 else if (sbuddy->note)
9480 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9484 return text;
9487 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9489 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9490 const PurpleStatus *status = purple_presence_get_active_status(presence);
9491 struct sipe_account_data *sip;
9492 struct sipe_buddy *sbuddy;
9493 char *note = NULL;
9494 gboolean is_oof_note = FALSE;
9495 char *activity = NULL;
9496 char *calendar = NULL;
9497 char *meeting_subject = NULL;
9498 char *meeting_location = NULL;
9500 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9501 if (sip) //happens on pidgin exit
9503 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9504 if (sbuddy)
9506 note = sbuddy->note;
9507 is_oof_note = sbuddy->is_oof_note;
9508 activity = sbuddy->activity;
9509 calendar = sipe_cal_get_description(sbuddy);
9510 meeting_subject = sbuddy->meeting_subject;
9511 meeting_location = sbuddy->meeting_location;
9515 //Layout
9516 if (purple_presence_is_online(presence))
9518 const char *status_str = activity ? activity : purple_status_get_name(status);
9520 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9522 if (purple_presence_is_online(presence) &&
9523 !is_empty(calendar))
9525 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9527 g_free(calendar);
9528 if (!is_empty(meeting_location))
9530 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9532 if (!is_empty(meeting_subject))
9534 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9537 if (note)
9539 char *tmp = g_strdup_printf("<i>%s</i>", note);
9540 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", buddy->name, note);
9542 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9543 g_free(tmp);
9546 if (sip && sip->ocs2007) {
9547 const int container_id = sipe_find_access_level(sip, "user", sipe_get_no_sip_uri(buddy->name));
9548 const char *access_level = sipe_get_access_level_name(container_id);
9550 purple_notify_user_info_add_pair(user_info, _("Access level"), access_level);
9554 #if PURPLE_VERSION_CHECK(2,5,0)
9555 static GHashTable *
9556 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9558 GHashTable *table;
9559 table = g_hash_table_new(g_str_hash, g_str_equal);
9560 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9561 return table;
9563 #endif
9565 static PurpleBuddy *
9566 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9568 PurpleBuddy *clone;
9569 const gchar *server_alias, *email;
9570 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9572 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9574 purple_blist_add_buddy(clone, NULL, group, NULL);
9576 server_alias = purple_buddy_get_server_alias(buddy);
9577 if (server_alias) {
9578 purple_blist_server_alias_buddy(clone, server_alias);
9581 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9582 if (email) {
9583 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9586 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9587 //for UI to update;
9588 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9589 return clone;
9592 static void
9593 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9595 PurpleBuddy *buddy, *b;
9596 PurpleConnection *gc;
9597 PurpleGroup * group = purple_find_group(group_name);
9599 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9601 buddy = (PurpleBuddy *)node;
9603 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
9604 gc = purple_account_get_connection(buddy->account);
9606 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9607 if (!b){
9608 purple_blist_add_buddy_clone(group, buddy);
9611 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9614 static void
9615 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9617 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9619 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
9621 /* 2007+ conference */
9622 if (sip->ocs2007)
9624 sipe_conf_add(sip, buddy->name);
9626 else /* 2005- multiparty chat */
9628 gchar *self = sip_uri_self(sip);
9629 struct sip_session *session;
9631 session = sipe_session_add_chat(sip);
9632 session->chat_title = sipe_chat_get_name(session->callid);
9633 session->roster_manager = g_strdup(self);
9635 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9636 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9637 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9638 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9640 g_free(self);
9644 static gboolean
9645 sipe_is_election_finished(struct sip_session *session)
9647 gboolean res = TRUE;
9649 SIPE_DIALOG_FOREACH {
9650 if (dialog->election_vote == 0) {
9651 res = FALSE;
9652 break;
9654 } SIPE_DIALOG_FOREACH_END;
9656 if (res) {
9657 session->is_voting_in_progress = FALSE;
9659 return res;
9662 static void
9663 sipe_election_start(struct sipe_account_data *sip,
9664 struct sip_session *session)
9666 int election_timeout;
9668 if (session->is_voting_in_progress) {
9669 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
9670 return;
9671 } else {
9672 session->is_voting_in_progress = TRUE;
9674 session->bid = rand();
9676 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
9678 SIPE_DIALOG_FOREACH {
9679 /* reset election_vote for each chat participant */
9680 dialog->election_vote = 0;
9682 /* send RequestRM to each chat participant*/
9683 sipe_send_election_request_rm(sip, dialog, session->bid);
9684 } SIPE_DIALOG_FOREACH_END;
9686 election_timeout = 15; /* sec */
9687 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9691 * @param who a URI to whom to invite to chat
9693 void
9694 sipe_invite_to_chat(struct sipe_account_data *sip,
9695 struct sip_session *session,
9696 const gchar *who)
9698 /* a conference */
9699 if (session->focus_uri)
9701 sipe_invite_conf(sip, session, who);
9703 else /* a multi-party chat */
9705 gchar *self = sip_uri_self(sip);
9706 if (session->roster_manager) {
9707 if (sipe_strcase_equal(session->roster_manager, self)) {
9708 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9709 } else {
9710 sipe_refer(sip, session, who);
9712 } else {
9713 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
9715 session->pending_invite_queue = slist_insert_unique_sorted(
9716 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9718 sipe_election_start(sip, session);
9720 g_free(self);
9724 void
9725 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9726 struct sip_session *session)
9728 gchar *invitee;
9729 GSList *entry = session->pending_invite_queue;
9731 while (entry) {
9732 invitee = entry->data;
9733 sipe_invite_to_chat(sip, session, invitee);
9734 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9735 g_free(invitee);
9739 static void
9740 sipe_election_result(struct sipe_account_data *sip,
9741 void *sess)
9743 struct sip_session *session = (struct sip_session *)sess;
9744 gchar *rival;
9745 gboolean has_won = TRUE;
9747 if (session->roster_manager) {
9748 SIPE_DEBUG_INFO(
9749 "sipe_election_result: RM has already been elected in the meantime. It is %s",
9750 session->roster_manager);
9751 return;
9754 session->is_voting_in_progress = FALSE;
9756 SIPE_DIALOG_FOREACH {
9757 if (dialog->election_vote < 0) {
9758 has_won = FALSE;
9759 rival = dialog->with;
9760 break;
9762 } SIPE_DIALOG_FOREACH_END;
9764 if (has_won) {
9765 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
9767 session->roster_manager = sip_uri_self(sip);
9769 SIPE_DIALOG_FOREACH {
9770 /* send SetRM to each chat participant*/
9771 sipe_send_election_set_rm(sip, dialog);
9772 } SIPE_DIALOG_FOREACH_END;
9773 } else {
9774 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
9776 session->bid = 0;
9778 sipe_process_pending_invite_queue(sip, session);
9782 * For 2007+ conference only.
9784 static void
9785 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9787 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9788 struct sip_session *session;
9790 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
9791 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
9793 session = sipe_session_find_chat_by_title(sip, chat_title);
9795 sipe_conf_modify_user_role(sip, session, buddy->name);
9799 * For 2007+ conference only.
9801 static void
9802 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9804 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9805 struct sip_session *session;
9807 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
9808 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
9810 session = sipe_session_find_chat_by_title(sip, chat_title);
9812 sipe_conf_delete_user(sip, session, buddy->name);
9815 static void
9816 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9818 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9819 struct sip_session *session;
9821 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
9822 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
9824 session = sipe_session_find_chat_by_title(sip, chat_title);
9826 sipe_invite_to_chat(sip, session, buddy->name);
9829 static void
9830 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9832 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9834 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
9835 if (phone) {
9836 char *tel_uri = sip_to_tel_uri(phone);
9838 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
9839 sip_csta_make_call(sip, tel_uri);
9841 g_free(tel_uri);
9845 static void
9846 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9848 const gchar *email;
9849 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
9851 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9852 if (email)
9854 char *mailto = g_strdup_printf("mailto:%s", email);
9855 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s", email);
9856 #ifndef _WIN32
9858 pid_t pid;
9859 char *const parmList[] = {"xdg-email", mailto, NULL};
9860 if ((pid = fork()) == -1)
9862 SIPE_DEBUG_INFO_NOFORMAT("fork() error");
9864 else if (pid == 0)
9866 execvp(parmList[0], parmList);
9867 SIPE_DEBUG_INFO_NOFORMAT("Return not expected. Must be an execvp() error.");
9870 #else
9872 BOOL ret;
9873 _flushall();
9874 errno = 0;
9875 //@TODO resolve env variable %WINDIR% first
9876 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9877 if (errno)
9879 SIPE_DEBUG_INFO("spawnl returned (%s)!", strerror(errno));
9882 #endif
9884 g_free(mailto);
9886 else
9888 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
9892 static void
9893 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy, const int *container_id)
9895 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9897 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: buddy->name=%s, container_id=%d",
9898 buddy->name, container_id ? *container_id : -1);
9899 if (container_id) {
9900 sipe_change_access_level(sip, *container_id, "user", sipe_get_no_sip_uri(buddy->name));
9905 * A menu which appear when right-clicking on buddy in contact list.
9907 static GList *
9908 sipe_buddy_menu(PurpleBuddy *buddy)
9910 PurpleBlistNode *g_node;
9911 PurpleGroup *group, *gr_parent;
9912 PurpleMenuAction *act;
9913 GList *menu = NULL;
9914 GList *menu_groups = NULL;
9915 GList *menu_access_levels = NULL;
9916 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9917 const char *email;
9918 const char *phone;
9919 const char *phone_disp_str;
9920 gchar *self = sip_uri_self(sip);
9922 SIPE_SESSION_FOREACH {
9923 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9925 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9927 PurpleConvChatBuddyFlags flags;
9928 PurpleConvChatBuddyFlags flags_us;
9930 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9931 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9932 if (session->focus_uri
9933 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9934 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9936 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9937 act = purple_menu_action_new(label,
9938 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9939 session->chat_title, NULL);
9940 g_free(label);
9941 menu = g_list_prepend(menu, act);
9944 if (session->focus_uri
9945 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9947 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9948 act = purple_menu_action_new(label,
9949 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9950 session->chat_title, NULL);
9951 g_free(label);
9952 menu = g_list_prepend(menu, act);
9955 else
9957 if (!session->focus_uri
9958 || (session->focus_uri && !session->locked))
9960 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9961 act = purple_menu_action_new(label,
9962 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9963 session->chat_title, NULL);
9964 g_free(label);
9965 menu = g_list_prepend(menu, act);
9969 } SIPE_SESSION_FOREACH_END;
9971 act = purple_menu_action_new(_("New chat"),
9972 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9973 NULL, NULL);
9974 menu = g_list_prepend(menu, act);
9976 if (sip->csta && !sip->csta->line_status) {
9977 gchar *tmp = NULL;
9978 /* work phone */
9979 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9980 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9981 if (phone) {
9982 gchar *label = g_strdup_printf(_("Work %s"),
9983 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9984 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9985 g_free(tmp);
9986 tmp = NULL;
9987 g_free(label);
9988 menu = g_list_prepend(menu, act);
9991 /* mobile phone */
9992 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9993 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9994 if (phone) {
9995 gchar *label = g_strdup_printf(_("Mobile %s"),
9996 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9997 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9998 g_free(tmp);
9999 tmp = NULL;
10000 g_free(label);
10001 menu = g_list_prepend(menu, act);
10004 /* home phone */
10005 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
10006 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
10007 if (phone) {
10008 gchar *label = g_strdup_printf(_("Home %s"),
10009 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10010 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10011 g_free(tmp);
10012 tmp = NULL;
10013 g_free(label);
10014 menu = g_list_prepend(menu, act);
10017 /* other phone */
10018 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
10019 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
10020 if (phone) {
10021 gchar *label = g_strdup_printf(_("Other %s"),
10022 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10023 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10024 g_free(tmp);
10025 tmp = NULL;
10026 g_free(label);
10027 menu = g_list_prepend(menu, act);
10030 /* custom1 phone */
10031 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
10032 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
10033 if (phone) {
10034 gchar *label = g_strdup_printf(_("Custom1 %s"),
10035 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10036 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10037 g_free(tmp);
10038 tmp = NULL;
10039 g_free(label);
10040 menu = g_list_prepend(menu, act);
10044 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
10045 if (email) {
10046 act = purple_menu_action_new(_("Send email..."),
10047 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
10048 NULL, NULL);
10049 menu = g_list_prepend(menu, act);
10052 /* Access Level */
10053 if (sip->ocs2007) {
10054 unsigned int i;
10055 int container_id = sipe_find_access_level(sip, "user", sipe_get_no_sip_uri(buddy->name));
10057 for (i = 1; i <= CONTAINERS_LEN; i++) {
10058 /* to put Blocked level last in menu list.
10059 * Blocked should remaim in the first place in the containers[] array.
10061 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
10062 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
10063 char *menu_name;
10065 /* current container/access level */
10066 if (((int)containers[j]) == container_id) {
10067 menu_name = g_strdup_printf("* %s", acc_level_name);
10068 } else {
10069 menu_name = g_strdup_printf(" %s", acc_level_name);
10072 act = purple_menu_action_new(menu_name,
10073 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
10074 (gpointer)&(containers[j]), NULL);
10075 g_free(menu_name);
10076 menu_access_levels = g_list_prepend(menu_access_levels, act);
10078 menu_access_levels = g_list_reverse(menu_access_levels);
10080 act = purple_menu_action_new(_("Access level"),
10081 NULL,
10082 NULL, menu_access_levels);
10083 menu = g_list_prepend(menu, act);
10086 /* Copy to */
10087 gr_parent = purple_buddy_get_group(buddy);
10088 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
10089 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
10090 continue;
10092 group = (PurpleGroup *)g_node;
10093 if (group == gr_parent)
10094 continue;
10096 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
10097 continue;
10099 act = purple_menu_action_new(purple_group_get_name(group),
10100 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
10101 group->name, NULL);
10102 menu_groups = g_list_prepend(menu_groups, act);
10104 menu_groups = g_list_reverse(menu_groups);
10106 act = purple_menu_action_new(_("Copy to"),
10107 NULL,
10108 NULL, menu_groups);
10109 menu = g_list_prepend(menu, act);
10111 menu = g_list_reverse(menu);
10113 g_free(self);
10114 return menu;
10117 static void
10118 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
10120 struct sipe_account_data *sip = chat->account->gc->proto_data;
10121 struct sip_session *session;
10123 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10124 sipe_conf_modify_conference_lock(sip, session, locked);
10127 static void
10128 sipe_chat_menu_unlock_cb(PurpleChat *chat)
10130 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
10131 sipe_conf_modify_lock(chat, FALSE);
10134 static void
10135 sipe_chat_menu_lock_cb(PurpleChat *chat)
10137 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
10138 sipe_conf_modify_lock(chat, TRUE);
10141 static GList *
10142 sipe_chat_menu(PurpleChat *chat)
10144 PurpleMenuAction *act;
10145 PurpleConvChatBuddyFlags flags_us;
10146 GList *menu = NULL;
10147 struct sipe_account_data *sip = chat->account->gc->proto_data;
10148 struct sip_session *session;
10149 gchar *self;
10151 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10152 if (!session) return NULL;
10154 self = sip_uri_self(sip);
10155 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
10157 if (session->focus_uri
10158 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
10160 if (session->locked) {
10161 act = purple_menu_action_new(_("Unlock"),
10162 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
10163 NULL, NULL);
10164 menu = g_list_prepend(menu, act);
10165 } else {
10166 act = purple_menu_action_new(_("Lock"),
10167 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
10168 NULL, NULL);
10169 menu = g_list_prepend(menu, act);
10173 menu = g_list_reverse(menu);
10175 g_free(self);
10176 return menu;
10179 static GList *
10180 sipe_blist_node_menu(PurpleBlistNode *node)
10182 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
10183 return sipe_buddy_menu((PurpleBuddy *) node);
10184 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
10185 return sipe_chat_menu((PurpleChat *)node);
10186 } else {
10187 return NULL;
10191 static gboolean
10192 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
10194 char *uri = trans->payload->data;
10196 PurpleNotifyUserInfo *info;
10197 PurpleBuddy *pbuddy = NULL;
10198 struct sipe_buddy *sbuddy;
10199 const char *alias = NULL;
10200 char *device_name = NULL;
10201 char *server_alias = NULL;
10202 char *phone_number = NULL;
10203 char *email = NULL;
10204 const char *site;
10205 char *first_name = NULL;
10206 char *last_name = NULL;
10208 if (!sip) return FALSE;
10210 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sip->username);
10212 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
10213 alias = purple_buddy_get_local_alias(pbuddy);
10215 //will query buddy UA's capabilities and send answer to log
10216 sipe_options_request(sip, uri);
10218 sbuddy = g_hash_table_lookup(sip->buddies, uri);
10219 if (sbuddy) {
10220 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
10223 info = purple_notify_user_info_new();
10225 if (msg->response != 200) {
10226 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg->response);
10227 } else {
10228 sipe_xml *searchResults;
10229 const sipe_xml *mrow;
10231 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
10232 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
10233 if (!searchResults) {
10234 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
10235 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
10236 const char *value;
10237 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
10238 email = g_strdup(sipe_xml_attribute(mrow, "email"));
10239 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
10241 /* For 2007 system we will take this from ContactCard -
10242 * it has cleaner tel: URIs at least
10244 if (!sip->ocs2007) {
10245 char *tel_uri = sip_to_tel_uri(phone_number);
10246 /* trims its parameters, so call first */
10247 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
10248 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
10249 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
10250 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
10251 g_free(tel_uri);
10254 if (server_alias && strlen(server_alias) > 0) {
10255 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10257 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
10258 purple_notify_user_info_add_pair(info, _("Job title"), value);
10260 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
10261 purple_notify_user_info_add_pair(info, _("Office"), value);
10263 if (phone_number && strlen(phone_number) > 0) {
10264 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
10266 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
10267 purple_notify_user_info_add_pair(info, _("Company"), value);
10269 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
10270 purple_notify_user_info_add_pair(info, _("City"), value);
10272 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
10273 purple_notify_user_info_add_pair(info, _("State"), value);
10275 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
10276 purple_notify_user_info_add_pair(info, _("Country"), value);
10278 if (email && strlen(email) > 0) {
10279 purple_notify_user_info_add_pair(info, _("Email address"), email);
10283 sipe_xml_free(searchResults);
10286 purple_notify_user_info_add_section_break(info);
10288 if (is_empty(server_alias)) {
10289 g_free(server_alias);
10290 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
10291 if (server_alias) {
10292 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10296 /* present alias if it differs from server alias */
10297 if (alias && !sipe_strequal(alias, server_alias))
10299 purple_notify_user_info_add_pair(info, _("Alias"), alias);
10302 if (is_empty(email)) {
10303 g_free(email);
10304 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
10305 if (email) {
10306 purple_notify_user_info_add_pair(info, _("Email address"), email);
10310 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
10311 if (site) {
10312 purple_notify_user_info_add_pair(info, _("Site"), site);
10315 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
10316 if (first_name && last_name) {
10317 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
10319 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
10320 g_free(link);
10322 g_free(first_name);
10323 g_free(last_name);
10325 if (device_name) {
10326 purple_notify_user_info_add_pair(info, _("Device"), device_name);
10329 /* show a buddy's user info in a nice dialog box */
10330 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
10331 uri, /* buddy's URI */
10332 info, /* body */
10333 NULL, /* callback called when dialog closed */
10334 NULL); /* userdata for callback */
10336 g_free(phone_number);
10337 g_free(server_alias);
10338 g_free(email);
10339 g_free(device_name);
10341 return TRUE;
10345 * AD search first, LDAP based
10347 static void sipe_get_info(PurpleConnection *gc, const char *username)
10349 struct sipe_account_data *sip = gc->proto_data;
10350 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
10351 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
10352 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
10353 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
10355 payload->destroy = g_free;
10356 payload->data = g_strdup(username);
10358 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
10359 send_soap_request_with_cb(sip, domain_uri, body,
10360 (TransCallback) process_get_info_response, payload);
10361 g_free(domain_uri);
10362 g_free(body);
10363 g_free(row);
10366 PurplePluginProtocolInfo prpl_info =
10368 OPT_PROTO_CHAT_TOPIC,
10369 NULL, /* user_splits */
10370 NULL, /* protocol_options */
10371 NO_BUDDY_ICONS, /* icon_spec */
10372 sipe_list_icon, /* list_icon */
10373 NULL, /* list_emblems */
10374 sipe_status_text, /* status_text */
10375 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10376 sipe_status_types, /* away_states */
10377 sipe_blist_node_menu, /* blist_node_menu */
10378 NULL, /* chat_info */
10379 NULL, /* chat_info_defaults */
10380 sipe_login, /* login */
10381 sipe_close, /* close */
10382 sipe_im_send, /* send_im */
10383 NULL, /* set_info */ // TODO maybe
10384 sipe_send_typing, /* send_typing */
10385 sipe_get_info, /* get_info */
10386 sipe_set_status, /* set_status */
10387 sipe_set_idle, /* set_idle */
10388 NULL, /* change_passwd */
10389 sipe_add_buddy, /* add_buddy */
10390 NULL, /* add_buddies */
10391 sipe_remove_buddy, /* remove_buddy */
10392 NULL, /* remove_buddies */
10393 sipe_add_permit, /* add_permit */
10394 sipe_add_deny, /* add_deny */
10395 sipe_add_deny, /* rem_permit */
10396 sipe_add_permit, /* rem_deny */
10397 dummy_permit_deny, /* set_permit_deny */
10398 NULL, /* join_chat */
10399 NULL, /* reject_chat */
10400 NULL, /* get_chat_name */
10401 sipe_chat_invite, /* chat_invite */
10402 sipe_chat_leave, /* chat_leave */
10403 NULL, /* chat_whisper */
10404 sipe_chat_send, /* chat_send */
10405 sipe_keep_alive, /* keepalive */
10406 NULL, /* register_user */
10407 NULL, /* get_cb_info */ // deprecated
10408 NULL, /* get_cb_away */ // deprecated
10409 sipe_alias_buddy, /* alias_buddy */
10410 sipe_group_buddy, /* group_buddy */
10411 sipe_rename_group, /* rename_group */
10412 NULL, /* buddy_free */
10413 sipe_convo_closed, /* convo_closed */
10414 purple_normalize_nocase, /* normalize */
10415 NULL, /* set_buddy_icon */
10416 sipe_remove_group, /* remove_group */
10417 NULL, /* get_cb_real_name */ // TODO?
10418 NULL, /* set_chat_topic */
10419 NULL, /* find_blist_chat */
10420 NULL, /* roomlist_get_list */
10421 NULL, /* roomlist_cancel */
10422 NULL, /* roomlist_expand_category */
10423 NULL, /* can_receive_file */
10424 sipe_ft_send_file, /* send_file */
10425 sipe_ft_new_xfer, /* new_xfer */
10426 NULL, /* offline_message */
10427 NULL, /* whiteboard_prpl_ops */
10428 sipe_send_raw, /* send_raw */
10429 NULL, /* roomlist_room_serialize */
10430 NULL, /* unregister_user */
10431 NULL, /* send_attention */
10432 NULL, /* get_attention_types */
10433 #if !PURPLE_VERSION_CHECK(2,5,0)
10434 /* Backward compatibility when compiling against 2.4.x API */
10435 (void (*)(void)) /* _purple_reserved4 */
10436 #endif
10437 sizeof(PurplePluginProtocolInfo), /* struct_size */
10438 #if PURPLE_VERSION_CHECK(2,5,0)
10439 sipe_get_account_text_table, /* get_account_text_table */
10440 #if PURPLE_VERSION_CHECK(2,6,0)
10441 NULL, /* initiate_media */
10442 NULL, /* get_media_caps */
10443 #if PURPLE_VERSION_CHECK(2,7,0)
10444 NULL, /* get_moods */
10445 #endif
10446 #endif
10447 #endif
10451 PurplePluginInfo info = {
10452 PURPLE_PLUGIN_MAGIC,
10453 PURPLE_MAJOR_VERSION,
10454 PURPLE_MINOR_VERSION,
10455 PURPLE_PLUGIN_PROTOCOL, /**< type */
10456 NULL, /**< ui_requirement */
10457 0, /**< flags */
10458 NULL, /**< dependencies */
10459 PURPLE_PRIORITY_DEFAULT, /**< priority */
10460 "prpl-sipe", /**< id */
10461 "Office Communicator", /**< name */
10462 PACKAGE_VERSION, /**< version */
10463 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10464 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10465 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10466 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10467 "Gabriel Burt <gburt@novell.com>, " /**< author */
10468 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10469 "pier11 <pier11@operamail.com>", /**< author */
10470 PACKAGE_URL, /**< homepage */
10471 sipe_plugin_load, /**< load */
10472 sipe_plugin_unload, /**< unload */
10473 sipe_plugin_destroy, /**< destroy */
10474 NULL, /**< ui_info */
10475 &prpl_info, /**< extra_info */
10476 NULL,
10477 sipe_actions,
10478 NULL,
10479 NULL,
10480 NULL,
10481 NULL
10484 void sipe_core_init(void)
10486 srand(time(NULL));
10487 sip_sec_init();
10489 #ifdef ENABLE_NLS
10490 SIPE_DEBUG_INFO("bindtextdomain = %s",
10491 bindtextdomain(PACKAGE_NAME, LOCALEDIR));
10492 SIPE_DEBUG_INFO("bind_textdomain_codeset = %s",
10493 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"));
10494 textdomain(PACKAGE_NAME);
10495 #endif
10496 #ifdef HAVE_GMIME
10497 g_mime_init(0);
10498 #endif
10501 void sipe_core_destroy(void)
10503 #ifdef HAVE_GMIME
10504 g_mime_shutdown();
10505 #endif
10506 sip_sec_destroy();
10510 Local Variables:
10511 mode: c
10512 c-file-style: "bsd"
10513 indent-tabs-mode: t
10514 tab-width: 8
10515 End: