Merge branch 'mob' of git+ssh://mob@repo.or.cz/srv/git/siplcs into mob
[siplcs.git] / src / core / sipe.c
blob95299dcf6717acffcaffb8c11f7a03ca63f30b30
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"
87 #include "xmlnode.h"
89 #include "core-depurple.h" /* Temporary for the core de-purple transition */
91 #include "sipmsg.h"
92 #include "sip-csta.h"
93 #include "sip-sec.h"
94 #include "sipe-backend.h"
95 #include "sipe-cal.h"
96 #include "sipe-chat.h"
97 #include "sipe-conf.h"
98 #include "sipe-core.h"
99 #include "sipe-dialog.h"
100 #include "sipe-ews.h"
101 #include "sipe-ft.h"
102 #include "sipe-mime.h"
103 #include "sipe-nls.h"
104 #include "sipe-session.h"
105 #include "sipe-sign.h"
106 #include "sipe-utils.h"
107 #include "sipe-xml.h"
108 #include "http-conn.h"
109 #include "uuid.h"
110 #include "sipe.h"
112 /* Backward compatibility when compiling against 2.4.x API */
113 #if !PURPLE_VERSION_CHECK(2,5,0)
114 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
115 #endif
117 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
119 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
120 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
122 /* Keep in sync with sipe_transport_type! */
123 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
124 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
126 /* Status identifiers (see also: sipe_status_types()) */
127 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
128 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
129 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
130 /* PURPLE_STATUS_UNAVAILABLE: */
131 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
132 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
133 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
134 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
135 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
136 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
137 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
138 /* PURPLE_STATUS_AWAY: */
139 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
140 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
141 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
142 /** Reuters status (user settable) */
143 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
144 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
145 /* ??? PURPLE_STATUS_MOBILE */
146 /* ??? PURPLE_STATUS_TUNE */
148 /* Status attributes (see also sipe_status_types() */
149 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
151 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
153 static struct sipe_activity_map_struct
155 sipe_activity type;
156 const char *token;
157 const char *desc;
158 const char *status_id;
160 } const sipe_activity_map[] =
162 /* This has nothing to do with Availability numbers, like 3500 (online).
163 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
165 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
166 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
167 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
168 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
169 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
170 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
171 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
172 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
173 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
174 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
175 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
176 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
177 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
178 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
179 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
181 /** @param x is sipe_activity */
182 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
185 /* Action name templates */
186 #define ACTION_NAME_PRESENCE "<presence><%s>"
188 static sipe_activity
189 sipe_get_activity_by_token(const char *token)
191 int i;
193 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
195 if (sipe_strequal(token, sipe_activity_map[i].token))
196 return sipe_activity_map[i].type;
199 return sipe_activity_map[0].type;
202 static const char *
203 sipe_get_activity_desc_by_token(const char *token)
205 if (!token) return NULL;
207 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
210 /** Allows to send typed messages from chat window again after account reinstantiation. */
211 static void
212 sipe_rejoin_chat(PurpleConversation *conv)
214 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
215 PURPLE_CONV_CHAT(conv)->left)
217 PURPLE_CONV_CHAT(conv)->left = FALSE;
218 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
222 static char *genbranch()
224 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
225 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
226 rand() & 0xFFFF, rand() & 0xFFFF);
230 static char *default_ua = NULL;
231 static const char*
232 sipe_get_useragent(struct sipe_account_data *sip)
234 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
235 if (is_empty(useragent)) {
236 if (!default_ua) {
237 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
238 /* ref: lzodefs.h */
239 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
240 #define SIPE_TARGET_PLATFORM "linux"
241 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
242 #define SIPE_TARGET_PLATFORM "bsd"
243 #elif defined(__APPLE__) || defined(__MACOS__)
244 #define SIPE_TARGET_PLATFORM "macosx"
245 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
246 #define SIPE_TARGET_PLATFORM "aix"
247 #elif defined(__solaris__) || defined(__sun)
248 #define SIPE_TARGET_PLATFORM "sun"
249 #elif defined(_WIN32)
250 #define SIPE_TARGET_PLATFORM "win"
251 #elif defined(__CYGWIN__)
252 #define SIPE_TARGET_PLATFORM "cygwin"
253 #elif defined(__hpux__)
254 #define SIPE_TARGET_PLATFORM "hpux"
255 #elif defined(__sgi__)
256 #define SIPE_TARGET_PLATFORM "irix"
257 #else
258 #define SIPE_TARGET_PLATFORM "unknown"
259 #endif
261 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
262 #define SIPE_TARGET_ARCH "x86_64"
263 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
264 #define SIPE_TARGET_ARCH "i386"
265 #elif defined(__ppc64__)
266 #define SIPE_TARGET_ARCH "ppc64"
267 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
268 #define SIPE_TARGET_ARCH "ppc"
269 #elif defined(__hppa__) || defined(__hppa)
270 #define SIPE_TARGET_ARCH "hppa"
271 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
272 #define SIPE_TARGET_ARCH "mips"
273 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
274 #define SIPE_TARGET_ARCH "s390"
275 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
276 #define SIPE_TARGET_ARCH "sparc"
277 #elif defined(__arm__)
278 #define SIPE_TARGET_ARCH "arm"
279 #else
280 #define SIPE_TARGET_ARCH "other"
281 #endif
283 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
284 purple_core_get_version(),
285 sip->server_version ? sip->server_version : "");
287 useragent = default_ua;
289 return useragent;
292 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
293 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
295 return "sipe";
298 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
300 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
301 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
302 gpointer data);
304 static void sipe_close(PurpleConnection *gc);
306 static void send_presence_status(struct sipe_account_data *sip);
308 static void sendout_pkt(PurpleConnection *gc, const char *buf);
310 static void sipe_keep_alive(PurpleConnection *gc)
312 struct sipe_account_data *sip = gc->proto_data;
313 if (sip->transport == SIPE_TRANSPORT_UDP) {
314 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
315 gchar buf[2] = {0, 0};
316 SIPE_DEBUG_INFO_NOFORMAT("sending keep alive");
317 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
318 } else {
319 time_t now = time(NULL);
320 if ((sip->keepalive_timeout > 0) &&
321 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
322 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
324 SIPE_DEBUG_INFO("sending keep alive %d", sip->keepalive_timeout);
325 sendout_pkt(gc, "\r\n\r\n");
326 sip->last_keepalive = now;
331 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
333 struct sip_connection *ret = NULL;
334 GSList *entry = sip->openconns;
335 while (entry) {
336 ret = entry->data;
337 if (ret->fd == fd) return ret;
338 entry = entry->next;
340 return NULL;
343 static void sipe_auth_free(struct sip_auth *auth)
345 g_free(auth->opaque);
346 auth->opaque = NULL;
347 g_free(auth->realm);
348 auth->realm = NULL;
349 g_free(auth->target);
350 auth->target = NULL;
351 auth->version = 0;
352 auth->type = AUTH_TYPE_UNSET;
353 auth->retries = 0;
354 auth->expires = 0;
355 g_free(auth->gssapi_data);
356 auth->gssapi_data = NULL;
357 sip_sec_destroy_context(auth->gssapi_context);
358 auth->gssapi_context = NULL;
361 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
363 struct sip_connection *ret = g_new0(struct sip_connection, 1);
364 ret->fd = fd;
365 sip->openconns = g_slist_append(sip->openconns, ret);
366 return ret;
369 static void connection_remove(struct sipe_account_data *sip, int fd)
371 struct sip_connection *conn = connection_find(sip, fd);
372 if (conn) {
373 sip->openconns = g_slist_remove(sip->openconns, conn);
374 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
375 g_free(conn->inbuf);
376 g_free(conn);
380 static void connection_free_all(struct sipe_account_data *sip)
382 struct sip_connection *ret = NULL;
383 GSList *entry = sip->openconns;
384 while (entry) {
385 ret = entry->data;
386 connection_remove(sip, ret->fd);
387 entry = sip->openconns;
391 static void
392 sipe_make_signature(struct sipe_account_data *sip,
393 struct sipmsg *msg);
395 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
397 gchar noncecount[9];
398 const char *authuser = sip->authuser;
399 gchar *response;
400 gchar *ret;
402 if (!authuser || strlen(authuser) < 1) {
403 authuser = sip->username;
406 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
407 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
408 gchar *version_str;
410 // If we have a signature for the message, include that
411 if (msg->signature) {
412 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);
415 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
416 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
417 gchar *gssapi_data;
418 gchar *opaque;
419 gchar *sign_str = NULL;
421 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
422 &(auth->expires),
423 auth->type,
424 purple_account_get_bool(sip->account, "sso", TRUE),
425 sip->authdomain ? sip->authdomain : "",
426 authuser,
427 sip->password,
428 auth->target,
429 auth->gssapi_data);
430 if (!gssapi_data || !auth->gssapi_context) {
431 sip->gc->wants_to_die = TRUE;
432 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
433 return NULL;
436 if (auth->version > 3) {
437 sipe_make_signature(sip, msg);
438 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
439 msg->rand, msg->num, msg->signature);
440 } else {
441 sign_str = g_strdup("");
444 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
445 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
446 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);
447 g_free(opaque);
448 g_free(gssapi_data);
449 g_free(version_str);
450 g_free(sign_str);
451 return ret;
454 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
455 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
456 g_free(version_str);
457 return ret;
459 } else { /* Digest */
461 /* Calculate new session key */
462 if (!auth->opaque) {
463 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth->gssapi_data, auth->realm);
464 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
465 authuser, auth->realm, sip->password,
466 auth->gssapi_data, NULL);
469 sprintf(noncecount, "%08d", auth->nc++);
470 response = purple_cipher_http_digest_calculate_response("md5",
471 msg->method, msg->target, NULL, NULL,
472 auth->gssapi_data, noncecount, NULL,
473 auth->opaque);
474 SIPE_DEBUG_INFO("Digest response %s", response);
476 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);
477 g_free(response);
478 return ret;
482 static char *parse_attribute(const char *attrname, const char *source)
484 const char *tmp, *tmp2;
485 char *retval = NULL;
486 int len = strlen(attrname);
488 if (g_str_has_prefix(source, attrname)) {
489 tmp = source + len;
490 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
491 if (tmp2)
492 retval = g_strndup(tmp, tmp2 - tmp);
493 else
494 retval = g_strdup(tmp);
497 return retval;
500 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
502 int i;
503 gchar **parts;
505 if (!hdr) {
506 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
507 return;
510 if (!g_strncasecmp(hdr, "NTLM", 4)) {
511 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
512 auth->type = AUTH_TYPE_NTLM;
513 hdr += 5;
514 auth->nc = 1;
515 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
516 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
517 auth->type = AUTH_TYPE_KERBEROS;
518 hdr += 9;
519 auth->nc = 3;
520 } else {
521 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
522 auth->type = AUTH_TYPE_DIGEST;
523 hdr += 7;
526 parts = g_strsplit(hdr, "\", ", 0);
527 for (i = 0; parts[i]; i++) {
528 char *tmp;
530 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
532 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
533 g_free(auth->gssapi_data);
534 auth->gssapi_data = tmp;
536 if (auth->type == AUTH_TYPE_NTLM) {
537 /* NTLM module extracts nonce from gssapi-data */
538 auth->nc = 3;
541 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
542 /* Only used with AUTH_TYPE_DIGEST */
543 g_free(auth->gssapi_data);
544 auth->gssapi_data = tmp;
545 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
546 g_free(auth->opaque);
547 auth->opaque = tmp;
548 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
549 g_free(auth->realm);
550 auth->realm = tmp;
552 if (auth->type == AUTH_TYPE_DIGEST) {
553 /* Throw away old session key */
554 g_free(auth->opaque);
555 auth->opaque = NULL;
556 auth->nc = 1;
558 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
559 g_free(auth->target);
560 auth->target = tmp;
561 } else if ((tmp = parse_attribute("version=", parts[i]))) {
562 auth->version = atoi(tmp);
563 g_free(tmp);
565 // uncomment to revert to previous functionality if version 3+ does not work.
566 // auth->version = 2;
568 g_strfreev(parts);
570 return;
573 static void sipe_canwrite_cb(gpointer data,
574 SIPE_UNUSED_PARAMETER gint source,
575 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
577 PurpleConnection *gc = data;
578 struct sipe_account_data *sip = gc->proto_data;
579 gsize max_write;
580 gssize written;
582 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
584 if (max_write == 0) {
585 if (sip->tx_handler != 0){
586 purple_input_remove(sip->tx_handler);
587 sip->tx_handler = 0;
589 return;
592 written = write(sip->fd, sip->txbuf->outptr, max_write);
594 if (written < 0 && errno == EAGAIN)
595 written = 0;
596 else if (written <= 0) {
597 /*TODO: do we really want to disconnect on a failure to write?*/
598 purple_connection_error(gc, _("Could not write"));
599 return;
602 purple_circ_buffer_mark_read(sip->txbuf, written);
605 static void sipe_canwrite_cb_ssl(gpointer data,
606 SIPE_UNUSED_PARAMETER gint src,
607 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
609 PurpleConnection *gc = data;
610 struct sipe_account_data *sip = gc->proto_data;
611 gsize max_write;
612 gssize written;
614 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
616 if (max_write == 0) {
617 if (sip->tx_handler != 0) {
618 purple_input_remove(sip->tx_handler);
619 sip->tx_handler = 0;
620 return;
624 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
626 if (written < 0 && errno == EAGAIN)
627 written = 0;
628 else if (written <= 0) {
629 /*TODO: do we really want to disconnect on a failure to write?*/
630 purple_connection_error(gc, _("Could not write"));
631 return;
634 purple_circ_buffer_mark_read(sip->txbuf, written);
637 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
639 static void send_later_cb(gpointer data, gint source,
640 SIPE_UNUSED_PARAMETER const gchar *error)
642 PurpleConnection *gc = data;
643 struct sipe_account_data *sip;
644 struct sip_connection *conn;
646 if (!PURPLE_CONNECTION_IS_VALID(gc))
648 if (source >= 0)
649 close(source);
650 return;
653 if (source < 0) {
654 purple_connection_error(gc, _("Could not connect"));
655 return;
658 sip = gc->proto_data;
659 sip->fd = source;
660 sip->connecting = FALSE;
661 sip->last_keepalive = time(NULL);
663 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
665 /* If there is more to write now, we need to register a handler */
666 if (sip->txbuf->bufused > 0)
667 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
669 conn = connection_create(sip, source);
670 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
673 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
675 struct sipe_account_data *sip;
677 if (!PURPLE_CONNECTION_IS_VALID(gc))
679 if (gsc) purple_ssl_close(gsc);
680 return NULL;
683 sip = gc->proto_data;
684 sip->fd = gsc->fd;
685 sip->gsc = gsc;
686 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
687 sip->connecting = FALSE;
688 sip->last_keepalive = time(NULL);
690 connection_create(sip, gsc->fd);
692 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
694 return sip;
697 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
698 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
700 PurpleConnection *gc = data;
701 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
702 if (sip == NULL) return;
704 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
706 /* If there is more to write now */
707 if (sip->txbuf->bufused > 0) {
708 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
713 static void sendlater(PurpleConnection *gc, const char *buf)
715 struct sipe_account_data *sip = gc->proto_data;
717 if (!sip->connecting) {
718 SIPE_DEBUG_INFO("connecting to %s port %d", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
719 if (sip->transport == SIPE_TRANSPORT_TLS){
720 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
721 } else {
722 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
723 purple_connection_error(gc, _("Could not create socket"));
726 sip->connecting = TRUE;
729 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
730 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
732 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
735 static void sendout_pkt(PurpleConnection *gc, const char *buf)
737 struct sipe_account_data *sip = gc->proto_data;
738 time_t currtime = time(NULL);
739 int writelen = strlen(buf);
740 char *tmp;
742 SIPE_DEBUG_INFO("sending - %s######\n%s######", ctime(&currtime), tmp = fix_newlines(buf));
743 g_free(tmp);
744 if (sip->transport == SIPE_TRANSPORT_UDP) {
745 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
746 SIPE_DEBUG_INFO_NOFORMAT("could not send packet");
748 } else {
749 int ret;
750 if (sip->fd < 0) {
751 sendlater(gc, buf);
752 return;
755 if (sip->tx_handler) {
756 ret = -1;
757 errno = EAGAIN;
758 } else{
759 if (sip->gsc){
760 ret = purple_ssl_write(sip->gsc, buf, writelen);
761 }else{
762 ret = write(sip->fd, buf, writelen);
766 if (ret < 0 && errno == EAGAIN)
767 ret = 0;
768 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
769 sendlater(gc, buf);
770 return;
773 if (ret < writelen) {
774 if (!sip->tx_handler){
775 if (sip->gsc){
776 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
778 else{
779 sip->tx_handler = purple_input_add(sip->fd,
780 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
781 gc);
785 /* XXX: is it OK to do this? You might get part of a request sent
786 with part of another. */
787 if (sip->txbuf->bufused > 0)
788 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
790 purple_circ_buffer_append(sip->txbuf, buf + ret,
791 writelen - ret);
796 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
798 sendout_pkt(gc, buf);
799 return len;
802 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
804 GSList *tmp = msg->headers;
805 gchar *name;
806 gchar *value;
807 GString *outstr = g_string_new("");
808 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
809 while (tmp) {
810 name = ((struct sipnameval*) (tmp->data))->name;
811 value = ((struct sipnameval*) (tmp->data))->value;
812 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
813 tmp = g_slist_next(tmp);
815 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
816 sendout_pkt(sip->gc, outstr->str);
817 g_string_free(outstr, TRUE);
820 static void
821 sipe_make_signature(struct sipe_account_data *sip,
822 struct sipmsg *msg)
824 if (sip->registrar.gssapi_context) {
825 struct sipmsg_breakdown msgbd;
826 gchar *signature_input_str;
827 msgbd.msg = msg;
828 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
829 msgbd.rand = g_strdup_printf("%08x", g_random_int());
830 sip->registrar.ntlm_num++;
831 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
832 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
833 if (signature_input_str != NULL) {
834 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
835 msg->signature = signature_hex;
836 msg->rand = g_strdup(msgbd.rand);
837 msg->num = g_strdup(msgbd.num);
838 g_free(signature_input_str);
840 sipmsg_breakdown_free(&msgbd);
844 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
846 gchar * buf;
848 if (sip->registrar.type == AUTH_TYPE_UNSET) {
849 return;
852 sipe_make_signature(sip, msg);
854 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
855 buf = auth_header(sip, &sip->registrar, msg);
856 if (buf) {
857 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
859 g_free(buf);
860 } 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")) {
861 sip->registrar.nc = 3;
862 sip->registrar.type = AUTH_TYPE_NTLM;
863 #ifdef HAVE_KERBEROS
864 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
865 sip->registrar.type = AUTH_TYPE_KERBEROS;
867 #endif
870 buf = auth_header(sip, &sip->registrar, msg);
871 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
872 g_free(buf);
873 } else {
874 SIPE_DEBUG_INFO("not adding auth header to msg w/ method %s", method);
878 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
879 const char *text, const char *body)
881 gchar *name;
882 gchar *value;
883 GString *outstr = g_string_new("");
884 struct sipe_account_data *sip = gc->proto_data;
885 gchar *contact;
886 GSList *tmp;
887 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
889 /* Can return NULL! */
890 contact = get_contact(sip);
891 if (contact) {
892 sipmsg_add_header(msg, "Contact", contact);
893 g_free(contact);
896 if (body) {
897 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
898 sipmsg_add_header(msg, "Content-Length", len);
899 g_free(len);
900 } else {
901 sipmsg_add_header(msg, "Content-Length", "0");
904 msg->response = code;
906 sipmsg_strip_headers(msg, keepers);
907 sipmsg_merge_new_headers(msg);
908 sign_outgoing_message(msg, sip, msg->method);
910 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
911 tmp = msg->headers;
912 while (tmp) {
913 name = ((struct sipnameval*) (tmp->data))->name;
914 value = ((struct sipnameval*) (tmp->data))->value;
916 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
917 tmp = g_slist_next(tmp);
919 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
920 sendout_pkt(gc, outstr->str);
921 g_string_free(outstr, TRUE);
924 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
926 if (sip->transactions) {
927 sip->transactions = g_slist_remove(sip->transactions, trans);
928 SIPE_DEBUG_INFO("sip->transactions count:%d after removal", g_slist_length(sip->transactions));
930 if (trans->msg) sipmsg_free(trans->msg);
931 if (trans->payload) {
932 (*trans->payload->destroy)(trans->payload->data);
933 g_free(trans->payload);
935 g_free(trans->key);
936 g_free(trans);
940 static struct transaction *
941 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
943 const gchar *call_id;
944 const gchar *cseq;
945 struct transaction *trans = g_new0(struct transaction, 1);
947 trans->time = time(NULL);
948 trans->msg = (struct sipmsg *)msg;
949 call_id = sipmsg_find_header(trans->msg, "Call-ID");
950 cseq = sipmsg_find_header(trans->msg, "CSeq");
951 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
952 trans->callback = callback;
953 sip->transactions = g_slist_append(sip->transactions, trans);
954 SIPE_DEBUG_INFO("sip->transactions count:%d after addition", g_slist_length(sip->transactions));
955 return trans;
958 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
960 struct transaction *trans;
961 GSList *transactions = sip->transactions;
962 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
963 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
964 gchar *key;
966 if (!call_id || !cseq) {
967 SIPE_DEBUG_ERROR_NOFORMAT("transaction_find: no Call-ID or CSeq!");
968 return NULL;
971 key = g_strdup_printf("<%s><%s>", call_id, cseq);
972 while (transactions) {
973 trans = transactions->data;
974 if (!g_strcasecmp(trans->key, key)) {
975 g_free(key);
976 return trans;
978 transactions = transactions->next;
981 g_free(key);
982 return NULL;
985 struct transaction *
986 send_sip_request(PurpleConnection *gc, const gchar *method,
987 const gchar *url, const gchar *to, const gchar *addheaders,
988 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
990 struct sipe_account_data *sip = gc->proto_data;
991 const char *addh = "";
992 char *buf;
993 struct sipmsg *msg;
994 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
995 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
996 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
997 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
998 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
999 gchar *route = g_strdup("");
1000 gchar *epid = get_epid(sip);
1001 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
1002 struct transaction *trans = NULL;
1004 if (dialog && dialog->routes)
1006 GSList *iter = dialog->routes;
1008 while(iter)
1010 char *tmp = route;
1011 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1012 g_free(tmp);
1013 iter = g_slist_next(iter);
1017 if (!ourtag && !dialog) {
1018 ourtag = gentag();
1021 if (sipe_strequal(method, "REGISTER")) {
1022 if (sip->regcallid) {
1023 g_free(callid);
1024 callid = g_strdup(sip->regcallid);
1025 } else {
1026 sip->regcallid = g_strdup(callid);
1028 cseq = ++sip->cseq;
1031 if (addheaders) addh = addheaders;
1033 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1034 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1035 "From: <sip:%s>%s%s;epid=%s\r\n"
1036 "To: <%s>%s%s%s%s\r\n"
1037 "Max-Forwards: 70\r\n"
1038 "CSeq: %d %s\r\n"
1039 "User-Agent: %s\r\n"
1040 "Call-ID: %s\r\n"
1041 "%s%s"
1042 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1043 method,
1044 dialog && dialog->request ? dialog->request : url,
1045 TRANSPORT_DESCRIPTOR,
1046 purple_network_get_my_ip(-1),
1047 sip->listenport,
1048 branch ? ";branch=" : "",
1049 branch ? branch : "",
1050 sip->username,
1051 ourtag ? ";tag=" : "",
1052 ourtag ? ourtag : "",
1053 epid,
1055 theirtag ? ";tag=" : "",
1056 theirtag ? theirtag : "",
1057 theirepid ? ";epid=" : "",
1058 theirepid ? theirepid : "",
1059 cseq,
1060 method,
1061 sipe_get_useragent(sip),
1062 callid,
1063 route,
1064 addh,
1065 body ? (gsize) strlen(body) : 0,
1066 body ? body : "");
1069 //printf ("parsing msg buf:\n%s\n\n", buf);
1070 msg = sipmsg_parse_msg(buf);
1072 g_free(buf);
1073 g_free(ourtag);
1074 g_free(theirtag);
1075 g_free(theirepid);
1076 g_free(branch);
1077 g_free(callid);
1078 g_free(route);
1079 g_free(epid);
1081 sign_outgoing_message (msg, sip, method);
1083 buf = sipmsg_to_string (msg);
1085 /* add to ongoing transactions */
1086 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1087 if (!sipe_strequal(method, "ACK")) {
1088 trans = transactions_add_buf(sip, msg, tc);
1089 } else {
1090 sipmsg_free(msg);
1092 sendout_pkt(gc, buf);
1093 g_free(buf);
1095 return trans;
1099 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1101 static void
1102 send_soap_request_with_cb(struct sipe_account_data *sip,
1103 gchar *from0,
1104 gchar *body,
1105 TransCallback callback,
1106 struct transaction_payload *payload)
1108 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1109 gchar *contact = get_contact(sip);
1110 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1111 "Content-Type: application/SOAP+xml\r\n",contact);
1113 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1114 trans->payload = payload;
1116 g_free(from);
1117 g_free(contact);
1118 g_free(hdr);
1121 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1123 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1126 static char *get_contact_register(struct sipe_account_data *sip)
1128 char *epid = get_epid(sip);
1129 char *uuid = generateUUIDfromEPID(epid);
1130 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1131 g_free(uuid);
1132 g_free(epid);
1133 return(buf);
1136 static void do_register_exp(struct sipe_account_data *sip, int expire)
1138 char *uri;
1139 char *expires;
1140 char *to;
1141 char *contact;
1142 char *hdr;
1144 if (!sip->sipdomain) return;
1146 uri = sip_uri_from_name(sip->sipdomain);
1147 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1148 to = sip_uri_self(sip);
1149 contact = get_contact_register(sip);
1150 hdr = g_strdup_printf("Contact: %s\r\n"
1151 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1152 "Event: registration\r\n"
1153 "Allow-Events: presence\r\n"
1154 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1155 "%s", contact, expires);
1156 g_free(contact);
1157 g_free(expires);
1159 sip->registerstatus = 1;
1161 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1162 process_register_response);
1164 g_free(hdr);
1165 g_free(uri);
1166 g_free(to);
1169 static void do_register_cb(struct sipe_account_data *sip,
1170 SIPE_UNUSED_PARAMETER void *unused)
1172 do_register_exp(sip, -1);
1173 sip->reregister_set = FALSE;
1176 static void do_register(struct sipe_account_data *sip)
1178 do_register_exp(sip, -1);
1181 static void
1182 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1184 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1185 send_soap_request(sip, body);
1186 g_free(body);
1189 static void
1190 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1192 if (allow) {
1193 SIPE_DEBUG_INFO("Authorizing contact %s", who);
1194 } else {
1195 SIPE_DEBUG_INFO("Blocking contact %s", who);
1198 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1201 static
1202 void sipe_auth_user_cb(void * data)
1204 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1205 if (!job) return;
1207 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1208 g_free(job);
1211 static
1212 void sipe_deny_user_cb(void * data)
1214 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1215 if (!job) return;
1217 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1218 g_free(job);
1221 static void
1222 sipe_add_permit(PurpleConnection *gc, const char *name)
1224 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1225 sipe_contact_allow_deny(sip, name, TRUE);
1228 static void
1229 sipe_add_deny(PurpleConnection *gc, const char *name)
1231 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1232 sipe_contact_allow_deny(sip, name, FALSE);
1235 /*static void
1236 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1238 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1239 sipe_contact_set_acl(sip, name, "");
1242 static void
1243 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1245 xmlnode *watchers;
1246 xmlnode *watcher;
1247 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1248 if (msg->response != 0 && msg->response != 200) return;
1250 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1252 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1253 if (!watchers) return;
1255 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1256 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1257 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1258 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1260 // TODO pull out optional displayName to pass as alias
1261 if (remote_user) {
1262 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1263 job->who = remote_user;
1264 job->sip = sip;
1265 purple_account_request_authorization(
1266 sip->account,
1267 remote_user,
1268 _("you"), /* id */
1269 alias,
1270 NULL, /* message */
1271 on_list,
1272 sipe_auth_user_cb,
1273 sipe_deny_user_cb,
1274 (void *) job);
1279 xmlnode_free(watchers);
1280 return;
1283 static void
1284 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1286 PurpleGroup * purple_group = purple_find_group(group->name);
1287 if (!purple_group) {
1288 purple_group = purple_group_new(group->name);
1289 purple_blist_add_group(purple_group, NULL);
1292 if (purple_group) {
1293 group->purple_group = purple_group;
1294 sip->groups = g_slist_append(sip->groups, group);
1295 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
1296 } else {
1297 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
1301 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1303 struct sipe_group *group;
1304 GSList *entry;
1305 if (sip == NULL) {
1306 return NULL;
1309 entry = sip->groups;
1310 while (entry) {
1311 group = entry->data;
1312 if (group->id == id) {
1313 return group;
1315 entry = entry->next;
1317 return NULL;
1320 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1322 struct sipe_group *group;
1323 GSList *entry;
1324 if (!sip || !name) {
1325 return NULL;
1328 entry = sip->groups;
1329 while (entry) {
1330 group = entry->data;
1331 if (sipe_strequal(group->name, name)) {
1332 return group;
1334 entry = entry->next;
1336 return NULL;
1339 static void
1340 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1342 gchar *body;
1343 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
1344 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1345 send_soap_request(sip, body);
1346 g_free(body);
1347 g_free(group->name);
1348 group->name = g_strdup(name);
1352 * Only appends if no such value already stored.
1353 * Like Set in Java.
1355 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1356 GSList * res = list;
1357 if (!g_slist_find_custom(list, data, func)) {
1358 res = g_slist_insert_sorted(list, data, func);
1360 return res;
1363 static int
1364 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1365 return group1->id - group2->id;
1369 * Returns string like "2 4 7 8" - group ids buddy belong to.
1371 static gchar *
1372 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1373 int i = 0;
1374 gchar *res;
1375 //creating array from GList, converting int to gchar*
1376 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1377 GSList *entry = buddy->groups;
1379 if (!ids_arr) return NULL;
1381 while (entry) {
1382 struct sipe_group * group = entry->data;
1383 ids_arr[i] = g_strdup_printf("%d", group->id);
1384 entry = entry->next;
1385 i++;
1387 ids_arr[i] = NULL;
1388 res = g_strjoinv(" ", ids_arr);
1389 g_strfreev(ids_arr);
1390 return res;
1394 * Sends buddy update to server
1396 static void
1397 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1399 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1400 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1402 if (buddy && purple_buddy) {
1403 const char *alias = purple_buddy_get_alias(purple_buddy);
1404 gchar *groups = sipe_get_buddy_groups_string(buddy);
1405 if (groups) {
1406 gchar *body;
1407 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
1409 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1410 alias, groups, "true", buddy->name, sip->contacts_delta++
1412 send_soap_request(sip, body);
1413 g_free(groups);
1414 g_free(body);
1419 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1421 if (msg->response == 200) {
1422 struct sipe_group *group;
1423 struct group_user_context *ctx = trans->payload->data;
1424 xmlnode *xml;
1425 xmlnode *node;
1426 char *group_id;
1427 struct sipe_buddy *buddy;
1429 xml = xmlnode_from_str(msg->body, msg->bodylen);
1430 if (!xml) {
1431 return FALSE;
1434 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1435 if (!node) {
1436 xmlnode_free(xml);
1437 return FALSE;
1440 group_id = xmlnode_get_data(node);
1441 if (!group_id) {
1442 xmlnode_free(xml);
1443 return FALSE;
1446 group = g_new0(struct sipe_group, 1);
1447 group->id = (int)g_ascii_strtod(group_id, NULL);
1448 g_free(group_id);
1449 group->name = g_strdup(ctx->group_name);
1451 sipe_group_add(sip, group);
1453 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1454 if (buddy) {
1455 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1458 sipe_group_set_user(sip, ctx->user_name);
1460 xmlnode_free(xml);
1461 return TRUE;
1463 return FALSE;
1466 static void sipe_group_context_destroy(gpointer data)
1468 struct group_user_context *ctx = data;
1469 g_free(ctx->group_name);
1470 g_free(ctx->user_name);
1471 g_free(ctx);
1474 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1476 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1477 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1478 gchar *body;
1479 ctx->group_name = g_strdup(name);
1480 ctx->user_name = g_strdup(who);
1481 payload->destroy = sipe_group_context_destroy;
1482 payload->data = ctx;
1484 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1485 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1486 g_free(body);
1490 * Data structure for scheduled actions
1493 struct scheduled_action {
1495 * Name of action.
1496 * Format is <Event>[<Data>...]
1497 * Example: <presence><sip:user@domain.com> or <registration>
1499 gchar *name;
1500 guint timeout_handler;
1501 gboolean repetitive;
1502 Action action;
1503 GDestroyNotify destroy;
1504 struct sipe_account_data *sip;
1505 void *payload;
1509 * A timer callback
1510 * Should return FALSE if repetitive action is not needed
1512 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1514 gboolean ret;
1515 SIPE_DEBUG_INFO_NOFORMAT("sipe_scheduled_exec: executing");
1516 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1517 SIPE_DEBUG_INFO("sip->timeouts count:%d after removal", g_slist_length(sched_action->sip->timeouts));
1518 (sched_action->action)(sched_action->sip, sched_action->payload);
1519 ret = sched_action->repetitive;
1520 if (sched_action->destroy) {
1521 (*sched_action->destroy)(sched_action->payload);
1523 g_free(sched_action->name);
1524 g_free(sched_action);
1525 return ret;
1529 * Kills action timer effectively cancelling
1530 * scheduled action
1532 * @param name of action
1534 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1536 GSList *entry;
1538 if (!sip->timeouts || !name) return;
1540 entry = sip->timeouts;
1541 while (entry) {
1542 struct scheduled_action *sched_action = entry->data;
1543 if(sipe_strequal(sched_action->name, name)) {
1544 GSList *to_delete = entry;
1545 entry = entry->next;
1546 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1547 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
1548 purple_timeout_remove(sched_action->timeout_handler);
1549 if (sched_action->destroy) {
1550 (*sched_action->destroy)(sched_action->payload);
1552 g_free(sched_action->name);
1553 g_free(sched_action);
1554 } else {
1555 entry = entry->next;
1560 static void
1561 sipe_schedule_action0(const gchar *name,
1562 int timeout,
1563 gboolean isSeconds,
1564 Action action,
1565 GDestroyNotify destroy,
1566 struct sipe_account_data *sip,
1567 void *payload)
1569 struct scheduled_action *sched_action;
1571 /* Make sure each action only exists once */
1572 sipe_cancel_scheduled_action(sip, name);
1574 SIPE_DEBUG_INFO("scheduling action %s timeout:%d(%s)", name, timeout, isSeconds ? "sec" : "msec");
1575 sched_action = g_new0(struct scheduled_action, 1);
1576 sched_action->repetitive = FALSE;
1577 sched_action->name = g_strdup(name);
1578 sched_action->action = action;
1579 sched_action->destroy = destroy;
1580 sched_action->sip = sip;
1581 sched_action->payload = payload;
1582 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1583 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1584 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1585 SIPE_DEBUG_INFO("sip->timeouts count:%d after addition", g_slist_length(sip->timeouts));
1588 void
1589 sipe_schedule_action(const gchar *name,
1590 int timeout,
1591 Action action,
1592 GDestroyNotify destroy,
1593 struct sipe_account_data *sip,
1594 void *payload)
1596 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1600 * Same as sipe_schedule_action() but timeout is in milliseconds.
1602 static void
1603 sipe_schedule_action_msec(const gchar *name,
1604 int timeout,
1605 Action action,
1606 GDestroyNotify destroy,
1607 struct sipe_account_data *sip,
1608 void *payload)
1610 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1613 static void
1614 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1615 time_t calculate_from);
1617 static int
1618 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1620 static const char*
1621 sipe_get_status_by_availability(int avail,
1622 char** activity);
1624 static void
1625 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1626 const char *status_id,
1627 const char *message,
1628 time_t do_not_publish[]);
1630 static void
1631 sipe_apply_calendar_status(struct sipe_account_data *sip,
1632 struct sipe_buddy *sbuddy,
1633 const char *status_id)
1635 time_t cal_avail_since;
1636 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1637 int avail;
1638 gchar *self_uri;
1640 if (!sbuddy) return;
1642 if (cal_status < SIPE_CAL_NO_DATA) {
1643 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
1644 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1647 /* scheduled Cal update call */
1648 if (!status_id) {
1649 status_id = sbuddy->last_non_cal_status_id;
1650 g_free(sbuddy->activity);
1651 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1654 if (!status_id) {
1655 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
1656 sbuddy->name ? sbuddy->name : "" );
1657 return;
1660 /* adjust to calendar status */
1661 if (cal_status != SIPE_CAL_NO_DATA) {
1662 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1664 if (cal_status == SIPE_CAL_BUSY
1665 && cal_avail_since > sbuddy->user_avail_since
1666 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1668 status_id = SIPE_STATUS_ID_BUSY;
1669 g_free(sbuddy->activity);
1670 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1672 avail = sipe_get_availability_by_status(status_id, NULL);
1674 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1675 if (cal_avail_since > sbuddy->activity_since) {
1676 if (cal_status == SIPE_CAL_OOF
1677 && avail >= 15000) /* 12000 in 2007 */
1679 g_free(sbuddy->activity);
1680 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1685 /* then set status_id actually */
1686 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
1687 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1689 /* set our account state to the one in roaming (including calendar info) */
1690 self_uri = sip_uri_self(sip);
1691 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1692 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1693 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1696 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
1697 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1699 g_free(self_uri);
1702 static void
1703 sipe_got_user_status(struct sipe_account_data *sip,
1704 const char* uri,
1705 const char *status_id)
1707 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1709 if (!sbuddy) return;
1711 /* Check if on 2005 system contact's calendar,
1712 * then set/preserve it.
1714 if (!sip->ocs2007) {
1715 sipe_apply_calendar_status(sip, sbuddy, status_id);
1716 } else {
1717 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1721 static void
1722 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1723 struct sipe_buddy *sbuddy,
1724 struct sipe_account_data *sip)
1726 sipe_apply_calendar_status(sip, sbuddy, NULL);
1730 * Updates contact's status
1731 * based on their calendar information.
1733 * Applicability: 2005 systems
1735 static void
1736 update_calendar_status(struct sipe_account_data *sip)
1738 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
1739 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1741 /* repeat scheduling */
1742 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1746 * Schedules process of contacts' status update
1747 * based on their calendar information.
1748 * Should be scheduled to the beginning of every
1749 * 15 min interval, like:
1750 * 13:00, 13:15, 13:30, 13:45, etc.
1752 * Applicability: 2005 systems
1754 static void
1755 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1756 time_t calculate_from)
1758 int interval = 15*60;
1759 /** start of the beginning of closest 15 min interval. */
1760 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1762 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
1763 asctime(localtime(&calculate_from)));
1764 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
1765 asctime(localtime(&next_start)));
1767 sipe_schedule_action("<+2005-cal-status>",
1768 (int)(next_start - time(NULL)),
1769 (Action)update_calendar_status,
1770 NULL,
1771 sip,
1772 NULL);
1776 * Schedules process of self status publish
1777 * based on own calendar information.
1778 * Should be scheduled to the beginning of every
1779 * 15 min interval, like:
1780 * 13:00, 13:15, 13:30, 13:45, etc.
1782 * Applicability: 2007+ systems
1784 static void
1785 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1786 time_t calculate_from)
1788 int interval = 5*60;
1789 /** start of the beginning of closest 5 min interval. */
1790 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1792 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1793 asctime(localtime(&calculate_from)));
1794 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1795 asctime(localtime(&next_start)));
1797 sipe_schedule_action("<+2007-cal-status>",
1798 (int)(next_start - time(NULL)),
1799 (Action)publish_calendar_status_self,
1800 NULL,
1801 sip,
1802 NULL);
1805 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1807 /** Should be g_free()'d
1809 static gchar *
1810 sipe_get_subscription_key(const gchar *event,
1811 const gchar *with)
1813 gchar *key = NULL;
1815 if (is_empty(event)) return NULL;
1817 if (event && sipe_strcase_equal(event, "presence")) {
1818 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1819 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1821 /* @TODO drop participated buddies' just_added flag */
1822 } else if (event) {
1823 /* Subscription is identified by <event> key */
1824 key = g_strdup_printf("<%s>", event);
1827 return key;
1830 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1831 SIPE_UNUSED_PARAMETER struct transaction *trans)
1833 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1834 const gchar *event = sipmsg_find_header(msg, "Event");
1835 gchar *key;
1837 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1838 if (!event) {
1839 struct sipmsg *request_msg = trans->msg;
1840 event = sipmsg_find_header(request_msg, "Event");
1843 key = sipe_get_subscription_key(event, with);
1845 /* 200 OK; 481 Call Leg Does Not Exist */
1846 if (key && (msg->response == 200 || msg->response == 481)) {
1847 if (g_hash_table_lookup(sip->subscriptions, key)) {
1848 g_hash_table_remove(sip->subscriptions, key);
1849 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
1853 /* create/store subscription dialog if not yet */
1854 if (msg->response == 200) {
1855 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1856 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1858 if (key) {
1859 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1860 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1862 subscription->dialog.callid = g_strdup(callid);
1863 subscription->dialog.cseq = atoi(cseq);
1864 subscription->dialog.with = g_strdup(with);
1865 subscription->event = g_strdup(event);
1866 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1868 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key);
1871 g_free(cseq);
1874 g_free(key);
1875 g_free(with);
1877 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1879 process_incoming_notify(sip, msg, FALSE, FALSE);
1881 return TRUE;
1884 static void sipe_subscribe_resource_uri(const char *name,
1885 SIPE_UNUSED_PARAMETER gpointer value,
1886 gchar **resources_uri)
1888 gchar *tmp = *resources_uri;
1889 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1890 g_free(tmp);
1893 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1895 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1896 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1897 gchar *tmp = *resources_uri;
1899 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1901 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1902 g_free(tmp);
1906 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1907 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1908 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1909 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1910 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1913 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1915 gchar *key;
1916 gchar *contact = get_contact(sip);
1917 gchar *request;
1918 gchar *content;
1919 gchar *require = "";
1920 gchar *accept = "";
1921 gchar *autoextend = "";
1922 gchar *content_type;
1923 struct sip_dialog *dialog;
1925 if (sip->ocs2007) {
1926 require = ", categoryList";
1927 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1928 content_type = "application/msrtc-adrl-categorylist+xml";
1929 content = g_strdup_printf(
1930 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1931 "<action name=\"subscribe\" id=\"63792024\">\n"
1932 "<adhocList>\n%s</adhocList>\n"
1933 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1934 "<category name=\"calendarData\"/>\n"
1935 "<category name=\"contactCard\"/>\n"
1936 "<category name=\"note\"/>\n"
1937 "<category name=\"state\"/>\n"
1938 "</categoryList>\n"
1939 "</action>\n"
1940 "</batchSub>", sip->username, resources_uri);
1941 } else {
1942 autoextend = "Supported: com.microsoft.autoextend\r\n";
1943 content_type = "application/adrl+xml";
1944 content = g_strdup_printf(
1945 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1946 "<create xmlns=\"\">\n%s</create>\n"
1947 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1949 g_free(resources_uri);
1951 request = g_strdup_printf(
1952 "Require: adhoclist%s\r\n"
1953 "Supported: eventlist\r\n"
1954 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1955 "Supported: ms-piggyback-first-notify\r\n"
1956 "%sSupported: ms-benotify\r\n"
1957 "Proxy-Require: ms-benotify\r\n"
1958 "Event: presence\r\n"
1959 "Content-Type: %s\r\n"
1960 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1961 g_free(contact);
1963 /* subscribe to buddy presence */
1964 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1965 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1966 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1967 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
1969 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1971 g_free(content);
1972 g_free(to);
1973 g_free(request);
1974 g_free(key);
1977 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1978 SIPE_UNUSED_PARAMETER void *unused)
1980 gchar *to = sip_uri_self(sip);
1981 gchar *resources_uri = g_strdup("");
1982 if (sip->ocs2007) {
1983 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1984 } else {
1985 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1988 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1991 struct presence_batched_routed {
1992 gchar *host;
1993 GSList *buddies;
1996 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1998 struct presence_batched_routed *data = payload;
1999 GSList *buddies = data->buddies;
2000 while (buddies) {
2001 g_free(buddies->data);
2002 buddies = buddies->next;
2004 g_slist_free(data->buddies);
2005 g_free(data->host);
2006 g_free(payload);
2009 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
2011 struct presence_batched_routed *data = payload;
2012 GSList *buddies = data->buddies;
2013 gchar *resources_uri = g_strdup("");
2014 while (buddies) {
2015 gchar *tmp = resources_uri;
2016 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2017 g_free(tmp);
2018 buddies = buddies->next;
2020 sipe_subscribe_presence_batched_to(sip, resources_uri,
2021 g_strdup(data->host));
2025 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2026 * The user sends a single SUBSCRIBE request to the subscribed contact.
2027 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2031 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2034 gchar *key;
2035 gchar *to = sip_uri((char *)buddy_name);
2036 gchar *tmp = get_contact(sip);
2037 gchar *request;
2038 gchar *content = NULL;
2039 gchar *autoextend = "";
2040 gchar *content_type = "";
2041 struct sip_dialog *dialog;
2042 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2043 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2045 if (sbuddy) sbuddy->just_added = FALSE;
2047 if (sip->ocs2007) {
2048 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2049 } else {
2050 autoextend = "Supported: com.microsoft.autoextend\r\n";
2053 request = g_strdup_printf(
2054 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2055 "Supported: ms-piggyback-first-notify\r\n"
2056 "%s%sSupported: ms-benotify\r\n"
2057 "Proxy-Require: ms-benotify\r\n"
2058 "Event: presence\r\n"
2059 "Contact: %s\r\n", autoextend, content_type, tmp);
2061 if (sip->ocs2007) {
2062 content = g_strdup_printf(
2063 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2064 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2065 "<resource uri=\"%s\"%s\n"
2066 "</adhocList>\n"
2067 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2068 "<category name=\"calendarData\"/>\n"
2069 "<category name=\"contactCard\"/>\n"
2070 "<category name=\"note\"/>\n"
2071 "<category name=\"state\"/>\n"
2072 "</categoryList>\n"
2073 "</action>\n"
2074 "</batchSub>", sip->username, to, context);
2077 g_free(tmp);
2079 /* subscribe to buddy presence */
2080 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2081 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2082 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2083 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2085 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2087 g_free(content);
2088 g_free(to);
2089 g_free(request);
2090 g_free(key);
2093 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2095 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
2097 if (!purple_status_is_active(status))
2098 return;
2100 if (account->gc) {
2101 struct sipe_account_data *sip = account->gc->proto_data;
2103 if (sip) {
2104 gchar *action_name;
2105 gchar *tmp;
2106 time_t now = time(NULL);
2107 const char *status_id = purple_status_get_id(status);
2108 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2109 sipe_activity activity = sipe_get_activity_by_token(status_id);
2110 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2112 /* when other point of presence clears note, but we are keeping
2113 * state if OOF note.
2115 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2116 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
2117 do_not_publish = FALSE;
2120 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
2121 status_id, (int)sip->do_not_publish[activity], (int)now);
2123 sip->do_not_publish[activity] = 0;
2124 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
2125 status_id, (int)sip->do_not_publish[activity]);
2127 if (do_not_publish)
2129 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
2130 return;
2133 g_free(sip->status);
2134 sip->status = g_strdup(status_id);
2136 /* hack to escape apostrof before comparison */
2137 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
2139 /* this will preserve OOF flag as well */
2140 if (!sipe_strequal(tmp, sip->note)) {
2141 sip->is_oof_note = FALSE;
2142 g_free(sip->note);
2143 sip->note = g_strdup(note);
2144 sip->note_since = time(NULL);
2146 g_free(tmp);
2148 /* schedule 2 sec to capture idle flag */
2149 action_name = g_strdup_printf("<%s>", "+set-status");
2150 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2151 g_free(action_name);
2155 static void
2156 sipe_set_idle(PurpleConnection * gc,
2157 int interval)
2159 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
2161 if (gc) {
2162 struct sipe_account_data *sip = gc->proto_data;
2164 if (sip) {
2165 sip->idle_switch = time(NULL);
2166 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2171 static void
2172 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2173 SIPE_UNUSED_PARAMETER const char *alias)
2175 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2176 sipe_group_set_user(sip, name);
2179 static void
2180 sipe_group_buddy(PurpleConnection *gc,
2181 const char *who,
2182 const char *old_group_name,
2183 const char *new_group_name)
2185 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2186 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2187 struct sipe_group * old_group = NULL;
2188 struct sipe_group * new_group;
2190 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
2191 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2193 if(!buddy) { // buddy not in roaming list
2194 return;
2197 if (old_group_name) {
2198 old_group = sipe_group_find_by_name(sip, old_group_name);
2200 new_group = sipe_group_find_by_name(sip, new_group_name);
2202 if (old_group) {
2203 buddy->groups = g_slist_remove(buddy->groups, old_group);
2204 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
2207 if (!new_group) {
2208 sipe_group_create(sip, new_group_name, who);
2209 } else {
2210 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2211 sipe_group_set_user(sip, who);
2215 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2217 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2219 /* libpurple can call us with undefined buddy or group */
2220 if (buddy && group) {
2221 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2223 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2224 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2225 purple_blist_rename_buddy(buddy, buddy_name);
2226 g_free(buddy_name);
2228 /* Prepend sip: if needed */
2229 if (!g_str_has_prefix(buddy->name, "sip:")) {
2230 gchar *buf = sip_uri_from_name(buddy->name);
2231 purple_blist_rename_buddy(buddy, buf);
2232 g_free(buf);
2235 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2236 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2237 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
2238 b->name = g_strdup(buddy->name);
2239 b->just_added = TRUE;
2240 g_hash_table_insert(sip->buddies, b->name, b);
2241 sipe_group_buddy(gc, b->name, NULL, group->name);
2242 /* @TODO should go to callback */
2243 sipe_subscribe_presence_single(sip, b->name);
2244 } else {
2245 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
2250 static void sipe_free_buddy(struct sipe_buddy *buddy)
2252 #ifndef _WIN32
2254 * We are calling g_hash_table_foreach_steal(). That means that no
2255 * key/value deallocation functions are called. Therefore the glib
2256 * hash code does not touch the key (buddy->name) or value (buddy)
2257 * of the to-be-deleted hash node at all. It follows that we
2259 * - MUST free the memory for the key ourselves and
2260 * - ARE allowed to do it in this function
2262 * Conclusion: glib must be broken on the Windows platform if sipe
2263 * crashes with SIGTRAP when closing. You'll have to live
2264 * with the memory leak until this is fixed.
2266 g_free(buddy->name);
2267 #endif
2268 g_free(buddy->activity);
2269 g_free(buddy->meeting_subject);
2270 g_free(buddy->meeting_location);
2271 g_free(buddy->note);
2273 g_free(buddy->cal_start_time);
2274 g_free(buddy->cal_free_busy_base64);
2275 g_free(buddy->cal_free_busy);
2276 g_free(buddy->last_non_cal_activity);
2278 sipe_cal_free_working_hours(buddy->cal_working_hours);
2280 g_free(buddy->device_name);
2281 g_slist_free(buddy->groups);
2282 g_free(buddy);
2286 * Unassociates buddy from group first.
2287 * Then see if no groups left, removes buddy completely.
2288 * Otherwise updates buddy groups on server.
2290 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2292 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2293 struct sipe_buddy *b;
2294 struct sipe_group *g = NULL;
2296 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2297 if (!buddy) return;
2299 b = g_hash_table_lookup(sip->buddies, buddy->name);
2300 if (!b) return;
2302 if (group) {
2303 g = sipe_group_find_by_name(sip, group->name);
2306 if (g) {
2307 b->groups = g_slist_remove(b->groups, g);
2308 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
2311 if (g_slist_length(b->groups) < 1) {
2312 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2313 sipe_cancel_scheduled_action(sip, action_name);
2314 g_free(action_name);
2316 g_hash_table_remove(sip->buddies, buddy->name);
2318 if (b->name) {
2319 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2320 send_soap_request(sip, body);
2321 g_free(body);
2324 sipe_free_buddy(b);
2325 } else {
2326 //updates groups on server
2327 sipe_group_set_user(sip, b->name);
2332 static void
2333 sipe_rename_group(PurpleConnection *gc,
2334 const char *old_name,
2335 PurpleGroup *group,
2336 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2338 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2339 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2340 if (s_group) {
2341 sipe_group_rename(sip, s_group, group->name);
2342 } else {
2343 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
2347 static void
2348 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2350 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2351 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2352 if (s_group) {
2353 gchar *body;
2354 SIPE_DEBUG_INFO("Deleting group %s", group->name);
2355 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2356 send_soap_request(sip, body);
2357 g_free(body);
2359 sip->groups = g_slist_remove(sip->groups, s_group);
2360 g_free(s_group->name);
2361 g_free(s_group);
2362 } else {
2363 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
2367 /** All statuses need message attribute to pass Note */
2368 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2370 PurpleStatusType *type;
2371 GList *types = NULL;
2373 /* Macros to reduce code repetition.
2374 Translators: noun */
2375 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2376 prim, id, name, \
2377 TRUE, user, FALSE, \
2378 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2379 NULL); \
2380 types = g_list_append(types, type);
2382 /* Online */
2383 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2384 NULL,
2385 NULL,
2386 TRUE);
2388 /* Busy */
2389 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2390 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2391 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2392 TRUE);
2394 /* Do Not Disturb */
2395 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2396 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2397 NULL,
2398 TRUE);
2400 /* Away */
2401 /* Goes first in the list as
2402 * purple picks the first status with the AWAY type
2403 * for idle.
2405 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2406 NULL,
2407 NULL,
2408 TRUE);
2410 /* Be Right Back */
2411 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2412 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2413 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2414 TRUE);
2416 /* Appear Offline */
2417 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2418 NULL,
2419 NULL,
2420 TRUE);
2422 /* Offline */
2423 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2424 NULL,
2425 NULL,
2426 TRUE);
2427 types = g_list_append(types, type);
2429 return types;
2433 * A callback for g_hash_table_foreach
2435 static void
2436 sipe_buddy_subscribe_cb(char *buddy_name,
2437 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2438 struct sipe_account_data *sip)
2440 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2441 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2442 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2443 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2445 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2446 g_free(action_name);
2450 * Removes entries from purple buddy list
2451 * that does not correspond ones in the roaming contact list.
2453 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2454 GSList *buddies = purple_find_buddies(sip->account, NULL);
2455 GSList *entry = buddies;
2456 struct sipe_buddy *buddy;
2457 PurpleBuddy *b;
2458 PurpleGroup *g;
2460 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
2461 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sip->buddies));
2462 while (entry) {
2463 b = entry->data;
2464 g = purple_buddy_get_group(b);
2465 buddy = g_hash_table_lookup(sip->buddies, b->name);
2466 if(buddy) {
2467 gboolean in_sipe_groups = FALSE;
2468 GSList *entry2 = buddy->groups;
2469 while (entry2) {
2470 struct sipe_group *group = entry2->data;
2471 if (sipe_strequal(group->name, g->name)) {
2472 in_sipe_groups = TRUE;
2473 break;
2475 entry2 = entry2->next;
2477 if(!in_sipe_groups) {
2478 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
2479 purple_blist_remove_buddy(b);
2481 } else {
2482 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
2483 purple_blist_remove_buddy(b);
2485 entry = entry->next;
2487 g_slist_free(buddies);
2490 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2492 int len = msg->bodylen;
2494 const gchar *tmp = sipmsg_find_header(msg, "Event");
2495 xmlnode *item;
2496 xmlnode *isc;
2497 const gchar *contacts_delta;
2498 xmlnode *group_node;
2499 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2500 return FALSE;
2503 /* Convert the contact from XML to Purple Buddies */
2504 isc = xmlnode_from_str(msg->body, len);
2505 if (!isc) {
2506 return FALSE;
2509 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2510 if (contacts_delta) {
2511 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2514 if (sipe_strequal(isc->name, "contactList")) {
2516 /* Parse groups */
2517 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2518 struct sipe_group * group = g_new0(struct sipe_group, 1);
2519 const char *name = xmlnode_get_attrib(group_node, "name");
2521 if (g_str_has_prefix(name, "~")) {
2522 name = _("Other Contacts");
2524 group->name = g_strdup(name);
2525 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2527 sipe_group_add(sip, group);
2530 // Make sure we have at least one group
2531 if (g_slist_length(sip->groups) == 0) {
2532 struct sipe_group * group = g_new0(struct sipe_group, 1);
2533 PurpleGroup *purple_group;
2534 group->name = g_strdup(_("Other Contacts"));
2535 group->id = 1;
2536 purple_group = purple_group_new(group->name);
2537 purple_blist_add_group(purple_group, NULL);
2538 sip->groups = g_slist_append(sip->groups, group);
2541 /* Parse contacts */
2542 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2543 const gchar *uri = xmlnode_get_attrib(item, "uri");
2544 const gchar *name = xmlnode_get_attrib(item, "name");
2545 gchar *buddy_name;
2546 struct sipe_buddy *buddy = NULL;
2547 gchar *tmp;
2548 gchar **item_groups;
2549 int i = 0;
2551 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2552 tmp = sip_uri_from_name(uri);
2553 buddy_name = g_ascii_strdown(tmp, -1);
2554 g_free(tmp);
2556 /* assign to group Other Contacts if nothing else received */
2557 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2558 if(is_empty(tmp)) {
2559 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2560 g_free(tmp);
2561 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2563 item_groups = g_strsplit(tmp, " ", 0);
2564 g_free(tmp);
2566 while (item_groups[i]) {
2567 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2569 // If couldn't find the right group for this contact, just put them in the first group we have
2570 if (group == NULL && g_slist_length(sip->groups) > 0) {
2571 group = sip->groups->data;
2574 if (group != NULL) {
2575 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2576 if (!b){
2577 b = purple_buddy_new(sip->account, buddy_name, uri);
2578 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2580 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
2583 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2584 if (name != NULL && strlen(name) != 0) {
2585 purple_blist_alias_buddy(b, name);
2587 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
2591 if (!buddy) {
2592 buddy = g_new0(struct sipe_buddy, 1);
2593 buddy->name = g_strdup(b->name);
2594 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2597 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2599 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
2600 } else {
2601 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2602 name);
2605 i++;
2606 } // while, contact groups
2607 g_strfreev(item_groups);
2608 g_free(buddy_name);
2610 } // for, contacts
2612 sipe_cleanup_local_blist(sip);
2614 /* Add self-contact if not there yet. 2005 systems. */
2615 /* This will resemble subscription to roaming_self in 2007 systems */
2616 if (!sip->ocs2007) {
2617 gchar *self_uri = sip_uri_self(sip);
2618 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2620 if (!buddy) {
2621 buddy = g_new0(struct sipe_buddy, 1);
2622 buddy->name = g_strdup(self_uri);
2623 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2625 g_free(self_uri);
2628 xmlnode_free(isc);
2630 /* subscribe to buddies */
2631 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2632 if (sip->batched_support) {
2633 sipe_subscribe_presence_batched(sip, NULL);
2634 } else {
2635 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2637 sip->subscribed_buddies = TRUE;
2639 /* for 2005 systems schedule contacts' status update
2640 * based on their calendar information
2642 if (!sip->ocs2007) {
2643 sipe_sched_calendar_status_update(sip, time(NULL));
2646 return 0;
2650 * Subscribe roaming contacts
2652 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2654 gchar *to = sip_uri_self(sip);
2655 gchar *tmp = get_contact(sip);
2656 gchar *hdr = g_strdup_printf(
2657 "Event: vnd-microsoft-roaming-contacts\r\n"
2658 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2659 "Supported: com.microsoft.autoextend\r\n"
2660 "Supported: ms-benotify\r\n"
2661 "Proxy-Require: ms-benotify\r\n"
2662 "Supported: ms-piggyback-first-notify\r\n"
2663 "Contact: %s\r\n", tmp);
2664 g_free(tmp);
2666 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2667 g_free(to);
2668 g_free(hdr);
2671 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2672 SIPE_UNUSED_PARAMETER void *unused)
2674 gchar *key;
2675 struct sip_dialog *dialog;
2676 gchar *to = sip_uri_self(sip);
2677 gchar *tmp = get_contact(sip);
2678 gchar *hdr = g_strdup_printf(
2679 "Event: presence.wpending\r\n"
2680 "Accept: text/xml+msrtc.wpending\r\n"
2681 "Supported: com.microsoft.autoextend\r\n"
2682 "Supported: ms-benotify\r\n"
2683 "Proxy-Require: ms-benotify\r\n"
2684 "Supported: ms-piggyback-first-notify\r\n"
2685 "Contact: %s\r\n", tmp);
2686 g_free(tmp);
2688 /* Subscription is identified by <event> key */
2689 key = g_strdup_printf("<%s>", "presence.wpending");
2690 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2691 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2693 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2695 g_free(to);
2696 g_free(hdr);
2697 g_free(key);
2701 * Fires on deregistration event initiated by server.
2702 * [MS-SIPREGE] SIP extension.
2705 // 2007 Example
2707 // Content-Type: text/registration-event
2708 // subscription-state: terminated;expires=0
2709 // ms-diagnostics-public: 4141;reason="User disabled"
2711 // deregistered;event=rejected
2713 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2715 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2716 gchar *event = NULL;
2717 gchar *reason = NULL;
2718 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2719 gchar *warning;
2721 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2722 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2724 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2725 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2726 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2727 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2728 } else {
2729 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2730 return;
2733 if (diagnostics != NULL) {
2734 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2735 } else { // for LCS2005
2736 int error_id = 0;
2737 if (event && sipe_strcase_equal(event, "unregistered")) {
2738 error_id = 4140; // [MS-SIPREGE]
2739 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2740 reason = g_strdup(_("you are already signed in at another location"));
2741 } else if (event && sipe_strcase_equal(event, "rejected")) {
2742 error_id = 4141;
2743 reason = g_strdup(_("user disabled")); // [MS-OCER]
2744 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2745 error_id = 4142;
2746 reason = g_strdup(_("user moved")); // [MS-OCER]
2749 g_free(event);
2750 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2751 g_free(reason);
2753 sip->gc->wants_to_die = TRUE;
2754 purple_connection_error(sip->gc, warning);
2755 g_free(warning);
2759 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2761 xmlnode *xn_provision_group_list;
2762 xmlnode *node;
2764 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2766 /* provisionGroup */
2767 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2768 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2769 g_free(sip->focus_factory_uri);
2770 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2771 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2772 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2773 break;
2776 xmlnode_free(xn_provision_group_list);
2779 /** for 2005 system */
2780 static void
2781 sipe_process_provisioning(struct sipe_account_data *sip,
2782 struct sipmsg *msg)
2784 xmlnode *xn_provision;
2785 xmlnode *node;
2787 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2788 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2789 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", xmlnode_get_attrib(node, "uri"));
2790 if ((node = xmlnode_get_child(node, "line"))) {
2791 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2792 const gchar *server = xmlnode_get_attrib(node, "server");
2793 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2794 sip_csta_open(sip, line_uri, server);
2797 xmlnode_free(xn_provision);
2800 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2802 const gchar *contacts_delta;
2803 xmlnode *xml;
2805 xml = xmlnode_from_str(msg->body, msg->bodylen);
2806 if (!xml)
2808 return;
2811 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2812 if (contacts_delta)
2814 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2817 xmlnode_free(xml);
2820 static void
2821 free_container(struct sipe_container *container)
2823 GSList *entry;
2825 if (!container) return;
2827 entry = container->members;
2828 while (entry) {
2829 void *data = entry->data;
2830 entry = g_slist_remove(entry, data);
2831 g_free(data);
2833 g_free(container);
2836 static void
2837 sipe_send_set_container_members(struct sipe_account_data *sip,
2838 guint container_id,
2839 guint container_version,
2840 const gchar* action,
2841 const gchar* type,
2842 const gchar* value)
2844 gchar *self = sip_uri_self(sip);
2845 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2846 gchar *contact;
2847 gchar *hdr;
2848 gchar *body = g_strdup_printf(
2849 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2850 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2851 "</setContainerMembers>",
2852 container_id,
2853 container_version,
2854 action,
2855 type,
2856 value_str);
2857 g_free(value_str);
2859 contact = get_contact(sip);
2860 hdr = g_strdup_printf("Contact: %s\r\n"
2861 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2862 g_free(contact);
2864 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2866 g_free(hdr);
2867 g_free(body);
2868 g_free(self);
2872 * Finds locally stored MS-PRES container member
2874 static struct sipe_container_member *
2875 sipe_find_container_member(struct sipe_container *container,
2876 const gchar *type,
2877 const gchar *value)
2879 struct sipe_container_member *member;
2880 GSList *entry;
2882 if (container == NULL || type == NULL) {
2883 return NULL;
2886 entry = container->members;
2887 while (entry) {
2888 member = entry->data;
2889 if (sipe_strcase_equal(member->type, type) &&
2890 sipe_strcase_equal(member->value, value))
2892 return member;
2894 entry = entry->next;
2896 return NULL;
2900 * Finds locally stored MS-PRES container by id
2902 static struct sipe_container *
2903 sipe_find_container(struct sipe_account_data *sip,
2904 guint id)
2906 struct sipe_container *container;
2907 GSList *entry;
2909 if (sip == NULL) {
2910 return NULL;
2913 entry = sip->containers;
2914 while (entry) {
2915 container = entry->data;
2916 if (id == container->id) {
2917 return container;
2919 entry = entry->next;
2921 return NULL;
2924 /**
2925 * Returns pointer to domain part in provided Email URL
2927 * @param email an email URL. Example: first.last@hq.company.com
2928 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
2930 * Doesn't allocates memory
2932 static const char *
2933 sipe_get_domain(const char *email)
2935 char *tmp;
2937 if (!email) return NULL;
2939 tmp = strstr(email, "@");
2941 if (tmp && ((tmp+1) < (email + strlen(email)))) {
2942 return tmp+1;
2943 } else {
2944 return NULL;
2949 /* @TODO: replace with binary search for faster access? */
2950 /** source: http://support.microsoft.com/kb/897567 */
2951 static const char * const public_domains [] = {
2952 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
2953 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
2954 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
2955 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
2956 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
2957 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
2958 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
2959 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
2960 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
2961 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
2962 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
2963 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
2964 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
2965 "yahoo.com",
2966 NULL};
2968 static gboolean
2969 sipe_is_public_domain(const char *domain)
2971 int i = 0;
2972 while (public_domains[i]) {
2973 if (sipe_strcase_equal(public_domains[i], domain)) {
2974 return TRUE;
2976 i++;
2978 return FALSE;
2982 * Access Levels
2983 * 32000 - Blocked
2984 * 400 - Personal
2985 * 300 - Team
2986 * 200 - Company
2987 * 100 - Public
2989 static const char *
2990 sipe_get_access_level_name(int container_id)
2992 switch(container_id) {
2993 case 32000: return _("Blocked");
2994 case 400: return _("Personal");
2995 case 300: return _("Team");
2996 case 200: return _("Company");
2997 case 100: return _("Public");
2999 return _("Unknown");
3002 static const guint containers[] = {32000, 400, 300, 200, 100};
3003 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
3005 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
3006 static int
3007 sipe_find_access_level(struct sipe_account_data *sip,
3008 const gchar *type,
3009 const gchar *value)
3011 unsigned int i = 0;
3013 for (i = 0; i < CONTAINERS_LEN; i++) {
3014 struct sipe_container_member *member;
3015 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3016 if (!container) continue;
3018 if (sipe_strequal("user", type)) {
3019 const char *domain;
3020 const char *sip_uri = value;
3022 member = sipe_find_container_member(container, "user", sip_uri);
3023 if (member) return containers[i];
3025 domain = sipe_get_domain(sip_uri);
3026 member = sipe_find_container_member(container, "domain", domain);
3027 if (member) {
3028 return containers[i];
3031 member = sipe_find_container_member(container, "sameEnterprise", NULL);
3032 if (member &&
3033 sipe_strcase_equal(sip->sipdomain, domain))
3035 return containers[i];
3038 member = sipe_find_container_member(container, "publicCloud", NULL);
3039 if (member && sipe_is_public_domain(domain))
3041 return containers[i];
3044 member = sipe_find_container_member(container, "everyone", NULL);
3045 if (member)
3047 return containers[i];
3049 } else {
3050 member = sipe_find_container_member(container, type, value);
3051 if (member) {
3052 return containers[i];
3057 return -1;
3060 static void
3061 sipe_change_access_level(struct sipe_account_data *sip,
3062 const int container_id, /* new access level*/
3063 const gchar *type,
3064 const gchar *value)
3066 unsigned int i;
3067 int current_container_id = -1;
3069 /* for each container: find/delete */
3070 for (i = 0; i < CONTAINERS_LEN; i++) {
3071 struct sipe_container_member *member;
3072 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3074 if (!container) continue;
3076 member = sipe_find_container_member(container, type, value);
3077 if (member) {
3078 current_container_id = containers[i];
3079 /* delete/publish current access level */
3080 if (container_id != current_container_id) {
3081 sipe_send_set_container_members(
3082 sip, current_container_id, container->version, "delete", type, value);
3087 /* assign/publish new access level */
3088 if (container_id != current_container_id) {
3089 struct sipe_container *container = sipe_find_container(sip, container_id);
3090 guint version = container ? container->version : 0;
3092 sipe_send_set_container_members(sip, container_id, version, "add", type, value);
3096 static void
3097 free_publication(struct sipe_publication *publication)
3099 g_free(publication->category);
3100 g_free(publication->cal_event_hash);
3101 g_free(publication->note);
3103 g_free(publication->working_hours_xml_str);
3104 g_free(publication->fb_start_str);
3105 g_free(publication->free_busy_base64);
3107 g_free(publication);
3110 /* key is <category><instance><container> */
3111 static gboolean
3112 sipe_is_our_publication(struct sipe_account_data *sip,
3113 const gchar *key)
3115 GSList *entry;
3117 /* filling keys for our publications if not yet cached */
3118 if (!sip->our_publication_keys) {
3119 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
3120 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
3121 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
3122 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
3123 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
3124 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
3125 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
3127 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
3128 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
3129 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
3130 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
3131 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
3132 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
3133 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
3134 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
3135 SIPE_DEBUG_INFO("\tNote : %u", 0);
3136 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
3138 /* device */
3139 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3140 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
3142 /* state:machineState */
3143 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3144 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
3145 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3146 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3148 /* state:userState */
3149 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3150 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3151 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3152 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3154 /* state:calendarState */
3155 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3156 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3157 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3158 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3160 /* state:calendarState OOF */
3161 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3162 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3163 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3164 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3166 /* note */
3167 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3168 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3169 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3170 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3171 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3172 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3174 /* note OOF */
3175 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3176 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3177 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3178 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3179 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3180 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3182 /* calendarData:WorkingHours */
3183 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3184 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3185 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3186 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3187 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3188 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3189 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3190 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3191 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3192 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3193 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3194 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3196 /* calendarData:FreeBusy */
3197 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3198 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3199 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3200 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3201 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3202 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3203 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3204 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3205 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3206 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3207 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3208 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3210 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3211 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3214 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3216 entry = sip->our_publication_keys;
3217 while (entry) {
3218 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3219 if (sipe_strequal(entry->data, key)) {
3220 return TRUE;
3222 entry = entry->next;
3224 return FALSE;
3227 /** Property names to store in blist.xml */
3228 #define ALIAS_PROP "alias"
3229 #define EMAIL_PROP "email"
3230 #define PHONE_PROP "phone"
3231 #define PHONE_DISPLAY_PROP "phone-display"
3232 #define PHONE_MOBILE_PROP "phone-mobile"
3233 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3234 #define PHONE_HOME_PROP "phone-home"
3235 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3236 #define PHONE_OTHER_PROP "phone-other"
3237 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3238 #define PHONE_CUSTOM1_PROP "phone-custom1"
3239 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3240 #define SITE_PROP "site"
3241 #define COMPANY_PROP "company"
3242 #define DEPARTMENT_PROP "department"
3243 #define TITLE_PROP "title"
3244 #define OFFICE_PROP "office"
3245 /** implies work address */
3246 #define ADDRESS_STREET_PROP "address-street"
3247 #define ADDRESS_CITY_PROP "address-city"
3248 #define ADDRESS_STATE_PROP "address-state"
3249 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3250 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3253 * Tries to figure out user first and last name
3254 * based on Display Name and email properties.
3256 * Allocates memory - must be g_free()'d
3258 * Examples to parse:
3259 * First Last
3260 * First Last - Company Name
3261 * Last, First
3262 * Last, First M.
3263 * Last, First (C)(STP) (Company)
3264 * first.last@company.com (preprocessed as "first last")
3265 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3267 * Unusable examples:
3268 * user@company.com (preprocessed as "user")
3269 * first.m.last@company.com (preprocessed as "first m last")
3270 * user.company.com@reuters.net (preprocessed as "user company com")
3272 static void
3273 sipe_get_first_last_names(struct sipe_account_data *sip,
3274 const char *uri,
3275 char **first_name,
3276 char **last_name)
3278 PurpleBuddy *p_buddy;
3279 char *display_name;
3280 const char *email;
3281 const char *first, *last;
3282 char *tmp;
3283 char **parts;
3284 gboolean has_comma = FALSE;
3286 if (!sip || !uri) return;
3288 p_buddy = purple_find_buddy(sip->account, uri);
3290 if (!p_buddy) return;
3292 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3293 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3295 if (!display_name && !email) return;
3297 /* if no display name, make "first last anything_else" out of email */
3298 if (email && !display_name) {
3299 display_name = g_strndup(email, strstr(email, "@") - email);
3300 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
3301 g_free(tmp);
3304 if (display_name) {
3305 has_comma = (strstr(display_name, ",") != NULL);
3306 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
3307 g_free(tmp);
3308 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
3309 g_free(tmp);
3312 parts = g_strsplit(display_name, " ", 0);
3314 if (!parts[0] || !parts[1]) {
3315 g_free(display_name);
3316 g_strfreev(parts);
3317 return;
3320 if (has_comma) {
3321 last = parts[0];
3322 first = parts[1];
3323 } else {
3324 first = parts[0];
3325 last = parts[1];
3328 if (first_name) {
3329 *first_name = g_strstrip(g_strdup(first));
3332 if (last_name) {
3333 *last_name = g_strstrip(g_strdup(last));
3336 g_free(display_name);
3337 g_strfreev(parts);
3341 * Update user information
3343 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3344 * @param property_name
3345 * @param property_value may be modified to strip white space
3347 static void
3348 sipe_update_user_info(struct sipe_account_data *sip,
3349 const char *uri,
3350 const char *property_name,
3351 char *property_value)
3353 GSList *buddies, *entry;
3355 if (!property_name || strlen(property_name) == 0) return;
3357 if (property_value)
3358 property_value = g_strstrip(property_value);
3360 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3361 while (entry) {
3362 const char *prop_str;
3363 const char *server_alias;
3364 PurpleBuddy *p_buddy = entry->data;
3366 /* for Display Name */
3367 if (sipe_strequal(property_name, ALIAS_PROP)) {
3368 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3369 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
3370 purple_blist_alias_buddy(p_buddy, property_value);
3373 server_alias = purple_buddy_get_server_alias(p_buddy);
3374 if (!is_empty(property_value) &&
3375 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3377 purple_blist_server_alias_buddy(p_buddy, property_value);
3380 /* for other properties */
3381 else {
3382 if (!is_empty(property_value)) {
3383 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3384 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3385 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3390 entry = entry->next;
3392 g_slist_free(buddies);
3396 * Update user phone
3397 * Suitable for both 2005 and 2007 systems.
3399 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3400 * @param phone_type
3401 * @param phone may be modified to strip white space
3402 * @param phone_display_string may be modified to strip white space
3404 static void
3405 sipe_update_user_phone(struct sipe_account_data *sip,
3406 const char *uri,
3407 const gchar *phone_type,
3408 gchar *phone,
3409 gchar *phone_display_string)
3411 const char *phone_node = PHONE_PROP; /* work phone by default */
3412 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3414 if(!phone || strlen(phone) == 0) return;
3416 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3417 phone_node = PHONE_MOBILE_PROP;
3418 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3419 } else if (sipe_strequal(phone_type, "home")) {
3420 phone_node = PHONE_HOME_PROP;
3421 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3422 } else if (sipe_strequal(phone_type, "other")) {
3423 phone_node = PHONE_OTHER_PROP;
3424 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3425 } else if (sipe_strequal(phone_type, "custom1")) {
3426 phone_node = PHONE_CUSTOM1_PROP;
3427 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3430 sipe_update_user_info(sip, uri, phone_node, phone);
3431 if (phone_display_string) {
3432 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3436 static void
3437 sipe_update_calendar(struct sipe_account_data *sip)
3439 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3441 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: started.");
3443 if (sipe_strequal(calendar, "EXCH")) {
3444 sipe_ews_update_calendar(sip);
3447 /* schedule repeat */
3448 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3450 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: finished.");
3454 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3455 * by using standard Purple's means of signals and saved statuses.
3457 * Thus all UI elements get updated: Status Button with Note, docklet.
3458 * This is ablolutely important as both our status and note can come
3459 * inbound (roaming) or be updated programmatically (e.g. based on our
3460 * calendar data).
3462 static void
3463 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3464 const char *status_id,
3465 const char *message,
3466 time_t do_not_publish[])
3468 PurpleStatus *status = purple_account_get_active_status(account);
3469 gboolean changed = TRUE;
3471 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3472 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3474 changed = FALSE;
3477 if (purple_savedstatus_is_idleaway()) {
3478 changed = FALSE;
3481 if (changed) {
3482 PurpleSavedStatus *saved_status;
3483 const PurpleStatusType *acct_status_type =
3484 purple_status_type_find_with_id(account->status_types, status_id);
3485 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3486 sipe_activity activity = sipe_get_activity_by_token(status_id);
3488 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3489 if (saved_status) {
3490 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3493 /* If this type+message is unique then create a new transient saved status
3494 * Ref: gtkstatusbox.c
3496 if (!saved_status) {
3497 GList *tmp;
3498 GList *active_accts = purple_accounts_get_all_active();
3500 saved_status = purple_savedstatus_new(NULL, primitive);
3501 purple_savedstatus_set_message(saved_status, message);
3503 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3504 purple_savedstatus_set_substatus(saved_status,
3505 (PurpleAccount *)tmp->data, acct_status_type, message);
3507 g_list_free(active_accts);
3510 do_not_publish[activity] = time(NULL);
3511 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3512 status_id, (int)do_not_publish[activity]);
3514 /* Set the status for each account */
3515 purple_savedstatus_activate(saved_status);
3519 struct hash_table_delete_payload {
3520 GHashTable *hash_table;
3521 guint container;
3524 static void
3525 sipe_remove_category_container_publications_cb(const char *name,
3526 struct sipe_publication *publication,
3527 struct hash_table_delete_payload *payload)
3529 if (publication->container == payload->container) {
3530 g_hash_table_remove(payload->hash_table, name);
3533 static void
3534 sipe_remove_category_container_publications(GHashTable *our_publications,
3535 const char *category,
3536 guint container)
3538 struct hash_table_delete_payload payload;
3539 payload.hash_table = g_hash_table_lookup(our_publications, category);
3541 if (!payload.hash_table) return;
3543 payload.container = container;
3544 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3547 static void
3548 send_publish_category_initial(struct sipe_account_data *sip);
3551 * When we receive some self (BE) NOTIFY with a new subscriber
3552 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3555 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3557 gchar *contact;
3558 gchar *to;
3559 xmlnode *xml;
3560 xmlnode *node;
3561 xmlnode *node2;
3562 char *display_name = NULL;
3563 char *uri;
3564 GSList *category_names = NULL;
3565 int aggreg_avail = 0;
3566 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3567 gboolean do_update_status = FALSE;
3568 gboolean has_note_cleaned = FALSE;
3570 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3572 xml = xmlnode_from_str(msg->body, msg->bodylen);
3573 if (!xml) return;
3575 contact = get_contact(sip);
3576 to = sip_uri_self(sip);
3579 /* categories */
3580 /* set list of categories participating in this XML */
3581 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3582 const gchar *name = xmlnode_get_attrib(node, "name");
3583 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3585 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3586 category_names ? (int) g_slist_length(category_names) : -1);
3587 /* drop category information */
3588 if (category_names) {
3589 GSList *entry = category_names;
3590 while (entry) {
3591 GHashTable *cat_publications;
3592 const gchar *category = entry->data;
3593 entry = entry->next;
3594 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
3595 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3596 if (cat_publications) {
3597 g_hash_table_remove(sip->our_publications, category);
3598 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
3602 g_slist_free(category_names);
3603 /* filling our categories reflected in roaming data */
3604 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3605 const char *tmp;
3606 const gchar *name = xmlnode_get_attrib(node, "name");
3607 guint container = xmlnode_get_int_attrib(node, "container", -1);
3608 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3609 guint version = xmlnode_get_int_attrib(node, "version", 0);
3610 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3611 sipe_utils_str_to_time(tmp) : 0;
3612 gchar *key;
3613 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3615 /* Ex. clear note: <category name="note"/> */
3616 if (container == (guint)-1) {
3617 g_free(sip->note);
3618 sip->note = NULL;
3619 do_update_status = TRUE;
3620 continue;
3623 /* Ex. clear note: <category name="note" container="200"/> */
3624 if (instance == (guint)-1) {
3625 if (container == 200) {
3626 g_free(sip->note);
3627 sip->note = NULL;
3628 do_update_status = TRUE;
3630 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
3631 sipe_remove_category_container_publications(
3632 sip->our_publications, name, container);
3633 continue;
3636 /* key is <category><instance><container> */
3637 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3638 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
3640 /* capture all userState publication for later clean up if required */
3641 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3642 xmlnode *xn_state = xmlnode_get_child(node, "state");
3644 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3645 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3646 publication->category = g_strdup(name);
3647 publication->instance = instance;
3648 publication->container = container;
3649 publication->version = version;
3651 if (!sip->user_state_publications) {
3652 sip->user_state_publications = g_hash_table_new_full(
3653 g_str_hash, g_str_equal,
3654 g_free, (GDestroyNotify)free_publication);
3656 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3657 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3658 key, version);
3662 if (sipe_is_our_publication(sip, key)) {
3663 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3665 publication->category = g_strdup(name);
3666 publication->instance = instance;
3667 publication->container = container;
3668 publication->version = version;
3670 /* filling publication->availability */
3671 if (sipe_strequal(name, "state")) {
3672 xmlnode *xn_state = xmlnode_get_child(node, "state");
3673 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3675 if (xn_avail) {
3676 gchar *avail_str = xmlnode_get_data(xn_avail);
3677 if (avail_str) {
3678 publication->availability = atoi(avail_str);
3680 g_free(avail_str);
3682 /* for calendarState */
3683 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3684 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3685 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3687 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3688 if (xn_activity) {
3689 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3690 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3692 event->is_meeting = TRUE;
3695 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3696 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3698 publication->cal_event_hash = sipe_cal_event_hash(event);
3699 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3700 publication->cal_event_hash);
3701 sipe_cal_event_free(event);
3704 /* filling publication->note */
3705 if (sipe_strequal(name, "note")) {
3706 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3708 if (!has_note_cleaned) {
3709 has_note_cleaned = TRUE;
3711 g_free(sip->note);
3712 sip->note = NULL;
3713 sip->note_since = publish_time;
3715 do_update_status = TRUE;
3718 g_free(publication->note);
3719 publication->note = NULL;
3720 if (xn_body) {
3721 char *tmp;
3723 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3724 g_free(tmp);
3725 if (publish_time >= sip->note_since) {
3726 g_free(sip->note);
3727 sip->note = g_strdup(publication->note);
3728 sip->note_since = publish_time;
3729 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3731 do_update_status = TRUE;
3736 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3737 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3738 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3739 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3740 if (xn_free_busy) {
3741 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3742 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3744 if (xn_working_hours) {
3745 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3749 if (!cat_publications) {
3750 cat_publications = g_hash_table_new_full(
3751 g_str_hash, g_str_equal,
3752 g_free, (GDestroyNotify)free_publication);
3753 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3754 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3756 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3757 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3759 g_free(key);
3761 /* aggregateState (not an our publication) from 2-nd container */
3762 if (sipe_strequal(name, "state") && container == 2) {
3763 xmlnode *xn_state = xmlnode_get_child(node, "state");
3765 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3766 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3767 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3769 if (xn_avail) {
3770 gchar *avail_str = xmlnode_get_data(xn_avail);
3771 if (avail_str) {
3772 aggreg_avail = atoi(avail_str);
3774 g_free(avail_str);
3777 if (xn_activity) {
3778 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3780 aggreg_activity = sipe_get_activity_by_token(activity_token);
3783 do_update_status = TRUE;
3787 /* userProperties published by server from AD */
3788 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3789 xmlnode *line;
3790 /* line, for Remote Call Control (RCC) */
3791 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3792 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3793 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3794 gchar *line_uri;
3796 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3798 line_uri = xmlnode_get_data(line);
3799 if (line_uri) {
3800 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
3801 sip_csta_open(sip, line_uri, line_server);
3803 g_free(line_uri);
3805 break;
3809 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
3810 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3812 /* containers */
3813 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3814 guint id = xmlnode_get_int_attrib(node, "id", 0);
3815 struct sipe_container *container = sipe_find_container(sip, id);
3817 if (container) {
3818 sip->containers = g_slist_remove(sip->containers, container);
3819 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
3820 free_container(container);
3822 container = g_new0(struct sipe_container, 1);
3823 container->id = id;
3824 container->version = xmlnode_get_int_attrib(node, "version", 0);
3825 sip->containers = g_slist_append(sip->containers, container);
3826 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
3828 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3829 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3830 member->type = xmlnode_get_attrib(node2, "type");
3831 member->value = xmlnode_get_attrib(node2, "value");
3832 container->members = g_slist_append(container->members, member);
3833 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
3834 member->type, member->value ? member->value : "");
3838 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
3839 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3840 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3841 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3842 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
3843 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
3844 /* initial set-up to let counterparties see your status */
3845 if (sameEnterpriseAL < 0) {
3846 struct sipe_container *container = sipe_find_container(sip, 200);
3847 guint version = container ? container->version : 0;
3848 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3850 if (federatedAL < 0) {
3851 struct sipe_container *container = sipe_find_container(sip, 100);
3852 guint version = container ? container->version : 0;
3853 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3855 sip->access_level_set = TRUE;
3858 /* subscribers */
3859 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3860 const char *user;
3861 const char *acknowledged;
3862 gchar *hdr;
3863 gchar *body;
3865 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3866 if (!user) continue;
3867 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
3868 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3869 uri = sip_uri_from_name(user);
3871 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3873 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3874 if(sipe_strcase_equal(acknowledged,"false")){
3875 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
3876 if (!purple_find_buddy(sip->account, uri)) {
3877 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3880 hdr = g_strdup_printf(
3881 "Contact: %s\r\n"
3882 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3884 body = g_strdup_printf(
3885 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3886 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3887 "</setSubscribers>", user);
3889 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3890 g_free(body);
3891 g_free(hdr);
3893 g_free(display_name);
3894 g_free(uri);
3897 g_free(contact);
3898 xmlnode_free(xml);
3900 /* Publish initial state if not yet.
3901 * Assuming this happens on initial responce to subscription to roaming-self
3902 * so we've already updated our roaming data in full.
3903 * Only for 2007+
3905 if (!sip->initial_state_published) {
3906 send_publish_category_initial(sip);
3907 sip->initial_state_published = TRUE;
3908 /* dalayed run */
3909 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3910 do_update_status = FALSE;
3911 } else if (aggreg_avail) {
3913 g_free(sip->status);
3914 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3915 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3916 } else {
3917 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3921 if (do_update_status) {
3922 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
3923 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3926 g_free(to);
3929 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3931 gchar *to = sip_uri_self(sip);
3932 gchar *tmp = get_contact(sip);
3933 gchar *hdr = g_strdup_printf(
3934 "Event: vnd-microsoft-roaming-ACL\r\n"
3935 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3936 "Supported: com.microsoft.autoextend\r\n"
3937 "Supported: ms-benotify\r\n"
3938 "Proxy-Require: ms-benotify\r\n"
3939 "Supported: ms-piggyback-first-notify\r\n"
3940 "Contact: %s\r\n", tmp);
3941 g_free(tmp);
3943 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3944 g_free(to);
3945 g_free(hdr);
3949 * To request for presence information about the user, access level settings that have already been configured by the user
3950 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3951 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3954 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3956 gchar *to = sip_uri_self(sip);
3957 gchar *tmp = get_contact(sip);
3958 gchar *hdr = g_strdup_printf(
3959 "Event: vnd-microsoft-roaming-self\r\n"
3960 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3961 "Supported: ms-benotify\r\n"
3962 "Proxy-Require: ms-benotify\r\n"
3963 "Supported: ms-piggyback-first-notify\r\n"
3964 "Contact: %s\r\n"
3965 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3967 gchar *body=g_strdup(
3968 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3969 "<roaming type=\"categories\"/>"
3970 "<roaming type=\"containers\"/>"
3971 "<roaming type=\"subscribers\"/></roamingList>");
3973 g_free(tmp);
3974 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3975 g_free(body);
3976 g_free(to);
3977 g_free(hdr);
3981 * For 2005 version
3983 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3985 gchar *to = sip_uri_self(sip);
3986 gchar *tmp = get_contact(sip);
3987 gchar *hdr = g_strdup_printf(
3988 "Event: vnd-microsoft-provisioning\r\n"
3989 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3990 "Supported: com.microsoft.autoextend\r\n"
3991 "Supported: ms-benotify\r\n"
3992 "Proxy-Require: ms-benotify\r\n"
3993 "Supported: ms-piggyback-first-notify\r\n"
3994 "Expires: 0\r\n"
3995 "Contact: %s\r\n", tmp);
3997 g_free(tmp);
3998 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3999 g_free(to);
4000 g_free(hdr);
4003 /** Subscription for provisioning information to help with initial
4004 * configuration. This subscription is a one-time query (denoted by the Expires header,
4005 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
4006 * configuration, meeting policies, and policy settings that Communicator must enforce.
4007 * TODO: for what we need this information.
4010 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
4012 gchar *to = sip_uri_self(sip);
4013 gchar *tmp = get_contact(sip);
4014 gchar *hdr = g_strdup_printf(
4015 "Event: vnd-microsoft-provisioning-v2\r\n"
4016 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
4017 "Supported: com.microsoft.autoextend\r\n"
4018 "Supported: ms-benotify\r\n"
4019 "Proxy-Require: ms-benotify\r\n"
4020 "Supported: ms-piggyback-first-notify\r\n"
4021 "Expires: 0\r\n"
4022 "Contact: %s\r\n"
4023 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
4024 gchar *body = g_strdup(
4025 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
4026 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
4027 "<provisioningGroup name=\"ucPolicy\"/>"
4028 "</provisioningGroupList>");
4030 g_free(tmp);
4031 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4032 g_free(body);
4033 g_free(to);
4034 g_free(hdr);
4037 static void
4038 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
4039 gpointer value, gpointer user_data)
4041 struct sip_subscription *subscription = value;
4042 struct sip_dialog *dialog = &subscription->dialog;
4043 struct sipe_account_data *sip = user_data;
4044 gchar *tmp = get_contact(sip);
4045 gchar *hdr = g_strdup_printf(
4046 "Event: %s\r\n"
4047 "Expires: 0\r\n"
4048 "Contact: %s\r\n", subscription->event, tmp);
4049 g_free(tmp);
4051 /* Rate limit to max. 25 requests per seconds */
4052 g_usleep(1000000 / 25);
4054 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
4055 g_free(hdr);
4058 /* IM Session (INVITE and MESSAGE methods) */
4060 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
4061 static gchar *
4062 get_end_points (struct sipe_account_data *sip,
4063 struct sip_session *session)
4065 gchar *res;
4067 if (session == NULL) {
4068 return NULL;
4071 res = g_strdup_printf("<sip:%s>", sip->username);
4073 SIPE_DIALOG_FOREACH {
4074 gchar *tmp = res;
4075 res = g_strdup_printf("%s, <%s>", res, dialog->with);
4076 g_free(tmp);
4078 if (dialog->theirepid) {
4079 tmp = res;
4080 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
4081 g_free(tmp);
4083 } SIPE_DIALOG_FOREACH_END;
4085 return res;
4088 static gboolean
4089 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
4090 struct sipmsg *msg,
4091 SIPE_UNUSED_PARAMETER struct transaction *trans)
4093 gboolean ret = TRUE;
4095 if (msg->response != 200) {
4096 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
4097 return FALSE;
4100 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
4102 return ret;
4106 * Asks UA/proxy about its capabilities.
4108 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
4110 gchar *to = sip_uri(who);
4111 gchar *contact = get_contact(sip);
4112 gchar *request = g_strdup_printf(
4113 "Accept: application/sdp\r\n"
4114 "Contact: %s\r\n", contact);
4115 g_free(contact);
4117 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
4119 g_free(to);
4120 g_free(request);
4123 static void
4124 sipe_notify_user(struct sipe_account_data *sip,
4125 struct sip_session *session,
4126 PurpleMessageFlags flags,
4127 const gchar *message)
4129 PurpleConversation *conv;
4131 if (!session->conv) {
4132 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
4133 } else {
4134 conv = session->conv;
4136 purple_conversation_write(conv, NULL, message, flags, time(NULL));
4139 void
4140 sipe_present_info(struct sipe_account_data *sip,
4141 struct sip_session *session,
4142 const gchar *message)
4144 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4147 static void
4148 sipe_present_err(struct sipe_account_data *sip,
4149 struct sip_session *session,
4150 const gchar *message)
4152 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4155 void
4156 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4157 struct sip_session *session,
4158 int sip_error,
4159 int sip_warning,
4160 const gchar *who,
4161 const gchar *message)
4163 char *msg, *msg_tmp, *msg_tmp2;
4164 const char *label;
4166 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
4167 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4168 g_free(msg_tmp);
4169 /* Service unavailable; Server Internal Error; Server Time-out */
4170 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4171 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4172 g_free(msg);
4173 msg = NULL;
4174 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4175 label = _("This message was not delivered to %s because the service is not available");
4176 } else if (sip_error == 486) { /* Busy Here */
4177 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4178 } else if (sip_error == 415) { /* Unsupported media type */
4179 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4180 } else {
4181 label = _("This message was not delivered to %s because one or more recipients are offline");
4184 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4185 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4186 msg ? ":" : "",
4187 msg ? msg : "");
4188 sipe_present_err(sip, session, msg_tmp);
4189 g_free(msg_tmp2);
4190 g_free(msg_tmp);
4191 g_free(msg);
4195 static gboolean
4196 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4197 SIPE_UNUSED_PARAMETER struct transaction *trans)
4199 gboolean ret = TRUE;
4200 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4201 struct sip_session *session = sipe_session_find_im(sip, with);
4202 struct sip_dialog *dialog;
4203 gchar *cseq;
4204 char *key;
4205 struct queued_message *message;
4207 if (!session) {
4208 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4209 g_free(with);
4210 return FALSE;
4213 dialog = sipe_dialog_find(session, with);
4214 if (!dialog) {
4215 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4216 g_free(with);
4217 return FALSE;
4220 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4221 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4222 g_free(cseq);
4223 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4225 if (msg->response >= 400) {
4226 PurpleBuddy *pbuddy;
4227 const char *alias = with;
4228 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4229 int warning = -1;
4231 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4233 if (warn_hdr) {
4234 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4235 if (parts[0]) {
4236 warning = atoi(parts[0]);
4238 g_strfreev(parts);
4241 /* cancel file transfer as rejected by server */
4242 if (msg->response == 606 && /* Not acceptable all. */
4243 warning == 309 && /* Message contents not allowed by policy */
4244 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4246 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4247 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4248 sipe_utils_nameval_free(parsed_body);
4251 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4252 alias = purple_buddy_get_alias(pbuddy);
4255 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4257 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4258 if (msg->response == 408 || /* Request timeout */
4259 msg->response == 480 || /* Temporarily Unavailable */
4260 msg->response == 481) { /* Call/Transaction Does Not Exist */
4261 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4262 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4265 ret = FALSE;
4266 } else {
4267 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4268 if (message_id) {
4269 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4270 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4271 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4274 g_hash_table_remove(session->unconfirmed_messages, key);
4275 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4276 key, g_hash_table_size(session->unconfirmed_messages));
4279 g_free(key);
4280 g_free(with);
4282 if (ret) sipe_im_process_queue(sip, session);
4283 return ret;
4286 static gboolean
4287 sipe_is_election_finished(struct sip_session *session);
4289 static void
4290 sipe_election_result(struct sipe_account_data *sip,
4291 void *sess);
4293 static gboolean
4294 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4295 SIPE_UNUSED_PARAMETER struct transaction *trans)
4297 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4298 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4299 struct sip_dialog *dialog;
4300 struct sip_session *session;
4302 session = sipe_session_find_chat_by_callid(sip, callid);
4303 if (!session) {
4304 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
4305 return FALSE;
4308 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4309 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4310 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4311 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4313 if (xn_request_rm_response) {
4314 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4315 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4317 dialog = sipe_dialog_find(session, with);
4318 if (!dialog) {
4319 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
4320 xmlnode_free(xn_action);
4321 return FALSE;
4324 if (allow && !g_strcasecmp(allow, "true")) {
4325 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
4326 dialog->election_vote = 1;
4327 } else if (allow && !g_strcasecmp(allow, "false")) {
4328 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
4329 dialog->election_vote = -1;
4332 if (sipe_is_election_finished(session)) {
4333 sipe_election_result(sip, session);
4336 } else if (xn_set_rm_response) {
4339 xmlnode_free(xn_action);
4343 return TRUE;
4346 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4348 gchar *hdr;
4349 gchar *tmp;
4350 char *msgtext = NULL;
4351 const gchar *msgr = "";
4352 gchar *tmp2 = NULL;
4354 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4355 char *msgformat;
4356 gchar *msgr_value;
4358 sipe_parse_html(msg, &msgformat, &msgtext);
4359 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
4361 msgr_value = sipmsg_get_msgr_string(msgformat);
4362 g_free(msgformat);
4363 if (msgr_value) {
4364 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4365 g_free(msgr_value);
4367 } else {
4368 msgtext = g_strdup(msg);
4371 tmp = get_contact(sip);
4372 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4373 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4374 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4375 if (content_type == NULL)
4376 content_type = "text/plain";
4378 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4379 g_free(tmp);
4380 g_free(tmp2);
4382 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4383 g_free(msgtext);
4384 g_free(hdr);
4388 void
4389 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4391 GSList *entry2 = session->outgoing_message_queue;
4392 while (entry2) {
4393 struct queued_message *msg = entry2->data;
4395 /* for multiparty chat or conference */
4396 if (session->is_multiparty || session->focus_uri) {
4397 gchar *who = sip_uri_self(sip);
4398 serv_got_chat_in(sip->gc, session->chat_id, who,
4399 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4400 g_free(who);
4403 SIPE_DIALOG_FOREACH {
4404 char *key;
4405 struct queued_message *message;
4407 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4409 message = g_new0(struct queued_message,1);
4410 message->body = g_strdup(msg->body);
4411 if (msg->content_type != NULL)
4412 message->content_type = g_strdup(msg->content_type);
4414 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4415 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4416 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4417 key, g_hash_table_size(session->unconfirmed_messages));
4418 g_free(key);
4420 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4421 } SIPE_DIALOG_FOREACH_END;
4423 entry2 = sipe_session_dequeue_message(session);
4427 static void
4428 sipe_refer_notify(struct sipe_account_data *sip,
4429 struct sip_session *session,
4430 const gchar *who,
4431 int status,
4432 const gchar *desc)
4434 gchar *hdr;
4435 gchar *body;
4436 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4438 hdr = g_strdup_printf(
4439 "Event: refer\r\n"
4440 "Subscription-State: %s\r\n"
4441 "Content-Type: message/sipfrag\r\n",
4442 status >= 200 ? "terminated" : "active");
4444 body = g_strdup_printf(
4445 "SIP/2.0 %d %s\r\n",
4446 status, desc);
4448 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4450 g_free(hdr);
4451 g_free(body);
4454 static gboolean
4455 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4457 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4458 struct sip_session *session;
4459 struct sip_dialog *dialog;
4460 char *cseq;
4461 char *key;
4462 struct queued_message *message;
4463 struct sipmsg *request_msg = trans->msg;
4465 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4466 gchar *referred_by;
4468 session = sipe_session_find_chat_by_callid(sip, callid);
4469 if (!session) {
4470 session = sipe_session_find_im(sip, with);
4472 if (!session) {
4473 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4474 g_free(with);
4475 return FALSE;
4478 dialog = sipe_dialog_find(session, with);
4479 if (!dialog) {
4480 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4481 g_free(with);
4482 return FALSE;
4485 sipe_dialog_parse(dialog, msg, TRUE);
4487 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4488 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4489 g_free(cseq);
4490 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4492 if (msg->response != 200) {
4493 PurpleBuddy *pbuddy;
4494 const char *alias = with;
4495 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4496 int warning = -1;
4498 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4500 if (warn_hdr) {
4501 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4502 if (parts[0]) {
4503 warning = atoi(parts[0]);
4505 g_strfreev(parts);
4508 /* cancel file transfer as rejected by server */
4509 if (msg->response == 606 && /* Not acceptable all. */
4510 warning == 309 && /* Message contents not allowed by policy */
4511 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4513 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4514 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4515 sipe_utils_nameval_free(parsed_body);
4518 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4519 alias = purple_buddy_get_alias(pbuddy);
4522 if (message) {
4523 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4524 } else {
4525 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4526 sipe_present_err(sip, session, tmp_msg);
4527 g_free(tmp_msg);
4530 sipe_dialog_remove(session, with);
4532 g_free(key);
4533 g_free(with);
4534 return FALSE;
4537 dialog->cseq = 0;
4538 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4539 dialog->outgoing_invite = NULL;
4540 dialog->is_established = TRUE;
4542 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4543 if (referred_by) {
4544 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4545 g_free(referred_by);
4548 /* add user to chat if it is a multiparty session */
4549 if (session->is_multiparty) {
4550 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4551 with, NULL,
4552 PURPLE_CBFLAGS_NONE, TRUE);
4555 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4556 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4557 sipe_session_dequeue_message(session);
4560 sipe_im_process_queue(sip, session);
4562 g_hash_table_remove(session->unconfirmed_messages, key);
4563 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4564 key, g_hash_table_size(session->unconfirmed_messages));
4566 g_free(key);
4567 g_free(with);
4568 return TRUE;
4572 void
4573 sipe_invite(struct sipe_account_data *sip,
4574 struct sip_session *session,
4575 const gchar *who,
4576 const gchar *msg_body,
4577 const gchar *msg_content_type,
4578 const gchar *referred_by,
4579 const gboolean is_triggered)
4581 gchar *hdr;
4582 gchar *to;
4583 gchar *contact;
4584 gchar *body;
4585 gchar *self;
4586 char *ms_text_format = NULL;
4587 gchar *roster_manager;
4588 gchar *end_points;
4589 gchar *referred_by_str;
4590 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4592 if (dialog && dialog->is_established) {
4593 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
4594 return;
4597 if (!dialog) {
4598 dialog = sipe_dialog_add(session);
4599 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4600 dialog->with = g_strdup(who);
4603 if (!(dialog->ourtag)) {
4604 dialog->ourtag = gentag();
4607 to = sip_uri(who);
4609 if (msg_body) {
4610 char *msgtext = NULL;
4611 char *base64_msg;
4612 const gchar *msgr = "";
4613 char *key;
4614 struct queued_message *message;
4615 gchar *tmp = NULL;
4617 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4618 char *msgformat;
4619 gchar *msgr_value;
4621 sipe_parse_html(msg_body, &msgformat, &msgtext);
4622 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
4624 msgr_value = sipmsg_get_msgr_string(msgformat);
4625 g_free(msgformat);
4626 if (msgr_value) {
4627 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4628 g_free(msgr_value);
4630 } else {
4631 msgtext = g_strdup(msg_body);
4634 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4635 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4636 msg_content_type ? msg_content_type : "text/plain",
4637 msgr,
4638 base64_msg);
4639 g_free(msgtext);
4640 g_free(tmp);
4641 g_free(base64_msg);
4643 message = g_new0(struct queued_message,1);
4644 message->body = g_strdup(msg_body);
4645 if (msg_content_type != NULL)
4646 message->content_type = g_strdup(msg_content_type);
4648 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4649 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4650 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4651 key, g_hash_table_size(session->unconfirmed_messages));
4652 g_free(key);
4655 contact = get_contact(sip);
4656 end_points = get_end_points(sip, session);
4657 self = sip_uri_self(sip);
4658 roster_manager = g_strdup_printf(
4659 "Roster-Manager: %s\r\n"
4660 "EndPoints: %s\r\n",
4661 self,
4662 end_points);
4663 referred_by_str = referred_by ?
4664 g_strdup_printf(
4665 "Referred-By: %s\r\n",
4666 referred_by)
4667 : g_strdup("");
4668 hdr = g_strdup_printf(
4669 "Supported: ms-sender\r\n"
4670 "%s"
4671 "%s"
4672 "%s"
4673 "%s"
4674 "Contact: %s\r\n%s"
4675 "Content-Type: application/sdp\r\n",
4676 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4677 referred_by_str,
4678 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4679 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4680 contact,
4681 ms_text_format ? ms_text_format : "");
4682 g_free(ms_text_format);
4683 g_free(self);
4685 body = g_strdup_printf(
4686 "v=0\r\n"
4687 "o=- 0 0 IN IP4 %s\r\n"
4688 "s=session\r\n"
4689 "c=IN IP4 %s\r\n"
4690 "t=0 0\r\n"
4691 "m=%s %d sip null\r\n"
4692 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4693 purple_network_get_my_ip(-1),
4694 purple_network_get_my_ip(-1),
4695 sip->ocs2007 ? "message" : "x-ms-message",
4696 sip->realport);
4698 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4699 to, to, hdr, body, dialog, process_invite_response);
4701 g_free(to);
4702 g_free(roster_manager);
4703 g_free(end_points);
4704 g_free(referred_by_str);
4705 g_free(body);
4706 g_free(hdr);
4707 g_free(contact);
4710 static void
4711 sipe_refer(struct sipe_account_data *sip,
4712 struct sip_session *session,
4713 const gchar *who)
4715 gchar *hdr;
4716 gchar *contact;
4717 gchar *epid = get_epid(sip);
4718 struct sip_dialog *dialog = sipe_dialog_find(session,
4719 session->roster_manager);
4720 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4722 contact = get_contact(sip);
4723 hdr = g_strdup_printf(
4724 "Contact: %s\r\n"
4725 "Refer-to: <%s>\r\n"
4726 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4727 "Require: com.microsoft.rtc-multiparty\r\n",
4728 contact,
4729 who,
4730 sip->username,
4731 ourtag ? ";tag=" : "",
4732 ourtag ? ourtag : "",
4733 epid);
4734 g_free(epid);
4736 send_sip_request(sip->gc, "REFER",
4737 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4739 g_free(hdr);
4740 g_free(contact);
4743 static void
4744 sipe_send_election_request_rm(struct sipe_account_data *sip,
4745 struct sip_dialog *dialog,
4746 int bid)
4748 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4750 gchar *body = g_strdup_printf(
4751 "<?xml version=\"1.0\"?>\r\n"
4752 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4753 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4754 sip->username, bid);
4756 send_sip_request(sip->gc, "INFO",
4757 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4759 g_free(body);
4762 static void
4763 sipe_send_election_set_rm(struct sipe_account_data *sip,
4764 struct sip_dialog *dialog)
4766 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4768 gchar *body = g_strdup_printf(
4769 "<?xml version=\"1.0\"?>\r\n"
4770 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4771 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4772 sip->username);
4774 send_sip_request(sip->gc, "INFO",
4775 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4777 g_free(body);
4780 static void
4781 sipe_session_close(struct sipe_account_data *sip,
4782 struct sip_session * session)
4784 if (session && session->focus_uri) {
4785 sipe_conf_immcu_closed(sip, session);
4786 conf_session_close(sip, session);
4789 if (session) {
4790 SIPE_DIALOG_FOREACH {
4791 /* @TODO slow down BYE message sending rate */
4792 /* @see single subscription code */
4793 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4794 } SIPE_DIALOG_FOREACH_END;
4796 sipe_session_remove(sip, session);
4800 static void
4801 sipe_session_close_all(struct sipe_account_data *sip)
4803 GSList *entry;
4804 while ((entry = sip->sessions) != NULL) {
4805 sipe_session_close(sip, entry->data);
4809 static void
4810 sipe_convo_closed(PurpleConnection * gc, const char *who)
4812 struct sipe_account_data *sip = gc->proto_data;
4814 SIPE_DEBUG_INFO("conversation with %s closed", who);
4815 sipe_session_close(sip, sipe_session_find_im(sip, who));
4818 static void
4819 sipe_chat_invite(PurpleConnection *gc, int id,
4820 SIPE_UNUSED_PARAMETER const char *message,
4821 const char *name)
4823 sipe_chat_create(gc->proto_data, id, name);
4826 static void
4827 sipe_chat_leave (PurpleConnection *gc, int id)
4829 struct sipe_account_data *sip = gc->proto_data;
4830 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4832 sipe_session_close(sip, session);
4835 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4836 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4838 struct sipe_account_data *sip = gc->proto_data;
4839 struct sip_session *session;
4840 struct sip_dialog *dialog;
4841 gchar *uri = sip_uri(who);
4843 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
4845 session = sipe_session_find_or_add_im(sip, uri);
4846 dialog = sipe_dialog_find(session, uri);
4848 // Queue the message
4849 sipe_session_enqueue_message(session, what, NULL);
4851 if (dialog && !dialog->outgoing_invite) {
4852 sipe_im_process_queue(sip, session);
4853 } else if (!dialog || !dialog->outgoing_invite) {
4854 // Need to send the INVITE to get the outgoing dialog setup
4855 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4858 g_free(uri);
4859 return 1;
4862 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4863 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4865 struct sipe_account_data *sip = gc->proto_data;
4866 struct sip_session *session;
4868 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
4870 session = sipe_session_find_chat_by_id(sip, id);
4872 // Queue the message
4873 if (session && session->dialogs) {
4874 sipe_session_enqueue_message(session,what,NULL);
4875 sipe_im_process_queue(sip, session);
4876 } else if (sip) {
4877 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4878 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4880 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
4881 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
4883 if (sip->ocs2007) {
4884 struct sip_session *session = sipe_session_add_chat(sip);
4886 session->is_multiparty = FALSE;
4887 session->focus_uri = g_strdup(proto_chat_id);
4888 sipe_session_enqueue_message(session, what, NULL);
4889 sipe_invite_conf_focus(sip, session);
4893 return 1;
4896 /* End IM Session (INVITE and MESSAGE methods) */
4898 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4900 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4901 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4902 gchar *from;
4903 struct sip_session *session;
4905 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
4907 /* Call Control protocol */
4908 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4910 process_incoming_info_csta(sip, msg);
4911 return;
4914 from = parse_from(sipmsg_find_header(msg, "From"));
4915 session = sipe_session_find_chat_by_callid(sip, callid);
4916 if (!session) {
4917 session = sipe_session_find_im(sip, from);
4919 if (!session) {
4920 g_free(from);
4921 return;
4924 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4926 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4927 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4928 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4930 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4932 if (xn_request_rm) {
4933 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4934 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4935 gchar *body = g_strdup_printf(
4936 "<?xml version=\"1.0\"?>\r\n"
4937 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4938 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4939 sip->username,
4940 session->bid < bid ? "true" : "false");
4941 send_sip_response(sip->gc, msg, 200, "OK", body);
4942 g_free(body);
4943 } else if (xn_set_rm) {
4944 gchar *body;
4945 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4946 g_free(session->roster_manager);
4947 session->roster_manager = g_strdup(rm);
4949 body = g_strdup_printf(
4950 "<?xml version=\"1.0\"?>\r\n"
4951 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4952 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4953 sip->username);
4954 send_sip_response(sip->gc, msg, 200, "OK", body);
4955 g_free(body);
4957 xmlnode_free(xn_action);
4960 else
4962 /* looks like purple lacks typing notification for chat */
4963 if (!session->is_multiparty && !session->focus_uri) {
4964 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4965 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4966 "status");
4967 if (sipe_strequal(status, "type")) {
4968 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4969 } else if (sipe_strequal(status, "idle")) {
4970 serv_got_typing_stopped(sip->gc, from);
4972 xmlnode_free(xn_keyboard_activity);
4975 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4977 g_free(from);
4980 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4982 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4983 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4984 struct sip_session *session;
4985 struct sip_dialog *dialog;
4987 /* collect dialog identification
4988 * we need callid, ourtag and theirtag to unambiguously identify dialog
4990 /* take data before 'msg' will be modified by send_sip_response */
4991 dialog = g_new0(struct sip_dialog, 1);
4992 dialog->callid = g_strdup(callid);
4993 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4994 dialog->with = g_strdup(from);
4995 sipe_dialog_parse(dialog, msg, FALSE);
4997 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4999 session = sipe_session_find_chat_by_callid(sip, callid);
5000 if (!session) {
5001 session = sipe_session_find_im(sip, from);
5003 if (!session) {
5004 sipe_dialog_free(dialog);
5005 g_free(from);
5006 return;
5009 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
5010 g_free(session->roster_manager);
5011 session->roster_manager = NULL;
5014 /* This what BYE is essentially for - terminating dialog */
5015 sipe_dialog_remove_3(session, dialog);
5016 sipe_dialog_free(dialog);
5017 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
5018 sipe_conf_immcu_closed(sip, session);
5019 } else if (session->is_multiparty) {
5020 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
5023 g_free(from);
5026 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
5028 gchar *self = sip_uri_self(sip);
5029 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5030 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5031 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
5032 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
5033 struct sip_session *session;
5034 struct sip_dialog *dialog;
5036 session = sipe_session_find_chat_by_callid(sip, callid);
5037 dialog = sipe_dialog_find(session, from);
5039 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
5040 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
5041 } else {
5042 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
5044 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
5047 g_free(self);
5048 g_free(from);
5049 g_free(refer_to);
5050 g_free(referred_by);
5053 static unsigned int
5054 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
5056 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
5057 struct sip_session *session;
5058 struct sip_dialog *dialog;
5060 if (state == PURPLE_NOT_TYPING)
5061 return 0;
5063 session = sipe_session_find_im(sip, who);
5064 dialog = sipe_dialog_find(session, who);
5066 if (session && dialog && dialog->is_established) {
5067 send_sip_request(gc, "INFO", who, who,
5068 "Content-Type: application/xml\r\n",
5069 SIPE_SEND_TYPING, dialog, NULL);
5071 return SIPE_TYPING_SEND_TIMEOUT;
5074 static gboolean resend_timeout(struct sipe_account_data *sip)
5076 GSList *tmp = sip->transactions;
5077 time_t currtime = time(NULL);
5078 while (tmp) {
5079 struct transaction *trans = tmp->data;
5080 tmp = tmp->next;
5081 SIPE_DEBUG_INFO("have open transaction age: %ld", (long int)currtime-trans->time);
5082 if ((currtime - trans->time > 5) && trans->retries >= 1) {
5083 /* TODO 408 */
5084 } else {
5085 if ((currtime - trans->time > 2) && trans->retries == 0) {
5086 trans->retries++;
5087 sendout_sipmsg(sip, trans->msg);
5091 return TRUE;
5094 static void do_reauthenticate_cb(struct sipe_account_data *sip,
5095 SIPE_UNUSED_PARAMETER void *unused)
5097 /* register again when security token expires */
5098 /* we have to start a new authentication as the security token
5099 * is almost expired by sending a not signed REGISTER message */
5100 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
5101 sipe_auth_free(&sip->registrar);
5102 sipe_auth_free(&sip->proxy);
5103 sip->registerstatus = 0;
5104 do_register(sip);
5105 sip->reauthenticate_set = FALSE;
5108 static gboolean
5109 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
5110 struct sipmsg *msg,
5111 GSList *parsed_body)
5113 gboolean found = FALSE;
5115 if (parsed_body) {
5116 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
5118 if (sipe_strequal(invitation_command, "INVITE")) {
5119 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
5120 found = TRUE;
5121 } else if (sipe_strequal(invitation_command, "CANCEL")) {
5122 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
5123 found = TRUE;
5124 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
5125 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
5126 found = TRUE;
5129 return found;
5132 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
5134 gchar *from;
5135 const gchar *contenttype;
5136 gboolean found = FALSE;
5138 from = parse_from(sipmsg_find_header(msg, "From"));
5140 if (!from) return;
5142 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
5144 contenttype = sipmsg_find_header(msg, "Content-Type");
5145 if (g_str_has_prefix(contenttype, "text/plain")
5146 || g_str_has_prefix(contenttype, "text/html")
5147 || g_str_has_prefix(contenttype, "multipart/related")
5148 || g_str_has_prefix(contenttype, "multipart/alternative"))
5150 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5151 gchar *html = get_html_message(contenttype, msg->body);
5153 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5154 if (!session) {
5155 session = sipe_session_find_im(sip, from);
5158 if (session && session->focus_uri) { /* a conference */
5159 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5160 gchar *sender = parse_from(tmp);
5161 g_free(tmp);
5162 serv_got_chat_in(sip->gc, session->chat_id, sender,
5163 PURPLE_MESSAGE_RECV, html, time(NULL));
5164 g_free(sender);
5165 } else if (session && session->is_multiparty) { /* a multiparty chat */
5166 serv_got_chat_in(sip->gc, session->chat_id, from,
5167 PURPLE_MESSAGE_RECV, html, time(NULL));
5168 } else {
5169 serv_got_im(sip->gc, from, html, 0, time(NULL));
5171 g_free(html);
5172 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5173 found = TRUE;
5175 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5176 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5177 xmlnode *state;
5178 gchar *statedata;
5180 if (!isc) {
5181 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
5182 g_free(from);
5183 return;
5186 state = xmlnode_get_child(isc, "state");
5188 if (!state) {
5189 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
5190 xmlnode_free(isc);
5191 g_free(from);
5192 return;
5195 statedata = xmlnode_get_data(state);
5196 if (statedata) {
5197 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5198 else serv_got_typing_stopped(sip->gc, from);
5200 g_free(statedata);
5202 xmlnode_free(isc);
5203 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5204 found = TRUE;
5205 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5206 GSList *body = sipe_ft_parse_msg_body(msg->body);
5207 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5208 sipe_utils_nameval_free(body);
5209 if (found) {
5210 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5213 if (!found) {
5214 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5215 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5216 if (!session) {
5217 session = sipe_session_find_im(sip, from);
5219 if (session) {
5220 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5221 from);
5222 sipe_present_err(sip, session, errmsg);
5223 g_free(errmsg);
5226 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
5227 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5229 g_free(from);
5232 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5234 gchar *body;
5235 gchar *newTag;
5236 const gchar *oldHeader;
5237 gchar *newHeader;
5238 gboolean is_multiparty = FALSE;
5239 gboolean is_triggered = FALSE;
5240 gboolean was_multiparty = TRUE;
5241 gboolean just_joined = FALSE;
5242 gchar *from;
5243 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5244 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5245 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5246 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5247 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5248 GSList *end_points = NULL;
5249 char *tmp = NULL;
5250 struct sip_session *session;
5251 const gchar *ms_text_format;
5253 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
5254 g_free(tmp);
5256 /* Invitation to join conference */
5257 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5258 process_incoming_invite_conf(sip, msg);
5259 return;
5262 /* Only accept text invitations */
5263 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5264 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5265 return;
5268 // TODO There *must* be a better way to clean up the To header to add a tag...
5269 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5270 oldHeader = sipmsg_find_header(msg, "To");
5271 newTag = gentag();
5272 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5273 sipmsg_remove_header_now(msg, "To");
5274 sipmsg_add_header_now(msg, "To", newHeader);
5275 g_free(newHeader);
5277 if (end_points_hdr) {
5278 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5280 if (g_slist_length(end_points) > 2) {
5281 is_multiparty = TRUE;
5284 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5285 is_triggered = TRUE;
5286 is_multiparty = TRUE;
5289 session = sipe_session_find_chat_by_callid(sip, callid);
5290 /* Convert to multiparty */
5291 if (session && is_multiparty && !session->is_multiparty) {
5292 g_free(session->with);
5293 session->with = NULL;
5294 was_multiparty = FALSE;
5295 session->is_multiparty = TRUE;
5296 session->chat_id = rand();
5299 if (!session && is_multiparty) {
5300 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5302 /* IM session */
5303 from = parse_from(sipmsg_find_header(msg, "From"));
5304 if (!session) {
5305 session = sipe_session_find_or_add_im(sip, from);
5308 if (session) {
5309 g_free(session->callid);
5310 session->callid = g_strdup(callid);
5312 session->is_multiparty = is_multiparty;
5313 if (roster_manager) {
5314 session->roster_manager = g_strdup(roster_manager);
5318 if (is_multiparty && end_points) {
5319 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5320 GSList *entry = end_points;
5321 while (entry) {
5322 struct sip_dialog *dialog;
5323 struct sipendpoint *end_point = entry->data;
5324 entry = entry->next;
5326 if (!g_strcasecmp(from, end_point->contact) ||
5327 !g_strcasecmp(to, end_point->contact))
5328 continue;
5330 dialog = sipe_dialog_find(session, end_point->contact);
5331 if (dialog) {
5332 g_free(dialog->theirepid);
5333 dialog->theirepid = end_point->epid;
5334 end_point->epid = NULL;
5335 } else {
5336 dialog = sipe_dialog_add(session);
5338 dialog->callid = g_strdup(session->callid);
5339 dialog->with = end_point->contact;
5340 end_point->contact = NULL;
5341 dialog->theirepid = end_point->epid;
5342 end_point->epid = NULL;
5344 just_joined = TRUE;
5346 /* send triggered INVITE */
5347 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5350 g_free(to);
5353 if (end_points) {
5354 GSList *entry = end_points;
5355 while (entry) {
5356 struct sipendpoint *end_point = entry->data;
5357 entry = entry->next;
5358 g_free(end_point->contact);
5359 g_free(end_point->epid);
5360 g_free(end_point);
5362 g_slist_free(end_points);
5365 if (session) {
5366 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5367 if (dialog) {
5368 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5369 sipe_dialog_parse_routes(dialog, msg, FALSE);
5370 } else {
5371 dialog = sipe_dialog_add(session);
5373 dialog->callid = g_strdup(session->callid);
5374 dialog->with = g_strdup(from);
5375 sipe_dialog_parse(dialog, msg, FALSE);
5377 if (!dialog->ourtag) {
5378 dialog->ourtag = newTag;
5379 newTag = NULL;
5382 just_joined = TRUE;
5384 } else {
5385 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5387 g_free(newTag);
5389 if (is_multiparty && !session->conv) {
5390 gchar *chat_title = sipe_chat_get_name(callid);
5391 gchar *self = sip_uri_self(sip);
5392 /* create prpl chat */
5393 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5394 session->chat_title = g_strdup(chat_title);
5395 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5396 /* add self */
5397 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5398 self, NULL,
5399 PURPLE_CBFLAGS_NONE, FALSE);
5400 g_free(chat_title);
5401 g_free(self);
5404 if (is_multiparty && !was_multiparty) {
5405 /* add current IM counterparty to chat */
5406 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5407 sipe_dialog_first(session)->with, NULL,
5408 PURPLE_CBFLAGS_NONE, FALSE);
5411 /* add inviting party to chat */
5412 if (just_joined && session->conv) {
5413 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5414 from, NULL,
5415 PURPLE_CBFLAGS_NONE, TRUE);
5418 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5420 /* This used only in 2005 official client, not 2007 or Reuters.
5421 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5422 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5424 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5425 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5426 if (is_multiparty ||
5427 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5429 if (ms_text_format) {
5430 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5432 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5433 if (tmp) {
5434 gsize len;
5435 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5437 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5439 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5440 sipe_utils_nameval_free(parsed_body);
5441 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5443 g_free(tmp);
5445 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5447 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5448 gchar *html = get_html_message(ms_text_format, NULL);
5449 if (html) {
5450 if (is_multiparty) {
5451 serv_got_chat_in(sip->gc, session->chat_id, from,
5452 PURPLE_MESSAGE_RECV, html, time(NULL));
5453 } else {
5454 serv_got_im(sip->gc, from, html, 0, time(NULL));
5456 g_free(html);
5457 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5463 g_free(from);
5465 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5466 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5467 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5469 body = g_strdup_printf(
5470 "v=0\r\n"
5471 "o=- 0 0 IN IP4 %s\r\n"
5472 "s=session\r\n"
5473 "c=IN IP4 %s\r\n"
5474 "t=0 0\r\n"
5475 "m=%s %d sip sip:%s\r\n"
5476 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5477 purple_network_get_my_ip(-1),
5478 purple_network_get_my_ip(-1),
5479 sip->ocs2007 ? "message" : "x-ms-message",
5480 sip->realport,
5481 sip->username);
5482 send_sip_response(sip->gc, msg, 200, "OK", body);
5483 g_free(body);
5486 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5488 gchar *body;
5490 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5491 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5492 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5494 body = g_strdup_printf(
5495 "v=0\r\n"
5496 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5497 "s=session\r\n"
5498 "c=IN IP4 0.0.0.0\r\n"
5499 "t=0 0\r\n"
5500 "m=%s %d sip sip:%s\r\n"
5501 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5502 sip->ocs2007 ? "message" : "x-ms-message",
5503 sip->realport,
5504 sip->username);
5505 send_sip_response(sip->gc, msg, 200, "OK", body);
5506 g_free(body);
5509 static const char*
5510 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5512 const char *res = "NTLM";
5513 #ifdef HAVE_KERBEROS
5514 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5515 res = "Kerberos";
5517 #else
5518 (void) sip; /* make compiler happy */
5519 #endif
5520 return res;
5523 static void sipe_connection_cleanup(struct sipe_account_data *);
5524 static void create_connection(struct sipe_account_data *, gchar *, int);
5526 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5527 SIPE_UNUSED_PARAMETER struct transaction *trans)
5529 gchar *tmp;
5530 const gchar *expires_header;
5531 int expires, i;
5532 GSList *hdr = msg->headers;
5533 struct sipnameval *elem;
5535 expires_header = sipmsg_find_header(msg, "Expires");
5536 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5537 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
5539 switch (msg->response) {
5540 case 200:
5541 if (expires == 0) {
5542 sip->registerstatus = 0;
5543 } else {
5544 const gchar *contact_hdr;
5545 gchar *gruu = NULL;
5546 gchar *epid;
5547 gchar *uuid;
5548 gchar *timeout;
5549 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5550 const char *auth_scheme;
5552 if (!sip->reregister_set) {
5553 gchar *action_name = g_strdup_printf("<%s>", "registration");
5554 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5555 g_free(action_name);
5556 sip->reregister_set = TRUE;
5559 sip->registerstatus = 3;
5561 if (server_hdr && !sip->server_version) {
5562 sip->server_version = g_strdup(server_hdr);
5563 g_free(default_ua);
5564 default_ua = NULL;
5567 auth_scheme = sipe_get_auth_scheme_name(sip);
5568 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5570 if (tmp) {
5571 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
5572 fill_auth(tmp, &sip->registrar);
5575 if (!sip->reauthenticate_set) {
5576 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5577 guint reauth_timeout;
5578 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5579 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5580 reauth_timeout = sip->registrar.expires - 300;
5581 } else {
5582 /* NTLM: we have to reauthenticate as our security token expires
5583 after eight hours (be five minutes early) */
5584 reauth_timeout = (8 * 3600) - 300;
5586 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5587 g_free(action_name);
5588 sip->reauthenticate_set = TRUE;
5591 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5593 epid = get_epid(sip);
5594 uuid = generateUUIDfromEPID(epid);
5595 g_free(epid);
5597 // There can be multiple Contact headers (one per location where the user is logged in) so
5598 // make sure to only get the one for this uuid
5599 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5600 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5601 if (valid_contact) {
5602 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5603 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5604 g_free(valid_contact);
5605 break;
5606 } else {
5607 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5610 g_free(uuid);
5612 g_free(sip->contact);
5613 if(gruu) {
5614 sip->contact = g_strdup_printf("<%s>", gruu);
5615 g_free(gruu);
5616 } else {
5617 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5618 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
5620 sip->ocs2007 = FALSE;
5621 sip->batched_support = FALSE;
5623 while(hdr)
5625 elem = hdr->data;
5626 if (sipe_strcase_equal(elem->name, "Supported")) {
5627 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5628 /* We interpret this as OCS2007+ indicator */
5629 sip->ocs2007 = TRUE;
5630 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5632 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5633 sip->batched_support = TRUE;
5634 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5637 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5638 gchar **caps = g_strsplit(elem->value,",",0);
5639 i = 0;
5640 while (caps[i]) {
5641 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5642 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5643 i++;
5645 g_strfreev(caps);
5647 hdr = g_slist_next(hdr);
5650 /* rejoin open chats to be able to use them by continue to send messages */
5651 purple_conversation_foreach(sipe_rejoin_chat);
5653 /* subscriptions */
5654 if (!sip->subscribed) { //do it just once, not every re-register
5656 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5657 (GCompareFunc)g_ascii_strcasecmp)) {
5658 sipe_subscribe_roaming_contacts(sip);
5661 /* For 2007+ it does not make sence to subscribe to:
5662 * vnd-microsoft-roaming-ACL
5663 * vnd-microsoft-provisioning (not v2)
5664 * presence.wpending
5665 * These are for backward compatibility.
5667 if (sip->ocs2007)
5669 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5670 (GCompareFunc)g_ascii_strcasecmp)) {
5671 sipe_subscribe_roaming_self(sip);
5673 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5674 (GCompareFunc)g_ascii_strcasecmp)) {
5675 sipe_subscribe_roaming_provisioning_v2(sip);
5678 /* For 2005- servers */
5679 else
5681 //sipe_options_request(sip, sip->sipdomain);
5683 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5684 (GCompareFunc)g_ascii_strcasecmp)) {
5685 sipe_subscribe_roaming_acl(sip);
5687 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5688 (GCompareFunc)g_ascii_strcasecmp)) {
5689 sipe_subscribe_roaming_provisioning(sip);
5691 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5692 (GCompareFunc)g_ascii_strcasecmp)) {
5693 sipe_subscribe_presence_wpending(sip, msg);
5696 /* For 2007+ we publish our initial statuses and calendar data only after
5697 * received our existing publications in sipe_process_roaming_self()
5698 * Only in this case we know versions of current publications made
5699 * on our behalf.
5701 /* For 2005- we publish our initial statuses only after
5702 * received our existing UserInfo data in response to
5703 * self subscription.
5704 * Only in this case we won't override existing UserInfo data
5705 * set earlier or by other client on our behalf.
5709 sip->subscribed = TRUE;
5712 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5713 "timeout=", ";", NULL);
5714 if (timeout != NULL) {
5715 sscanf(timeout, "%u", &sip->keepalive_timeout);
5716 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5717 sip->keepalive_timeout);
5718 g_free(timeout);
5721 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5723 break;
5724 case 301:
5726 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5728 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5729 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5730 gchar **tmp;
5731 gchar *hostname;
5732 int port = 0;
5733 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5734 int i = 1;
5736 tmp = g_strsplit(parts[0], ":", 0);
5737 hostname = g_strdup(tmp[0]);
5738 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5739 g_strfreev(tmp);
5741 while (parts[i]) {
5742 tmp = g_strsplit(parts[i], "=", 0);
5743 if (tmp[1]) {
5744 if (g_strcasecmp("transport", tmp[0]) == 0) {
5745 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5746 transport = SIPE_TRANSPORT_TCP;
5747 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5748 transport = SIPE_TRANSPORT_UDP;
5752 g_strfreev(tmp);
5753 i++;
5755 g_strfreev(parts);
5757 /* Close old connection */
5758 sipe_connection_cleanup(sip);
5760 /* Create new connection */
5761 sip->transport = transport;
5762 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5763 hostname, port, TRANSPORT_DESCRIPTOR);
5764 create_connection(sip, hostname, port);
5766 g_free(redirect);
5768 break;
5769 case 401:
5770 if (sip->registerstatus != 2) {
5771 const char *auth_scheme;
5772 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5773 if (sip->registrar.retries > 3) {
5774 sip->gc->wants_to_die = TRUE;
5775 purple_connection_error(sip->gc, _("Authentication failed"));
5776 return TRUE;
5779 auth_scheme = sipe_get_auth_scheme_name(sip);
5780 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5782 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
5783 if (!tmp) {
5784 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5785 sip->gc->wants_to_die = TRUE;
5786 purple_connection_error(sip->gc, tmp2);
5787 g_free(tmp2);
5788 return TRUE;
5790 fill_auth(tmp, &sip->registrar);
5791 sip->registerstatus = 2;
5792 if (sip->account->disconnecting) {
5793 do_register_exp(sip, 0);
5794 } else {
5795 do_register(sip);
5798 break;
5799 case 403:
5801 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5802 gchar **reason = NULL;
5803 gchar *warning;
5804 if (diagnostics != NULL) {
5805 /* Example header:
5806 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5808 reason = g_strsplit(diagnostics, "\"", 0);
5810 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5811 (reason && reason[1]) ? reason[1] : _("no reason given"));
5812 g_strfreev(reason);
5814 sip->gc->wants_to_die = TRUE;
5815 purple_connection_error(sip->gc, warning);
5816 g_free(warning);
5817 return TRUE;
5819 break;
5820 case 404:
5822 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5823 gchar *reason = NULL;
5824 gchar *warning;
5825 if (diagnostics != NULL) {
5826 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5828 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5829 diagnostics ? (reason ? reason : _("no reason given")) :
5830 _("SIP is either not enabled for the destination URI or it does not exist"));
5831 g_free(reason);
5833 sip->gc->wants_to_die = TRUE;
5834 purple_connection_error(sip->gc, warning);
5835 g_free(warning);
5836 return TRUE;
5838 break;
5839 case 503:
5840 case 504: /* Server time-out */
5842 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5843 gchar *reason = NULL;
5844 gchar *warning;
5845 if (diagnostics != NULL) {
5846 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5848 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5849 g_free(reason);
5851 sip->gc->wants_to_die = TRUE;
5852 purple_connection_error(sip->gc, warning);
5853 g_free(warning);
5854 return TRUE;
5856 break;
5858 return TRUE;
5862 * Returns 2005-style activity and Availability.
5864 * @param status Sipe statis id.
5866 static void
5867 sipe_get_act_avail_by_status_2005(const char *status,
5868 int *activity,
5869 int *availability)
5871 int avail = 300; /* online */
5872 int act = 400; /* Available */
5874 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5875 act = 100;
5876 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5877 // act = 150;
5878 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5879 act = 300;
5880 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5881 act = 400;
5882 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5883 // act = 500;
5884 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5885 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5886 act = 600;
5887 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5888 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5889 avail = 0; /* offline */
5890 act = 100;
5891 } else {
5892 act = 400; /* Available */
5895 if (activity) *activity = act;
5896 if (availability) *availability = avail;
5900 * [MS-SIP] 2.2.1
5902 * @param activity 2005 aggregated activity. Ex.: 600
5903 * @param availablity 2005 aggregated availablity. Ex.: 300
5905 static const char *
5906 sipe_get_status_by_act_avail_2005(const int activity,
5907 const int availablity,
5908 char **activity_desc)
5910 const char *status_id = NULL;
5911 const char *act = NULL;
5913 if (activity < 150) {
5914 status_id = SIPE_STATUS_ID_AWAY;
5915 } else if (activity < 200) {
5916 //status_id = SIPE_STATUS_ID_LUNCH;
5917 status_id = SIPE_STATUS_ID_AWAY;
5918 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5919 } else if (activity < 300) {
5920 //status_id = SIPE_STATUS_ID_IDLE;
5921 status_id = SIPE_STATUS_ID_AWAY;
5922 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5923 } else if (activity < 400) {
5924 status_id = SIPE_STATUS_ID_BRB;
5925 } else if (activity < 500) {
5926 status_id = SIPE_STATUS_ID_AVAILABLE;
5927 } else if (activity < 600) {
5928 //status_id = SIPE_STATUS_ID_ON_PHONE;
5929 status_id = SIPE_STATUS_ID_BUSY;
5930 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5931 } else if (activity < 700) {
5932 status_id = SIPE_STATUS_ID_BUSY;
5933 } else if (activity < 800) {
5934 status_id = SIPE_STATUS_ID_AWAY;
5935 } else {
5936 status_id = SIPE_STATUS_ID_AVAILABLE;
5939 if (availablity < 100)
5940 status_id = SIPE_STATUS_ID_OFFLINE;
5942 if (activity_desc && act) {
5943 g_free(*activity_desc);
5944 *activity_desc = g_strdup(act);
5947 return status_id;
5951 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5953 static const char*
5954 sipe_get_status_by_availability(int avail,
5955 char** activity_desc)
5957 const char *status;
5958 const char *act = NULL;
5960 if (avail < 3000) {
5961 status = SIPE_STATUS_ID_OFFLINE;
5962 } else if (avail < 4500) {
5963 status = SIPE_STATUS_ID_AVAILABLE;
5964 } else if (avail < 6000) {
5965 //status = SIPE_STATUS_ID_IDLE;
5966 status = SIPE_STATUS_ID_AVAILABLE;
5967 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5968 } else if (avail < 7500) {
5969 status = SIPE_STATUS_ID_BUSY;
5970 } else if (avail < 9000) {
5971 //status = SIPE_STATUS_ID_BUSYIDLE;
5972 status = SIPE_STATUS_ID_BUSY;
5973 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5974 } else if (avail < 12000) {
5975 status = SIPE_STATUS_ID_DND;
5976 } else if (avail < 15000) {
5977 status = SIPE_STATUS_ID_BRB;
5978 } else if (avail < 18000) {
5979 status = SIPE_STATUS_ID_AWAY;
5980 } else {
5981 status = SIPE_STATUS_ID_OFFLINE;
5984 if (activity_desc && act) {
5985 g_free(*activity_desc);
5986 *activity_desc = g_strdup(act);
5989 return status;
5993 * Returns 2007-style availability value
5995 * @param sipe_status_id (in)
5996 * @param activity_token (out) Must be g_free()'d after use if consumed.
5998 static int
5999 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
6001 int availability;
6002 sipe_activity activity = SIPE_ACTIVITY_UNSET;
6004 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
6005 availability = 15500;
6006 if (!activity_token || !(*activity_token)) {
6007 activity = SIPE_ACTIVITY_AWAY;
6009 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
6010 availability = 12500;
6011 activity = SIPE_ACTIVITY_BRB;
6012 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
6013 availability = 9500;
6014 activity = SIPE_ACTIVITY_DND;
6015 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
6016 availability = 6500;
6017 if (!activity_token || !(*activity_token)) {
6018 activity = SIPE_ACTIVITY_BUSY;
6020 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
6021 availability = 3500;
6022 activity = SIPE_ACTIVITY_ONLINE;
6023 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
6024 availability = 0;
6025 } else {
6026 // Offline or invisible
6027 availability = 18500;
6028 activity = SIPE_ACTIVITY_OFFLINE;
6031 if (activity_token) {
6032 *activity_token = g_strdup(sipe_activity_map[activity].token);
6034 return availability;
6037 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
6039 const char *uri;
6040 sipe_xml *xn_categories;
6041 const sipe_xml *xn_category;
6042 const char *status = NULL;
6043 gboolean do_update_status = FALSE;
6044 gboolean has_note_cleaned = FALSE;
6045 gboolean has_free_busy_cleaned = FALSE;
6047 xn_categories = sipe_xml_parse(data, len);
6048 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
6050 for (xn_category = sipe_xml_child(xn_categories, "category");
6051 xn_category ;
6052 xn_category = sipe_xml_twin(xn_category) )
6054 const sipe_xml *xn_node;
6055 const char *tmp;
6056 const char *attrVar = sipe_xml_attribute(xn_category, "name");
6057 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
6058 sipe_utils_str_to_time(tmp) : 0;
6060 /* contactCard */
6061 if (sipe_strequal(attrVar, "contactCard"))
6063 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
6065 if (card) {
6066 const sipe_xml *node;
6067 /* identity - Display Name and email */
6068 node = sipe_xml_child(card, "identity");
6069 if (node) {
6070 char* display_name = sipe_xml_data(
6071 sipe_xml_child(node, "name/displayName"));
6072 char* email = sipe_xml_data(
6073 sipe_xml_child(node, "email"));
6075 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6076 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6078 g_free(display_name);
6079 g_free(email);
6081 /* company */
6082 node = sipe_xml_child(card, "company");
6083 if (node) {
6084 char* company = sipe_xml_data(node);
6085 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
6086 g_free(company);
6088 /* department */
6089 node = sipe_xml_child(card, "department");
6090 if (node) {
6091 char* department = sipe_xml_data(node);
6092 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
6093 g_free(department);
6095 /* title */
6096 node = sipe_xml_child(card, "title");
6097 if (node) {
6098 char* title = sipe_xml_data(node);
6099 sipe_update_user_info(sip, uri, TITLE_PROP, title);
6100 g_free(title);
6102 /* office */
6103 node = sipe_xml_child(card, "office");
6104 if (node) {
6105 char* office = sipe_xml_data(node);
6106 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
6107 g_free(office);
6109 /* site (url) */
6110 node = sipe_xml_child(card, "url");
6111 if (node) {
6112 char* site = sipe_xml_data(node);
6113 sipe_update_user_info(sip, uri, SITE_PROP, site);
6114 g_free(site);
6116 /* phone */
6117 for (node = sipe_xml_child(card, "phone");
6118 node;
6119 node = sipe_xml_twin(node))
6121 const char *phone_type = sipe_xml_attribute(node, "type");
6122 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
6123 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
6125 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
6127 g_free(phone);
6128 g_free(phone_display_string);
6130 /* address */
6131 for (node = sipe_xml_child(card, "address");
6132 node;
6133 node = sipe_xml_twin(node))
6135 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
6136 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
6137 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
6138 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
6139 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
6140 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
6142 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
6143 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
6144 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
6145 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
6146 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
6148 g_free(street);
6149 g_free(city);
6150 g_free(state);
6151 g_free(zipcode);
6152 g_free(country_code);
6154 break;
6159 /* note */
6160 else if (sipe_strequal(attrVar, "note"))
6162 if (uri) {
6163 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6165 if (!has_note_cleaned) {
6166 has_note_cleaned = TRUE;
6168 g_free(sbuddy->note);
6169 sbuddy->note = NULL;
6170 sbuddy->is_oof_note = FALSE;
6171 sbuddy->note_since = publish_time;
6173 do_update_status = TRUE;
6175 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6176 /* clean up in case no 'note' element is supplied
6177 * which indicate note removal in client
6179 g_free(sbuddy->note);
6180 sbuddy->note = NULL;
6181 sbuddy->is_oof_note = FALSE;
6182 sbuddy->note_since = publish_time;
6184 xn_node = sipe_xml_child(xn_category, "note/body");
6185 if (xn_node) {
6186 char *tmp;
6187 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6188 g_free(tmp);
6189 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6190 sbuddy->note_since = publish_time;
6192 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6193 uri, sbuddy->note ? sbuddy->note : "");
6195 /* to trigger UI refresh in case no status info is supplied in this update */
6196 do_update_status = TRUE;
6200 /* state */
6201 else if(sipe_strequal(attrVar, "state"))
6203 char *tmp;
6204 int availability;
6205 const sipe_xml *xn_availability;
6206 const sipe_xml *xn_activity;
6207 const sipe_xml *xn_meeting_subject;
6208 const sipe_xml *xn_meeting_location;
6209 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6211 xn_node = sipe_xml_child(xn_category, "state");
6212 if (!xn_node) continue;
6213 xn_availability = sipe_xml_child(xn_node, "availability");
6214 if (!xn_availability) continue;
6215 xn_activity = sipe_xml_child(xn_node, "activity");
6216 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6217 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6219 tmp = sipe_xml_data(xn_availability);
6220 availability = atoi(tmp);
6221 g_free(tmp);
6223 /* activity, meeting_subject, meeting_location */
6224 if (sbuddy) {
6225 char *tmp = NULL;
6227 /* activity */
6228 g_free(sbuddy->activity);
6229 sbuddy->activity = NULL;
6230 if (xn_activity) {
6231 const char *token = sipe_xml_attribute(xn_activity, "token");
6232 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6234 /* from token */
6235 if (!is_empty(token)) {
6236 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6238 /* from custom element */
6239 if (xn_custom) {
6240 char *custom = sipe_xml_data(xn_custom);
6242 if (!is_empty(custom)) {
6243 sbuddy->activity = custom;
6244 custom = NULL;
6246 g_free(custom);
6249 /* meeting_subject */
6250 g_free(sbuddy->meeting_subject);
6251 sbuddy->meeting_subject = NULL;
6252 if (xn_meeting_subject) {
6253 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6255 if (!is_empty(meeting_subject)) {
6256 sbuddy->meeting_subject = meeting_subject;
6257 meeting_subject = NULL;
6259 g_free(meeting_subject);
6261 /* meeting_location */
6262 g_free(sbuddy->meeting_location);
6263 sbuddy->meeting_location = NULL;
6264 if (xn_meeting_location) {
6265 char *meeting_location = sipe_xml_data(xn_meeting_location);
6267 if (!is_empty(meeting_location)) {
6268 sbuddy->meeting_location = meeting_location;
6269 meeting_location = NULL;
6271 g_free(meeting_location);
6274 status = sipe_get_status_by_availability(availability, &tmp);
6275 if (sbuddy->activity && tmp) {
6276 char *tmp2 = sbuddy->activity;
6278 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6279 g_free(tmp);
6280 g_free(tmp2);
6281 } else if (tmp) {
6282 sbuddy->activity = tmp;
6286 do_update_status = TRUE;
6288 /* calendarData */
6289 else if(sipe_strequal(attrVar, "calendarData"))
6291 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6292 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6293 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6295 if (sbuddy && xn_free_busy) {
6296 if (!has_free_busy_cleaned) {
6297 has_free_busy_cleaned = TRUE;
6299 g_free(sbuddy->cal_start_time);
6300 sbuddy->cal_start_time = NULL;
6302 g_free(sbuddy->cal_free_busy_base64);
6303 sbuddy->cal_free_busy_base64 = NULL;
6305 g_free(sbuddy->cal_free_busy);
6306 sbuddy->cal_free_busy = NULL;
6308 sbuddy->cal_free_busy_published = publish_time;
6311 if (publish_time >= sbuddy->cal_free_busy_published) {
6312 g_free(sbuddy->cal_start_time);
6313 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6315 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6316 15 : 0;
6318 g_free(sbuddy->cal_free_busy_base64);
6319 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6321 g_free(sbuddy->cal_free_busy);
6322 sbuddy->cal_free_busy = NULL;
6324 sbuddy->cal_free_busy_published = publish_time;
6326 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);
6330 if (sbuddy && xn_working_hours) {
6331 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6336 if (do_update_status) {
6337 if (!status) { /* no status category in this update, using contact's current status */
6338 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6339 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6340 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6341 status = purple_status_get_id(pstatus);
6344 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
6345 sipe_got_user_status(sip, uri, status);
6348 sipe_xml_free(xn_categories);
6351 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6353 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6354 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
6355 payload->host = g_strdup(host);
6356 payload->buddies = server;
6357 sipe_subscribe_presence_batched_routed(sip, payload);
6358 sipe_subscribe_presence_batched_routed_free(payload);
6361 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6363 xmlnode *xn_list;
6364 xmlnode *xn_resource;
6365 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6366 g_free, NULL);
6367 GSList *server;
6368 gchar *host;
6370 xn_list = xmlnode_from_str(data, len);
6372 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6373 xn_resource;
6374 xn_resource = xmlnode_get_next_twin(xn_resource) )
6376 const char *uri, *state;
6377 xmlnode *xn_instance;
6379 xn_instance = xmlnode_get_child(xn_resource, "instance");
6380 if (!xn_instance) continue;
6382 uri = xmlnode_get_attrib(xn_resource, "uri");
6383 state = xmlnode_get_attrib(xn_instance, "state");
6384 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
6386 if (strstr(state, "resubscribe")) {
6387 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6389 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6390 gchar *user = g_strdup(uri);
6391 host = g_strdup(poolFqdn);
6392 server = g_hash_table_lookup(servers, host);
6393 server = g_slist_append(server, user);
6394 g_hash_table_insert(servers, host, server);
6395 } else {
6396 sipe_subscribe_presence_single(sip, (void *) uri);
6401 /* Send out any deferred poolFqdn subscriptions */
6402 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6403 g_hash_table_destroy(servers);
6405 xmlnode_free(xn_list);
6408 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6410 gchar *uri;
6411 gchar *getbasic;
6412 gchar *activity = NULL;
6413 xmlnode *pidf;
6414 xmlnode *basicstatus = NULL, *tuple, *status;
6415 gboolean isonline = FALSE;
6416 xmlnode *display_name_node;
6418 pidf = xmlnode_from_str(data, len);
6419 if (!pidf) {
6420 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
6421 return;
6424 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6426 if ((status = xmlnode_get_child(tuple, "status"))) {
6427 basicstatus = xmlnode_get_child(status, "basic");
6431 if (!basicstatus) {
6432 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6433 xmlnode_free(pidf);
6434 return;
6437 getbasic = xmlnode_get_data(basicstatus);
6438 if (!getbasic) {
6439 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6440 xmlnode_free(pidf);
6441 return;
6444 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
6445 if (strstr(getbasic, "open")) {
6446 isonline = TRUE;
6448 g_free(getbasic);
6450 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6452 display_name_node = xmlnode_get_child(pidf, "display-name");
6453 if (display_name_node) {
6454 char * display_name = xmlnode_get_data(display_name_node);
6456 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6457 g_free(display_name);
6460 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6461 if ((status = xmlnode_get_child(tuple, "status"))) {
6462 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6463 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6464 activity = xmlnode_get_data(basicstatus);
6465 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
6471 if (isonline) {
6472 const gchar * status_id = NULL;
6473 if (activity) {
6474 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6475 status_id = SIPE_STATUS_ID_BUSY;
6476 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6477 status_id = SIPE_STATUS_ID_AWAY;
6481 if (!status_id) {
6482 status_id = SIPE_STATUS_ID_AVAILABLE;
6485 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
6486 sipe_got_user_status(sip, uri, status_id);
6487 } else {
6488 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6491 g_free(activity);
6492 g_free(uri);
6493 xmlnode_free(pidf);
6496 /** 2005 */
6497 static void
6498 sipe_user_info_has_updated(struct sipe_account_data *sip,
6499 xmlnode *xn_userinfo)
6501 xmlnode *xn_states;
6503 g_free(sip->user_states);
6504 sip->user_states = NULL;
6505 if ((xn_states = xmlnode_get_child(xn_userinfo, "states")) != NULL) {
6506 gchar *orig = sip->user_states = xmlnode_to_str(xn_states, NULL);
6508 /* this is a hack-around to remove added newline after inner element,
6509 * state in this case, where it shouldn't be.
6510 * After several use of xmlnode_to_str, amount of added newlines
6511 * grows significantly.
6513 if (orig) {
6514 gchar c, *stripped = orig;
6515 while ((c = *orig++)) {
6516 if ((c != '\n') /* && (c != '\r') */) {
6517 *stripped++ = c;
6520 *stripped = '\0';
6524 /* Publish initial state if not yet.
6525 * Assuming this happens on initial responce to self subscription
6526 * so we've already updated our UserInfo.
6528 if (!sip->initial_state_published) {
6529 send_presence_soap(sip, FALSE);
6530 /* dalayed run */
6531 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6535 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6537 char *activity = NULL;
6538 const char *epid;
6539 const char *status_id = NULL;
6540 const char *name;
6541 char *uri;
6542 char *self_uri = sip_uri_self(sip);
6543 int avl;
6544 int act;
6545 const char *device_name = NULL;
6546 const char *cal_start_time = NULL;
6547 const char *cal_granularity = NULL;
6548 char *cal_free_busy_base64 = NULL;
6549 struct sipe_buddy *sbuddy;
6550 xmlnode *node;
6551 xmlnode *xn_presentity;
6552 xmlnode *xn_availability;
6553 xmlnode *xn_activity;
6554 xmlnode *xn_display_name;
6555 xmlnode *xn_email;
6556 xmlnode *xn_phone_number;
6557 xmlnode *xn_userinfo;
6558 xmlnode *xn_note;
6559 xmlnode *xn_oof;
6560 xmlnode *xn_state;
6561 xmlnode *xn_contact;
6562 char *note;
6563 char *free_activity;
6564 int user_avail;
6565 const char *user_avail_nil;
6566 int res_avail;
6567 time_t user_avail_since = 0;
6568 time_t activity_since = 0;
6570 /* fix for Reuters environment on Linux */
6571 if (data && strstr(data, "encoding=\"utf-16\"")) {
6572 char *tmp_data;
6573 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6574 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6575 g_free(tmp_data);
6576 } else {
6577 xn_presentity = xmlnode_from_str(data, len);
6580 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6581 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6582 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6583 xn_email = xmlnode_get_child(xn_presentity, "email");
6584 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6585 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6586 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6587 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6588 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6589 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6590 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6591 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6592 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6593 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6595 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6596 user_avail = 0;
6597 user_avail_since = 0;
6600 free_activity = NULL;
6602 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6603 uri = sip_uri_from_name(name);
6604 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6605 epid = xmlnode_get_attrib(xn_availability, "epid");
6606 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6608 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6609 res_avail = sipe_get_availability_by_status(status_id, NULL);
6610 if (user_avail > res_avail) {
6611 res_avail = user_avail;
6612 status_id = sipe_get_status_by_availability(user_avail, NULL);
6615 if (xn_display_name) {
6616 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6617 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6618 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6619 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6620 char *tel_uri = sip_to_tel_uri(phone_number);
6622 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6623 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6624 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6625 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6627 g_free(tel_uri);
6628 g_free(phone_label);
6629 g_free(phone_number);
6630 g_free(email);
6631 g_free(display_name);
6634 if (xn_contact) {
6635 /* tel */
6636 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6638 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6639 const char *phone_type = xmlnode_get_attrib(node, "type");
6640 char* phone = xmlnode_get_data(node);
6642 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6644 g_free(phone);
6648 /* devicePresence */
6649 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6650 xmlnode *xn_device_name;
6651 xmlnode *xn_calendar_info;
6652 xmlnode *xn_state;
6653 char *state;
6655 /* deviceName */
6656 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6657 xn_device_name = xmlnode_get_child(node, "deviceName");
6658 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6661 /* calendarInfo */
6662 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6663 if (xn_calendar_info) {
6664 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6666 if (cal_start_time) {
6667 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6668 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6670 if (cal_start_time_t_tmp > cal_start_time_t) {
6671 cal_start_time = cal_start_time_tmp;
6672 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6673 g_free(cal_free_busy_base64);
6674 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6676 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);
6678 } else {
6679 cal_start_time = cal_start_time_tmp;
6680 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6681 g_free(cal_free_busy_base64);
6682 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6684 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);
6688 /* state */
6689 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6690 if (xn_state) {
6691 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6692 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6694 state = xmlnode_get_data(xn_state);
6695 if (dev_avail_since > user_avail_since &&
6696 dev_avail >= res_avail)
6698 res_avail = dev_avail;
6699 if (!is_empty(state))
6701 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6702 g_free(activity);
6703 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6704 } else if (sipe_strequal(state, "presenting")) {
6705 g_free(activity);
6706 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6707 } else {
6708 activity = state;
6709 state = NULL;
6711 activity_since = dev_avail_since;
6713 status_id = sipe_get_status_by_availability(res_avail, &activity);
6715 g_free(state);
6719 /* oof */
6720 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6721 g_free(activity);
6722 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6723 activity_since = 0;
6726 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6727 if (sbuddy)
6729 g_free(sbuddy->activity);
6730 sbuddy->activity = activity;
6731 activity = NULL;
6733 sbuddy->activity_since = activity_since;
6735 sbuddy->user_avail = user_avail;
6736 sbuddy->user_avail_since = user_avail_since;
6738 g_free(sbuddy->note);
6739 sbuddy->note = NULL;
6740 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6742 sbuddy->is_oof_note = (xn_oof != NULL);
6744 g_free(sbuddy->device_name);
6745 sbuddy->device_name = NULL;
6746 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6748 if (!is_empty(cal_free_busy_base64)) {
6749 g_free(sbuddy->cal_start_time);
6750 sbuddy->cal_start_time = g_strdup(cal_start_time);
6752 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6754 g_free(sbuddy->cal_free_busy_base64);
6755 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6756 cal_free_busy_base64 = NULL;
6758 g_free(sbuddy->cal_free_busy);
6759 sbuddy->cal_free_busy = NULL;
6762 sbuddy->last_non_cal_status_id = status_id;
6763 g_free(sbuddy->last_non_cal_activity);
6764 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6766 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6767 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6769 sip->is_oof_note = sbuddy->is_oof_note;
6771 g_free(sip->note);
6772 sip->note = g_strdup(sbuddy->note);
6774 sip->note_since = time(NULL);
6777 g_free(sip->status);
6778 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6781 g_free(cal_free_busy_base64);
6782 g_free(activity);
6784 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
6785 sipe_got_user_status(sip, uri, status_id);
6787 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6788 sipe_user_info_has_updated(sip, xn_userinfo);
6791 g_free(note);
6792 xmlnode_free(xn_presentity);
6793 g_free(uri);
6794 g_free(self_uri);
6797 static void sipe_presence_mime_cb(gpointer user_data,
6798 const gchar *type,
6799 const gchar *body,
6800 gsize length)
6802 if (strstr(type,"application/rlmi+xml")) {
6803 process_incoming_notify_rlmi_resub(user_data, body, length);
6804 } else if (strstr(type, "text/xml+msrtc.pidf")) {
6805 process_incoming_notify_msrtc(user_data, body, length);
6806 } else {
6807 process_incoming_notify_rlmi(user_data, body, length);
6811 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6813 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6815 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
6817 if (ctype &&
6818 (strstr(ctype, "application/rlmi+xml") ||
6819 strstr(ctype, "application/msrtc-event-categories+xml")))
6821 if (strstr(ctype, "multipart"))
6823 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sip);
6825 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6827 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6829 else if(strstr(ctype, "application/rlmi+xml"))
6831 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6834 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6836 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6838 else
6840 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6844 static void sipe_presence_timeout_mime_cb(gpointer user_data,
6845 SIPE_UNUSED_PARAMETER const gchar *type,
6846 const gchar *body,
6847 gsize length)
6849 GSList **buddies = user_data;
6850 xmlnode *xml = xmlnode_from_str(body, length);
6852 if (xml && !sipe_strequal(xml->name, "list")) {
6853 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6854 *buddies = g_slist_append(*buddies, uri);
6857 xmlnode_free(xml);
6860 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6862 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6863 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6865 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
6867 if (ctype &&
6868 strstr(ctype, "multipart") &&
6869 (strstr(ctype, "application/rlmi+xml") ||
6870 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6871 GSList *buddies = NULL;
6872 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6874 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
6876 payload->host = g_strdup(who);
6877 payload->buddies = buddies;
6878 sipe_schedule_action(action_name, timeout,
6879 sipe_subscribe_presence_batched_routed,
6880 sipe_subscribe_presence_batched_routed_free,
6881 sip, payload);
6882 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
6884 } else {
6885 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6886 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
6888 g_free(action_name);
6892 * Dispatcher for all incoming subscription information
6893 * whether it comes from NOTIFY, BENOTIFY requests or
6894 * piggy-backed to subscription's OK responce.
6896 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6897 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6899 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6901 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6902 const gchar *event = sipmsg_find_header(msg, "Event");
6903 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6904 char *tmp;
6906 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
6907 event ? event : "",
6908 tmp = fix_newlines(msg->body));
6909 g_free(tmp);
6910 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
6912 /* implicit subscriptions */
6913 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6914 sipe_process_imdn(sip, msg);
6917 if (event) {
6918 /* for one off subscriptions (send with Expire: 0) */
6919 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
6921 sipe_process_provisioning_v2(sip, msg);
6923 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
6925 sipe_process_provisioning(sip, msg);
6927 else if (sipe_strcase_equal(event, "presence"))
6929 sipe_process_presence(sip, msg);
6931 else if (sipe_strcase_equal(event, "registration-notify"))
6933 sipe_process_registration_notify(sip, msg);
6936 if (!subscription_state || strstr(subscription_state, "active"))
6938 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
6940 sipe_process_roaming_contacts(sip, msg);
6942 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
6944 sipe_process_roaming_self(sip, msg);
6946 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
6948 sipe_process_roaming_acl(sip, msg);
6950 else if (sipe_strcase_equal(event, "presence.wpending"))
6952 sipe_process_presence_wpending(sip, msg);
6954 else if (sipe_strcase_equal(event, "conference"))
6956 sipe_process_conference(sip, msg);
6961 /* The server sends status 'terminated' */
6962 if (subscription_state && strstr(subscription_state, "terminated") ) {
6963 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6964 gchar *key = sipe_get_subscription_key(event, who);
6966 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
6967 g_free(who);
6969 if (g_hash_table_lookup(sip->subscriptions, key)) {
6970 g_hash_table_remove(sip->subscriptions, key);
6971 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
6974 g_free(key);
6977 if (!request && event) {
6978 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6979 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6980 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
6982 if (timeout) {
6983 /* 2 min ahead of expiration */
6984 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6986 if (sipe_strcase_equal(event, "presence.wpending") &&
6987 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6989 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6990 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6991 g_free(action_name);
6993 else if (sipe_strcase_equal(event, "presence") &&
6994 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6996 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6997 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6999 if (sip->batched_support) {
7000 sipe_process_presence_timeout(sip, msg, who, timeout);
7002 else {
7003 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7004 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
7006 g_free(action_name);
7007 g_free(who);
7012 /* The client responses on received a NOTIFY message */
7013 if (request && !benotify)
7015 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7020 * Whether user manually changed status or
7021 * it was changed automatically due to user
7022 * became inactive/active again
7024 static gboolean
7025 sipe_is_user_state(struct sipe_account_data *sip)
7027 gboolean res;
7028 time_t now = time(NULL);
7030 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
7031 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
7033 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
7035 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
7036 return res;
7039 static void
7040 send_presence_soap0(struct sipe_account_data *sip,
7041 gboolean do_publish_calendar,
7042 gboolean do_reset_status)
7044 struct sipe_ews* ews = sip->ews;
7045 int availability = 0;
7046 int activity = 0;
7047 gchar *body;
7048 gchar *tmp;
7049 gchar *tmp2 = NULL;
7050 gchar *res_note = NULL;
7051 gchar *res_oof = NULL;
7052 const gchar *note_pub = NULL;
7053 gchar *states = NULL;
7054 gchar *calendar_data = NULL;
7055 gchar *epid = get_epid(sip);
7056 time_t now = time(NULL);
7057 gchar *since_time_str = sipe_utils_time_to_str(now);
7058 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
7059 const char *user_input;
7060 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
7062 if (oof_note && sip->note) {
7063 SIPE_DEBUG_INFO("ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
7064 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
7067 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
7069 if (!sip->initial_state_published ||
7070 do_reset_status)
7072 g_free(sip->status);
7073 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
7076 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
7078 /* Note */
7079 if (pub_oof) {
7080 note_pub = oof_note;
7081 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
7082 ews->published = TRUE;
7083 } else if (sip->note) {
7084 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
7085 g_free(sip->note);
7086 sip->note = NULL;
7087 sip->is_oof_note = FALSE;
7088 sip->note_since = 0;
7089 } else {
7090 note_pub = sip->note;
7091 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
7095 if (note_pub)
7097 /* to protocol internal plain text format */
7098 tmp = sipe_backend_markup_strip_html(note_pub);
7099 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
7100 g_free(tmp);
7103 /* User State */
7104 if (!do_reset_status) {
7105 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
7107 gchar *activity_token = NULL;
7108 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
7110 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
7111 avail_2007,
7112 since_time_str,
7113 epid,
7114 activity_token);
7115 g_free(activity_token);
7117 else /* preserve existing publication */
7119 if (sip->user_states) {
7120 states = g_strdup(sip->user_states);
7123 } else {
7124 /* do nothing - then User state will be erased */
7126 sip->initial_state_published = TRUE;
7128 /* CalendarInfo */
7129 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
7131 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7132 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7133 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
7134 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
7135 fb_start_str,
7136 free_busy_base64);
7137 g_free(fb_start_str);
7138 g_free(free_busy_base64);
7141 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
7143 /* forming resulting XML */
7144 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7145 sip->username,
7146 availability,
7147 activity,
7148 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7149 res_note ? res_note : "",
7150 res_oof ? res_oof : "",
7151 states ? states : "",
7152 calendar_data ? calendar_data : "",
7153 epid,
7154 since_time_str,
7155 since_time_str,
7156 user_input);
7157 g_free(tmp);
7158 g_free(tmp2);
7159 g_free(res_note);
7160 g_free(states);
7161 g_free(calendar_data);
7163 send_soap_request(sip, body);
7165 g_free(body);
7166 g_free(since_time_str);
7167 g_free(epid);
7170 void
7171 send_presence_soap(struct sipe_account_data *sip,
7172 gboolean do_publish_calendar)
7174 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7178 static gboolean
7179 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7180 struct sipmsg *msg,
7181 struct transaction *trans)
7183 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7185 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7186 xmlnode *xml;
7187 xmlnode *node;
7188 gchar *fault_code;
7189 GHashTable *faults;
7190 int index_our;
7191 gboolean has_device_publication = FALSE;
7193 xml = xmlnode_from_str(msg->body, msg->bodylen);
7195 /* test if version mismatch fault */
7196 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7197 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7198 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
7199 g_free(fault_code);
7200 xmlnode_free(xml);
7201 return TRUE;
7203 g_free(fault_code);
7205 /* accumulating information about faulty versions */
7206 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7207 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7208 node;
7209 node = xmlnode_get_next_twin(node))
7211 const gchar *index = xmlnode_get_attrib(node, "index");
7212 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7214 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7215 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
7217 xmlnode_free(xml);
7219 /* here we are parsing own request to figure out what publication
7220 * referensed here only by index went wrong
7222 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7224 /* publication */
7225 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7226 index_our = 1; /* starts with 1 - our first publication */
7227 node;
7228 node = xmlnode_get_next_twin(node), index_our++)
7230 gchar *idx = g_strdup_printf("%d", index_our);
7231 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7232 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7233 g_free(idx);
7235 if (sipe_strequal("device", categoryName)) {
7236 has_device_publication = TRUE;
7239 if (curVersion) { /* fault exist on this index */
7240 const gchar *container = xmlnode_get_attrib(node, "container");
7241 const gchar *instance = xmlnode_get_attrib(node, "instance");
7242 /* key is <category><instance><container> */
7243 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7244 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
7246 if (category) {
7247 struct sipe_publication *publication =
7248 g_hash_table_lookup(category, key);
7250 SIPE_DEBUG_INFO("key is %s", key);
7252 if (publication) {
7253 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7254 key, curVersion, publication->version);
7255 /* updating publication's version to the correct one */
7256 publication->version = atoi(curVersion);
7258 } else {
7259 /* We somehow lost this category from our publications... */
7260 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
7261 publication->category = g_strdup(categoryName);
7262 publication->instance = atoi(instance);
7263 publication->container = atoi(container);
7264 publication->version = atoi(curVersion);
7265 category = g_hash_table_new_full(g_str_hash, g_str_equal,
7266 g_free, (GDestroyNotify)free_publication);
7267 g_hash_table_insert(category, g_strdup(key), publication);
7268 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
7269 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
7271 g_free(key);
7274 xmlnode_free(xml);
7275 g_hash_table_destroy(faults);
7277 /* rebublishing with right versions */
7278 if (has_device_publication) {
7279 send_publish_category_initial(sip);
7280 } else {
7281 send_presence_status(sip);
7284 return TRUE;
7288 * Returns 'device' XML part for publication.
7289 * Must be g_free'd after use.
7291 static gchar *
7292 sipe_publish_get_category_device(struct sipe_account_data *sip)
7294 gchar *uri;
7295 gchar *doc;
7296 gchar *epid = get_epid(sip);
7297 gchar *uuid = generateUUIDfromEPID(epid);
7298 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7299 /* key is <category><instance><container> */
7300 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7301 struct sipe_publication *publication =
7302 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7304 g_free(key);
7305 g_free(epid);
7307 uri = sip_uri_self(sip);
7308 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7309 device_instance,
7310 publication ? publication->version : 0,
7311 uuid,
7312 uri,
7313 "00:00:00+01:00", /* @TODO make timezone real*/
7314 g_get_host_name()
7317 g_free(uri);
7318 g_free(uuid);
7320 return doc;
7324 * A service method - use
7325 * - send_publish_get_category_state_machine and
7326 * - send_publish_get_category_state_user instead.
7327 * Must be g_free'd after use.
7329 static gchar *
7330 sipe_publish_get_category_state(struct sipe_account_data *sip,
7331 gboolean is_user_state)
7333 int availability = sipe_get_availability_by_status(sip->status, NULL);
7334 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7335 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7336 /* key is <category><instance><container> */
7337 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7338 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7339 struct sipe_publication *publication_2 =
7340 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7341 struct sipe_publication *publication_3 =
7342 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7344 g_free(key_2);
7345 g_free(key_3);
7347 if (publication_2 && (publication_2->availability == availability))
7349 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7350 return NULL; /* nothing to update */
7353 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7354 instance,
7355 publication_2 ? publication_2->version : 0,
7356 availability,
7357 instance,
7358 publication_3 ? publication_3->version : 0,
7359 availability);
7363 * Only Busy and OOF calendar event are published.
7364 * Different instances are used for that.
7366 * Must be g_free'd after use.
7368 static gchar *
7369 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7370 struct sipe_cal_event *event,
7371 const char *uri,
7372 int cal_satus)
7374 gchar *start_time_str;
7375 int availability = 0;
7376 gchar *res;
7377 gchar *tmp = NULL;
7378 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7379 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7380 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7382 /* key is <category><instance><container> */
7383 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7384 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7385 struct sipe_publication *publication_2 =
7386 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7387 struct sipe_publication *publication_3 =
7388 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7390 g_free(key_2);
7391 g_free(key_3);
7393 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7394 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7395 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
7396 return NULL;
7399 if (event &&
7400 publication_3 &&
7401 (publication_3->availability == availability) &&
7402 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7404 g_free(tmp);
7405 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7406 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
7407 return NULL; /* nothing to update */
7409 g_free(tmp);
7411 if (event &&
7412 (event->cal_status == SIPE_CAL_BUSY ||
7413 event->cal_status == SIPE_CAL_OOF))
7415 gchar *availability_xml_str = NULL;
7416 gchar *activity_xml_str = NULL;
7418 if (event->cal_status == SIPE_CAL_BUSY) {
7419 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7422 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7423 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7424 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7425 "minAvailability=\"6500\"",
7426 "maxAvailability=\"8999\"");
7427 } else if (event->cal_status == SIPE_CAL_OOF) {
7428 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7429 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7430 "minAvailability=\"12000\"",
7431 "");
7433 start_time_str = sipe_utils_time_to_str(event->start_time);
7435 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7436 instance,
7437 publication_2 ? publication_2->version : 0,
7438 uri,
7439 start_time_str,
7440 availability_xml_str ? availability_xml_str : "",
7441 activity_xml_str ? activity_xml_str : "",
7442 event->subject ? event->subject : "",
7443 event->location ? event->location : "",
7445 instance,
7446 publication_3 ? publication_3->version : 0,
7447 uri,
7448 start_time_str,
7449 availability_xml_str ? availability_xml_str : "",
7450 activity_xml_str ? activity_xml_str : "",
7451 event->subject ? event->subject : "",
7452 event->location ? event->location : ""
7454 g_free(start_time_str);
7455 g_free(availability_xml_str);
7456 g_free(activity_xml_str);
7459 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7461 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7462 instance,
7463 publication_2 ? publication_2->version : 0,
7465 instance,
7466 publication_3 ? publication_3->version : 0
7470 return res;
7474 * Returns 'machineState' XML part for publication.
7475 * Must be g_free'd after use.
7477 static gchar *
7478 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7480 return sipe_publish_get_category_state(sip, FALSE);
7484 * Returns 'userState' XML part for publication.
7485 * Must be g_free'd after use.
7487 static gchar *
7488 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7490 return sipe_publish_get_category_state(sip, TRUE);
7494 * Returns 'note' XML part for publication.
7495 * Must be g_free'd after use.
7497 * Protocol format for Note is plain text.
7499 * @param note a note in Sipe internal HTML format
7500 * @param note_type either personal or OOF
7502 static gchar *
7503 sipe_publish_get_category_note(struct sipe_account_data *sip,
7504 const char *note, /* html */
7505 const char *note_type,
7506 time_t note_start,
7507 time_t note_end)
7509 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7510 /* key is <category><instance><container> */
7511 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7512 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7513 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7515 struct sipe_publication *publication_note_200 =
7516 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7517 struct sipe_publication *publication_note_300 =
7518 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7519 struct sipe_publication *publication_note_400 =
7520 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7522 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7523 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7524 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7525 char *res, *tmp1, *tmp2, *tmp3;
7526 char *start_time_attr;
7527 char *end_time_attr;
7529 g_free(tmp);
7530 tmp = NULL;
7531 g_free(key_note_200);
7532 g_free(key_note_300);
7533 g_free(key_note_400);
7535 /* we even need to republish empty note */
7536 if (sipe_strequal(n1, n2))
7538 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7539 g_free(n1);
7540 return NULL; /* nothing to update */
7543 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7544 g_free(tmp);
7545 tmp = NULL;
7546 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7547 g_free(tmp);
7549 if (n1) {
7550 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7551 instance,
7552 200,
7553 publication_note_200 ? publication_note_200->version : 0,
7554 note_type,
7555 start_time_attr ? start_time_attr : "",
7556 end_time_attr ? end_time_attr : "",
7557 n1);
7559 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7560 instance,
7561 300,
7562 publication_note_300 ? publication_note_300->version : 0,
7563 note_type,
7564 start_time_attr ? start_time_attr : "",
7565 end_time_attr ? end_time_attr : "",
7566 n1);
7568 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7569 instance,
7570 400,
7571 publication_note_400 ? publication_note_400->version : 0,
7572 note_type,
7573 start_time_attr ? start_time_attr : "",
7574 end_time_attr ? end_time_attr : "",
7575 n1);
7576 } else {
7577 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7578 "note",
7579 instance,
7580 200,
7581 publication_note_200 ? publication_note_200->version : 0,
7582 "static");
7583 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7584 "note",
7585 instance,
7586 300,
7587 publication_note_200 ? publication_note_200->version : 0,
7588 "static");
7589 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7590 "note",
7591 instance,
7592 400,
7593 publication_note_200 ? publication_note_200->version : 0,
7594 "static");
7596 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7598 g_free(start_time_attr);
7599 g_free(end_time_attr);
7600 g_free(tmp1);
7601 g_free(tmp2);
7602 g_free(tmp3);
7603 g_free(n1);
7605 return res;
7609 * Returns 'calendarData' XML part with WorkingHours for publication.
7610 * Must be g_free'd after use.
7612 static gchar *
7613 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7615 struct sipe_ews* ews = sip->ews;
7617 /* key is <category><instance><container> */
7618 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7619 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7620 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7621 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7622 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7623 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7625 struct sipe_publication *publication_cal_1 =
7626 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7627 struct sipe_publication *publication_cal_100 =
7628 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7629 struct sipe_publication *publication_cal_200 =
7630 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7631 struct sipe_publication *publication_cal_300 =
7632 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7633 struct sipe_publication *publication_cal_400 =
7634 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7635 struct sipe_publication *publication_cal_32000 =
7636 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7638 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7639 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7641 g_free(key_cal_1);
7642 g_free(key_cal_100);
7643 g_free(key_cal_200);
7644 g_free(key_cal_300);
7645 g_free(key_cal_400);
7646 g_free(key_cal_32000);
7648 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7649 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7650 return NULL;
7653 if (sipe_strequal(n1, n2))
7655 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7656 return NULL; /* nothing to update */
7659 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7660 /* 1 */
7661 publication_cal_1 ? publication_cal_1->version : 0,
7662 ews->email,
7663 ews->working_hours_xml_str,
7664 /* 100 - Public */
7665 publication_cal_100 ? publication_cal_100->version : 0,
7666 /* 200 - Company */
7667 publication_cal_200 ? publication_cal_200->version : 0,
7668 ews->email,
7669 ews->working_hours_xml_str,
7670 /* 300 - Team */
7671 publication_cal_300 ? publication_cal_300->version : 0,
7672 ews->email,
7673 ews->working_hours_xml_str,
7674 /* 400 - Personal */
7675 publication_cal_400 ? publication_cal_400->version : 0,
7676 ews->email,
7677 ews->working_hours_xml_str,
7678 /* 32000 - Blocked */
7679 publication_cal_32000 ? publication_cal_32000->version : 0
7684 * Returns 'calendarData' XML part with FreeBusy for publication.
7685 * Must be g_free'd after use.
7687 static gchar *
7688 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7690 struct sipe_ews* ews = sip->ews;
7691 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7692 char *fb_start_str;
7693 char *free_busy_base64;
7694 const char *st;
7695 const char *fb;
7696 char *res;
7698 /* key is <category><instance><container> */
7699 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7700 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7701 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7702 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7703 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7704 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7706 struct sipe_publication *publication_cal_1 =
7707 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7708 struct sipe_publication *publication_cal_100 =
7709 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7710 struct sipe_publication *publication_cal_200 =
7711 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7712 struct sipe_publication *publication_cal_300 =
7713 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7714 struct sipe_publication *publication_cal_400 =
7715 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7716 struct sipe_publication *publication_cal_32000 =
7717 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7719 g_free(key_cal_1);
7720 g_free(key_cal_100);
7721 g_free(key_cal_200);
7722 g_free(key_cal_300);
7723 g_free(key_cal_400);
7724 g_free(key_cal_32000);
7726 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7727 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7728 return NULL;
7731 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7732 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7734 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7735 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7737 /* we will rebuplish the same data to refresh publication time,
7738 * so if data from multiple sources, most recent will be choosen
7740 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7742 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7743 // g_free(fb_start_str);
7744 // g_free(free_busy_base64);
7745 // return NULL; /* nothing to update */
7748 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7749 /* 1 */
7750 cal_data_instance,
7751 publication_cal_1 ? publication_cal_1->version : 0,
7752 /* 100 - Public */
7753 cal_data_instance,
7754 publication_cal_100 ? publication_cal_100->version : 0,
7755 /* 200 - Company */
7756 cal_data_instance,
7757 publication_cal_200 ? publication_cal_200->version : 0,
7758 ews->email,
7759 fb_start_str,
7760 free_busy_base64,
7761 /* 300 - Team */
7762 cal_data_instance,
7763 publication_cal_300 ? publication_cal_300->version : 0,
7764 ews->email,
7765 fb_start_str,
7766 free_busy_base64,
7767 /* 400 - Personal */
7768 cal_data_instance,
7769 publication_cal_400 ? publication_cal_400->version : 0,
7770 ews->email,
7771 fb_start_str,
7772 free_busy_base64,
7773 /* 32000 - Blocked */
7774 cal_data_instance,
7775 publication_cal_32000 ? publication_cal_32000->version : 0
7778 g_free(fb_start_str);
7779 g_free(free_busy_base64);
7780 return res;
7783 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7785 gchar *uri;
7786 gchar *doc;
7787 gchar *tmp;
7788 gchar *hdr;
7790 uri = sip_uri_self(sip);
7791 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7792 uri,
7793 publications);
7795 tmp = get_contact(sip);
7796 hdr = g_strdup_printf("Contact: %s\r\n"
7797 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7799 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7801 g_free(tmp);
7802 g_free(hdr);
7803 g_free(uri);
7804 g_free(doc);
7807 static void
7808 send_publish_category_initial(struct sipe_account_data *sip)
7810 gchar *pub_device = sipe_publish_get_category_device(sip);
7811 gchar *pub_machine;
7812 gchar *publications;
7814 g_free(sip->status);
7815 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7817 pub_machine = sipe_publish_get_category_state_machine(sip);
7818 publications = g_strdup_printf("%s%s",
7819 pub_device,
7820 pub_machine ? pub_machine : "");
7821 g_free(pub_device);
7822 g_free(pub_machine);
7824 send_presence_publish(sip, publications);
7825 g_free(publications);
7828 static void
7829 send_presence_category_publish(struct sipe_account_data *sip)
7831 gchar *pub_state = sipe_is_user_state(sip) ?
7832 sipe_publish_get_category_state_user(sip) :
7833 sipe_publish_get_category_state_machine(sip);
7834 gchar *pub_note = sipe_publish_get_category_note(sip,
7835 sip->note,
7836 sip->is_oof_note ? "OOF" : "personal",
7839 gchar *publications;
7841 if (!pub_state && !pub_note) {
7842 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
7843 return;
7846 publications = g_strdup_printf("%s%s",
7847 pub_state ? pub_state : "",
7848 pub_note ? pub_note : "");
7850 g_free(pub_state);
7851 g_free(pub_note);
7853 send_presence_publish(sip, publications);
7854 g_free(publications);
7858 * Publishes self status
7859 * based on own calendar information.
7861 * For 2007+
7863 void
7864 publish_calendar_status_self(struct sipe_account_data *sip)
7866 struct sipe_cal_event* event = NULL;
7867 gchar *pub_cal_working_hours = NULL;
7868 gchar *pub_cal_free_busy = NULL;
7869 gchar *pub_calendar = NULL;
7870 gchar *pub_calendar2 = NULL;
7871 gchar *pub_oof_note = NULL;
7872 const gchar *oof_note;
7873 time_t oof_start = 0;
7874 time_t oof_end = 0;
7876 if (!sip->ews) {
7877 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
7878 return;
7881 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
7882 if (sip->ews->cal_events) {
7883 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7886 if (!event) {
7887 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
7888 } else {
7889 char *desc = sipe_cal_event_describe(event);
7890 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7891 g_free(desc);
7894 /* Logic
7895 if OOF
7896 OOF publish, Busy clean
7897 ilse if Busy
7898 OOF clean, Busy publish
7899 else
7900 OOF clean, Busy clean
7902 if (event && event->cal_status == SIPE_CAL_OOF) {
7903 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7904 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7905 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7906 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7907 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7908 } else {
7909 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7910 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7913 oof_note = sipe_ews_get_oof_note(sip->ews);
7914 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7915 oof_start = sip->ews->oof_start;
7916 oof_end = sip->ews->oof_end;
7918 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7920 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7921 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7923 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7924 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
7925 } else {
7926 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7927 pub_cal_working_hours ? pub_cal_working_hours : "",
7928 pub_cal_free_busy ? pub_cal_free_busy : "",
7929 pub_calendar ? pub_calendar : "",
7930 pub_calendar2 ? pub_calendar2 : "",
7931 pub_oof_note ? pub_oof_note : "");
7933 send_presence_publish(sip, publications);
7934 g_free(publications);
7937 g_free(pub_cal_working_hours);
7938 g_free(pub_cal_free_busy);
7939 g_free(pub_calendar);
7940 g_free(pub_calendar2);
7941 g_free(pub_oof_note);
7943 /* repeat scheduling */
7944 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7947 static void send_presence_status(struct sipe_account_data *sip)
7949 PurpleStatus * status = purple_account_get_active_status(sip->account);
7951 if (!status) return;
7953 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
7954 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7955 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7957 if (sip->ocs2007) {
7958 send_presence_category_publish(sip);
7959 } else {
7960 send_presence_soap(sip, FALSE);
7964 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7966 gboolean found = FALSE;
7967 const char *method = msg->method ? msg->method : "NOT FOUND";
7968 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
7969 if (msg->response == 0) { /* request */
7970 if (sipe_strequal(method, "MESSAGE")) {
7971 process_incoming_message(sip, msg);
7972 found = TRUE;
7973 } else if (sipe_strequal(method, "NOTIFY")) {
7974 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
7975 process_incoming_notify(sip, msg, TRUE, FALSE);
7976 found = TRUE;
7977 } else if (sipe_strequal(method, "BENOTIFY")) {
7978 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
7979 process_incoming_notify(sip, msg, TRUE, TRUE);
7980 found = TRUE;
7981 } else if (sipe_strequal(method, "INVITE")) {
7982 process_incoming_invite(sip, msg);
7983 found = TRUE;
7984 } else if (sipe_strequal(method, "REFER")) {
7985 process_incoming_refer(sip, msg);
7986 found = TRUE;
7987 } else if (sipe_strequal(method, "OPTIONS")) {
7988 process_incoming_options(sip, msg);
7989 found = TRUE;
7990 } else if (sipe_strequal(method, "INFO")) {
7991 process_incoming_info(sip, msg);
7992 found = TRUE;
7993 } else if (sipe_strequal(method, "ACK")) {
7994 // ACK's don't need any response
7995 found = TRUE;
7996 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7997 // LCS 2005 sends us these - just respond 200 OK
7998 found = TRUE;
7999 send_sip_response(sip->gc, msg, 200, "OK", NULL);
8000 } else if (sipe_strequal(method, "BYE")) {
8001 process_incoming_bye(sip, msg);
8002 found = TRUE;
8003 } else {
8004 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
8006 } else { /* response */
8007 struct transaction *trans = transactions_find(sip, msg);
8008 if (trans) {
8009 if (msg->response == 407) {
8010 gchar *resend, *auth;
8011 const gchar *ptmp;
8013 if (sip->proxy.retries > 30) return;
8014 sip->proxy.retries++;
8015 /* do proxy authentication */
8017 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
8019 fill_auth(ptmp, &sip->proxy);
8020 auth = auth_header(sip, &sip->proxy, trans->msg);
8021 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
8022 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
8023 g_free(auth);
8024 resend = sipmsg_to_string(trans->msg);
8025 /* resend request */
8026 sendout_pkt(sip->gc, resend);
8027 g_free(resend);
8028 } else {
8029 if (msg->response < 200) {
8030 /* ignore provisional response */
8031 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
8032 } else {
8033 sip->proxy.retries = 0;
8034 if (sipe_strequal(trans->msg->method, "REGISTER")) {
8035 if (msg->response == 401)
8037 sip->registrar.retries++;
8039 else
8041 sip->registrar.retries = 0;
8043 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
8044 } else {
8045 if (msg->response == 401) {
8046 gchar *resend, *auth, *ptmp;
8047 const char* auth_scheme;
8049 if (sip->registrar.retries > 4) return;
8050 sip->registrar.retries++;
8052 auth_scheme = sipe_get_auth_scheme_name(sip);
8053 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
8055 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
8056 if (!ptmp) {
8057 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
8058 sip->gc->wants_to_die = TRUE;
8059 purple_connection_error(sip->gc, tmp2);
8060 g_free(tmp2);
8061 return;
8064 fill_auth(ptmp, &sip->registrar);
8065 auth = auth_header(sip, &sip->registrar, trans->msg);
8066 sipmsg_remove_header_now(trans->msg, "Authorization");
8067 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
8068 g_free(auth);
8069 resend = sipmsg_to_string(trans->msg);
8070 /* resend request */
8071 sendout_pkt(sip->gc, resend);
8072 g_free(resend);
8076 if (trans->callback) {
8077 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
8078 /* call the callback to process response*/
8079 (trans->callback)(sip, msg, trans);
8082 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
8083 transactions_remove(sip, trans);
8087 found = TRUE;
8088 } else {
8089 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
8092 if (!found) {
8093 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
8097 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
8099 char *cur;
8100 char *dummy;
8101 char *tmp;
8102 struct sipmsg *msg;
8103 int restlen;
8104 cur = conn->inbuf;
8106 /* according to the RFC remove CRLF at the beginning */
8107 while (*cur == '\r' || *cur == '\n') {
8108 cur++;
8110 if (cur != conn->inbuf) {
8111 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
8112 conn->inbufused = strlen(conn->inbuf);
8115 /* Received a full Header? */
8116 sip->processing_input = TRUE;
8117 while (sip->processing_input &&
8118 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
8119 time_t currtime = time(NULL);
8120 cur += 2;
8121 cur[0] = '\0';
8122 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
8123 g_free(tmp);
8124 msg = sipmsg_parse_header(conn->inbuf);
8125 cur[0] = '\r';
8126 cur += 2;
8127 restlen = conn->inbufused - (cur - conn->inbuf);
8128 if (msg && restlen >= msg->bodylen) {
8129 dummy = g_malloc(msg->bodylen + 1);
8130 memcpy(dummy, cur, msg->bodylen);
8131 dummy[msg->bodylen] = '\0';
8132 msg->body = dummy;
8133 cur += msg->bodylen;
8134 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
8135 conn->inbufused = strlen(conn->inbuf);
8136 } else {
8137 if (msg){
8138 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen, msg->bodylen, (int)strlen(conn->inbuf));
8139 sipmsg_free(msg);
8141 return;
8144 /*if (msg->body) {
8145 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8148 // Verify the signature before processing it
8149 if (sip->registrar.gssapi_context) {
8150 struct sipmsg_breakdown msgbd;
8151 gchar *signature_input_str;
8152 gchar *rspauth;
8153 msgbd.msg = msg;
8154 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8155 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8157 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8159 if (rspauth != NULL) {
8160 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8161 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8162 process_input_message(sip, msg);
8163 } else {
8164 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8165 purple_connection_error(sip->gc, _("Invalid message signature received"));
8166 sip->gc->wants_to_die = TRUE;
8168 } else if (msg->response == 401) {
8169 purple_connection_error(sip->gc, _("Authentication failed"));
8170 sip->gc->wants_to_die = TRUE;
8172 g_free(signature_input_str);
8174 g_free(rspauth);
8175 sipmsg_breakdown_free(&msgbd);
8176 } else {
8177 process_input_message(sip, msg);
8180 sipmsg_free(msg);
8184 static void sipe_udp_process(gpointer data, gint source,
8185 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8187 PurpleConnection *gc = data;
8188 struct sipe_account_data *sip = gc->proto_data;
8189 int len;
8191 static char buffer[65536];
8192 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8193 time_t currtime = time(NULL);
8194 struct sipmsg *msg;
8195 buffer[len] = '\0';
8196 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), buffer);
8197 msg = sipmsg_parse_msg(buffer);
8198 if (msg) process_input_message(sip, msg);
8202 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8204 struct sipe_account_data *sip = gc->proto_data;
8205 PurpleSslConnection *gsc = sip->gsc;
8207 SIPE_DEBUG_ERROR("%s", debug);
8208 purple_connection_error(gc, msg);
8210 /* Invalidate this connection. Next send will open a new one */
8211 if (gsc) {
8212 connection_remove(sip, gsc->fd);
8213 purple_ssl_close(gsc);
8215 sip->gsc = NULL;
8216 sip->fd = -1;
8219 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8220 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8222 PurpleConnection *gc = data;
8223 struct sipe_account_data *sip;
8224 struct sip_connection *conn;
8225 int readlen, len;
8226 gboolean firstread = TRUE;
8228 /* NOTE: This check *IS* necessary */
8229 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8230 purple_ssl_close(gsc);
8231 return;
8234 sip = gc->proto_data;
8235 conn = connection_find(sip, gsc->fd);
8236 if (conn == NULL) {
8237 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
8238 gc->wants_to_die = TRUE;
8239 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8240 return;
8243 /* Read all available data from the SSL connection */
8244 do {
8245 /* Increase input buffer size as needed */
8246 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8247 conn->inbuflen += SIMPLE_BUF_INC;
8248 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8249 SIPE_DEBUG_INFO("sipe_input_cb_ssl: new input buffer length %d", conn->inbuflen);
8252 /* Try to read as much as there is space left in the buffer */
8253 readlen = conn->inbuflen - conn->inbufused - 1;
8254 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8256 if (len < 0 && errno == EAGAIN) {
8257 /* Try again later */
8258 return;
8259 } else if (len < 0) {
8260 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8261 return;
8262 } else if (firstread && (len == 0)) {
8263 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8264 return;
8267 conn->inbufused += len;
8268 firstread = FALSE;
8270 /* Equivalence indicates that there is possibly more data to read */
8271 } while (len == readlen);
8273 conn->inbuf[conn->inbufused] = '\0';
8274 process_input(sip, conn);
8278 static void sipe_input_cb(gpointer data, gint source,
8279 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8281 PurpleConnection *gc = data;
8282 struct sipe_account_data *sip = gc->proto_data;
8283 int len;
8284 struct sip_connection *conn = connection_find(sip, source);
8285 if (!conn) {
8286 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found!");
8287 return;
8290 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8291 conn->inbuflen += SIMPLE_BUF_INC;
8292 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8295 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8297 if (len < 0 && errno == EAGAIN)
8298 return;
8299 else if (len <= 0) {
8300 SIPE_DEBUG_INFO_NOFORMAT("sipe_input_cb: read error");
8301 connection_remove(sip, source);
8302 if (sip->fd == source) sip->fd = -1;
8303 return;
8306 conn->inbufused += len;
8307 conn->inbuf[conn->inbufused] = '\0';
8309 process_input(sip, conn);
8312 /* Callback for new connections on incoming TCP port */
8313 static void sipe_newconn_cb(gpointer data, gint source,
8314 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8316 PurpleConnection *gc = data;
8317 struct sipe_account_data *sip = gc->proto_data;
8318 struct sip_connection *conn;
8320 int newfd = accept(source, NULL, NULL);
8322 conn = connection_create(sip, newfd);
8324 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8327 static void login_cb(gpointer data, gint source,
8328 SIPE_UNUSED_PARAMETER const gchar *error_message)
8330 PurpleConnection *gc = data;
8331 struct sipe_account_data *sip;
8332 struct sip_connection *conn;
8334 if (!PURPLE_CONNECTION_IS_VALID(gc))
8336 if (source >= 0)
8337 close(source);
8338 return;
8341 if (source < 0) {
8342 purple_connection_error(gc, _("Could not connect"));
8343 return;
8346 sip = gc->proto_data;
8347 sip->fd = source;
8348 sip->last_keepalive = time(NULL);
8350 conn = connection_create(sip, source);
8352 do_register(sip);
8354 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8357 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8358 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8360 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8361 if (sip == NULL) return;
8363 do_register(sip);
8366 static guint sipe_ht_hash_nick(const char *nick)
8368 char *lc = g_utf8_strdown(nick, -1);
8369 guint bucket = g_str_hash(lc);
8370 g_free(lc);
8372 return bucket;
8375 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8377 char *nick1_norm = NULL;
8378 char *nick2_norm = NULL;
8379 gboolean equal;
8381 if (nick1 == NULL && nick2 == NULL) return TRUE;
8382 if (nick1 == NULL || nick2 == NULL ||
8383 !g_utf8_validate(nick1, -1, NULL) ||
8384 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
8386 nick1_norm = g_utf8_casefold(nick1, -1);
8387 nick2_norm = g_utf8_casefold(nick2, -1);
8388 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
8389 g_free(nick2_norm);
8390 g_free(nick1_norm);
8392 return equal;
8395 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8397 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8399 sip->listen_data = NULL;
8401 if (listenfd == -1) {
8402 purple_connection_error(sip->gc, _("Could not create listen socket"));
8403 return;
8406 sip->fd = listenfd;
8408 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8409 sip->listenfd = sip->fd;
8411 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8413 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8414 do_register(sip);
8417 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8418 SIPE_UNUSED_PARAMETER const char *error_message)
8420 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8422 sip->query_data = NULL;
8424 if (!hosts || !hosts->data) {
8425 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8426 return;
8429 hosts = g_slist_remove(hosts, hosts->data);
8430 g_free(sip->serveraddr);
8431 sip->serveraddr = hosts->data;
8432 hosts = g_slist_remove(hosts, hosts->data);
8433 while (hosts) {
8434 void *tmp = hosts->data;
8435 hosts = g_slist_remove(hosts, tmp);
8436 hosts = g_slist_remove(hosts, tmp);
8437 g_free(tmp);
8440 /* create socket for incoming connections */
8441 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8442 sipe_udp_host_resolved_listen_cb, sip);
8443 if (sip->listen_data == NULL) {
8444 purple_connection_error(sip->gc, _("Could not create listen socket"));
8445 return;
8449 static const struct sipe_service_data *current_service = NULL;
8451 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8452 PurpleSslErrorType error,
8453 gpointer data)
8455 PurpleConnection *gc = data;
8456 struct sipe_account_data *sip;
8458 /* If the connection is already disconnected, we don't need to do anything else */
8459 if (!PURPLE_CONNECTION_IS_VALID(gc))
8460 return;
8462 sip = gc->proto_data;
8463 current_service = sip->service_data;
8464 if (current_service) {
8465 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8466 current_service->transport ? current_service->transport : "NULL",
8467 current_service->service ? current_service->service : "NULL");
8470 sip->fd = -1;
8471 sip->gsc = NULL;
8473 switch(error) {
8474 case PURPLE_SSL_CONNECT_FAILED:
8475 purple_connection_error(gc, _("Connection failed"));
8476 break;
8477 case PURPLE_SSL_HANDSHAKE_FAILED:
8478 purple_connection_error(gc, _("SSL handshake failed"));
8479 break;
8480 case PURPLE_SSL_CERTIFICATE_INVALID:
8481 purple_connection_error(gc, _("SSL certificate invalid"));
8482 break;
8486 static void
8487 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8489 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8490 PurpleProxyConnectData *connect_data;
8492 sip->listen_data = NULL;
8494 sip->listenfd = listenfd;
8495 if (sip->listenfd == -1) {
8496 purple_connection_error(sip->gc, _("Could not create listen socket"));
8497 return;
8500 SIPE_DEBUG_INFO("listenfd: %d", sip->listenfd);
8501 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8502 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8503 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8504 sipe_newconn_cb, sip->gc);
8505 SIPE_DEBUG_INFO("connecting to %s port %d",
8506 sip->realhostname, sip->realport);
8507 /* open tcp connection to the server */
8508 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8509 sip->realport, login_cb, sip->gc);
8511 if (connect_data == NULL) {
8512 purple_connection_error(sip->gc, _("Could not create socket"));
8516 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8518 PurpleAccount *account = sip->account;
8519 PurpleConnection *gc = sip->gc;
8521 if (port == 0) {
8522 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8525 sip->realhostname = hostname;
8526 sip->realport = port;
8528 SIPE_DEBUG_INFO("create_connection - hostname: %s port: %d",
8529 hostname, port);
8531 /* TODO: is there a good default grow size? */
8532 if (sip->transport != SIPE_TRANSPORT_UDP)
8533 sip->txbuf = purple_circ_buffer_new(0);
8535 if (sip->transport == SIPE_TRANSPORT_TLS) {
8536 /* SSL case */
8537 if (!purple_ssl_is_supported()) {
8538 gc->wants_to_die = TRUE;
8539 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8540 return;
8543 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
8545 sip->gsc = purple_ssl_connect(account, hostname, port,
8546 login_cb_ssl, sipe_ssl_connect_failure, gc);
8547 if (sip->gsc == NULL) {
8548 purple_connection_error(gc, _("Could not create SSL context"));
8549 return;
8551 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8552 /* UDP case */
8553 SIPE_DEBUG_INFO_NOFORMAT("using UDP");
8555 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8556 if (sip->query_data == NULL) {
8557 purple_connection_error(gc, _("Could not resolve hostname"));
8559 } else {
8560 /* TCP case */
8561 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
8562 /* create socket for incoming connections */
8563 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8564 sipe_tcp_connect_listen_cb, sip);
8565 if (sip->listen_data == NULL) {
8566 purple_connection_error(gc, _("Could not create listen socket"));
8567 return;
8572 /* Service list for autodection */
8573 static const struct sipe_service_data service_autodetect[] = {
8574 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8575 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8576 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8577 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8578 { NULL, NULL, 0 }
8581 /* Service list for SSL/TLS */
8582 static const struct sipe_service_data service_tls[] = {
8583 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8584 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8585 { NULL, NULL, 0 }
8588 /* Service list for TCP */
8589 static const struct sipe_service_data service_tcp[] = {
8590 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8591 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8592 { NULL, NULL, 0 }
8595 /* Service list for UDP */
8596 static const struct sipe_service_data service_udp[] = {
8597 { "sip", "udp", SIPE_TRANSPORT_UDP },
8598 { NULL, NULL, 0 }
8601 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8602 static void resolve_next_service(struct sipe_account_data *sip,
8603 const struct sipe_service_data *start)
8605 if (start) {
8606 sip->service_data = start;
8607 } else {
8608 sip->service_data++;
8609 if (sip->service_data->service == NULL) {
8610 gchar *hostname;
8611 /* Try connecting to the SIP hostname directly */
8612 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8613 if (sip->auto_transport) {
8614 // If SSL is supported, default to using it; OCS servers aren't configured
8615 // by default to accept TCP
8616 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8617 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8618 SIPE_DEBUG_INFO_NOFORMAT("set transport type..");
8621 hostname = g_strdup(sip->sipdomain);
8622 create_connection(sip, hostname, 0);
8623 return;
8627 /* Try to resolve next service */
8628 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8629 sip->service_data->transport,
8630 sip->sipdomain,
8631 srvresolved, sip);
8634 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8636 struct sipe_account_data *sip = data;
8638 sip->srv_query_data = NULL;
8640 /* find the host to connect to */
8641 if (results) {
8642 gchar *hostname = g_strdup(resp->hostname);
8643 int port = resp->port;
8644 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8645 hostname, port);
8646 g_free(resp);
8648 sip->transport = sip->service_data->type;
8650 create_connection(sip, hostname, port);
8651 } else {
8652 resolve_next_service(sip, NULL);
8656 static void sipe_login(PurpleAccount *account)
8658 PurpleConnection *gc;
8659 struct sipe_account_data *sip;
8660 gchar **signinname_login, **userserver;
8661 const char *transport;
8662 const char *email;
8664 const char *username = purple_account_get_username(account);
8665 gc = purple_account_get_connection(account);
8667 SIPE_DEBUG_INFO("sipe_login: username '%s'", username);
8669 if (strpbrk(username, "\t\v\r\n") != NULL) {
8670 gc->wants_to_die = TRUE;
8671 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8672 return;
8675 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8676 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8677 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8678 sip->gc = gc;
8679 sip->account = account;
8680 sip->reregister_set = FALSE;
8681 sip->reauthenticate_set = FALSE;
8682 sip->subscribed = FALSE;
8683 sip->subscribed_buddies = FALSE;
8684 sip->initial_state_published = FALSE;
8686 /* username format: <username>,[<optional login>] */
8687 signinname_login = g_strsplit(username, ",", 2);
8688 SIPE_DEBUG_INFO("sipe_login: signinname[0] '%s'", signinname_login[0]);
8690 /* ensure that username format is name@domain */
8691 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8692 g_strfreev(signinname_login);
8693 gc->wants_to_die = TRUE;
8694 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8695 return;
8697 sip->username = g_strdup(signinname_login[0]);
8699 /* ensure that email format is name@domain if provided */
8700 email = purple_account_get_string(sip->account, "email", NULL);
8701 if (!is_empty(email) &&
8702 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8704 gc->wants_to_die = TRUE;
8705 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8706 return;
8708 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8710 /* login name specified? */
8711 if (signinname_login[1] && strlen(signinname_login[1])) {
8712 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8713 gboolean has_domain = domain_user[1] != NULL;
8714 SIPE_DEBUG_INFO("sipe_login: signinname[1] '%s'", signinname_login[1]);
8715 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8716 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8717 SIPE_DEBUG_INFO("sipe_login: auth domain '%s' user '%s'",
8718 sip->authdomain ? sip->authdomain : "", sip->authuser);
8719 g_strfreev(domain_user);
8722 userserver = g_strsplit(signinname_login[0], "@", 2);
8723 SIPE_DEBUG_INFO("sipe_login: user '%s' server '%s'", userserver[0], userserver[1]);
8724 purple_connection_set_display_name(gc, userserver[0]);
8725 sip->sipdomain = g_strdup(userserver[1]);
8726 g_strfreev(userserver);
8727 g_strfreev(signinname_login);
8729 if (strchr(sip->username, ' ') != NULL) {
8730 gc->wants_to_die = TRUE;
8731 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8732 return;
8735 sip->password = g_strdup(purple_connection_get_password(gc));
8737 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8738 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8739 g_free, (GDestroyNotify)g_hash_table_destroy);
8740 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8741 g_free, (GDestroyNotify)sipe_subscription_free);
8743 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8745 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8747 g_free(sip->status);
8748 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8750 sip->auto_transport = FALSE;
8751 transport = purple_account_get_string(account, "transport", "auto");
8752 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8753 if (userserver[0]) {
8754 /* Use user specified server[:port] */
8755 int port = 0;
8757 if (userserver[1])
8758 port = atoi(userserver[1]);
8760 SIPE_DEBUG_INFO("sipe_login: user specified SIP server %s:%d",
8761 userserver[0], port);
8763 if (sipe_strequal(transport, "auto")) {
8764 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8765 } else if (sipe_strequal(transport, "tls")) {
8766 sip->transport = SIPE_TRANSPORT_TLS;
8767 } else if (sipe_strequal(transport, "tcp")) {
8768 sip->transport = SIPE_TRANSPORT_TCP;
8769 } else {
8770 sip->transport = SIPE_TRANSPORT_UDP;
8773 create_connection(sip, g_strdup(userserver[0]), port);
8774 } else {
8775 /* Server auto-discovery */
8776 if (sipe_strequal(transport, "auto")) {
8777 sip->auto_transport = TRUE;
8778 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8779 current_service++;
8780 resolve_next_service(sip, current_service);
8781 } else {
8782 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8784 } else if (sipe_strequal(transport, "tls")) {
8785 resolve_next_service(sip, service_tls);
8786 } else if (sipe_strequal(transport, "tcp")) {
8787 resolve_next_service(sip, service_tcp);
8788 } else {
8789 resolve_next_service(sip, service_udp);
8792 g_strfreev(userserver);
8795 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8797 connection_free_all(sip);
8799 g_free(sip->epid);
8800 sip->epid = NULL;
8802 if (sip->query_data != NULL)
8803 purple_dnsquery_destroy(sip->query_data);
8804 sip->query_data = NULL;
8806 if (sip->srv_query_data != NULL)
8807 purple_srv_cancel(sip->srv_query_data);
8808 sip->srv_query_data = NULL;
8810 if (sip->listen_data != NULL)
8811 purple_network_listen_cancel(sip->listen_data);
8812 sip->listen_data = NULL;
8814 if (sip->gsc != NULL)
8815 purple_ssl_close(sip->gsc);
8816 sip->gsc = NULL;
8818 sipe_auth_free(&sip->registrar);
8819 sipe_auth_free(&sip->proxy);
8821 if (sip->txbuf)
8822 purple_circ_buffer_destroy(sip->txbuf);
8823 sip->txbuf = NULL;
8825 g_free(sip->realhostname);
8826 sip->realhostname = NULL;
8828 g_free(sip->server_version);
8829 sip->server_version = NULL;
8831 if (sip->listenpa)
8832 purple_input_remove(sip->listenpa);
8833 sip->listenpa = 0;
8834 if (sip->tx_handler)
8835 purple_input_remove(sip->tx_handler);
8836 sip->tx_handler = 0;
8837 if (sip->resendtimeout)
8838 purple_timeout_remove(sip->resendtimeout);
8839 sip->resendtimeout = 0;
8840 if (sip->timeouts) {
8841 GSList *entry = sip->timeouts;
8842 while (entry) {
8843 struct scheduled_action *sched_action = entry->data;
8844 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
8845 purple_timeout_remove(sched_action->timeout_handler);
8846 if (sched_action->destroy) {
8847 (*sched_action->destroy)(sched_action->payload);
8849 g_free(sched_action->name);
8850 g_free(sched_action);
8851 entry = entry->next;
8854 g_slist_free(sip->timeouts);
8856 if (sip->allow_events) {
8857 GSList *entry = sip->allow_events;
8858 while (entry) {
8859 g_free(entry->data);
8860 entry = entry->next;
8863 g_slist_free(sip->allow_events);
8865 if (sip->containers) {
8866 GSList *entry = sip->containers;
8867 while (entry) {
8868 free_container((struct sipe_container *)entry->data);
8869 entry = entry->next;
8872 g_slist_free(sip->containers);
8874 if (sip->contact)
8875 g_free(sip->contact);
8876 sip->contact = NULL;
8877 if (sip->regcallid)
8878 g_free(sip->regcallid);
8879 sip->regcallid = NULL;
8881 if (sip->serveraddr)
8882 g_free(sip->serveraddr);
8883 sip->serveraddr = NULL;
8885 if (sip->focus_factory_uri)
8886 g_free(sip->focus_factory_uri);
8887 sip->focus_factory_uri = NULL;
8889 sip->fd = -1;
8890 sip->processing_input = FALSE;
8892 if (sip->ews) {
8893 sipe_ews_free(sip->ews);
8895 sip->ews = NULL;
8899 * A callback for g_hash_table_foreach_remove
8901 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8902 SIPE_UNUSED_PARAMETER gpointer user_data)
8904 sipe_free_buddy((struct sipe_buddy *) buddy);
8906 /* We must return TRUE as the key/value have already been deleted */
8907 return(TRUE);
8910 static void sipe_close(PurpleConnection *gc)
8912 struct sipe_account_data *sip = gc->proto_data;
8914 if (sip) {
8915 /* leave all conversations */
8916 sipe_session_close_all(sip);
8917 sipe_session_remove_all(sip);
8919 if (sip->csta) {
8920 sip_csta_close(sip);
8923 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8924 /* unsubscribe all */
8925 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8927 /* unregister */
8928 do_register_exp(sip, 0);
8931 sipe_connection_cleanup(sip);
8932 g_free(sip->sipdomain);
8933 g_free(sip->username);
8934 g_free(sip->email);
8935 g_free(sip->password);
8936 g_free(sip->authdomain);
8937 g_free(sip->authuser);
8938 g_free(sip->status);
8939 g_free(sip->note);
8940 g_free(sip->user_states);
8942 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8943 g_hash_table_destroy(sip->buddies);
8944 g_hash_table_destroy(sip->our_publications);
8945 g_hash_table_destroy(sip->user_state_publications);
8946 g_hash_table_destroy(sip->subscriptions);
8947 g_hash_table_destroy(sip->filetransfers);
8949 if (sip->groups) {
8950 GSList *entry = sip->groups;
8951 while (entry) {
8952 struct sipe_group *group = entry->data;
8953 g_free(group->name);
8954 g_free(group);
8955 entry = entry->next;
8958 g_slist_free(sip->groups);
8960 if (sip->our_publication_keys) {
8961 GSList *entry = sip->our_publication_keys;
8962 while (entry) {
8963 g_free(entry->data);
8964 entry = entry->next;
8967 g_slist_free(sip->our_publication_keys);
8969 while (sip->transactions)
8970 transactions_remove(sip, sip->transactions->data);
8972 g_free(gc->proto_data);
8973 gc->proto_data = NULL;
8976 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8977 SIPE_UNUSED_PARAMETER void *user_data)
8979 PurpleAccount *acct = purple_connection_get_account(gc);
8980 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8981 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8982 if (conv == NULL)
8983 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8984 purple_conversation_present(conv);
8985 g_free(id);
8988 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8989 SIPE_UNUSED_PARAMETER void *user_data)
8992 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8993 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8996 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8997 SIPE_UNUSED_PARAMETER struct transaction *trans)
8999 PurpleNotifySearchResults *results;
9000 PurpleNotifySearchColumn *column;
9001 xmlnode *searchResults;
9002 xmlnode *mrow;
9003 int match_count = 0;
9004 gboolean more = FALSE;
9005 gchar *secondary;
9007 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
9009 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9010 if (!searchResults) {
9011 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
9012 return FALSE;
9015 results = purple_notify_searchresults_new();
9017 if (results == NULL) {
9018 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
9019 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
9021 xmlnode_free(searchResults);
9022 return FALSE;
9025 column = purple_notify_searchresults_column_new(_("User name"));
9026 purple_notify_searchresults_column_add(results, column);
9028 column = purple_notify_searchresults_column_new(_("Name"));
9029 purple_notify_searchresults_column_add(results, column);
9031 column = purple_notify_searchresults_column_new(_("Company"));
9032 purple_notify_searchresults_column_add(results, column);
9034 column = purple_notify_searchresults_column_new(_("Country"));
9035 purple_notify_searchresults_column_add(results, column);
9037 column = purple_notify_searchresults_column_new(_("Email"));
9038 purple_notify_searchresults_column_add(results, column);
9040 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
9041 GList *row = NULL;
9043 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
9044 row = g_list_append(row, g_strdup(uri_parts[1]));
9045 g_strfreev(uri_parts);
9047 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
9048 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
9049 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
9050 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
9052 purple_notify_searchresults_row_add(results, row);
9053 match_count++;
9056 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
9057 char *data = xmlnode_get_data(mrow);
9058 more = (g_strcasecmp(data, "true") == 0);
9059 g_free(data);
9062 secondary = g_strdup_printf(
9063 dngettext(PACKAGE_NAME,
9064 "Found %d contact%s:",
9065 "Found %d contacts%s:", match_count),
9066 match_count, more ? _(" (more matched your query)") : "");
9068 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
9069 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
9070 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
9072 g_free(secondary);
9073 xmlnode_free(searchResults);
9074 return TRUE;
9077 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
9079 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
9080 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
9081 unsigned i = 0;
9083 if (!attrs) return;
9085 do {
9086 PurpleRequestField *field = entries->data;
9087 const char *id = purple_request_field_get_id(field);
9088 const char *value = purple_request_field_string_get_value(field);
9090 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
9092 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
9093 } while ((entries = g_list_next(entries)) != NULL);
9094 attrs[i] = NULL;
9096 if (i > 0) {
9097 struct sipe_account_data *sip = gc->proto_data;
9098 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9099 gchar *query = g_strjoinv(NULL, attrs);
9100 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
9101 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
9102 send_soap_request_with_cb(sip, domain_uri, body,
9103 (TransCallback) process_search_contact_response, NULL);
9104 g_free(domain_uri);
9105 g_free(body);
9106 g_free(query);
9109 g_strfreev(attrs);
9112 static void sipe_show_find_contact(PurplePluginAction *action)
9114 PurpleConnection *gc = (PurpleConnection *) action->context;
9115 PurpleRequestFields *fields;
9116 PurpleRequestFieldGroup *group;
9117 PurpleRequestField *field;
9119 fields = purple_request_fields_new();
9120 group = purple_request_field_group_new(NULL);
9121 purple_request_fields_add_group(fields, group);
9123 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
9124 purple_request_field_group_add_field(group, field);
9125 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
9126 purple_request_field_group_add_field(group, field);
9127 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
9128 purple_request_field_group_add_field(group, field);
9129 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
9130 purple_request_field_group_add_field(group, field);
9132 purple_request_fields(gc,
9133 _("Search"),
9134 _("Search for a contact"),
9135 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
9136 fields,
9137 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
9138 _("_Cancel"), NULL,
9139 purple_connection_get_account(gc), NULL, NULL, gc);
9142 static void sipe_show_about_plugin(PurplePluginAction *action)
9144 PurpleConnection *gc = (PurpleConnection *) action->context;
9145 char *tmp = g_strdup_printf(
9147 * Non-translatable parts, like markup, are hard-coded
9148 * into the format string. This requires more translatable
9149 * texts but it makes the translations less error prone.
9151 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
9152 "<br/>"
9153 /* 1 */ "%s:<br/>"
9154 "<li> - MS Office Communications Server 2007 R2</li><br/>"
9155 "<li> - MS Office Communications Server 2007</li><br/>"
9156 "<li> - MS Live Communications Server 2005</li><br/>"
9157 "<li> - MS Live Communications Server 2003</li><br/>"
9158 "<li> - Reuters Messaging</li><br/>"
9159 "<br/>"
9160 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
9161 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
9162 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
9163 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
9164 /* 8 */ "%s: GPLv2+<br/>"
9165 "<br/>"
9166 /* 9 */ "%s:<br/>"
9167 " - CERN<br/>"
9168 " - Reuters Messaging network<br/>"
9169 " - Deutsche Bank<br/>"
9170 " - Merrill Lynch<br/>"
9171 " - Wachovia<br/>"
9172 " - Intel<br/>"
9173 " - Nokia<br/>"
9174 " - HP<br/>"
9175 " - Symantec<br/>"
9176 " - Accenture<br/>"
9177 " - Capgemini<br/>"
9178 " - Siemens<br/>"
9179 " - Alcatel-Lucent<br/>"
9180 " - BT<br/>"
9181 "<br/>"
9182 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9183 "<br/>"
9184 /* 12 */ "<b>%s:</b><br/>"
9185 " - Anibal Avelar<br/>"
9186 " - Gabriel Burt<br/>"
9187 " - Stefan Becker<br/>"
9188 " - pier11<br/>"
9189 " - Jakub Adam<br/>"
9190 " - Tomáš Hrabčík<br/>"
9191 "<br/>"
9192 /* 13 */ "%s<br/>"
9194 /* The next 13 texts make up the SIPE about note text */
9195 /* About note, part 1/13: introduction */
9196 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9197 /* About note, part 2/13: home page URL (label) */
9198 _("Home"),
9199 /* About note, part 3/13: support forum URL (label) */
9200 _("Support"),
9201 /* About note, part 4/13: support forum name (hyperlink text) */
9202 _("Help Forum"),
9203 /* About note, part 5/13: bug tracker URL (label) */
9204 _("Report Problems"),
9205 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9206 _("Bug Tracker"),
9207 /* About note, part 7/13: translation service URL (label) */
9208 _("Translations"),
9209 /* About note, part 8/13: license type (label) */
9210 _("License"),
9211 /* About note, part 9/13: known users */
9212 _("We support users in such organizations as"),
9213 /* About note, part 10/13: translation request, text before Transifex.net URL */
9214 /* append a space if text is not empty */
9215 _("Please help us to translate SIPE to your native language here at "),
9216 /* About note, part 11/13: translation request, text after Transifex.net URL */
9217 /* start with a space if text is not empty */
9218 _(" using convenient web interface"),
9219 /* About note, part 12/13: author list (header) */
9220 _("Authors"),
9221 /* About note, part 13/13: Localization credit */
9222 /* PLEASE NOTE: do *NOT* simply translate the english original */
9223 /* but write something similar to the following sentence: */
9224 /* "Localization for <language name> (<language code>): <name>" */
9225 _("Original texts in English (en): SIPE developers")
9227 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9228 g_free(tmp);
9231 static void sipe_republish_calendar(PurplePluginAction *action)
9233 PurpleConnection *gc = (PurpleConnection *) action->context;
9234 struct sipe_account_data *sip = gc->proto_data;
9236 sipe_update_calendar(sip);
9239 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9240 gpointer value,
9241 GString* str)
9243 struct sipe_publication *publication = value;
9245 g_string_append_printf( str,
9246 SIPE_PUB_XML_PUBLICATION_CLEAR,
9247 publication->category,
9248 publication->instance,
9249 publication->container,
9250 publication->version,
9251 "static");
9254 static void sipe_reset_status(PurplePluginAction *action)
9256 PurpleConnection *gc = (PurpleConnection *) action->context;
9257 struct sipe_account_data *sip = gc->proto_data;
9259 if (sip->ocs2007) /* 2007+ */
9261 GString* str = g_string_new(NULL);
9262 gchar *publications;
9264 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9265 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
9266 return;
9269 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9270 publications = g_string_free(str, FALSE);
9272 send_presence_publish(sip, publications);
9273 g_free(publications);
9275 else /* 2005 */
9277 send_presence_soap0(sip, FALSE, TRUE);
9281 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9282 gpointer context)
9284 PurpleConnection *gc = (PurpleConnection *)context;
9285 struct sipe_account_data *sip = gc->proto_data;
9286 GList *menu = NULL;
9287 PurplePluginAction *act;
9288 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9290 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9291 menu = g_list_prepend(menu, act);
9293 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9294 menu = g_list_prepend(menu, act);
9296 if (sipe_strequal(calendar, "EXCH")) {
9297 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9298 menu = g_list_prepend(menu, act);
9301 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9302 menu = g_list_prepend(menu, act);
9304 menu = g_list_reverse(menu);
9306 return menu;
9309 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9313 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9315 return TRUE;
9319 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9321 return TRUE;
9325 static char *sipe_status_text(PurpleBuddy *buddy)
9327 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9328 const PurpleStatus *status = purple_presence_get_active_status(presence);
9329 const char *status_id = purple_status_get_id(status);
9330 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9331 struct sipe_buddy *sbuddy;
9332 char *text = NULL;
9334 if (!sip) return NULL; /* happens on pidgin exit */
9336 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9337 if (sbuddy) {
9338 const char *activity_str = sbuddy->activity ?
9339 sbuddy->activity :
9340 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9341 purple_status_get_name(status) : NULL;
9343 if (activity_str && sbuddy->note)
9345 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9347 else if (activity_str)
9349 text = g_strdup(activity_str);
9351 else if (sbuddy->note)
9353 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9357 return text;
9360 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9362 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9363 const PurpleStatus *status = purple_presence_get_active_status(presence);
9364 struct sipe_account_data *sip;
9365 struct sipe_buddy *sbuddy;
9366 char *note = NULL;
9367 gboolean is_oof_note = FALSE;
9368 char *activity = NULL;
9369 char *calendar = NULL;
9370 char *meeting_subject = NULL;
9371 char *meeting_location = NULL;
9373 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9374 if (sip) //happens on pidgin exit
9376 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9377 if (sbuddy)
9379 note = sbuddy->note;
9380 is_oof_note = sbuddy->is_oof_note;
9381 activity = sbuddy->activity;
9382 calendar = sipe_cal_get_description(sbuddy);
9383 meeting_subject = sbuddy->meeting_subject;
9384 meeting_location = sbuddy->meeting_location;
9388 //Layout
9389 if (purple_presence_is_online(presence))
9391 const char *status_str = activity ? activity : purple_status_get_name(status);
9393 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9395 if (purple_presence_is_online(presence) &&
9396 !is_empty(calendar))
9398 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9400 g_free(calendar);
9401 if (!is_empty(meeting_location))
9403 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9405 if (!is_empty(meeting_subject))
9407 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9410 if (note)
9412 char *tmp = g_strdup_printf("<i>%s</i>", note);
9413 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", buddy->name, note);
9415 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9416 g_free(tmp);
9419 if (sip && sip->ocs2007) {
9420 const int container_id = sipe_find_access_level(sip, "user", buddy->name);
9421 const char *access_level = sipe_get_access_level_name(container_id);
9423 purple_notify_user_info_add_pair(user_info, _("Access level"), access_level);
9427 #if PURPLE_VERSION_CHECK(2,5,0)
9428 static GHashTable *
9429 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9431 GHashTable *table;
9432 table = g_hash_table_new(g_str_hash, g_str_equal);
9433 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9434 return table;
9436 #endif
9438 static PurpleBuddy *
9439 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9441 PurpleBuddy *clone;
9442 const gchar *server_alias, *email;
9443 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9445 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9447 purple_blist_add_buddy(clone, NULL, group, NULL);
9449 server_alias = purple_buddy_get_server_alias(buddy);
9450 if (server_alias) {
9451 purple_blist_server_alias_buddy(clone, server_alias);
9454 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9455 if (email) {
9456 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9459 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9460 //for UI to update;
9461 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9462 return clone;
9465 static void
9466 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9468 PurpleBuddy *buddy, *b;
9469 PurpleConnection *gc;
9470 PurpleGroup * group = purple_find_group(group_name);
9472 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9474 buddy = (PurpleBuddy *)node;
9476 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
9477 gc = purple_account_get_connection(buddy->account);
9479 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9480 if (!b){
9481 purple_blist_add_buddy_clone(group, buddy);
9484 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9487 static void
9488 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9490 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9492 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
9494 /* 2007+ conference */
9495 if (sip->ocs2007)
9497 sipe_conf_add(sip, buddy->name);
9499 else /* 2005- multiparty chat */
9501 gchar *self = sip_uri_self(sip);
9502 struct sip_session *session;
9504 session = sipe_session_add_chat(sip);
9505 session->chat_title = sipe_chat_get_name(session->callid);
9506 session->roster_manager = g_strdup(self);
9508 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9509 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9510 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9511 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9513 g_free(self);
9517 static gboolean
9518 sipe_is_election_finished(struct sip_session *session)
9520 gboolean res = TRUE;
9522 SIPE_DIALOG_FOREACH {
9523 if (dialog->election_vote == 0) {
9524 res = FALSE;
9525 break;
9527 } SIPE_DIALOG_FOREACH_END;
9529 if (res) {
9530 session->is_voting_in_progress = FALSE;
9532 return res;
9535 static void
9536 sipe_election_start(struct sipe_account_data *sip,
9537 struct sip_session *session)
9539 int election_timeout;
9541 if (session->is_voting_in_progress) {
9542 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
9543 return;
9544 } else {
9545 session->is_voting_in_progress = TRUE;
9547 session->bid = rand();
9549 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
9551 SIPE_DIALOG_FOREACH {
9552 /* reset election_vote for each chat participant */
9553 dialog->election_vote = 0;
9555 /* send RequestRM to each chat participant*/
9556 sipe_send_election_request_rm(sip, dialog, session->bid);
9557 } SIPE_DIALOG_FOREACH_END;
9559 election_timeout = 15; /* sec */
9560 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9564 * @param who a URI to whom to invite to chat
9566 void
9567 sipe_invite_to_chat(struct sipe_account_data *sip,
9568 struct sip_session *session,
9569 const gchar *who)
9571 /* a conference */
9572 if (session->focus_uri)
9574 sipe_invite_conf(sip, session, who);
9576 else /* a multi-party chat */
9578 gchar *self = sip_uri_self(sip);
9579 if (session->roster_manager) {
9580 if (sipe_strcase_equal(session->roster_manager, self)) {
9581 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9582 } else {
9583 sipe_refer(sip, session, who);
9585 } else {
9586 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
9588 session->pending_invite_queue = slist_insert_unique_sorted(
9589 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9591 sipe_election_start(sip, session);
9593 g_free(self);
9597 void
9598 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9599 struct sip_session *session)
9601 gchar *invitee;
9602 GSList *entry = session->pending_invite_queue;
9604 while (entry) {
9605 invitee = entry->data;
9606 sipe_invite_to_chat(sip, session, invitee);
9607 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9608 g_free(invitee);
9612 static void
9613 sipe_election_result(struct sipe_account_data *sip,
9614 void *sess)
9616 struct sip_session *session = (struct sip_session *)sess;
9617 gchar *rival;
9618 gboolean has_won = TRUE;
9620 if (session->roster_manager) {
9621 SIPE_DEBUG_INFO(
9622 "sipe_election_result: RM has already been elected in the meantime. It is %s",
9623 session->roster_manager);
9624 return;
9627 session->is_voting_in_progress = FALSE;
9629 SIPE_DIALOG_FOREACH {
9630 if (dialog->election_vote < 0) {
9631 has_won = FALSE;
9632 rival = dialog->with;
9633 break;
9635 } SIPE_DIALOG_FOREACH_END;
9637 if (has_won) {
9638 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
9640 session->roster_manager = sip_uri_self(sip);
9642 SIPE_DIALOG_FOREACH {
9643 /* send SetRM to each chat participant*/
9644 sipe_send_election_set_rm(sip, dialog);
9645 } SIPE_DIALOG_FOREACH_END;
9646 } else {
9647 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
9649 session->bid = 0;
9651 sipe_process_pending_invite_queue(sip, session);
9655 * For 2007+ conference only.
9657 static void
9658 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9660 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9661 struct sip_session *session;
9663 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
9664 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
9666 session = sipe_session_find_chat_by_title(sip, chat_title);
9668 sipe_conf_modify_user_role(sip, session, buddy->name);
9672 * For 2007+ conference only.
9674 static void
9675 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9677 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9678 struct sip_session *session;
9680 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
9681 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
9683 session = sipe_session_find_chat_by_title(sip, chat_title);
9685 sipe_conf_delete_user(sip, session, buddy->name);
9688 static void
9689 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9691 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9692 struct sip_session *session;
9694 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
9695 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
9697 session = sipe_session_find_chat_by_title(sip, chat_title);
9699 sipe_invite_to_chat(sip, session, buddy->name);
9702 static void
9703 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9705 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9707 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
9708 if (phone) {
9709 char *tel_uri = sip_to_tel_uri(phone);
9711 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
9712 sip_csta_make_call(sip, tel_uri);
9714 g_free(tel_uri);
9718 static void
9719 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9721 const gchar *email;
9722 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
9724 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9725 if (email)
9727 char *mailto = g_strdup_printf("mailto:%s", email);
9728 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s", email);
9729 #ifndef _WIN32
9731 pid_t pid;
9732 char *const parmList[] = {"xdg-email", mailto, NULL};
9733 if ((pid = fork()) == -1)
9735 SIPE_DEBUG_INFO_NOFORMAT("fork() error");
9737 else if (pid == 0)
9739 execvp(parmList[0], parmList);
9740 SIPE_DEBUG_INFO_NOFORMAT("Return not expected. Must be an execvp() error.");
9743 #else
9745 BOOL ret;
9746 _flushall();
9747 errno = 0;
9748 //@TODO resolve env variable %WINDIR% first
9749 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9750 if (errno)
9752 SIPE_DEBUG_INFO("spawnl returned (%s)!", strerror(errno));
9755 #endif
9757 g_free(mailto);
9759 else
9761 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
9765 static void
9766 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy, const int *container_id)
9768 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9770 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: buddy->name=%s, container_id=%d",
9771 buddy->name, container_id ? *container_id : -1);
9772 if (container_id) {
9773 sipe_change_access_level(sip, *container_id, "user", buddy->name);
9778 * A menu which appear when right-clicking on buddy in contact list.
9780 static GList *
9781 sipe_buddy_menu(PurpleBuddy *buddy)
9783 PurpleBlistNode *g_node;
9784 PurpleGroup *group, *gr_parent;
9785 PurpleMenuAction *act;
9786 GList *menu = NULL;
9787 GList *menu_groups = NULL;
9788 GList *menu_access_levels = NULL;
9789 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9790 const char *email;
9791 const char *phone;
9792 const char *phone_disp_str;
9793 gchar *self = sip_uri_self(sip);
9794 int i;
9796 SIPE_SESSION_FOREACH {
9797 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9799 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9801 PurpleConvChatBuddyFlags flags;
9802 PurpleConvChatBuddyFlags flags_us;
9804 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9805 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9806 if (session->focus_uri
9807 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9808 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9810 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9811 act = purple_menu_action_new(label,
9812 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9813 session->chat_title, NULL);
9814 g_free(label);
9815 menu = g_list_prepend(menu, act);
9818 if (session->focus_uri
9819 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9821 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9822 act = purple_menu_action_new(label,
9823 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9824 session->chat_title, NULL);
9825 g_free(label);
9826 menu = g_list_prepend(menu, act);
9829 else
9831 if (!session->focus_uri
9832 || (session->focus_uri && !session->locked))
9834 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9835 act = purple_menu_action_new(label,
9836 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9837 session->chat_title, NULL);
9838 g_free(label);
9839 menu = g_list_prepend(menu, act);
9843 } SIPE_SESSION_FOREACH_END;
9845 act = purple_menu_action_new(_("New chat"),
9846 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9847 NULL, NULL);
9848 menu = g_list_prepend(menu, act);
9850 if (sip->csta && !sip->csta->line_status) {
9851 gchar *tmp = NULL;
9852 /* work phone */
9853 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9854 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9855 if (phone) {
9856 gchar *label = g_strdup_printf(_("Work %s"),
9857 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9858 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9859 g_free(tmp);
9860 tmp = NULL;
9861 g_free(label);
9862 menu = g_list_prepend(menu, act);
9865 /* mobile phone */
9866 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9867 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9868 if (phone) {
9869 gchar *label = g_strdup_printf(_("Mobile %s"),
9870 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9871 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9872 g_free(tmp);
9873 tmp = NULL;
9874 g_free(label);
9875 menu = g_list_prepend(menu, act);
9878 /* home phone */
9879 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9880 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9881 if (phone) {
9882 gchar *label = g_strdup_printf(_("Home %s"),
9883 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9884 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9885 g_free(tmp);
9886 tmp = NULL;
9887 g_free(label);
9888 menu = g_list_prepend(menu, act);
9891 /* other phone */
9892 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9893 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9894 if (phone) {
9895 gchar *label = g_strdup_printf(_("Other %s"),
9896 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9897 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9898 g_free(tmp);
9899 tmp = NULL;
9900 g_free(label);
9901 menu = g_list_prepend(menu, act);
9904 /* custom1 phone */
9905 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9906 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9907 if (phone) {
9908 gchar *label = g_strdup_printf(_("Custom1 %s"),
9909 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9910 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9911 g_free(tmp);
9912 tmp = NULL;
9913 g_free(label);
9914 menu = g_list_prepend(menu, act);
9918 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9919 if (email) {
9920 act = purple_menu_action_new(_("Send email..."),
9921 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9922 NULL, NULL);
9923 menu = g_list_prepend(menu, act);
9926 /* Access Level */
9927 /* get current access level */
9928 for (i = 0; i < CONTAINERS_LEN; i++) {
9929 act = purple_menu_action_new(sipe_get_access_level_name(containers[i]),
9930 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
9931 (gpointer)&(containers[i]), NULL);
9932 menu_access_levels = g_list_prepend(menu_access_levels, act);
9934 menu_access_levels = g_list_reverse(menu_access_levels);
9936 act = purple_menu_action_new(_("Access level"),
9937 NULL,
9938 NULL, menu_access_levels);
9939 menu = g_list_prepend(menu, act);
9941 /* Copy to */
9942 gr_parent = purple_buddy_get_group(buddy);
9943 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9944 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9945 continue;
9947 group = (PurpleGroup *)g_node;
9948 if (group == gr_parent)
9949 continue;
9951 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9952 continue;
9954 act = purple_menu_action_new(purple_group_get_name(group),
9955 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9956 group->name, NULL);
9957 menu_groups = g_list_prepend(menu_groups, act);
9959 menu_groups = g_list_reverse(menu_groups);
9961 act = purple_menu_action_new(_("Copy to"),
9962 NULL,
9963 NULL, menu_groups);
9964 menu = g_list_prepend(menu, act);
9966 menu = g_list_reverse(menu);
9968 g_free(self);
9969 return menu;
9972 static void
9973 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9975 struct sipe_account_data *sip = chat->account->gc->proto_data;
9976 struct sip_session *session;
9978 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9979 sipe_conf_modify_conference_lock(sip, session, locked);
9982 static void
9983 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9985 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
9986 sipe_conf_modify_lock(chat, FALSE);
9989 static void
9990 sipe_chat_menu_lock_cb(PurpleChat *chat)
9992 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
9993 sipe_conf_modify_lock(chat, TRUE);
9996 static GList *
9997 sipe_chat_menu(PurpleChat *chat)
9999 PurpleMenuAction *act;
10000 PurpleConvChatBuddyFlags flags_us;
10001 GList *menu = NULL;
10002 struct sipe_account_data *sip = chat->account->gc->proto_data;
10003 struct sip_session *session;
10004 gchar *self;
10006 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10007 if (!session) return NULL;
10009 self = sip_uri_self(sip);
10010 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
10012 if (session->focus_uri
10013 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
10015 if (session->locked) {
10016 act = purple_menu_action_new(_("Unlock"),
10017 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
10018 NULL, NULL);
10019 menu = g_list_prepend(menu, act);
10020 } else {
10021 act = purple_menu_action_new(_("Lock"),
10022 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
10023 NULL, NULL);
10024 menu = g_list_prepend(menu, act);
10028 menu = g_list_reverse(menu);
10030 g_free(self);
10031 return menu;
10034 static GList *
10035 sipe_blist_node_menu(PurpleBlistNode *node)
10037 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
10038 return sipe_buddy_menu((PurpleBuddy *) node);
10039 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
10040 return sipe_chat_menu((PurpleChat *)node);
10041 } else {
10042 return NULL;
10046 static gboolean
10047 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
10049 char *uri = trans->payload->data;
10051 PurpleNotifyUserInfo *info;
10052 PurpleBuddy *pbuddy = NULL;
10053 struct sipe_buddy *sbuddy;
10054 const char *alias = NULL;
10055 char *device_name = NULL;
10056 char *server_alias = NULL;
10057 char *phone_number = NULL;
10058 char *email = NULL;
10059 const char *site;
10060 char *first_name = NULL;
10061 char *last_name = NULL;
10063 if (!sip) return FALSE;
10065 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sip->username);
10067 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
10068 alias = purple_buddy_get_local_alias(pbuddy);
10070 //will query buddy UA's capabilities and send answer to log
10071 sipe_options_request(sip, uri);
10073 sbuddy = g_hash_table_lookup(sip->buddies, uri);
10074 if (sbuddy) {
10075 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
10078 info = purple_notify_user_info_new();
10080 if (msg->response != 200) {
10081 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg->response);
10082 } else {
10083 xmlnode *searchResults;
10084 xmlnode *mrow;
10086 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
10087 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
10088 if (!searchResults) {
10089 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
10090 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
10091 const char *value;
10092 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
10093 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
10094 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
10096 /* For 2007 system we will take this from ContactCard -
10097 * it has cleaner tel: URIs at least
10099 if (!sip->ocs2007) {
10100 char *tel_uri = sip_to_tel_uri(phone_number);
10101 /* trims its parameters, so call first */
10102 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
10103 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
10104 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
10105 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
10106 g_free(tel_uri);
10109 if (server_alias && strlen(server_alias) > 0) {
10110 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10112 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
10113 purple_notify_user_info_add_pair(info, _("Job title"), value);
10115 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
10116 purple_notify_user_info_add_pair(info, _("Office"), value);
10118 if (phone_number && strlen(phone_number) > 0) {
10119 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
10121 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
10122 purple_notify_user_info_add_pair(info, _("Company"), value);
10124 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
10125 purple_notify_user_info_add_pair(info, _("City"), value);
10127 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
10128 purple_notify_user_info_add_pair(info, _("State"), value);
10130 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
10131 purple_notify_user_info_add_pair(info, _("Country"), value);
10133 if (email && strlen(email) > 0) {
10134 purple_notify_user_info_add_pair(info, _("Email address"), email);
10138 xmlnode_free(searchResults);
10141 purple_notify_user_info_add_section_break(info);
10143 if (is_empty(server_alias)) {
10144 g_free(server_alias);
10145 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
10146 if (server_alias) {
10147 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10151 /* present alias if it differs from server alias */
10152 if (alias && !sipe_strequal(alias, server_alias))
10154 purple_notify_user_info_add_pair(info, _("Alias"), alias);
10157 if (is_empty(email)) {
10158 g_free(email);
10159 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
10160 if (email) {
10161 purple_notify_user_info_add_pair(info, _("Email address"), email);
10165 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
10166 if (site) {
10167 purple_notify_user_info_add_pair(info, _("Site"), site);
10170 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
10171 if (first_name && last_name) {
10172 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
10174 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
10175 g_free(link);
10177 g_free(first_name);
10178 g_free(last_name);
10180 if (device_name) {
10181 purple_notify_user_info_add_pair(info, _("Device"), device_name);
10184 /* show a buddy's user info in a nice dialog box */
10185 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
10186 uri, /* buddy's URI */
10187 info, /* body */
10188 NULL, /* callback called when dialog closed */
10189 NULL); /* userdata for callback */
10191 g_free(phone_number);
10192 g_free(server_alias);
10193 g_free(email);
10194 g_free(device_name);
10196 return TRUE;
10200 * AD search first, LDAP based
10202 static void sipe_get_info(PurpleConnection *gc, const char *username)
10204 struct sipe_account_data *sip = gc->proto_data;
10205 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
10206 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
10207 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
10208 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
10210 payload->destroy = g_free;
10211 payload->data = g_strdup(username);
10213 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
10214 send_soap_request_with_cb(sip, domain_uri, body,
10215 (TransCallback) process_get_info_response, payload);
10216 g_free(domain_uri);
10217 g_free(body);
10218 g_free(row);
10221 PurplePluginProtocolInfo prpl_info =
10223 OPT_PROTO_CHAT_TOPIC,
10224 NULL, /* user_splits */
10225 NULL, /* protocol_options */
10226 NO_BUDDY_ICONS, /* icon_spec */
10227 sipe_list_icon, /* list_icon */
10228 NULL, /* list_emblems */
10229 sipe_status_text, /* status_text */
10230 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10231 sipe_status_types, /* away_states */
10232 sipe_blist_node_menu, /* blist_node_menu */
10233 NULL, /* chat_info */
10234 NULL, /* chat_info_defaults */
10235 sipe_login, /* login */
10236 sipe_close, /* close */
10237 sipe_im_send, /* send_im */
10238 NULL, /* set_info */ // TODO maybe
10239 sipe_send_typing, /* send_typing */
10240 sipe_get_info, /* get_info */
10241 sipe_set_status, /* set_status */
10242 sipe_set_idle, /* set_idle */
10243 NULL, /* change_passwd */
10244 sipe_add_buddy, /* add_buddy */
10245 NULL, /* add_buddies */
10246 sipe_remove_buddy, /* remove_buddy */
10247 NULL, /* remove_buddies */
10248 sipe_add_permit, /* add_permit */
10249 sipe_add_deny, /* add_deny */
10250 sipe_add_deny, /* rem_permit */
10251 sipe_add_permit, /* rem_deny */
10252 dummy_permit_deny, /* set_permit_deny */
10253 NULL, /* join_chat */
10254 NULL, /* reject_chat */
10255 NULL, /* get_chat_name */
10256 sipe_chat_invite, /* chat_invite */
10257 sipe_chat_leave, /* chat_leave */
10258 NULL, /* chat_whisper */
10259 sipe_chat_send, /* chat_send */
10260 sipe_keep_alive, /* keepalive */
10261 NULL, /* register_user */
10262 NULL, /* get_cb_info */ // deprecated
10263 NULL, /* get_cb_away */ // deprecated
10264 sipe_alias_buddy, /* alias_buddy */
10265 sipe_group_buddy, /* group_buddy */
10266 sipe_rename_group, /* rename_group */
10267 NULL, /* buddy_free */
10268 sipe_convo_closed, /* convo_closed */
10269 purple_normalize_nocase, /* normalize */
10270 NULL, /* set_buddy_icon */
10271 sipe_remove_group, /* remove_group */
10272 NULL, /* get_cb_real_name */ // TODO?
10273 NULL, /* set_chat_topic */
10274 NULL, /* find_blist_chat */
10275 NULL, /* roomlist_get_list */
10276 NULL, /* roomlist_cancel */
10277 NULL, /* roomlist_expand_category */
10278 NULL, /* can_receive_file */
10279 sipe_ft_send_file, /* send_file */
10280 sipe_ft_new_xfer, /* new_xfer */
10281 NULL, /* offline_message */
10282 NULL, /* whiteboard_prpl_ops */
10283 sipe_send_raw, /* send_raw */
10284 NULL, /* roomlist_room_serialize */
10285 NULL, /* unregister_user */
10286 NULL, /* send_attention */
10287 NULL, /* get_attention_types */
10288 #if !PURPLE_VERSION_CHECK(2,5,0)
10289 /* Backward compatibility when compiling against 2.4.x API */
10290 (void (*)(void)) /* _purple_reserved4 */
10291 #endif
10292 sizeof(PurplePluginProtocolInfo), /* struct_size */
10293 #if PURPLE_VERSION_CHECK(2,5,0)
10294 sipe_get_account_text_table, /* get_account_text_table */
10295 #if PURPLE_VERSION_CHECK(2,6,0)
10296 NULL, /* initiate_media */
10297 NULL, /* get_media_caps */
10298 #endif
10299 #endif
10303 PurplePluginInfo info = {
10304 PURPLE_PLUGIN_MAGIC,
10305 PURPLE_MAJOR_VERSION,
10306 PURPLE_MINOR_VERSION,
10307 PURPLE_PLUGIN_PROTOCOL, /**< type */
10308 NULL, /**< ui_requirement */
10309 0, /**< flags */
10310 NULL, /**< dependencies */
10311 PURPLE_PRIORITY_DEFAULT, /**< priority */
10312 "prpl-sipe", /**< id */
10313 "Office Communicator", /**< name */
10314 PACKAGE_VERSION, /**< version */
10315 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10316 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10317 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10318 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10319 "Gabriel Burt <gburt@novell.com>, " /**< author */
10320 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10321 "pier11 <pier11@operamail.com>", /**< author */
10322 PACKAGE_URL, /**< homepage */
10323 sipe_plugin_load, /**< load */
10324 sipe_plugin_unload, /**< unload */
10325 sipe_plugin_destroy, /**< destroy */
10326 NULL, /**< ui_info */
10327 &prpl_info, /**< extra_info */
10328 NULL,
10329 sipe_actions,
10330 NULL,
10331 NULL,
10332 NULL,
10333 NULL
10336 void sipe_core_init(void)
10338 srand(time(NULL));
10339 sip_sec_init();
10341 #ifdef ENABLE_NLS
10342 SIPE_DEBUG_INFO("bindtextdomain = %s",
10343 bindtextdomain(PACKAGE_NAME, LOCALEDIR));
10344 SIPE_DEBUG_INFO("bind_textdomain_codeset = %s",
10345 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"));
10346 textdomain(PACKAGE_NAME);
10347 #endif
10348 #ifdef HAVE_GMIME
10349 g_mime_init(0);
10350 #endif
10353 void sipe_core_destroy(void)
10355 #ifdef HAVE_GMIME
10356 g_mime_shutdown();
10357 #endif
10358 sip_sec_destroy();
10362 Local Variables:
10363 mode: c
10364 c-file-style: "bsd"
10365 indent-tabs-mode: t
10366 tab-width: 8
10367 End: