Fix for login with Connection type: Auto
[siplcs.git] / src / core / sipe.c
blob9b16baff44891f85df34e0967224d3a1a90806de
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 #ifdef _DLL
42 #define _WS2TCPIP_H_
43 #define _WINSOCK2API_
44 #define _LIBC_INTERNAL_
45 #endif /* _DLL */
46 /* for network */
47 #include "libc_interface.h"
48 #else
49 #include <sys/types.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #endif /* _WIN32 */
54 #include <time.h>
55 #include <stdlib.h>
56 #include <stdio.h>
57 #include <errno.h>
58 #include <string.h>
59 #include <unistd.h>
61 #include <glib.h>
63 #include "sipe-common.h"
65 #include "account.h"
66 #include "blist.h"
67 #include "connection.h"
68 #include "conversation.h"
69 #include "core.h"
70 #include "circbuffer.h"
71 #include "dnsquery.h"
72 #include "dnssrv.h"
73 #include "ft.h"
74 #include "network.h"
75 #include "notify.h"
76 #include "plugin.h"
77 #include "privacy.h"
78 #include "request.h"
79 #include "savedstatuses.h"
80 #include "sslconn.h"
81 #include "version.h"
83 #include "core-depurple.h" /* Temporary for the core de-purple transition */
85 #include "sipmsg.h"
86 #include "sip-csta.h"
87 #include "sip-sec.h"
88 #include "sipe-backend.h"
89 #include "sipe-buddy.h"
90 #include "sipe-cal.h"
91 #include "sipe-chat.h"
92 #include "sipe-conf.h"
93 #include "sipe-core.h"
94 #include "sipe-core-private.h"
95 #include "sipe-dialog.h"
96 #include "sipe-digest.h"
97 #include "sipe-ews.h"
98 #include "sipe-ft.h"
99 #include "sipe-mime.h"
100 #include "sipe-nls.h"
101 #include "sipe-session.h"
102 #include "sipe-sign.h"
103 #include "sipe-utils.h"
104 #include "sipe-xml.h"
105 #include "http-conn.h"
106 #include "uuid.h"
107 #include "sipe.h"
109 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
111 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
112 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
114 /* Keep in sync with sipe_transport_type! */
115 static const char *transport_descriptor[] = { "", "tls", "tcp", "udp" };
116 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
118 /* Status identifiers (see also: sipe_status_types()) */
119 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
120 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
121 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
122 /* PURPLE_STATUS_UNAVAILABLE: */
123 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
124 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
125 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
126 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
127 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
128 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
129 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
130 /* PURPLE_STATUS_AWAY: */
131 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
132 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
133 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
134 /** Reuters status (user settable) */
135 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
136 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
137 /* ??? PURPLE_STATUS_MOBILE */
138 /* ??? PURPLE_STATUS_TUNE */
140 /* Status attributes (see also sipe_status_types() */
141 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
143 #ifdef HAVE_GMIME
144 /* pls. don't add multipart/related - it's not used in IM modality */
145 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
146 #else
147 /* this is a rediculous hack as Pidgin's MIME implementastion doesn't support (or have bug) in multipart/alternative */
148 /* OCS/OC won't use multipart/related so we don't advertase it */
149 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
150 #endif
152 static struct sipe_activity_map_struct
154 sipe_activity type;
155 const char *token;
156 const char *desc;
157 const char *status_id;
159 } const sipe_activity_map[] =
161 /* This has nothing to do with Availability numbers, like 3500 (online).
162 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
164 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
165 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
166 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
167 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
168 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
169 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
170 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
171 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
172 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
173 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
174 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
175 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
176 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
177 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
178 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
180 /** @param x is sipe_activity */
181 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
184 /* Action name templates */
185 #define ACTION_NAME_PRESENCE "<presence><%s>"
187 static sipe_activity
188 sipe_get_activity_by_token(const char *token)
190 int i;
192 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
194 if (sipe_strequal(token, sipe_activity_map[i].token))
195 return sipe_activity_map[i].type;
198 return sipe_activity_map[0].type;
201 static const char *
202 sipe_get_activity_desc_by_token(const char *token)
204 if (!token) return NULL;
206 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
209 /** Allows to send typed messages from chat window again after account reinstantiation. */
210 static void
211 sipe_rejoin_chat(PurpleConversation *conv)
213 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
214 PURPLE_CONV_CHAT(conv)->left)
216 PURPLE_CONV_CHAT(conv)->left = FALSE;
217 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
221 static char *genbranch()
223 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
224 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
225 rand() & 0xFFFF, rand() & 0xFFFF);
229 static char *default_ua = NULL;
230 static const char*
231 sipe_get_useragent(struct sipe_account_data *sip)
233 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
234 if (is_empty(useragent)) {
235 if (!default_ua) {
236 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
237 /* ref: lzodefs.h */
238 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
239 #define SIPE_TARGET_PLATFORM "linux"
240 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
241 #define SIPE_TARGET_PLATFORM "bsd"
242 #elif defined(__APPLE__) || defined(__MACOS__)
243 #define SIPE_TARGET_PLATFORM "macosx"
244 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
245 #define SIPE_TARGET_PLATFORM "aix"
246 #elif defined(__solaris__) || defined(__sun)
247 #define SIPE_TARGET_PLATFORM "sun"
248 #elif defined(_WIN32)
249 #define SIPE_TARGET_PLATFORM "win"
250 #elif defined(__CYGWIN__)
251 #define SIPE_TARGET_PLATFORM "cygwin"
252 #elif defined(__hpux__)
253 #define SIPE_TARGET_PLATFORM "hpux"
254 #elif defined(__sgi__)
255 #define SIPE_TARGET_PLATFORM "irix"
256 #else
257 #define SIPE_TARGET_PLATFORM "unknown"
258 #endif
260 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
261 #define SIPE_TARGET_ARCH "x86_64"
262 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
263 #define SIPE_TARGET_ARCH "i386"
264 #elif defined(__ppc64__)
265 #define SIPE_TARGET_ARCH "ppc64"
266 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
267 #define SIPE_TARGET_ARCH "ppc"
268 #elif defined(__hppa__) || defined(__hppa)
269 #define SIPE_TARGET_ARCH "hppa"
270 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
271 #define SIPE_TARGET_ARCH "mips"
272 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
273 #define SIPE_TARGET_ARCH "s390"
274 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
275 #define SIPE_TARGET_ARCH "sparc"
276 #elif defined(__arm__)
277 #define SIPE_TARGET_ARCH "arm"
278 #else
279 #define SIPE_TARGET_ARCH "other"
280 #endif
282 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
283 purple_core_get_version(),
284 sip->server_version ? sip->server_version : "");
286 useragent = default_ua;
288 return useragent;
291 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
293 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
294 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
295 gpointer data);
297 static void send_presence_status(struct sipe_core_private *sipe_private,
298 void *unused);
300 static void sendout_pkt(PurpleConnection *gc, const char *buf);
302 void sipe_keep_alive(PurpleConnection *gc)
304 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
305 if (sip->transport == SIPE_TRANSPORT_UDP) {
306 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
307 gchar buf[2] = {0, 0};
308 SIPE_DEBUG_INFO_NOFORMAT("sending keep alive");
309 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
310 } else {
311 time_t now = time(NULL);
312 if ((sip->keepalive_timeout > 0) &&
313 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
314 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
316 SIPE_DEBUG_INFO("sending keep alive %d", sip->keepalive_timeout);
317 sendout_pkt(gc, "\r\n\r\n");
318 sip->last_keepalive = now;
323 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
325 struct sip_connection *ret = NULL;
326 GSList *entry = sip->openconns;
327 while (entry) {
328 ret = entry->data;
329 if (ret->fd == fd) return ret;
330 entry = entry->next;
332 return NULL;
335 static void sipe_auth_free(struct sip_auth *auth)
337 g_free(auth->opaque);
338 auth->opaque = NULL;
339 g_free(auth->realm);
340 auth->realm = NULL;
341 g_free(auth->target);
342 auth->target = NULL;
343 auth->version = 0;
344 auth->type = AUTH_TYPE_UNSET;
345 auth->retries = 0;
346 auth->expires = 0;
347 g_free(auth->gssapi_data);
348 auth->gssapi_data = NULL;
349 sip_sec_destroy_context(auth->gssapi_context);
350 auth->gssapi_context = NULL;
353 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
355 struct sip_connection *ret = g_new0(struct sip_connection, 1);
356 ret->fd = fd;
357 sip->openconns = g_slist_append(sip->openconns, ret);
358 return ret;
361 static void connection_remove(struct sipe_account_data *sip, int fd)
363 struct sip_connection *conn = connection_find(sip, fd);
364 if (conn) {
365 sip->openconns = g_slist_remove(sip->openconns, conn);
366 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
367 g_free(conn->inbuf);
368 g_free(conn);
372 static void connection_free_all(struct sipe_account_data *sip)
374 struct sip_connection *ret = NULL;
375 GSList *entry = sip->openconns;
376 while (entry) {
377 ret = entry->data;
378 connection_remove(sip, ret->fd);
379 entry = sip->openconns;
383 static void
384 sipe_make_signature(struct sipe_account_data *sip,
385 struct sipmsg *msg);
387 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
389 const char *authuser = sip->authuser;
390 gchar *ret;
392 if (!authuser || strlen(authuser) < 1) {
393 authuser = sip->username;
396 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
397 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
398 gchar *version_str;
400 // If we have a signature for the message, include that
401 if (msg->signature) {
402 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);
405 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
406 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
407 gchar *gssapi_data;
408 gchar *opaque;
409 gchar *sign_str = NULL;
411 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
412 &(auth->expires),
413 auth->type,
414 purple_account_get_bool(sip->account, "sso", TRUE),
415 sip->authdomain ? sip->authdomain : "",
416 authuser,
417 sip->password,
418 auth->target,
419 auth->gssapi_data);
420 if (!gssapi_data || !auth->gssapi_context) {
421 sip->gc->wants_to_die = TRUE;
422 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
423 return NULL;
426 if (auth->version > 3) {
427 sipe_make_signature(sip, msg);
428 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
429 msg->rand, msg->num, msg->signature);
430 } else {
431 sign_str = g_strdup("");
434 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
435 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
436 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);
437 g_free(opaque);
438 g_free(gssapi_data);
439 g_free(version_str);
440 g_free(sign_str);
441 return ret;
444 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
445 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
446 g_free(version_str);
447 return ret;
449 } else { /* Digest */
450 gchar *string;
451 gchar *hex_digest;
452 guchar digest[SIPE_DIGEST_MD5_LENGTH];
454 /* Calculate new session key */
455 if (!auth->opaque) {
456 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth->gssapi_data, auth->realm);
457 if (sip->password) {
459 * Calculate a session key for HTTP MD5 Digest authentation
461 * See RFC 2617 for more information.
463 string = g_strdup_printf("%s:%s:%s",
464 authuser,
465 auth->realm,
466 sip->password);
467 sipe_digest_md5((guchar *)string, strlen(string), digest);
468 g_free(string);
469 auth->opaque = buff_to_hex_str(digest, sizeof(digest));
474 * Calculate a response for HTTP MD5 Digest authentication
476 * See RFC 2617 for more information.
478 string = g_strdup_printf("%s:%s", msg->method, msg->target);
479 sipe_digest_md5((guchar *)string, strlen(string), digest);
480 g_free(string);
482 hex_digest = buff_to_hex_str(digest, sizeof(digest));
483 string = g_strdup_printf("%s:%s:%s", auth->opaque, auth->gssapi_data, hex_digest);
484 g_free(hex_digest);
485 sipe_digest_md5((guchar *)string, strlen(string), digest);
486 g_free(string);
488 hex_digest = buff_to_hex_str(digest, sizeof(digest));
489 SIPE_DEBUG_INFO("Digest response %s", hex_digest);
490 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%08d\", response=\"%s\"", authuser, auth->realm, auth->gssapi_data, msg->target, auth->nc++, hex_digest);
491 g_free(hex_digest);
492 return ret;
496 static char *parse_attribute(const char *attrname, const char *source)
498 const char *tmp, *tmp2;
499 char *retval = NULL;
500 int len = strlen(attrname);
502 if (g_str_has_prefix(source, attrname)) {
503 tmp = source + len;
504 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
505 if (tmp2)
506 retval = g_strndup(tmp, tmp2 - tmp);
507 else
508 retval = g_strdup(tmp);
511 return retval;
514 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
516 int i;
517 gchar **parts;
519 if (!hdr) {
520 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
521 return;
524 if (!g_strncasecmp(hdr, "NTLM", 4)) {
525 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
526 auth->type = AUTH_TYPE_NTLM;
527 hdr += 5;
528 auth->nc = 1;
529 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
530 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
531 auth->type = AUTH_TYPE_KERBEROS;
532 hdr += 9;
533 auth->nc = 3;
534 } else {
535 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
536 auth->type = AUTH_TYPE_DIGEST;
537 hdr += 7;
540 parts = g_strsplit(hdr, "\", ", 0);
541 for (i = 0; parts[i]; i++) {
542 char *tmp;
544 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
546 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
547 g_free(auth->gssapi_data);
548 auth->gssapi_data = tmp;
550 if (auth->type == AUTH_TYPE_NTLM) {
551 /* NTLM module extracts nonce from gssapi-data */
552 auth->nc = 3;
555 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
556 /* Only used with AUTH_TYPE_DIGEST */
557 g_free(auth->gssapi_data);
558 auth->gssapi_data = tmp;
559 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
560 g_free(auth->opaque);
561 auth->opaque = tmp;
562 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
563 g_free(auth->realm);
564 auth->realm = tmp;
566 if (auth->type == AUTH_TYPE_DIGEST) {
567 /* Throw away old session key */
568 g_free(auth->opaque);
569 auth->opaque = NULL;
570 auth->nc = 1;
572 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
573 g_free(auth->target);
574 auth->target = tmp;
575 } else if ((tmp = parse_attribute("version=", parts[i]))) {
576 auth->version = atoi(tmp);
577 g_free(tmp);
579 // uncomment to revert to previous functionality if version 3+ does not work.
580 // auth->version = 2;
582 g_strfreev(parts);
584 return;
587 static void sipe_canwrite_cb(gpointer data,
588 SIPE_UNUSED_PARAMETER gint source,
589 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
591 PurpleConnection *gc = data;
592 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
593 gsize max_write;
594 gssize written;
596 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
598 if (max_write == 0) {
599 if (sip->tx_handler != 0){
600 purple_input_remove(sip->tx_handler);
601 sip->tx_handler = 0;
603 return;
606 written = write(sip->fd, sip->txbuf->outptr, max_write);
608 if (written < 0 && errno == EAGAIN)
609 written = 0;
610 else if (written <= 0) {
611 /*TODO: do we really want to disconnect on a failure to write?*/
612 purple_connection_error(gc, _("Could not write"));
613 return;
616 purple_circ_buffer_mark_read(sip->txbuf, written);
619 static void sipe_canwrite_cb_ssl(gpointer data,
620 SIPE_UNUSED_PARAMETER gint src,
621 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
623 PurpleConnection *gc = data;
624 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
625 gsize max_write;
626 gssize written;
628 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
630 if (max_write == 0) {
631 if (sip->tx_handler != 0) {
632 purple_input_remove(sip->tx_handler);
633 sip->tx_handler = 0;
634 return;
638 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
640 if (written < 0 && errno == EAGAIN)
641 written = 0;
642 else if (written <= 0) {
643 /*TODO: do we really want to disconnect on a failure to write?*/
644 purple_connection_error(gc, _("Could not write"));
645 return;
648 purple_circ_buffer_mark_read(sip->txbuf, written);
651 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
653 static void send_later_cb(gpointer data, gint source,
654 SIPE_UNUSED_PARAMETER const gchar *error)
656 PurpleConnection *gc = data;
657 struct sipe_account_data *sip;
658 struct sip_connection *conn;
660 if (!PURPLE_CONNECTION_IS_VALID(gc))
662 if (source >= 0)
663 close(source);
664 return;
667 if (source < 0) {
668 purple_connection_error(gc, _("Could not connect"));
669 return;
672 sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
673 sip->fd = source;
674 sip->connecting = FALSE;
675 sip->last_keepalive = time(NULL);
677 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
679 /* If there is more to write now, we need to register a handler */
680 if (sip->txbuf->bufused > 0)
681 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
683 conn = connection_create(sip, source);
684 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
687 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
689 struct sipe_account_data *sip;
691 if (!PURPLE_CONNECTION_IS_VALID(gc))
693 if (gsc) purple_ssl_close(gsc);
694 return NULL;
697 sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
698 sip->fd = gsc->fd;
699 sip->gsc = gsc;
700 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
701 sip->connecting = FALSE;
702 sip->last_keepalive = time(NULL);
704 connection_create(sip, gsc->fd);
706 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
708 return sip;
711 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
712 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
714 PurpleConnection *gc = data;
715 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
716 if (sip == NULL) return;
718 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
720 /* If there is more to write now */
721 if (sip->txbuf->bufused > 0) {
722 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
727 static void sendlater(PurpleConnection *gc, const char *buf)
729 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
731 if (!sip->connecting) {
732 SIPE_DEBUG_INFO("connecting to %s port %d", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
733 if (sip->transport == SIPE_TRANSPORT_TLS){
734 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
735 } else {
736 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
737 purple_connection_error(gc, _("Could not create socket"));
740 sip->connecting = TRUE;
743 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
744 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
746 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
749 static void sendout_pkt(PurpleConnection *gc, const char *buf)
751 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
752 time_t currtime = time(NULL);
753 int writelen = strlen(buf);
754 char *tmp;
756 SIPE_DEBUG_INFO("sending - %s######\n%s######", ctime(&currtime), tmp = fix_newlines(buf));
757 g_free(tmp);
758 if (sip->transport == SIPE_TRANSPORT_UDP) {
759 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
760 SIPE_DEBUG_INFO_NOFORMAT("could not send packet");
762 } else {
763 int ret;
764 if (sip->fd < 0) {
765 sendlater(gc, buf);
766 return;
769 if (sip->tx_handler) {
770 ret = -1;
771 errno = EAGAIN;
772 } else{
773 if (sip->gsc){
774 ret = purple_ssl_write(sip->gsc, buf, writelen);
775 }else{
776 ret = write(sip->fd, buf, writelen);
780 if (ret < 0 && errno == EAGAIN)
781 ret = 0;
782 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
783 sendlater(gc, buf);
784 return;
787 if (ret < writelen) {
788 if (!sip->tx_handler){
789 if (sip->gsc){
790 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
792 else{
793 sip->tx_handler = purple_input_add(sip->fd,
794 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
795 gc);
799 /* XXX: is it OK to do this? You might get part of a request sent
800 with part of another. */
801 if (sip->txbuf->bufused > 0)
802 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
804 purple_circ_buffer_append(sip->txbuf, buf + ret,
805 writelen - ret);
810 int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
812 sendout_pkt(gc, buf);
813 return len;
816 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
818 GSList *tmp = msg->headers;
819 gchar *name;
820 gchar *value;
821 GString *outstr = g_string_new("");
822 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
823 while (tmp) {
824 name = ((struct sipnameval*) (tmp->data))->name;
825 value = ((struct sipnameval*) (tmp->data))->value;
826 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
827 tmp = g_slist_next(tmp);
829 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
830 sendout_pkt(sip->gc, outstr->str);
831 g_string_free(outstr, TRUE);
834 static void
835 sipe_make_signature(struct sipe_account_data *sip,
836 struct sipmsg *msg)
838 if (sip->registrar.gssapi_context) {
839 struct sipmsg_breakdown msgbd;
840 gchar *signature_input_str;
841 msgbd.msg = msg;
842 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
843 msgbd.rand = g_strdup_printf("%08x", g_random_int());
844 sip->registrar.ntlm_num++;
845 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
846 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
847 if (signature_input_str != NULL) {
848 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
849 msg->signature = signature_hex;
850 msg->rand = g_strdup(msgbd.rand);
851 msg->num = g_strdup(msgbd.num);
852 g_free(signature_input_str);
854 sipmsg_breakdown_free(&msgbd);
858 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
860 gchar * buf;
862 if (sip->registrar.type == AUTH_TYPE_UNSET) {
863 return;
866 sipe_make_signature(sip, msg);
868 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
869 buf = auth_header(sip, &sip->registrar, msg);
870 if (buf) {
871 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
873 g_free(buf);
874 } 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")) {
875 sip->registrar.nc = 3;
876 sip->registrar.type = AUTH_TYPE_NTLM;
877 #ifdef HAVE_LIBKRB5
878 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
879 sip->registrar.type = AUTH_TYPE_KERBEROS;
881 #endif
884 buf = auth_header(sip, &sip->registrar, msg);
885 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
886 g_free(buf);
887 } else {
888 SIPE_DEBUG_INFO("not adding auth header to msg w/ method %s", method);
892 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
893 const char *text, const char *body)
895 gchar *name;
896 gchar *value;
897 GString *outstr = g_string_new("");
898 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
899 gchar *contact;
900 GSList *tmp;
901 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
903 /* Can return NULL! */
904 contact = get_contact(sip);
905 if (contact) {
906 sipmsg_add_header(msg, "Contact", contact);
907 g_free(contact);
910 if (body) {
911 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
912 sipmsg_add_header(msg, "Content-Length", len);
913 g_free(len);
914 } else {
915 sipmsg_add_header(msg, "Content-Length", "0");
918 msg->response = code;
920 sipmsg_strip_headers(msg, keepers);
921 sipmsg_merge_new_headers(msg);
922 sign_outgoing_message(msg, sip, msg->method);
924 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
925 tmp = msg->headers;
926 while (tmp) {
927 name = ((struct sipnameval*) (tmp->data))->name;
928 value = ((struct sipnameval*) (tmp->data))->value;
930 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
931 tmp = g_slist_next(tmp);
933 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
934 sendout_pkt(gc, outstr->str);
935 g_string_free(outstr, TRUE);
938 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
940 if (sip->transactions) {
941 sip->transactions = g_slist_remove(sip->transactions, trans);
942 SIPE_DEBUG_INFO("sip->transactions count:%d after removal", g_slist_length(sip->transactions));
944 if (trans->msg) sipmsg_free(trans->msg);
945 if (trans->payload) {
946 (*trans->payload->destroy)(trans->payload->data);
947 g_free(trans->payload);
949 g_free(trans->key);
950 g_free(trans);
954 static struct transaction *
955 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
957 const gchar *call_id;
958 const gchar *cseq;
959 struct transaction *trans = g_new0(struct transaction, 1);
961 trans->time = time(NULL);
962 trans->msg = (struct sipmsg *)msg;
963 call_id = sipmsg_find_header(trans->msg, "Call-ID");
964 cseq = sipmsg_find_header(trans->msg, "CSeq");
965 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
966 trans->callback = callback;
967 sip->transactions = g_slist_append(sip->transactions, trans);
968 SIPE_DEBUG_INFO("sip->transactions count:%d after addition", g_slist_length(sip->transactions));
969 return trans;
972 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
974 struct transaction *trans;
975 GSList *transactions = sip->transactions;
976 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
977 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
978 gchar *key;
980 if (!call_id || !cseq) {
981 SIPE_DEBUG_ERROR_NOFORMAT("transaction_find: no Call-ID or CSeq!");
982 return NULL;
985 key = g_strdup_printf("<%s><%s>", call_id, cseq);
986 while (transactions) {
987 trans = transactions->data;
988 if (!g_strcasecmp(trans->key, key)) {
989 g_free(key);
990 return trans;
992 transactions = transactions->next;
995 g_free(key);
996 return NULL;
999 struct transaction *
1000 send_sip_request(PurpleConnection *gc, const gchar *method,
1001 const gchar *url, const gchar *to, const gchar *addheaders,
1002 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
1004 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
1005 const char *addh = "";
1006 char *buf;
1007 struct sipmsg *msg;
1008 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
1009 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
1010 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
1011 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
1012 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
1013 gchar *route = g_strdup("");
1014 gchar *epid = get_epid(sip);
1015 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
1016 struct transaction *trans = NULL;
1018 if (dialog && dialog->routes)
1020 GSList *iter = dialog->routes;
1022 while(iter)
1024 char *tmp = route;
1025 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1026 g_free(tmp);
1027 iter = g_slist_next(iter);
1031 if (!ourtag && !dialog) {
1032 ourtag = gentag();
1035 if (sipe_strequal(method, "REGISTER")) {
1036 if (sip->regcallid) {
1037 g_free(callid);
1038 callid = g_strdup(sip->regcallid);
1039 } else {
1040 sip->regcallid = g_strdup(callid);
1042 cseq = ++sip->cseq;
1045 if (addheaders) addh = addheaders;
1047 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1048 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1049 "From: <sip:%s>%s%s;epid=%s\r\n"
1050 "To: <%s>%s%s%s%s\r\n"
1051 "Max-Forwards: 70\r\n"
1052 "CSeq: %d %s\r\n"
1053 "User-Agent: %s\r\n"
1054 "Call-ID: %s\r\n"
1055 "%s%s"
1056 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1057 method,
1058 dialog && dialog->request ? dialog->request : url,
1059 TRANSPORT_DESCRIPTOR,
1060 sipe_backend_network_ip_address(),
1061 sip->listenport,
1062 branch ? ";branch=" : "",
1063 branch ? branch : "",
1064 sip->username,
1065 ourtag ? ";tag=" : "",
1066 ourtag ? ourtag : "",
1067 epid,
1069 theirtag ? ";tag=" : "",
1070 theirtag ? theirtag : "",
1071 theirepid ? ";epid=" : "",
1072 theirepid ? theirepid : "",
1073 cseq,
1074 method,
1075 sipe_get_useragent(sip),
1076 callid,
1077 route,
1078 addh,
1079 body ? (gsize) strlen(body) : 0,
1080 body ? body : "");
1083 //printf ("parsing msg buf:\n%s\n\n", buf);
1084 msg = sipmsg_parse_msg(buf);
1086 g_free(buf);
1087 g_free(ourtag);
1088 g_free(theirtag);
1089 g_free(theirepid);
1090 g_free(branch);
1091 g_free(callid);
1092 g_free(route);
1093 g_free(epid);
1095 sign_outgoing_message (msg, sip, method);
1097 buf = sipmsg_to_string (msg);
1099 /* add to ongoing transactions */
1100 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1101 if (!sipe_strequal(method, "ACK")) {
1102 trans = transactions_add_buf(sip, msg, tc);
1103 } else {
1104 sipmsg_free(msg);
1106 sendout_pkt(gc, buf);
1107 g_free(buf);
1109 return trans;
1113 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1115 static void
1116 send_soap_request_with_cb(struct sipe_account_data *sip,
1117 gchar *from0,
1118 gchar *body,
1119 TransCallback callback,
1120 struct transaction_payload *payload)
1122 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1123 gchar *contact = get_contact(sip);
1124 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1125 "Content-Type: application/SOAP+xml\r\n",contact);
1127 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1128 trans->payload = payload;
1130 g_free(from);
1131 g_free(contact);
1132 g_free(hdr);
1135 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1137 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1140 static char *get_contact_register(struct sipe_account_data *sip)
1142 char *epid = get_epid(sip);
1143 char *uuid = generateUUIDfromEPID(epid);
1144 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", sipe_backend_network_ip_address(), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1145 g_free(uuid);
1146 g_free(epid);
1147 return(buf);
1150 static void do_register_exp(struct sipe_account_data *sip, int expire)
1152 char *uri;
1153 char *expires;
1154 char *to;
1155 char *contact;
1156 char *hdr;
1158 if (!SIP_TO_CORE_PUBLIC->sip_domain) return;
1160 uri = sip_uri_from_name(SIP_TO_CORE_PUBLIC->sip_domain);
1161 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1162 to = sip_uri_self(sip);
1163 contact = get_contact_register(sip);
1164 hdr = g_strdup_printf("Contact: %s\r\n"
1165 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1166 "Event: registration\r\n"
1167 "Allow-Events: presence\r\n"
1168 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1169 "%s", contact, expires);
1170 g_free(contact);
1171 g_free(expires);
1173 sip->registerstatus = 1;
1175 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1176 process_register_response);
1178 g_free(hdr);
1179 g_free(uri);
1180 g_free(to);
1183 static void do_register_cb(struct sipe_core_private *sipe_private,
1184 SIPE_UNUSED_PARAMETER void *unused)
1186 struct sipe_account_data *sip = sipe_private->temporary;
1187 do_register_exp(sip, -1);
1188 sip->reregister_set = FALSE;
1191 static void do_register(struct sipe_account_data *sip)
1193 do_register_exp(sip, -1);
1197 * Returns pointer to URI without sip: prefix if any
1199 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
1200 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
1202 * Doesn't allocate memory
1204 static const char *
1205 sipe_get_no_sip_uri(const char *sip_uri)
1207 const char *prefix = "sip:";
1208 if (!sip_uri) return NULL;
1210 if (g_str_has_prefix(sip_uri, prefix)) {
1211 return (sip_uri+strlen(prefix));
1212 } else {
1213 return sip_uri;
1217 static void
1218 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1220 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1221 send_soap_request(sip, body);
1222 g_free(body);
1225 static void
1226 sipe_change_access_level(struct sipe_account_data *sip,
1227 const int container_id,
1228 const gchar *type,
1229 const gchar *value);
1231 void
1232 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
1233 const gchar * who, gboolean allow)
1235 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
1236 if (allow) {
1237 SIPE_DEBUG_INFO("Authorizing contact %s", who);
1238 } else {
1239 SIPE_DEBUG_INFO("Blocking contact %s", who);
1242 if (sip->ocs2007) {
1243 sipe_change_access_level(sip, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
1244 } else {
1245 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1249 static
1250 void sipe_auth_user_cb(void * data)
1252 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1253 if (!job) return;
1255 sipe_core_contact_allow_deny(job->sip->public, job->who, TRUE);
1256 g_free(job);
1259 static
1260 void sipe_deny_user_cb(void * data)
1262 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1263 if (!job) return;
1265 sipe_core_contact_allow_deny(job->sip->public, job->who, FALSE);
1266 g_free(job);
1269 /** @applicable: 2005-
1271 static void
1272 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1274 sipe_xml *watchers;
1275 const sipe_xml *watcher;
1276 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1277 if (msg->response != 0 && msg->response != 200) return;
1279 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1281 watchers = sipe_xml_parse(msg->body, msg->bodylen);
1282 if (!watchers) return;
1284 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
1285 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
1286 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
1287 gboolean on_list = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, remote_user) != NULL;
1289 // TODO pull out optional displayName to pass as alias
1290 if (remote_user) {
1291 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1292 job->who = remote_user;
1293 job->sip = sip;
1294 purple_account_request_authorization(
1295 sip->account,
1296 remote_user,
1297 _("you"), /* id */
1298 alias,
1299 NULL, /* message */
1300 on_list,
1301 sipe_auth_user_cb,
1302 sipe_deny_user_cb,
1303 (void *) job);
1308 sipe_xml_free(watchers);
1309 return;
1312 static void
1313 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1315 PurpleGroup * purple_group = purple_find_group(group->name);
1316 if (!purple_group) {
1317 purple_group = purple_group_new(group->name);
1318 purple_blist_add_group(purple_group, NULL);
1321 if (purple_group) {
1322 group->purple_group = purple_group;
1323 sip->groups = g_slist_append(sip->groups, group);
1324 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
1325 } else {
1326 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
1330 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1332 struct sipe_group *group;
1333 GSList *entry;
1334 if (sip == NULL) {
1335 return NULL;
1338 entry = sip->groups;
1339 while (entry) {
1340 group = entry->data;
1341 if (group->id == id) {
1342 return group;
1344 entry = entry->next;
1346 return NULL;
1349 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1351 struct sipe_group *group;
1352 GSList *entry;
1353 if (!sip || !name) {
1354 return NULL;
1357 entry = sip->groups;
1358 while (entry) {
1359 group = entry->data;
1360 if (sipe_strequal(group->name, name)) {
1361 return group;
1363 entry = entry->next;
1365 return NULL;
1368 static void
1369 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1371 gchar *body;
1372 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
1373 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1374 send_soap_request(sip, body);
1375 g_free(body);
1376 g_free(group->name);
1377 group->name = g_strdup(name);
1381 * Only appends if no such value already stored.
1382 * Like Set in Java.
1384 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1385 GSList * res = list;
1386 if (!g_slist_find_custom(list, data, func)) {
1387 res = g_slist_insert_sorted(list, data, func);
1389 return res;
1392 static int
1393 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1394 return group1->id - group2->id;
1398 * Returns string like "2 4 7 8" - group ids buddy belong to.
1400 static gchar *
1401 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1402 int i = 0;
1403 gchar *res;
1404 //creating array from GList, converting int to gchar*
1405 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1406 GSList *entry = buddy->groups;
1408 if (!ids_arr) return NULL;
1410 while (entry) {
1411 struct sipe_group * group = entry->data;
1412 ids_arr[i] = g_strdup_printf("%d", group->id);
1413 entry = entry->next;
1414 i++;
1416 ids_arr[i] = NULL;
1417 res = g_strjoinv(" ", ids_arr);
1418 g_strfreev(ids_arr);
1419 return res;
1423 * Sends buddy update to server
1425 void
1426 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
1428 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
1429 struct sipe_buddy *buddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, who);
1430 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1432 if (buddy && purple_buddy) {
1433 const char *alias = purple_buddy_get_alias(purple_buddy);
1434 gchar *groups = sipe_get_buddy_groups_string(buddy);
1435 if (groups) {
1436 gchar *body;
1437 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
1439 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1440 alias, groups, "true", buddy->name, sip->contacts_delta++
1442 send_soap_request(sip, body);
1443 g_free(groups);
1444 g_free(body);
1449 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1451 if (msg->response == 200) {
1452 struct sipe_group *group;
1453 struct group_user_context *ctx = trans->payload->data;
1454 sipe_xml *xml;
1455 const sipe_xml *node;
1456 char *group_id;
1457 struct sipe_buddy *buddy;
1459 xml = sipe_xml_parse(msg->body, msg->bodylen);
1460 if (!xml) {
1461 return FALSE;
1464 node = sipe_xml_child(xml, "Body/addGroup/groupID");
1465 if (!node) {
1466 sipe_xml_free(xml);
1467 return FALSE;
1470 group_id = sipe_xml_data(node);
1471 if (!group_id) {
1472 sipe_xml_free(xml);
1473 return FALSE;
1476 group = g_new0(struct sipe_group, 1);
1477 group->id = (int)g_ascii_strtod(group_id, NULL);
1478 g_free(group_id);
1479 group->name = g_strdup(ctx->group_name);
1481 sipe_group_add(sip, group);
1483 buddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, ctx->user_name);
1484 if (buddy) {
1485 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1488 sipe_core_group_set_user(SIP_TO_CORE_PUBLIC, ctx->user_name);
1490 sipe_xml_free(xml);
1491 return TRUE;
1493 return FALSE;
1496 static void sipe_group_context_destroy(gpointer data)
1498 struct group_user_context *ctx = data;
1499 g_free(ctx->group_name);
1500 g_free(ctx->user_name);
1501 g_free(ctx);
1504 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1506 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1507 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1508 gchar *body;
1509 ctx->group_name = g_strdup(name);
1510 ctx->user_name = g_strdup(who);
1511 payload->destroy = sipe_group_context_destroy;
1512 payload->data = ctx;
1514 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1515 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1516 g_free(body);
1520 * Data structure for scheduled actions
1523 struct scheduled_action {
1525 * Name of action.
1526 * Format is <Event>[<Data>...]
1527 * Example: <presence><sip:user@domain.com> or <registration>
1529 gchar *name;
1530 guint timeout_handler;
1531 gboolean repetitive;
1532 Action action;
1533 GDestroyNotify destroy;
1534 struct sipe_core_private *sipe_private;
1535 void *payload;
1539 * A timer callback
1540 * Should return FALSE if repetitive action is not needed
1542 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1544 gboolean ret;
1545 SIPE_DEBUG_INFO_NOFORMAT("sipe_scheduled_exec: executing");
1546 sched_action->sipe_private->timeouts = g_slist_remove(sched_action->sipe_private->timeouts, sched_action);
1547 SIPE_DEBUG_INFO("sipe_private->timeouts count:%d after removal", g_slist_length(sched_action->sipe_private->timeouts));
1548 (sched_action->action)(sched_action->sipe_private, sched_action->payload);
1549 ret = sched_action->repetitive;
1550 if (sched_action->destroy) {
1551 (*sched_action->destroy)(sched_action->payload);
1553 g_free(sched_action->name);
1554 g_free(sched_action);
1555 return ret;
1559 * Kills action timer effectively cancelling
1560 * scheduled action
1562 * @param name of action
1564 static void sipe_cancel_scheduled_action(struct sipe_core_private *sipe_private,
1565 const gchar *name)
1567 GSList *entry;
1569 if (!sipe_private->timeouts || !name) return;
1571 entry = sipe_private->timeouts;
1572 while (entry) {
1573 struct scheduled_action *sched_action = entry->data;
1574 if(sipe_strequal(sched_action->name, name)) {
1575 GSList *to_delete = entry;
1576 entry = entry->next;
1577 sipe_private->timeouts = g_slist_delete_link(sipe_private->timeouts, to_delete);
1578 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
1579 purple_timeout_remove(sched_action->timeout_handler);
1580 if (sched_action->destroy) {
1581 (*sched_action->destroy)(sched_action->payload);
1583 g_free(sched_action->name);
1584 g_free(sched_action);
1585 } else {
1586 entry = entry->next;
1591 static void
1592 sipe_schedule_action0(const gchar *name,
1593 int timeout,
1594 gboolean isSeconds,
1595 Action action,
1596 GDestroyNotify destroy,
1597 struct sipe_core_private *sipe_private,
1598 void *payload)
1600 struct scheduled_action *sched_action;
1602 /* Make sure each action only exists once */
1603 sipe_cancel_scheduled_action(sipe_private, name);
1605 SIPE_DEBUG_INFO("scheduling action %s timeout:%d(%s)", name, timeout, isSeconds ? "sec" : "msec");
1606 sched_action = g_new0(struct scheduled_action, 1);
1607 sched_action->repetitive = FALSE;
1608 sched_action->name = g_strdup(name);
1609 sched_action->action = action;
1610 sched_action->destroy = destroy;
1611 sched_action->sipe_private = sipe_private;
1612 sched_action->payload = payload;
1613 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1614 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1615 sipe_private->timeouts = g_slist_append(sipe_private->timeouts, sched_action);
1616 SIPE_DEBUG_INFO("sipe_private->timeouts count:%d after addition", g_slist_length(sipe_private->timeouts));
1619 void
1620 sipe_schedule_action(const gchar *name,
1621 int timeout,
1622 Action action,
1623 GDestroyNotify destroy,
1624 struct sipe_core_private *sipe_private,
1625 void *payload)
1627 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sipe_private, payload);
1631 * Same as sipe_schedule_action() but timeout is in milliseconds.
1633 static void
1634 sipe_schedule_action_msec(const gchar *name,
1635 int timeout,
1636 Action action,
1637 GDestroyNotify destroy,
1638 struct sipe_core_private *sipe_private,
1639 void *payload)
1641 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sipe_private, payload);
1644 static void
1645 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1646 time_t calculate_from);
1648 static int
1649 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1651 static const char*
1652 sipe_get_status_by_availability(int avail,
1653 char** activity);
1655 static void
1656 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1657 const char *status_id,
1658 const char *message,
1659 time_t do_not_publish[]);
1661 static void
1662 sipe_apply_calendar_status(struct sipe_account_data *sip,
1663 struct sipe_buddy *sbuddy,
1664 const char *status_id)
1666 time_t cal_avail_since;
1667 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1668 int avail;
1669 gchar *self_uri;
1671 if (!sbuddy) return;
1673 if (cal_status < SIPE_CAL_NO_DATA) {
1674 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
1675 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1678 /* scheduled Cal update call */
1679 if (!status_id) {
1680 status_id = sbuddy->last_non_cal_status_id;
1681 g_free(sbuddy->activity);
1682 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1685 if (!status_id) {
1686 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
1687 sbuddy->name ? sbuddy->name : "" );
1688 return;
1691 /* adjust to calendar status */
1692 if (cal_status != SIPE_CAL_NO_DATA) {
1693 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1695 if (cal_status == SIPE_CAL_BUSY
1696 && cal_avail_since > sbuddy->user_avail_since
1697 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1699 status_id = SIPE_STATUS_ID_BUSY;
1700 g_free(sbuddy->activity);
1701 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1703 avail = sipe_get_availability_by_status(status_id, NULL);
1705 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1706 if (cal_avail_since > sbuddy->activity_since) {
1707 if (cal_status == SIPE_CAL_OOF
1708 && avail >= 15000) /* 12000 in 2007 */
1710 g_free(sbuddy->activity);
1711 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1716 /* then set status_id actually */
1717 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
1718 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1720 /* set our account state to the one in roaming (including calendar info) */
1721 self_uri = sip_uri_self(sip);
1722 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1723 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1724 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1727 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
1728 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1730 g_free(self_uri);
1733 static void
1734 sipe_got_user_status(struct sipe_account_data *sip,
1735 const char* uri,
1736 const char *status_id)
1738 struct sipe_buddy *sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri);
1740 if (!sbuddy) return;
1742 /* Check if on 2005 system contact's calendar,
1743 * then set/preserve it.
1745 if (!sip->ocs2007) {
1746 sipe_apply_calendar_status(sip, sbuddy, status_id);
1747 } else {
1748 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1752 static void
1753 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1754 struct sipe_buddy *sbuddy,
1755 struct sipe_account_data *sip)
1757 sipe_apply_calendar_status(sip, sbuddy, NULL);
1761 * Updates contact's status
1762 * based on their calendar information.
1764 * Applicability: 2005 systems
1766 static void
1767 update_calendar_status(struct sipe_core_private *sipe_private,
1768 SIPE_UNUSED_PARAMETER void *unused)
1770 struct sipe_account_data *sip = sipe_private->temporary;
1772 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
1773 g_hash_table_foreach(SIP_TO_CORE_PRIVATE->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1775 /* repeat scheduling */
1776 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1780 * Schedules process of contacts' status update
1781 * based on their calendar information.
1782 * Should be scheduled to the beginning of every
1783 * 15 min interval, like:
1784 * 13:00, 13:15, 13:30, 13:45, etc.
1786 * Applicability: 2005 systems
1788 static void
1789 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1790 time_t calculate_from)
1792 int interval = 15*60;
1793 /** start of the beginning of closest 15 min interval. */
1794 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1796 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
1797 asctime(localtime(&calculate_from)));
1798 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
1799 asctime(localtime(&next_start)));
1801 sipe_schedule_action("<+2005-cal-status>",
1802 (int)(next_start - time(NULL)),
1803 update_calendar_status,
1804 NULL,
1805 SIP_TO_CORE_PRIVATE,
1806 NULL);
1810 * Schedules process of self status publish
1811 * based on own calendar information.
1812 * Should be scheduled to the beginning of every
1813 * 15 min interval, like:
1814 * 13:00, 13:15, 13:30, 13:45, etc.
1816 * Applicability: 2007+ systems
1818 static void
1819 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1820 time_t calculate_from)
1822 int interval = 5*60;
1823 /** start of the beginning of closest 5 min interval. */
1824 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1826 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1827 asctime(localtime(&calculate_from)));
1828 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1829 asctime(localtime(&next_start)));
1831 sipe_schedule_action("<+2007-cal-status>",
1832 (int)(next_start - time(NULL)),
1833 publish_calendar_status_self,
1834 NULL,
1835 SIP_TO_CORE_PRIVATE,
1836 NULL);
1839 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1841 /** Should be g_free()'d
1843 static gchar *
1844 sipe_get_subscription_key(const gchar *event,
1845 const gchar *with)
1847 gchar *key = NULL;
1849 if (is_empty(event)) return NULL;
1851 if (event && sipe_strcase_equal(event, "presence")) {
1852 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1853 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1855 /* @TODO drop participated buddies' just_added flag */
1856 } else if (event) {
1857 /* Subscription is identified by <event> key */
1858 key = g_strdup_printf("<%s>", event);
1861 return key;
1864 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1865 SIPE_UNUSED_PARAMETER struct transaction *trans)
1867 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1868 const gchar *event = sipmsg_find_header(msg, "Event");
1869 gchar *key;
1871 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1872 if (!event) {
1873 struct sipmsg *request_msg = trans->msg;
1874 event = sipmsg_find_header(request_msg, "Event");
1877 key = sipe_get_subscription_key(event, with);
1879 /* 200 OK; 481 Call Leg Does Not Exist */
1880 if (key && (msg->response == 200 || msg->response == 481)) {
1881 if (g_hash_table_lookup(sip->subscriptions, key)) {
1882 g_hash_table_remove(sip->subscriptions, key);
1883 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
1887 /* create/store subscription dialog if not yet */
1888 if (msg->response == 200) {
1889 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1890 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1892 if (key) {
1893 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1894 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1896 subscription->dialog.callid = g_strdup(callid);
1897 subscription->dialog.cseq = atoi(cseq);
1898 subscription->dialog.with = g_strdup(with);
1899 subscription->event = g_strdup(event);
1900 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1902 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key);
1905 g_free(cseq);
1908 g_free(key);
1909 g_free(with);
1911 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1913 process_incoming_notify(sip, msg, FALSE, FALSE);
1915 return TRUE;
1918 static void sipe_subscribe_resource_uri(const char *name,
1919 SIPE_UNUSED_PARAMETER gpointer value,
1920 gchar **resources_uri)
1922 gchar *tmp = *resources_uri;
1923 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1924 g_free(tmp);
1927 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1929 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1930 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1931 gchar *tmp = *resources_uri;
1933 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1935 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1936 g_free(tmp);
1940 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1941 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1942 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1943 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1944 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1947 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1949 gchar *key;
1950 gchar *contact = get_contact(sip);
1951 gchar *request;
1952 gchar *content;
1953 gchar *require = "";
1954 gchar *accept = "";
1955 gchar *autoextend = "";
1956 gchar *content_type;
1957 struct sip_dialog *dialog;
1959 if (sip->ocs2007) {
1960 require = ", categoryList";
1961 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1962 content_type = "application/msrtc-adrl-categorylist+xml";
1963 content = g_strdup_printf(
1964 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1965 "<action name=\"subscribe\" id=\"63792024\">\n"
1966 "<adhocList>\n%s</adhocList>\n"
1967 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1968 "<category name=\"calendarData\"/>\n"
1969 "<category name=\"contactCard\"/>\n"
1970 "<category name=\"note\"/>\n"
1971 "<category name=\"state\"/>\n"
1972 "</categoryList>\n"
1973 "</action>\n"
1974 "</batchSub>", sip->username, resources_uri);
1975 } else {
1976 autoextend = "Supported: com.microsoft.autoextend\r\n";
1977 content_type = "application/adrl+xml";
1978 content = g_strdup_printf(
1979 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1980 "<create xmlns=\"\">\n%s</create>\n"
1981 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1983 g_free(resources_uri);
1985 request = g_strdup_printf(
1986 "Require: adhoclist%s\r\n"
1987 "Supported: eventlist\r\n"
1988 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1989 "Supported: ms-piggyback-first-notify\r\n"
1990 "%sSupported: ms-benotify\r\n"
1991 "Proxy-Require: ms-benotify\r\n"
1992 "Event: presence\r\n"
1993 "Content-Type: %s\r\n"
1994 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1995 g_free(contact);
1997 /* subscribe to buddy presence */
1998 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1999 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2000 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2001 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2003 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2005 g_free(content);
2006 g_free(to);
2007 g_free(request);
2008 g_free(key);
2011 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
2012 SIPE_UNUSED_PARAMETER void *unused)
2014 gchar *to = sip_uri_self(sip);
2015 gchar *resources_uri = g_strdup("");
2016 if (sip->ocs2007) {
2017 g_hash_table_foreach(SIP_TO_CORE_PRIVATE->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
2018 } else {
2019 g_hash_table_foreach(SIP_TO_CORE_PRIVATE->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
2022 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
2025 struct presence_batched_routed {
2026 gchar *host;
2027 GSList *buddies;
2030 static void sipe_subscribe_presence_batched_routed_free(void *payload)
2032 struct presence_batched_routed *data = payload;
2033 GSList *buddies = data->buddies;
2034 while (buddies) {
2035 g_free(buddies->data);
2036 buddies = buddies->next;
2038 g_slist_free(data->buddies);
2039 g_free(data->host);
2040 g_free(payload);
2043 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
2044 void *payload)
2046 struct sipe_account_data *sip = sipe_private->temporary;
2047 struct presence_batched_routed *data = payload;
2048 GSList *buddies = data->buddies;
2049 gchar *resources_uri = g_strdup("");
2050 while (buddies) {
2051 gchar *tmp = resources_uri;
2052 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2053 g_free(tmp);
2054 buddies = buddies->next;
2056 sipe_subscribe_presence_batched_to(sip, resources_uri,
2057 g_strdup(data->host));
2061 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2062 * The user sends a single SUBSCRIBE request to the subscribed contact.
2063 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2067 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
2068 void *buddy_name)
2070 struct sipe_account_data *sip = sipe_private->temporary;
2071 gchar *key;
2072 gchar *to = sip_uri((char *)buddy_name);
2073 gchar *tmp = get_contact(sip);
2074 gchar *request;
2075 gchar *content = NULL;
2076 gchar *autoextend = "";
2077 gchar *content_type = "";
2078 struct sip_dialog *dialog;
2079 struct sipe_buddy *sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, to);
2080 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2082 if (sbuddy) sbuddy->just_added = FALSE;
2084 if (sip->ocs2007) {
2085 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2086 } else {
2087 autoextend = "Supported: com.microsoft.autoextend\r\n";
2090 request = g_strdup_printf(
2091 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2092 "Supported: ms-piggyback-first-notify\r\n"
2093 "%s%sSupported: ms-benotify\r\n"
2094 "Proxy-Require: ms-benotify\r\n"
2095 "Event: presence\r\n"
2096 "Contact: %s\r\n", autoextend, content_type, tmp);
2098 if (sip->ocs2007) {
2099 content = g_strdup_printf(
2100 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2101 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2102 "<resource uri=\"%s\"%s\n"
2103 "</adhocList>\n"
2104 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2105 "<category name=\"calendarData\"/>\n"
2106 "<category name=\"contactCard\"/>\n"
2107 "<category name=\"note\"/>\n"
2108 "<category name=\"state\"/>\n"
2109 "</categoryList>\n"
2110 "</action>\n"
2111 "</batchSub>", sip->username, to, context);
2114 g_free(tmp);
2116 /* subscribe to buddy presence */
2117 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2118 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2119 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2120 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2122 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2124 g_free(content);
2125 g_free(to);
2126 g_free(request);
2127 g_free(key);
2130 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2132 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
2134 if (!purple_status_is_active(status))
2135 return;
2137 if (account->gc) {
2138 struct sipe_account_data *sip = PURPLE_ACCOUNT_TO_SIPE_ACCOUNT_DATA;
2140 if (sip) {
2141 gchar *action_name;
2142 gchar *tmp;
2143 time_t now = time(NULL);
2144 const char *status_id = purple_status_get_id(status);
2145 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2146 sipe_activity activity = sipe_get_activity_by_token(status_id);
2147 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2149 /* when other point of presence clears note, but we are keeping
2150 * state if OOF note.
2152 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2153 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
2154 do_not_publish = FALSE;
2157 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
2158 status_id, (int)sip->do_not_publish[activity], (int)now);
2160 sip->do_not_publish[activity] = 0;
2161 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
2162 status_id, (int)sip->do_not_publish[activity]);
2164 if (do_not_publish)
2166 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
2167 return;
2170 g_free(sip->status);
2171 sip->status = g_strdup(status_id);
2173 /* hack to escape apostrof before comparison */
2174 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
2176 /* this will preserve OOF flag as well */
2177 if (!sipe_strequal(tmp, sip->note)) {
2178 sip->is_oof_note = FALSE;
2179 g_free(sip->note);
2180 sip->note = g_strdup(note);
2181 sip->note_since = time(NULL);
2183 g_free(tmp);
2185 /* schedule 2 sec to capture idle flag */
2186 action_name = g_strdup_printf("<%s>", "+set-status");
2187 sipe_schedule_action(action_name,
2188 SIPE_IDLE_SET_DELAY,
2189 send_presence_status,
2190 NULL,
2191 SIP_TO_CORE_PRIVATE,
2192 NULL);
2193 g_free(action_name);
2198 void
2199 sipe_set_idle(PurpleConnection * gc,
2200 int interval)
2202 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
2204 if (gc) {
2205 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
2207 if (sip) {
2208 sip->idle_switch = time(NULL);
2209 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2214 void
2215 sipe_group_buddy(PurpleConnection *gc,
2216 const char *who,
2217 const char *old_group_name,
2218 const char *new_group_name)
2220 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
2221 struct sipe_buddy * buddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, who);
2222 struct sipe_group * old_group = NULL;
2223 struct sipe_group * new_group;
2225 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
2226 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2228 if(!buddy) { // buddy not in roaming list
2229 return;
2232 if (old_group_name) {
2233 old_group = sipe_group_find_by_name(sip, old_group_name);
2235 new_group = sipe_group_find_by_name(sip, new_group_name);
2237 if (old_group) {
2238 buddy->groups = g_slist_remove(buddy->groups, old_group);
2239 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
2242 if (!new_group) {
2243 sipe_group_create(sip, new_group_name, who);
2244 } else {
2245 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2246 sipe_core_group_set_user(SIP_TO_CORE_PUBLIC, who);
2250 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2252 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2254 /* libpurple can call us with undefined buddy or group */
2255 if (buddy && group) {
2256 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
2258 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2259 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2260 purple_blist_rename_buddy(buddy, buddy_name);
2261 g_free(buddy_name);
2263 /* Prepend sip: if needed */
2264 if (!g_str_has_prefix(buddy->name, "sip:")) {
2265 gchar *buf = sip_uri_from_name(buddy->name);
2266 purple_blist_rename_buddy(buddy, buf);
2267 g_free(buf);
2270 if (!g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, buddy->name)) {
2271 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2272 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
2273 b->name = g_strdup(buddy->name);
2274 b->just_added = TRUE;
2275 g_hash_table_insert(SIP_TO_CORE_PRIVATE->buddies, b->name, b);
2276 sipe_group_buddy(gc, b->name, NULL, group->name);
2277 /* @TODO should go to callback */
2278 sipe_subscribe_presence_single(SIP_TO_CORE_PRIVATE,
2279 b->name);
2280 } else {
2281 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
2286 static void sipe_free_buddy(struct sipe_buddy *buddy)
2288 #ifndef _WIN32
2290 * We are calling g_hash_table_foreach_steal(). That means that no
2291 * key/value deallocation functions are called. Therefore the glib
2292 * hash code does not touch the key (buddy->name) or value (buddy)
2293 * of the to-be-deleted hash node at all. It follows that we
2295 * - MUST free the memory for the key ourselves and
2296 * - ARE allowed to do it in this function
2298 * Conclusion: glib must be broken on the Windows platform if sipe
2299 * crashes with SIGTRAP when closing. You'll have to live
2300 * with the memory leak until this is fixed.
2302 g_free(buddy->name);
2303 #endif
2304 g_free(buddy->activity);
2305 g_free(buddy->meeting_subject);
2306 g_free(buddy->meeting_location);
2307 g_free(buddy->note);
2309 g_free(buddy->cal_start_time);
2310 g_free(buddy->cal_free_busy_base64);
2311 g_free(buddy->cal_free_busy);
2312 g_free(buddy->last_non_cal_activity);
2314 sipe_cal_free_working_hours(buddy->cal_working_hours);
2316 g_free(buddy->device_name);
2317 g_slist_free(buddy->groups);
2318 g_free(buddy);
2322 * Unassociates buddy from group first.
2323 * Then see if no groups left, removes buddy completely.
2324 * Otherwise updates buddy groups on server.
2326 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2328 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
2329 struct sipe_buddy *b;
2330 struct sipe_group *g = NULL;
2332 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2333 if (!buddy) return;
2335 b = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, buddy->name);
2336 if (!b) return;
2338 if (group) {
2339 g = sipe_group_find_by_name(sip, group->name);
2342 if (g) {
2343 b->groups = g_slist_remove(b->groups, g);
2344 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
2347 if (g_slist_length(b->groups) < 1) {
2348 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2349 sipe_cancel_scheduled_action(SIP_TO_CORE_PRIVATE, action_name);
2350 g_free(action_name);
2352 g_hash_table_remove(SIP_TO_CORE_PRIVATE->buddies, buddy->name);
2354 if (b->name) {
2355 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2356 send_soap_request(sip, body);
2357 g_free(body);
2360 sipe_free_buddy(b);
2361 } else {
2362 //updates groups on server
2363 sipe_core_group_set_user(SIP_TO_CORE_PUBLIC, b->name);
2368 void
2369 sipe_rename_group(PurpleConnection *gc,
2370 const char *old_name,
2371 PurpleGroup *group,
2372 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2374 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
2375 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2376 if (s_group) {
2377 sipe_group_rename(sip, s_group, group->name);
2378 } else {
2379 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
2383 void
2384 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2386 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
2387 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2388 if (s_group) {
2389 gchar *body;
2390 SIPE_DEBUG_INFO("Deleting group %s", group->name);
2391 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2392 send_soap_request(sip, body);
2393 g_free(body);
2395 sip->groups = g_slist_remove(sip->groups, s_group);
2396 g_free(s_group->name);
2397 g_free(s_group);
2398 } else {
2399 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
2404 * A callback for g_hash_table_foreach
2406 static void
2407 sipe_buddy_subscribe_cb(char *buddy_name,
2408 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2409 struct sipe_account_data *sip)
2411 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2412 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2413 guint time_range = (g_hash_table_size(SIP_TO_CORE_PRIVATE->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2414 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2416 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, SIP_TO_CORE_PRIVATE, g_strdup(buddy_name));
2417 g_free(action_name);
2421 * Removes entries from purple buddy list
2422 * that does not correspond ones in the roaming contact list.
2424 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2425 GSList *buddies = purple_find_buddies(sip->account, NULL);
2426 GSList *entry = buddies;
2427 struct sipe_buddy *buddy;
2428 PurpleBuddy *b;
2429 PurpleGroup *g;
2431 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
2432 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(SIP_TO_CORE_PRIVATE->buddies));
2433 while (entry) {
2434 b = entry->data;
2435 g = purple_buddy_get_group(b);
2436 buddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, b->name);
2437 if(buddy) {
2438 gboolean in_sipe_groups = FALSE;
2439 GSList *entry2 = buddy->groups;
2440 while (entry2) {
2441 struct sipe_group *group = entry2->data;
2442 if (sipe_strequal(group->name, g->name)) {
2443 in_sipe_groups = TRUE;
2444 break;
2446 entry2 = entry2->next;
2448 if(!in_sipe_groups) {
2449 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
2450 purple_blist_remove_buddy(b);
2452 } else {
2453 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
2454 purple_blist_remove_buddy(b);
2456 entry = entry->next;
2458 g_slist_free(buddies);
2461 static int
2462 sipe_find_access_level(struct sipe_account_data *sip,
2463 const gchar *type,
2464 const gchar *value,
2465 gboolean *is_group_access);
2467 static void
2468 sipe_refresh_blocked_status_cb(char *buddy_name,
2469 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2470 struct sipe_account_data *sip)
2472 int container_id = sipe_find_access_level(sip, "user", buddy_name, NULL);
2473 gboolean blocked = (container_id == 32000);
2474 gboolean blocked_in_blist = !purple_privacy_check(sip->account, buddy_name);
2476 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
2477 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
2479 if (blocked != blocked_in_blist) {
2480 if (blocked) {
2481 purple_privacy_deny_add(sip->account, buddy_name, TRUE);
2482 } else {
2483 purple_privacy_deny_remove(sip->account, buddy_name, TRUE);
2486 /* stupid workaround to make pidgin re-render screen to reflect our changes */
2488 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, buddy_name);
2489 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
2490 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
2492 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
2493 sipe_got_user_status(sip, buddy_name, purple_status_get_id(pstatus));
2499 static void
2500 sipe_refresh_blocked_status(struct sipe_account_data *sip)
2502 g_hash_table_foreach(SIP_TO_CORE_PRIVATE->buddies, (GHFunc) sipe_refresh_blocked_status_cb , (gpointer)sip);
2505 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2507 int len = msg->bodylen;
2509 const gchar *tmp = sipmsg_find_header(msg, "Event");
2510 const sipe_xml *item;
2511 sipe_xml *isc;
2512 const gchar *contacts_delta;
2513 const sipe_xml *group_node;
2514 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2515 return FALSE;
2518 /* Convert the contact from XML to Purple Buddies */
2519 isc = sipe_xml_parse(msg->body, len);
2520 if (!isc) {
2521 return FALSE;
2524 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
2525 if (contacts_delta) {
2526 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2529 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
2531 /* Parse groups */
2532 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
2533 struct sipe_group * group = g_new0(struct sipe_group, 1);
2534 const char *name = sipe_xml_attribute(group_node, "name");
2536 if (g_str_has_prefix(name, "~")) {
2537 name = _("Other Contacts");
2539 group->name = g_strdup(name);
2540 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
2542 sipe_group_add(sip, group);
2545 // Make sure we have at least one group
2546 if (g_slist_length(sip->groups) == 0) {
2547 struct sipe_group * group = g_new0(struct sipe_group, 1);
2548 PurpleGroup *purple_group;
2549 group->name = g_strdup(_("Other Contacts"));
2550 group->id = 1;
2551 purple_group = purple_group_new(group->name);
2552 purple_blist_add_group(purple_group, NULL);
2553 sip->groups = g_slist_append(sip->groups, group);
2556 /* Parse contacts */
2557 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
2558 const gchar *uri = sipe_xml_attribute(item, "uri");
2559 const gchar *name = sipe_xml_attribute(item, "name");
2560 gchar *buddy_name;
2561 struct sipe_buddy *buddy = NULL;
2562 gchar *tmp;
2563 gchar **item_groups;
2564 int i = 0;
2566 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2567 tmp = sip_uri_from_name(uri);
2568 buddy_name = g_ascii_strdown(tmp, -1);
2569 g_free(tmp);
2571 /* assign to group Other Contacts if nothing else received */
2572 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
2573 if(is_empty(tmp)) {
2574 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2575 g_free(tmp);
2576 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2578 item_groups = g_strsplit(tmp, " ", 0);
2579 g_free(tmp);
2581 while (item_groups[i]) {
2582 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2584 // If couldn't find the right group for this contact, just put them in the first group we have
2585 if (group == NULL && g_slist_length(sip->groups) > 0) {
2586 group = sip->groups->data;
2589 if (group != NULL) {
2590 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2591 if (!b){
2592 b = purple_buddy_new(sip->account, buddy_name, uri);
2593 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2595 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
2598 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2599 if (name != NULL && strlen(name) != 0) {
2600 purple_blist_alias_buddy(b, name);
2602 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
2606 if (!buddy) {
2607 buddy = g_new0(struct sipe_buddy, 1);
2608 buddy->name = g_strdup(b->name);
2609 g_hash_table_insert(SIP_TO_CORE_PRIVATE->buddies, buddy->name, buddy);
2612 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2614 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
2615 } else {
2616 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2617 name);
2620 i++;
2621 } // while, contact groups
2622 g_strfreev(item_groups);
2623 g_free(buddy_name);
2625 } // for, contacts
2627 sipe_cleanup_local_blist(sip);
2629 /* Add self-contact if not there yet. 2005 systems. */
2630 /* This will resemble subscription to roaming_self in 2007 systems */
2631 if (!sip->ocs2007) {
2632 gchar *self_uri = sip_uri_self(sip);
2633 struct sipe_buddy *buddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, self_uri);
2635 if (!buddy) {
2636 buddy = g_new0(struct sipe_buddy, 1);
2637 buddy->name = g_strdup(self_uri);
2638 g_hash_table_insert(SIP_TO_CORE_PRIVATE->buddies, buddy->name, buddy);
2640 g_free(self_uri);
2643 sipe_xml_free(isc);
2645 /* subscribe to buddies */
2646 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2647 if (sip->batched_support) {
2648 sipe_subscribe_presence_batched(sip, NULL);
2649 } else {
2650 g_hash_table_foreach(SIP_TO_CORE_PRIVATE->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2652 sip->subscribed_buddies = TRUE;
2654 /* for 2005 systems schedule contacts' status update
2655 * based on their calendar information
2657 if (!sip->ocs2007) {
2658 sipe_sched_calendar_status_update(sip, time(NULL));
2661 return 0;
2665 * Subscribe roaming contacts
2667 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2669 gchar *to = sip_uri_self(sip);
2670 gchar *tmp = get_contact(sip);
2671 gchar *hdr = g_strdup_printf(
2672 "Event: vnd-microsoft-roaming-contacts\r\n"
2673 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2674 "Supported: com.microsoft.autoextend\r\n"
2675 "Supported: ms-benotify\r\n"
2676 "Proxy-Require: ms-benotify\r\n"
2677 "Supported: ms-piggyback-first-notify\r\n"
2678 "Contact: %s\r\n", tmp);
2679 g_free(tmp);
2681 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2682 g_free(to);
2683 g_free(hdr);
2686 static void sipe_subscribe_presence_wpending(struct sipe_core_private *sipe_private,
2687 SIPE_UNUSED_PARAMETER void *unused)
2689 struct sipe_account_data *sip = sipe_private->temporary;
2690 gchar *key;
2691 struct sip_dialog *dialog;
2692 gchar *to = sip_uri_self(sip);
2693 gchar *tmp = get_contact(sip);
2694 gchar *hdr = g_strdup_printf(
2695 "Event: presence.wpending\r\n"
2696 "Accept: text/xml+msrtc.wpending\r\n"
2697 "Supported: com.microsoft.autoextend\r\n"
2698 "Supported: ms-benotify\r\n"
2699 "Proxy-Require: ms-benotify\r\n"
2700 "Supported: ms-piggyback-first-notify\r\n"
2701 "Contact: %s\r\n", tmp);
2702 g_free(tmp);
2704 /* Subscription is identified by <event> key */
2705 key = g_strdup_printf("<%s>", "presence.wpending");
2706 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2707 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2709 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2711 g_free(to);
2712 g_free(hdr);
2713 g_free(key);
2717 * Fires on deregistration event initiated by server.
2718 * [MS-SIPREGE] SIP extension.
2721 // 2007 Example
2723 // Content-Type: text/registration-event
2724 // subscription-state: terminated;expires=0
2725 // ms-diagnostics-public: 4141;reason="User disabled"
2727 // deregistered;event=rejected
2729 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2731 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2732 gchar *event = NULL;
2733 gchar *reason = NULL;
2734 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2735 gchar *warning;
2737 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2738 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2740 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2741 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2742 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2743 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2744 } else {
2745 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2746 return;
2749 if (diagnostics != NULL) {
2750 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2751 } else { // for LCS2005
2752 int error_id = 0;
2753 if (event && sipe_strcase_equal(event, "unregistered")) {
2754 error_id = 4140; // [MS-SIPREGE]
2755 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2756 reason = g_strdup(_("you are already signed in at another location"));
2757 } else if (event && sipe_strcase_equal(event, "rejected")) {
2758 error_id = 4141;
2759 reason = g_strdup(_("user disabled")); // [MS-OCER]
2760 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2761 error_id = 4142;
2762 reason = g_strdup(_("user moved")); // [MS-OCER]
2765 g_free(event);
2766 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2767 g_free(reason);
2769 sip->gc->wants_to_die = TRUE;
2770 purple_connection_error(sip->gc, warning);
2771 g_free(warning);
2775 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2777 sipe_xml *xn_provision_group_list;
2778 const sipe_xml *node;
2780 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
2782 /* provisionGroup */
2783 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
2784 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
2785 g_free(sip->focus_factory_uri);
2786 sip->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
2787 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2788 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2789 break;
2792 sipe_xml_free(xn_provision_group_list);
2795 /** for 2005 system */
2796 static void
2797 sipe_process_provisioning(struct sipe_account_data *sip,
2798 struct sipmsg *msg)
2800 sipe_xml *xn_provision;
2801 const sipe_xml *node;
2803 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
2804 if ((node = sipe_xml_child(xn_provision, "user"))) {
2805 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
2806 if ((node = sipe_xml_child(node, "line"))) {
2807 const gchar *line_uri = sipe_xml_attribute(node, "uri");
2808 const gchar *server = sipe_xml_attribute(node, "server");
2809 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2810 sip_csta_open(sip, line_uri, server);
2813 sipe_xml_free(xn_provision);
2816 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2818 const gchar *contacts_delta;
2819 sipe_xml *xml;
2821 xml = sipe_xml_parse(msg->body, msg->bodylen);
2822 if (!xml)
2824 return;
2827 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
2828 if (contacts_delta)
2830 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2833 sipe_xml_free(xml);
2836 static void
2837 free_container_member(struct sipe_container_member *member)
2839 if (!member) return;
2841 g_free(member->type);
2842 g_free(member->value);
2843 g_free(member);
2846 static void
2847 free_container(struct sipe_container *container)
2849 GSList *entry;
2851 if (!container) return;
2853 entry = container->members;
2854 while (entry) {
2855 void *data = entry->data;
2856 entry = g_slist_remove(entry, data);
2857 free_container_member((struct sipe_container_member *)data);
2859 g_free(container);
2862 static void
2863 sipe_send_container_members_prepare(const guint container_id,
2864 const guint container_version,
2865 const gchar *action,
2866 const gchar *type,
2867 const gchar *value,
2868 char **container_xmls)
2870 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2871 gchar *body;
2873 if (!container_xmls) return;
2875 body = g_strdup_printf(
2876 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
2877 container_id,
2878 container_version,
2879 action,
2880 type,
2881 value_str);
2882 g_free(value_str);
2884 if ((*container_xmls) == NULL) {
2885 *container_xmls = body;
2886 } else {
2887 char *tmp = *container_xmls;
2889 *container_xmls = g_strconcat(*container_xmls, body, NULL);
2890 g_free(tmp);
2891 g_free(body);
2895 static void
2896 sipe_send_set_container_members(struct sipe_account_data *sip,
2897 char *container_xmls)
2899 gchar *self;
2900 gchar *contact;
2901 gchar *hdr;
2902 gchar *body;
2904 if (!container_xmls) return;
2906 self = sip_uri_self(sip);
2907 body = g_strdup_printf(
2908 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2909 "%s"
2910 "</setContainerMembers>",
2911 container_xmls);
2913 contact = get_contact(sip);
2914 hdr = g_strdup_printf("Contact: %s\r\n"
2915 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2916 g_free(contact);
2918 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2920 g_free(hdr);
2921 g_free(body);
2922 g_free(self);
2926 * Finds locally stored MS-PRES container member
2928 static struct sipe_container_member *
2929 sipe_find_container_member(struct sipe_container *container,
2930 const gchar *type,
2931 const gchar *value)
2933 struct sipe_container_member *member;
2934 GSList *entry;
2936 if (container == NULL || type == NULL) {
2937 return NULL;
2940 entry = container->members;
2941 while (entry) {
2942 member = entry->data;
2943 if (sipe_strcase_equal(member->type, type) &&
2944 sipe_strcase_equal(member->value, value))
2946 return member;
2948 entry = entry->next;
2950 return NULL;
2954 * Finds locally stored MS-PRES container by id
2956 static struct sipe_container *
2957 sipe_find_container(struct sipe_account_data *sip,
2958 guint id)
2960 struct sipe_container *container;
2961 GSList *entry;
2963 if (sip == NULL) {
2964 return NULL;
2967 entry = sip->containers;
2968 while (entry) {
2969 container = entry->data;
2970 if (id == container->id) {
2971 return container;
2973 entry = entry->next;
2975 return NULL;
2978 static GSList *
2979 sipe_get_access_domains(struct sipe_account_data *sip)
2981 struct sipe_container *container;
2982 struct sipe_container_member *member;
2983 GSList *entry;
2984 GSList *entry2;
2985 GSList *res = NULL;
2987 if (!sip) return NULL;
2989 entry = sip->containers;
2990 while (entry) {
2991 container = entry->data;
2993 entry2 = container->members;
2994 while (entry2) {
2995 member = entry2->data;
2996 if (sipe_strcase_equal(member->type, "domain"))
2998 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
3000 entry2 = entry2->next;
3002 entry = entry->next;
3004 return res;
3008 * Returns pointer to domain part in provided Email URL
3010 * @param email an email URL. Example: first.last@hq.company.com
3011 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
3013 * Doesn't allocate memory
3015 static const char *
3016 sipe_get_domain(const char *email)
3018 char *tmp;
3020 if (!email) return NULL;
3022 tmp = strstr(email, "@");
3024 if (tmp && ((tmp+1) < (email + strlen(email)))) {
3025 return tmp+1;
3026 } else {
3027 return NULL;
3032 /* @TODO: replace with binary search for faster access? */
3033 /** source: http://support.microsoft.com/kb/897567 */
3034 static const char * const public_domains [] = {
3035 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
3036 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
3037 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
3038 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
3039 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
3040 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
3041 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
3042 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
3043 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
3044 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
3045 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
3046 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
3047 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
3048 "yahoo.com",
3049 NULL};
3051 static gboolean
3052 sipe_is_public_domain(const char *domain)
3054 int i = 0;
3055 while (public_domains[i]) {
3056 if (sipe_strcase_equal(public_domains[i], domain)) {
3057 return TRUE;
3059 i++;
3061 return FALSE;
3065 * Access Levels
3066 * 32000 - Blocked
3067 * 400 - Personal
3068 * 300 - Team
3069 * 200 - Company
3070 * 100 - Public
3072 static const char *
3073 sipe_get_access_level_name(int container_id)
3075 switch(container_id) {
3076 case 32000: return _("Blocked");
3077 case 400: return _("Personal");
3078 case 300: return _("Team");
3079 case 200: return _("Company");
3080 case 100: return _("Public");
3082 return _("Unknown");
3085 static const guint containers[] = {32000, 400, 300, 200, 100};
3086 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
3089 static int
3090 sipe_find_member_access_level(struct sipe_account_data *sip,
3091 const gchar *type,
3092 const gchar *value)
3094 unsigned int i = 0;
3095 const gchar *value_mod = value;
3097 if (!type) return -1;
3099 if (sipe_strequal("user", type)) {
3100 value_mod = sipe_get_no_sip_uri(value);
3103 for (i = 0; i < CONTAINERS_LEN; i++) {
3104 struct sipe_container_member *member;
3105 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3106 if (!container) continue;
3108 member = sipe_find_container_member(container, type, value_mod);
3109 if (member) return containers[i];
3112 return -1;
3115 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
3116 static int
3117 sipe_find_access_level(struct sipe_account_data *sip,
3118 const gchar *type,
3119 const gchar *value,
3120 gboolean *is_group_access)
3122 int container_id = -1;
3124 if (sipe_strequal("user", type)) {
3125 const char *domain;
3126 const char *no_sip_uri = sipe_get_no_sip_uri(value);
3128 container_id = sipe_find_member_access_level(sip, "user", no_sip_uri);
3129 if (container_id >= 0) {
3130 if (is_group_access) *is_group_access = FALSE;
3131 return container_id;
3134 domain = sipe_get_domain(no_sip_uri);
3135 container_id = sipe_find_member_access_level(sip, "domain", domain);
3136 if (container_id >= 0) {
3137 if (is_group_access) *is_group_access = TRUE;
3138 return container_id;
3141 container_id = sipe_find_member_access_level(sip, "sameEnterprise", NULL);
3142 if ((container_id >= 0) && sipe_strcase_equal(SIP_TO_CORE_PUBLIC->sip_domain, domain)) {
3143 if (is_group_access) *is_group_access = TRUE;
3144 return container_id;
3147 container_id = sipe_find_member_access_level(sip, "publicCloud", NULL);
3148 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
3149 if (is_group_access) *is_group_access = TRUE;
3150 return container_id;
3153 container_id = sipe_find_member_access_level(sip, "everyone", NULL);
3154 if ((container_id >= 0)) {
3155 if (is_group_access) *is_group_access = TRUE;
3156 return container_id;
3158 } else {
3159 container_id = sipe_find_member_access_level(sip, type, value);
3160 if (is_group_access) *is_group_access = FALSE;
3163 return container_id;
3167 * @param container_id a new access level. If -1 then current access level
3168 * is just removed (I.e. the member is removed from all containers).
3169 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
3170 * @param value a value for member. E.g. SIP URI for "user" member type.
3172 static void
3173 sipe_change_access_level(struct sipe_account_data *sip,
3174 const int container_id,
3175 const gchar *type,
3176 const gchar *value)
3178 unsigned int i;
3179 int current_container_id = -1;
3180 char *container_xmls = NULL;
3182 /* for each container: find/delete */
3183 for (i = 0; i < CONTAINERS_LEN; i++) {
3184 struct sipe_container_member *member;
3185 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3187 if (!container) continue;
3189 member = sipe_find_container_member(container, type, value);
3190 if (member) {
3191 current_container_id = containers[i];
3192 /* delete/publish current access level */
3193 if (container_id < 0 || container_id != current_container_id) {
3194 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
3195 /* remove member from our cache, to be able to recalculate AL below */
3196 container->members = g_slist_remove(container->members, member);
3197 current_container_id = -1;
3202 /* recalculate AL below */
3203 current_container_id = sipe_find_access_level(sip, type, value, NULL);
3205 /* assign/publish new access level */
3206 if (container_id != current_container_id && container_id >= 0) {
3207 struct sipe_container *container = sipe_find_container(sip, container_id);
3208 guint version = container ? container->version : 0;
3210 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
3213 if (container_xmls) {
3214 sipe_send_set_container_members(sip, container_xmls);
3216 g_free(container_xmls);
3219 static void
3220 free_publication(struct sipe_publication *publication)
3222 g_free(publication->category);
3223 g_free(publication->cal_event_hash);
3224 g_free(publication->note);
3226 g_free(publication->working_hours_xml_str);
3227 g_free(publication->fb_start_str);
3228 g_free(publication->free_busy_base64);
3230 g_free(publication);
3233 /* key is <category><instance><container> */
3234 static gboolean
3235 sipe_is_our_publication(struct sipe_account_data *sip,
3236 const gchar *key)
3238 GSList *entry;
3240 /* filling keys for our publications if not yet cached */
3241 if (!sip->our_publication_keys) {
3242 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
3243 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
3244 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
3245 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
3246 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
3247 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
3248 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
3250 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
3251 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
3252 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
3253 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
3254 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
3255 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
3256 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
3257 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
3258 SIPE_DEBUG_INFO("\tNote : %u", 0);
3259 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
3261 /* device */
3262 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3263 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
3265 /* state:machineState */
3266 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3267 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
3268 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3269 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3271 /* state:userState */
3272 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3273 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3274 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3275 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3277 /* state:calendarState */
3278 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3279 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3280 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3281 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3283 /* state:calendarState OOF */
3284 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3285 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3286 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3287 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3289 /* note */
3290 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3291 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3292 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3293 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3294 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3295 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3297 /* note OOF */
3298 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3299 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3300 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3301 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3302 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3303 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3305 /* calendarData:WorkingHours */
3306 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3307 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3308 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3309 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3310 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3311 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3312 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3313 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3314 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3315 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3316 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3317 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3319 /* calendarData:FreeBusy */
3320 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3321 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3322 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3323 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3324 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3325 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3326 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3327 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3328 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3329 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3330 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3331 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3333 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3334 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3337 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3339 entry = sip->our_publication_keys;
3340 while (entry) {
3341 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3342 if (sipe_strequal(entry->data, key)) {
3343 return TRUE;
3345 entry = entry->next;
3347 return FALSE;
3350 /** Property names to store in blist.xml */
3351 #define ALIAS_PROP "alias"
3352 #define EMAIL_PROP "email"
3353 #define PHONE_PROP "phone"
3354 #define PHONE_DISPLAY_PROP "phone-display"
3355 #define PHONE_MOBILE_PROP "phone-mobile"
3356 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3357 #define PHONE_HOME_PROP "phone-home"
3358 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3359 #define PHONE_OTHER_PROP "phone-other"
3360 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3361 #define PHONE_CUSTOM1_PROP "phone-custom1"
3362 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3363 #define SITE_PROP "site"
3364 #define COMPANY_PROP "company"
3365 #define DEPARTMENT_PROP "department"
3366 #define TITLE_PROP "title"
3367 #define OFFICE_PROP "office"
3368 /** implies work address */
3369 #define ADDRESS_STREET_PROP "address-street"
3370 #define ADDRESS_CITY_PROP "address-city"
3371 #define ADDRESS_STATE_PROP "address-state"
3372 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3373 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3376 * Tries to figure out user first and last name
3377 * based on Display Name and email properties.
3379 * Allocates memory - must be g_free()'d
3381 * Examples to parse:
3382 * First Last
3383 * First Last - Company Name
3384 * Last, First
3385 * Last, First M.
3386 * Last, First (C)(STP) (Company)
3387 * first.last@company.com (preprocessed as "first last")
3388 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3390 * Unusable examples:
3391 * user@company.com (preprocessed as "user")
3392 * first.m.last@company.com (preprocessed as "first m last")
3393 * user.company.com@reuters.net (preprocessed as "user company com")
3395 static void
3396 sipe_get_first_last_names(struct sipe_account_data *sip,
3397 const char *uri,
3398 char **first_name,
3399 char **last_name)
3401 PurpleBuddy *p_buddy;
3402 char *display_name;
3403 const char *email;
3404 const char *first, *last;
3405 char *tmp;
3406 char **parts;
3407 gboolean has_comma = FALSE;
3409 if (!sip || !uri) return;
3411 p_buddy = purple_find_buddy(sip->account, uri);
3413 if (!p_buddy) return;
3415 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3416 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3418 if (!display_name && !email) return;
3420 /* if no display name, make "first last anything_else" out of email */
3421 if (email && !display_name) {
3422 display_name = g_strndup(email, strstr(email, "@") - email);
3423 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
3424 g_free(tmp);
3427 if (display_name) {
3428 has_comma = (strstr(display_name, ",") != NULL);
3429 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
3430 g_free(tmp);
3431 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
3432 g_free(tmp);
3435 parts = g_strsplit(display_name, " ", 0);
3437 if (!parts[0] || !parts[1]) {
3438 g_free(display_name);
3439 g_strfreev(parts);
3440 return;
3443 if (has_comma) {
3444 last = parts[0];
3445 first = parts[1];
3446 } else {
3447 first = parts[0];
3448 last = parts[1];
3451 if (first_name) {
3452 *first_name = g_strstrip(g_strdup(first));
3455 if (last_name) {
3456 *last_name = g_strstrip(g_strdup(last));
3459 g_free(display_name);
3460 g_strfreev(parts);
3464 * Update user information
3466 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3467 * @param property_name
3468 * @param property_value may be modified to strip white space
3470 static void
3471 sipe_update_user_info(struct sipe_account_data *sip,
3472 const char *uri,
3473 const char *property_name,
3474 char *property_value)
3476 GSList *buddies, *entry;
3478 if (!property_name || strlen(property_name) == 0) return;
3480 if (property_value)
3481 property_value = g_strstrip(property_value);
3483 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3484 while (entry) {
3485 const char *prop_str;
3486 const char *server_alias;
3487 PurpleBuddy *p_buddy = entry->data;
3489 /* for Display Name */
3490 if (sipe_strequal(property_name, ALIAS_PROP)) {
3491 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3492 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
3493 purple_blist_alias_buddy(p_buddy, property_value);
3496 server_alias = purple_buddy_get_server_alias(p_buddy);
3497 if (!is_empty(property_value) &&
3498 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3500 purple_blist_server_alias_buddy(p_buddy, property_value);
3503 /* for other properties */
3504 else {
3505 if (!is_empty(property_value)) {
3506 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3507 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3508 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3513 entry = entry->next;
3515 g_slist_free(buddies);
3519 * Update user phone
3520 * Suitable for both 2005 and 2007 systems.
3522 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3523 * @param phone_type
3524 * @param phone may be modified to strip white space
3525 * @param phone_display_string may be modified to strip white space
3527 static void
3528 sipe_update_user_phone(struct sipe_account_data *sip,
3529 const char *uri,
3530 const gchar *phone_type,
3531 gchar *phone,
3532 gchar *phone_display_string)
3534 const char *phone_node = PHONE_PROP; /* work phone by default */
3535 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3537 if(!phone || strlen(phone) == 0) return;
3539 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3540 phone_node = PHONE_MOBILE_PROP;
3541 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3542 } else if (sipe_strequal(phone_type, "home")) {
3543 phone_node = PHONE_HOME_PROP;
3544 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3545 } else if (sipe_strequal(phone_type, "other")) {
3546 phone_node = PHONE_OTHER_PROP;
3547 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3548 } else if (sipe_strequal(phone_type, "custom1")) {
3549 phone_node = PHONE_CUSTOM1_PROP;
3550 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3553 sipe_update_user_info(sip, uri, phone_node, phone);
3554 if (phone_display_string) {
3555 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3559 void
3560 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
3562 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
3563 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3565 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: started.");
3567 if (sipe_strequal(calendar, "EXCH")) {
3568 sipe_ews_update_calendar(sip);
3571 /* schedule repeat */
3572 sipe_schedule_action("<+update-calendar>",
3573 UPDATE_CALENDAR_INTERVAL,
3574 (Action)sipe_core_update_calendar,
3575 NULL,
3576 SIP_TO_CORE_PRIVATE,
3577 NULL);
3579 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: finished.");
3583 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3584 * by using standard Purple's means of signals and saved statuses.
3586 * Thus all UI elements get updated: Status Button with Note, docklet.
3587 * This is ablolutely important as both our status and note can come
3588 * inbound (roaming) or be updated programmatically (e.g. based on our
3589 * calendar data).
3591 static void
3592 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3593 const char *status_id,
3594 const char *message,
3595 time_t do_not_publish[])
3597 PurpleStatus *status = purple_account_get_active_status(account);
3598 gboolean changed = TRUE;
3600 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3601 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3603 changed = FALSE;
3606 if (purple_savedstatus_is_idleaway()) {
3607 changed = FALSE;
3610 if (changed) {
3611 PurpleSavedStatus *saved_status;
3612 const PurpleStatusType *acct_status_type =
3613 purple_status_type_find_with_id(account->status_types, status_id);
3614 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3615 sipe_activity activity = sipe_get_activity_by_token(status_id);
3617 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3618 if (saved_status) {
3619 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3622 /* If this type+message is unique then create a new transient saved status
3623 * Ref: gtkstatusbox.c
3625 if (!saved_status) {
3626 GList *tmp;
3627 GList *active_accts = purple_accounts_get_all_active();
3629 saved_status = purple_savedstatus_new(NULL, primitive);
3630 purple_savedstatus_set_message(saved_status, message);
3632 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3633 purple_savedstatus_set_substatus(saved_status,
3634 (PurpleAccount *)tmp->data, acct_status_type, message);
3636 g_list_free(active_accts);
3639 do_not_publish[activity] = time(NULL);
3640 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3641 status_id, (int)do_not_publish[activity]);
3643 /* Set the status for each account */
3644 purple_savedstatus_activate(saved_status);
3648 struct hash_table_delete_payload {
3649 GHashTable *hash_table;
3650 guint container;
3653 static void
3654 sipe_remove_category_container_publications_cb(const char *name,
3655 struct sipe_publication *publication,
3656 struct hash_table_delete_payload *payload)
3658 if (publication->container == payload->container) {
3659 g_hash_table_remove(payload->hash_table, name);
3662 static void
3663 sipe_remove_category_container_publications(GHashTable *our_publications,
3664 const char *category,
3665 guint container)
3667 struct hash_table_delete_payload payload;
3668 payload.hash_table = g_hash_table_lookup(our_publications, category);
3670 if (!payload.hash_table) return;
3672 payload.container = container;
3673 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3676 static void
3677 send_publish_category_initial(struct sipe_account_data *sip);
3680 * When we receive some self (BE) NOTIFY with a new subscriber
3681 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3684 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3686 gchar *contact;
3687 gchar *to;
3688 sipe_xml *xml;
3689 const sipe_xml *node;
3690 const sipe_xml *node2;
3691 char *display_name = NULL;
3692 char *uri;
3693 GSList *category_names = NULL;
3694 int aggreg_avail = 0;
3695 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3696 gboolean do_update_status = FALSE;
3697 gboolean has_note_cleaned = FALSE;
3699 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3701 xml = sipe_xml_parse(msg->body, msg->bodylen);
3702 if (!xml) return;
3704 contact = get_contact(sip);
3705 to = sip_uri_self(sip);
3708 /* categories */
3709 /* set list of categories participating in this XML */
3710 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3711 const gchar *name = sipe_xml_attribute(node, "name");
3712 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3714 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3715 category_names ? (int) g_slist_length(category_names) : -1);
3716 /* drop category information */
3717 if (category_names) {
3718 GSList *entry = category_names;
3719 while (entry) {
3720 GHashTable *cat_publications;
3721 const gchar *category = entry->data;
3722 entry = entry->next;
3723 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
3724 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3725 if (cat_publications) {
3726 g_hash_table_remove(sip->our_publications, category);
3727 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
3731 g_slist_free(category_names);
3732 /* filling our categories reflected in roaming data */
3733 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3734 const char *tmp;
3735 const gchar *name = sipe_xml_attribute(node, "name");
3736 guint container = sipe_xml_int_attribute(node, "container", -1);
3737 guint instance = sipe_xml_int_attribute(node, "instance", -1);
3738 guint version = sipe_xml_int_attribute(node, "version", 0);
3739 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
3740 sipe_utils_str_to_time(tmp) : 0;
3741 gchar *key;
3742 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3744 /* Ex. clear note: <category name="note"/> */
3745 if (container == (guint)-1) {
3746 g_free(sip->note);
3747 sip->note = NULL;
3748 do_update_status = TRUE;
3749 continue;
3752 /* Ex. clear note: <category name="note" container="200"/> */
3753 if (instance == (guint)-1) {
3754 if (container == 200) {
3755 g_free(sip->note);
3756 sip->note = NULL;
3757 do_update_status = TRUE;
3759 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
3760 sipe_remove_category_container_publications(
3761 sip->our_publications, name, container);
3762 continue;
3765 /* key is <category><instance><container> */
3766 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3767 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
3769 /* capture all userState publication for later clean up if required */
3770 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3771 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3773 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
3774 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3775 publication->category = g_strdup(name);
3776 publication->instance = instance;
3777 publication->container = container;
3778 publication->version = version;
3780 if (!sip->user_state_publications) {
3781 sip->user_state_publications = g_hash_table_new_full(
3782 g_str_hash, g_str_equal,
3783 g_free, (GDestroyNotify)free_publication);
3785 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3786 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3787 key, version);
3791 if (sipe_is_our_publication(sip, key)) {
3792 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3794 publication->category = g_strdup(name);
3795 publication->instance = instance;
3796 publication->container = container;
3797 publication->version = version;
3799 /* filling publication->availability */
3800 if (sipe_strequal(name, "state")) {
3801 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3802 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3804 if (xn_avail) {
3805 gchar *avail_str = sipe_xml_data(xn_avail);
3806 if (avail_str) {
3807 publication->availability = atoi(avail_str);
3809 g_free(avail_str);
3811 /* for calendarState */
3812 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
3813 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3814 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3816 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
3817 if (xn_activity) {
3818 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
3819 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3821 event->is_meeting = TRUE;
3824 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
3825 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
3827 publication->cal_event_hash = sipe_cal_event_hash(event);
3828 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3829 publication->cal_event_hash);
3830 sipe_cal_event_free(event);
3833 /* filling publication->note */
3834 if (sipe_strequal(name, "note")) {
3835 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
3837 if (!has_note_cleaned) {
3838 has_note_cleaned = TRUE;
3840 g_free(sip->note);
3841 sip->note = NULL;
3842 sip->note_since = publish_time;
3844 do_update_status = TRUE;
3847 g_free(publication->note);
3848 publication->note = NULL;
3849 if (xn_body) {
3850 char *tmp;
3852 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
3853 g_free(tmp);
3854 if (publish_time >= sip->note_since) {
3855 g_free(sip->note);
3856 sip->note = g_strdup(publication->note);
3857 sip->note_since = publish_time;
3858 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
3860 do_update_status = TRUE;
3865 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3866 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3867 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
3868 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
3869 if (xn_free_busy) {
3870 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3871 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
3873 if (xn_working_hours) {
3874 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
3878 if (!cat_publications) {
3879 cat_publications = g_hash_table_new_full(
3880 g_str_hash, g_str_equal,
3881 g_free, (GDestroyNotify)free_publication);
3882 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3883 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3885 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3886 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3888 g_free(key);
3890 /* aggregateState (not an our publication) from 2-nd container */
3891 if (sipe_strequal(name, "state") && container == 2) {
3892 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3894 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
3895 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3896 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3898 if (xn_avail) {
3899 gchar *avail_str = sipe_xml_data(xn_avail);
3900 if (avail_str) {
3901 aggreg_avail = atoi(avail_str);
3903 g_free(avail_str);
3906 if (xn_activity) {
3907 const char *activity_token = sipe_xml_attribute(xn_activity, "token");
3909 aggreg_activity = sipe_get_activity_by_token(activity_token);
3912 do_update_status = TRUE;
3916 /* userProperties published by server from AD */
3917 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3918 const sipe_xml *line;
3919 /* line, for Remote Call Control (RCC) */
3920 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
3921 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
3922 const gchar *line_type = sipe_xml_attribute(line, "lineType");
3923 gchar *line_uri;
3925 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3927 line_uri = sipe_xml_data(line);
3928 if (line_uri) {
3929 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
3930 sip_csta_open(sip, line_uri, line_server);
3932 g_free(line_uri);
3934 break;
3938 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
3939 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3941 /* containers */
3942 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
3943 guint id = sipe_xml_int_attribute(node, "id", 0);
3944 struct sipe_container *container = sipe_find_container(sip, id);
3946 if (container) {
3947 sip->containers = g_slist_remove(sip->containers, container);
3948 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
3949 free_container(container);
3951 container = g_new0(struct sipe_container, 1);
3952 container->id = id;
3953 container->version = sipe_xml_int_attribute(node, "version", 0);
3954 sip->containers = g_slist_append(sip->containers, container);
3955 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
3957 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
3958 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3959 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
3960 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
3961 container->members = g_slist_append(container->members, member);
3962 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
3963 member->type, member->value ? member->value : "");
3967 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
3968 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
3969 char *container_xmls = NULL;
3970 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL, NULL);
3971 int federatedAL = sipe_find_access_level(sip, "federated", NULL, NULL);
3973 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
3974 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
3975 /* initial set-up to let counterparties see your status */
3976 if (sameEnterpriseAL < 0) {
3977 struct sipe_container *container = sipe_find_container(sip, 200);
3978 guint version = container ? container->version : 0;
3979 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
3981 if (federatedAL < 0) {
3982 struct sipe_container *container = sipe_find_container(sip, 100);
3983 guint version = container ? container->version : 0;
3984 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
3986 sip->access_level_set = TRUE;
3988 if (container_xmls) {
3989 sipe_send_set_container_members(sip, container_xmls);
3991 g_free(container_xmls);
3994 /* Refresh contacts' blocked status */
3995 sipe_refresh_blocked_status(sip);
3997 /* subscribers */
3998 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
3999 const char *user;
4000 const char *acknowledged;
4001 gchar *hdr;
4002 gchar *body;
4004 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
4005 if (!user) continue;
4006 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
4007 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
4008 uri = sip_uri_from_name(user);
4010 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
4012 acknowledged= sipe_xml_attribute(node, "acknowledged");
4013 if(sipe_strcase_equal(acknowledged,"false")){
4014 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
4015 if (!purple_find_buddy(sip->account, uri)) {
4016 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
4019 hdr = g_strdup_printf(
4020 "Contact: %s\r\n"
4021 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
4023 body = g_strdup_printf(
4024 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
4025 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
4026 "</setSubscribers>", user);
4028 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
4029 g_free(body);
4030 g_free(hdr);
4032 g_free(display_name);
4033 g_free(uri);
4036 g_free(contact);
4037 sipe_xml_free(xml);
4039 /* Publish initial state if not yet.
4040 * Assuming this happens on initial responce to subscription to roaming-self
4041 * so we've already updated our roaming data in full.
4042 * Only for 2007+
4044 if (!sip->initial_state_published) {
4045 send_publish_category_initial(sip);
4046 sip->initial_state_published = TRUE;
4047 /* dalayed run */
4048 sipe_schedule_action("<+update-calendar>",
4049 UPDATE_CALENDAR_DELAY,
4050 (Action)sipe_core_update_calendar,
4051 NULL,
4052 SIP_TO_CORE_PRIVATE,
4053 NULL);
4054 do_update_status = FALSE;
4055 } else if (aggreg_avail) {
4057 g_free(sip->status);
4058 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
4059 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
4060 } else {
4061 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
4065 if (do_update_status) {
4066 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
4067 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
4070 g_free(to);
4073 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
4075 gchar *to = sip_uri_self(sip);
4076 gchar *tmp = get_contact(sip);
4077 gchar *hdr = g_strdup_printf(
4078 "Event: vnd-microsoft-roaming-ACL\r\n"
4079 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
4080 "Supported: com.microsoft.autoextend\r\n"
4081 "Supported: ms-benotify\r\n"
4082 "Proxy-Require: ms-benotify\r\n"
4083 "Supported: ms-piggyback-first-notify\r\n"
4084 "Contact: %s\r\n", tmp);
4085 g_free(tmp);
4087 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
4088 g_free(to);
4089 g_free(hdr);
4093 * To request for presence information about the user, access level settings that have already been configured by the user
4094 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
4095 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
4098 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
4100 gchar *to = sip_uri_self(sip);
4101 gchar *tmp = get_contact(sip);
4102 gchar *hdr = g_strdup_printf(
4103 "Event: vnd-microsoft-roaming-self\r\n"
4104 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
4105 "Supported: ms-benotify\r\n"
4106 "Proxy-Require: ms-benotify\r\n"
4107 "Supported: ms-piggyback-first-notify\r\n"
4108 "Contact: %s\r\n"
4109 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
4111 gchar *body=g_strdup(
4112 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
4113 "<roaming type=\"categories\"/>"
4114 "<roaming type=\"containers\"/>"
4115 "<roaming type=\"subscribers\"/></roamingList>");
4117 g_free(tmp);
4118 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4119 g_free(body);
4120 g_free(to);
4121 g_free(hdr);
4125 * For 2005 version
4127 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
4129 gchar *to = sip_uri_self(sip);
4130 gchar *tmp = get_contact(sip);
4131 gchar *hdr = g_strdup_printf(
4132 "Event: vnd-microsoft-provisioning\r\n"
4133 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
4134 "Supported: com.microsoft.autoextend\r\n"
4135 "Supported: ms-benotify\r\n"
4136 "Proxy-Require: ms-benotify\r\n"
4137 "Supported: ms-piggyback-first-notify\r\n"
4138 "Expires: 0\r\n"
4139 "Contact: %s\r\n", tmp);
4141 g_free(tmp);
4142 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
4143 g_free(to);
4144 g_free(hdr);
4147 /** Subscription for provisioning information to help with initial
4148 * configuration. This subscription is a one-time query (denoted by the Expires header,
4149 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
4150 * configuration, meeting policies, and policy settings that Communicator must enforce.
4151 * TODO: for what we need this information.
4154 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
4156 gchar *to = sip_uri_self(sip);
4157 gchar *tmp = get_contact(sip);
4158 gchar *hdr = g_strdup_printf(
4159 "Event: vnd-microsoft-provisioning-v2\r\n"
4160 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
4161 "Supported: com.microsoft.autoextend\r\n"
4162 "Supported: ms-benotify\r\n"
4163 "Proxy-Require: ms-benotify\r\n"
4164 "Supported: ms-piggyback-first-notify\r\n"
4165 "Expires: 0\r\n"
4166 "Contact: %s\r\n"
4167 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
4168 gchar *body = g_strdup(
4169 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
4170 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
4171 "<provisioningGroup name=\"ucPolicy\"/>"
4172 "</provisioningGroupList>");
4174 g_free(tmp);
4175 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4176 g_free(body);
4177 g_free(to);
4178 g_free(hdr);
4181 static void
4182 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
4183 gpointer value, gpointer user_data)
4185 struct sip_subscription *subscription = value;
4186 struct sip_dialog *dialog = &subscription->dialog;
4187 struct sipe_account_data *sip = user_data;
4188 gchar *tmp = get_contact(sip);
4189 gchar *hdr = g_strdup_printf(
4190 "Event: %s\r\n"
4191 "Expires: 0\r\n"
4192 "Contact: %s\r\n", subscription->event, tmp);
4193 g_free(tmp);
4195 /* Rate limit to max. 25 requests per seconds */
4196 g_usleep(1000000 / 25);
4198 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
4199 g_free(hdr);
4202 /* IM Session (INVITE and MESSAGE methods) */
4204 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
4205 static gchar *
4206 get_end_points (struct sipe_account_data *sip,
4207 struct sip_session *session)
4209 gchar *res;
4211 if (session == NULL) {
4212 return NULL;
4215 res = g_strdup_printf("<sip:%s>", sip->username);
4217 SIPE_DIALOG_FOREACH {
4218 gchar *tmp = res;
4219 res = g_strdup_printf("%s, <%s>", res, dialog->with);
4220 g_free(tmp);
4222 if (dialog->theirepid) {
4223 tmp = res;
4224 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
4225 g_free(tmp);
4227 } SIPE_DIALOG_FOREACH_END;
4229 return res;
4232 static gboolean
4233 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
4234 struct sipmsg *msg,
4235 SIPE_UNUSED_PARAMETER struct transaction *trans)
4237 gboolean ret = TRUE;
4239 if (msg->response != 200) {
4240 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
4241 return FALSE;
4244 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
4246 return ret;
4250 * Asks UA/proxy about its capabilities.
4252 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
4254 gchar *to = sip_uri(who);
4255 gchar *contact = get_contact(sip);
4256 gchar *request = g_strdup_printf(
4257 "Accept: application/sdp\r\n"
4258 "Contact: %s\r\n", contact);
4259 g_free(contact);
4261 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
4263 g_free(to);
4264 g_free(request);
4267 static void
4268 sipe_notify_user(struct sipe_account_data *sip,
4269 struct sip_session *session,
4270 PurpleMessageFlags flags,
4271 const gchar *message)
4273 PurpleConversation *conv;
4275 if (!session->conv) {
4276 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
4277 } else {
4278 conv = session->conv;
4280 purple_conversation_write(conv, NULL, message, flags, time(NULL));
4283 void
4284 sipe_present_info(struct sipe_account_data *sip,
4285 struct sip_session *session,
4286 const gchar *message)
4288 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4291 static void
4292 sipe_present_err(struct sipe_account_data *sip,
4293 struct sip_session *session,
4294 const gchar *message)
4296 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4299 void
4300 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4301 struct sip_session *session,
4302 int sip_error,
4303 int sip_warning,
4304 const gchar *who,
4305 const gchar *message)
4307 char *msg, *msg_tmp, *msg_tmp2;
4308 const char *label;
4310 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
4311 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4312 g_free(msg_tmp);
4313 /* Service unavailable; Server Internal Error; Server Time-out */
4314 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4315 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4316 g_free(msg);
4317 msg = NULL;
4318 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4319 label = _("This message was not delivered to %s because the service is not available");
4320 } else if (sip_error == 486) { /* Busy Here */
4321 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4322 } else if (sip_error == 415) { /* Unsupported media type */
4323 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4324 } else {
4325 label = _("This message was not delivered to %s because one or more recipients are offline");
4328 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4329 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4330 msg ? ":" : "",
4331 msg ? msg : "");
4332 sipe_present_err(sip, session, msg_tmp);
4333 g_free(msg_tmp2);
4334 g_free(msg_tmp);
4335 g_free(msg);
4339 static gboolean
4340 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4341 SIPE_UNUSED_PARAMETER struct transaction *trans)
4343 gboolean ret = TRUE;
4344 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4345 struct sip_session *session = sipe_session_find_im(sip, with);
4346 struct sip_dialog *dialog;
4347 gchar *cseq;
4348 char *key;
4349 struct queued_message *message;
4351 if (!session) {
4352 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4353 g_free(with);
4354 return FALSE;
4357 dialog = sipe_dialog_find(session, with);
4358 if (!dialog) {
4359 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4360 g_free(with);
4361 return FALSE;
4364 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4365 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4366 g_free(cseq);
4367 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4369 if (msg->response >= 400) {
4370 PurpleBuddy *pbuddy;
4371 const char *alias = with;
4372 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4373 int warning = -1;
4375 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4377 if (warn_hdr) {
4378 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4379 if (parts[0]) {
4380 warning = atoi(parts[0]);
4382 g_strfreev(parts);
4385 /* cancel file transfer as rejected by server */
4386 if (msg->response == 606 && /* Not acceptable all. */
4387 warning == 309 && /* Message contents not allowed by policy */
4388 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4390 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4391 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4392 sipe_utils_nameval_free(parsed_body);
4395 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4396 alias = purple_buddy_get_alias(pbuddy);
4399 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4401 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4402 if (msg->response == 408 || /* Request timeout */
4403 msg->response == 480 || /* Temporarily Unavailable */
4404 msg->response == 481) { /* Call/Transaction Does Not Exist */
4405 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4406 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4409 ret = FALSE;
4410 } else {
4411 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4412 if (message_id) {
4413 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4414 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4415 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4418 g_hash_table_remove(session->unconfirmed_messages, key);
4419 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4420 key, g_hash_table_size(session->unconfirmed_messages));
4423 g_free(key);
4424 g_free(with);
4426 if (ret) sipe_im_process_queue(sip, session);
4427 return ret;
4430 static gboolean
4431 sipe_is_election_finished(struct sip_session *session);
4433 static void
4434 sipe_election_result(struct sipe_core_private *sipe_private,
4435 void *sess);
4437 static gboolean
4438 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4439 SIPE_UNUSED_PARAMETER struct transaction *trans)
4441 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4442 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4443 struct sip_dialog *dialog;
4444 struct sip_session *session;
4446 session = sipe_session_find_chat_by_callid(sip, callid);
4447 if (!session) {
4448 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
4449 return FALSE;
4452 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4453 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
4454 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
4455 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
4457 if (xn_request_rm_response) {
4458 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
4459 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
4461 dialog = sipe_dialog_find(session, with);
4462 if (!dialog) {
4463 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
4464 sipe_xml_free(xn_action);
4465 return FALSE;
4468 if (allow && !g_strcasecmp(allow, "true")) {
4469 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
4470 dialog->election_vote = 1;
4471 } else if (allow && !g_strcasecmp(allow, "false")) {
4472 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
4473 dialog->election_vote = -1;
4476 if (sipe_is_election_finished(session)) {
4477 sipe_election_result(SIP_TO_CORE_PRIVATE,
4478 session);
4481 } else if (xn_set_rm_response) {
4484 sipe_xml_free(xn_action);
4488 return TRUE;
4491 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4493 gchar *hdr;
4494 gchar *tmp;
4495 char *msgtext = NULL;
4496 const gchar *msgr = "";
4497 gchar *tmp2 = NULL;
4499 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4500 char *msgformat;
4501 gchar *msgr_value;
4503 sipe_parse_html(msg, &msgformat, &msgtext);
4504 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
4506 msgr_value = sipmsg_get_msgr_string(msgformat);
4507 g_free(msgformat);
4508 if (msgr_value) {
4509 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4510 g_free(msgr_value);
4512 } else {
4513 msgtext = g_strdup(msg);
4516 tmp = get_contact(sip);
4517 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4518 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4519 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4520 if (content_type == NULL)
4521 content_type = "text/plain";
4523 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4524 g_free(tmp);
4525 g_free(tmp2);
4527 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4528 g_free(msgtext);
4529 g_free(hdr);
4533 void
4534 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4536 GSList *entry2 = session->outgoing_message_queue;
4537 while (entry2) {
4538 struct queued_message *msg = entry2->data;
4540 /* for multiparty chat or conference */
4541 if (session->is_multiparty || session->focus_uri) {
4542 gchar *who = sip_uri_self(sip);
4543 serv_got_chat_in(sip->gc, session->chat_id, who,
4544 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4545 g_free(who);
4548 SIPE_DIALOG_FOREACH {
4549 char *key;
4550 struct queued_message *message;
4552 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4554 message = g_new0(struct queued_message,1);
4555 message->body = g_strdup(msg->body);
4556 if (msg->content_type != NULL)
4557 message->content_type = g_strdup(msg->content_type);
4559 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4560 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4561 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4562 key, g_hash_table_size(session->unconfirmed_messages));
4563 g_free(key);
4565 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4566 } SIPE_DIALOG_FOREACH_END;
4568 entry2 = sipe_session_dequeue_message(session);
4572 static void
4573 sipe_refer_notify(struct sipe_account_data *sip,
4574 struct sip_session *session,
4575 const gchar *who,
4576 int status,
4577 const gchar *desc)
4579 gchar *hdr;
4580 gchar *body;
4581 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4583 hdr = g_strdup_printf(
4584 "Event: refer\r\n"
4585 "Subscription-State: %s\r\n"
4586 "Content-Type: message/sipfrag\r\n",
4587 status >= 200 ? "terminated" : "active");
4589 body = g_strdup_printf(
4590 "SIP/2.0 %d %s\r\n",
4591 status, desc);
4593 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4595 g_free(hdr);
4596 g_free(body);
4599 static gboolean
4600 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4602 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4603 struct sip_session *session;
4604 struct sip_dialog *dialog;
4605 char *cseq;
4606 char *key;
4607 struct queued_message *message;
4608 struct sipmsg *request_msg = trans->msg;
4610 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4611 gchar *referred_by;
4613 session = sipe_session_find_chat_by_callid(sip, callid);
4614 if (!session) {
4615 session = sipe_session_find_im(sip, with);
4617 if (!session) {
4618 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4619 g_free(with);
4620 return FALSE;
4623 dialog = sipe_dialog_find(session, with);
4624 if (!dialog) {
4625 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4626 g_free(with);
4627 return FALSE;
4630 sipe_dialog_parse(dialog, msg, TRUE);
4632 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4633 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4634 g_free(cseq);
4635 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4637 if (msg->response != 200) {
4638 PurpleBuddy *pbuddy;
4639 const char *alias = with;
4640 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4641 int warning = -1;
4643 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4645 if (warn_hdr) {
4646 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4647 if (parts[0]) {
4648 warning = atoi(parts[0]);
4650 g_strfreev(parts);
4653 /* cancel file transfer as rejected by server */
4654 if (msg->response == 606 && /* Not acceptable all. */
4655 warning == 309 && /* Message contents not allowed by policy */
4656 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4658 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4659 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4660 sipe_utils_nameval_free(parsed_body);
4663 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4664 alias = purple_buddy_get_alias(pbuddy);
4667 if (message) {
4668 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4669 } else {
4670 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4671 sipe_present_err(sip, session, tmp_msg);
4672 g_free(tmp_msg);
4675 sipe_dialog_remove(session, with);
4677 g_free(key);
4678 g_free(with);
4679 return FALSE;
4682 dialog->cseq = 0;
4683 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4684 dialog->outgoing_invite = NULL;
4685 dialog->is_established = TRUE;
4687 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4688 if (referred_by) {
4689 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4690 g_free(referred_by);
4693 /* add user to chat if it is a multiparty session */
4694 if (session->is_multiparty) {
4695 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4696 with, NULL,
4697 PURPLE_CBFLAGS_NONE, TRUE);
4700 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4701 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4702 sipe_session_dequeue_message(session);
4705 sipe_im_process_queue(sip, session);
4707 g_hash_table_remove(session->unconfirmed_messages, key);
4708 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4709 key, g_hash_table_size(session->unconfirmed_messages));
4711 g_free(key);
4712 g_free(with);
4713 return TRUE;
4717 void
4718 sipe_invite(struct sipe_account_data *sip,
4719 struct sip_session *session,
4720 const gchar *who,
4721 const gchar *msg_body,
4722 const gchar *msg_content_type,
4723 const gchar *referred_by,
4724 const gboolean is_triggered)
4726 gchar *hdr;
4727 gchar *to;
4728 gchar *contact;
4729 gchar *body;
4730 gchar *self;
4731 char *ms_text_format = NULL;
4732 gchar *roster_manager;
4733 gchar *end_points;
4734 gchar *referred_by_str;
4735 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4737 if (dialog && dialog->is_established) {
4738 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
4739 return;
4742 if (!dialog) {
4743 dialog = sipe_dialog_add(session);
4744 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4745 dialog->with = g_strdup(who);
4748 if (!(dialog->ourtag)) {
4749 dialog->ourtag = gentag();
4752 to = sip_uri(who);
4754 if (msg_body) {
4755 char *msgtext = NULL;
4756 char *base64_msg;
4757 const gchar *msgr = "";
4758 char *key;
4759 struct queued_message *message;
4760 gchar *tmp = NULL;
4762 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4763 char *msgformat;
4764 gchar *msgr_value;
4766 sipe_parse_html(msg_body, &msgformat, &msgtext);
4767 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
4769 msgr_value = sipmsg_get_msgr_string(msgformat);
4770 g_free(msgformat);
4771 if (msgr_value) {
4772 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4773 g_free(msgr_value);
4775 } else {
4776 msgtext = g_strdup(msg_body);
4779 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4780 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4781 msg_content_type ? msg_content_type : "text/plain",
4782 msgr,
4783 base64_msg);
4784 g_free(msgtext);
4785 g_free(tmp);
4786 g_free(base64_msg);
4788 message = g_new0(struct queued_message,1);
4789 message->body = g_strdup(msg_body);
4790 if (msg_content_type != NULL)
4791 message->content_type = g_strdup(msg_content_type);
4793 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4794 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4795 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4796 key, g_hash_table_size(session->unconfirmed_messages));
4797 g_free(key);
4800 contact = get_contact(sip);
4801 end_points = get_end_points(sip, session);
4802 self = sip_uri_self(sip);
4803 roster_manager = g_strdup_printf(
4804 "Roster-Manager: %s\r\n"
4805 "EndPoints: %s\r\n",
4806 self,
4807 end_points);
4808 referred_by_str = referred_by ?
4809 g_strdup_printf(
4810 "Referred-By: %s\r\n",
4811 referred_by)
4812 : g_strdup("");
4813 hdr = g_strdup_printf(
4814 "Supported: ms-sender\r\n"
4815 "%s"
4816 "%s"
4817 "%s"
4818 "%s"
4819 "Contact: %s\r\n%s"
4820 "Content-Type: application/sdp\r\n",
4821 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4822 referred_by_str,
4823 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4824 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4825 contact,
4826 ms_text_format ? ms_text_format : "");
4827 g_free(ms_text_format);
4828 g_free(self);
4830 body = g_strdup_printf(
4831 "v=0\r\n"
4832 "o=- 0 0 IN IP4 %s\r\n"
4833 "s=session\r\n"
4834 "c=IN IP4 %s\r\n"
4835 "t=0 0\r\n"
4836 "m=%s %d sip null\r\n"
4837 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4838 sipe_backend_network_ip_address(),
4839 sipe_backend_network_ip_address(),
4840 sip->ocs2007 ? "message" : "x-ms-message",
4841 sip->realport);
4843 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4844 to, to, hdr, body, dialog, process_invite_response);
4846 g_free(to);
4847 g_free(roster_manager);
4848 g_free(end_points);
4849 g_free(referred_by_str);
4850 g_free(body);
4851 g_free(hdr);
4852 g_free(contact);
4855 static void
4856 sipe_refer(struct sipe_account_data *sip,
4857 struct sip_session *session,
4858 const gchar *who)
4860 gchar *hdr;
4861 gchar *contact;
4862 gchar *epid = get_epid(sip);
4863 struct sip_dialog *dialog = sipe_dialog_find(session,
4864 session->roster_manager);
4865 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4867 contact = get_contact(sip);
4868 hdr = g_strdup_printf(
4869 "Contact: %s\r\n"
4870 "Refer-to: <%s>\r\n"
4871 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4872 "Require: com.microsoft.rtc-multiparty\r\n",
4873 contact,
4874 who,
4875 sip->username,
4876 ourtag ? ";tag=" : "",
4877 ourtag ? ourtag : "",
4878 epid);
4879 g_free(epid);
4881 send_sip_request(sip->gc, "REFER",
4882 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4884 g_free(hdr);
4885 g_free(contact);
4888 static void
4889 sipe_send_election_request_rm(struct sipe_account_data *sip,
4890 struct sip_dialog *dialog,
4891 int bid)
4893 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4895 gchar *body = g_strdup_printf(
4896 "<?xml version=\"1.0\"?>\r\n"
4897 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4898 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4899 sip->username, bid);
4901 send_sip_request(sip->gc, "INFO",
4902 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4904 g_free(body);
4907 static void
4908 sipe_send_election_set_rm(struct sipe_account_data *sip,
4909 struct sip_dialog *dialog)
4911 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4913 gchar *body = g_strdup_printf(
4914 "<?xml version=\"1.0\"?>\r\n"
4915 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4916 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4917 sip->username);
4919 send_sip_request(sip->gc, "INFO",
4920 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4922 g_free(body);
4925 static void
4926 sipe_session_close(struct sipe_account_data *sip,
4927 struct sip_session * session)
4929 if (session && session->focus_uri) {
4930 sipe_conf_immcu_closed(sip, session);
4931 conf_session_close(sip, session);
4934 if (session) {
4935 SIPE_DIALOG_FOREACH {
4936 /* @TODO slow down BYE message sending rate */
4937 /* @see single subscription code */
4938 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4939 } SIPE_DIALOG_FOREACH_END;
4941 sipe_session_remove(sip, session);
4945 static void
4946 sipe_session_close_all(struct sipe_account_data *sip)
4948 GSList *entry;
4949 while ((entry = sip->sessions) != NULL) {
4950 sipe_session_close(sip, entry->data);
4954 void
4955 sipe_convo_closed(PurpleConnection * gc, const char *who)
4957 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
4959 SIPE_DEBUG_INFO("conversation with %s closed", who);
4960 sipe_session_close(sip, sipe_session_find_im(sip, who));
4963 void
4964 sipe_chat_leave (PurpleConnection *gc, int id)
4966 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
4967 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4969 sipe_session_close(sip, session);
4972 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4973 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4975 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
4976 struct sip_session *session;
4977 struct sip_dialog *dialog;
4978 gchar *uri = sip_uri(who);
4980 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
4982 session = sipe_session_find_or_add_im(sip, uri);
4983 dialog = sipe_dialog_find(session, uri);
4985 // Queue the message
4986 sipe_session_enqueue_message(session, what, NULL);
4988 if (dialog && !dialog->outgoing_invite) {
4989 sipe_im_process_queue(sip, session);
4990 } else if (!dialog || !dialog->outgoing_invite) {
4991 // Need to send the INVITE to get the outgoing dialog setup
4992 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4995 g_free(uri);
4996 return 1;
4999 int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
5000 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
5002 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
5003 struct sip_session *session;
5005 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
5007 session = sipe_session_find_chat_by_id(sip, id);
5009 // Queue the message
5010 if (session && session->dialogs) {
5011 sipe_session_enqueue_message(session,what,NULL);
5012 sipe_im_process_queue(sip, session);
5013 } else if (sip) {
5014 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
5015 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
5017 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
5018 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
5020 if (sip->ocs2007) {
5021 struct sip_session *session = sipe_session_add_chat(sip);
5023 session->is_multiparty = FALSE;
5024 session->focus_uri = g_strdup(proto_chat_id);
5025 sipe_session_enqueue_message(session, what, NULL);
5026 sipe_invite_conf_focus(sip, session);
5030 return 1;
5033 /* End IM Session (INVITE and MESSAGE methods) */
5035 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
5037 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
5038 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5039 gchar *from;
5040 struct sip_session *session;
5042 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
5044 /* Call Control protocol */
5045 if (g_str_has_prefix(contenttype, "application/csta+xml"))
5047 process_incoming_info_csta(sip, msg);
5048 return;
5051 from = parse_from(sipmsg_find_header(msg, "From"));
5052 session = sipe_session_find_chat_by_callid(sip, callid);
5053 if (!session) {
5054 session = sipe_session_find_im(sip, from);
5056 if (!session) {
5057 g_free(from);
5058 return;
5061 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
5063 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
5064 const sipe_xml *xn_request_rm = sipe_xml_child(xn_action, "RequestRM");
5065 const sipe_xml *xn_set_rm = sipe_xml_child(xn_action, "SetRM");
5067 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
5069 if (xn_request_rm) {
5070 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
5071 int bid = sipe_xml_int_attribute(xn_request_rm, "bid", 0);
5072 gchar *body = g_strdup_printf(
5073 "<?xml version=\"1.0\"?>\r\n"
5074 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5075 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
5076 sip->username,
5077 session->bid < bid ? "true" : "false");
5078 send_sip_response(sip->gc, msg, 200, "OK", body);
5079 g_free(body);
5080 } else if (xn_set_rm) {
5081 gchar *body;
5082 const char *rm = sipe_xml_attribute(xn_set_rm, "uri");
5083 g_free(session->roster_manager);
5084 session->roster_manager = g_strdup(rm);
5086 body = g_strdup_printf(
5087 "<?xml version=\"1.0\"?>\r\n"
5088 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5089 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
5090 sip->username);
5091 send_sip_response(sip->gc, msg, 200, "OK", body);
5092 g_free(body);
5094 sipe_xml_free(xn_action);
5097 else
5099 /* looks like purple lacks typing notification for chat */
5100 if (!session->is_multiparty && !session->focus_uri) {
5101 sipe_xml *xn_keyboard_activity = sipe_xml_parse(msg->body, msg->bodylen);
5102 const char *status = sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity, "status"),
5103 "status");
5104 if (sipe_strequal(status, "type")) {
5105 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
5106 } else if (sipe_strequal(status, "idle")) {
5107 serv_got_typing_stopped(sip->gc, from);
5109 sipe_xml_free(xn_keyboard_activity);
5112 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5114 g_free(from);
5117 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
5119 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5120 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5121 struct sip_session *session;
5122 struct sip_dialog *dialog;
5124 /* collect dialog identification
5125 * we need callid, ourtag and theirtag to unambiguously identify dialog
5127 /* take data before 'msg' will be modified by send_sip_response */
5128 dialog = g_new0(struct sip_dialog, 1);
5129 dialog->callid = g_strdup(callid);
5130 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
5131 dialog->with = g_strdup(from);
5132 sipe_dialog_parse(dialog, msg, FALSE);
5134 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5136 session = sipe_session_find_chat_by_callid(sip, callid);
5137 if (!session) {
5138 session = sipe_session_find_im(sip, from);
5140 if (!session) {
5141 sipe_dialog_free(dialog);
5142 g_free(from);
5143 return;
5146 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
5147 g_free(session->roster_manager);
5148 session->roster_manager = NULL;
5151 /* This what BYE is essentially for - terminating dialog */
5152 sipe_dialog_remove_3(session, dialog);
5153 sipe_dialog_free(dialog);
5154 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
5155 sipe_conf_immcu_closed(sip, session);
5156 } else if (session->is_multiparty) {
5157 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
5160 g_free(from);
5163 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
5165 gchar *self = sip_uri_self(sip);
5166 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5167 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5168 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
5169 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
5170 struct sip_session *session;
5171 struct sip_dialog *dialog;
5173 session = sipe_session_find_chat_by_callid(sip, callid);
5174 dialog = sipe_dialog_find(session, from);
5176 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
5177 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
5178 } else {
5179 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
5181 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
5184 g_free(self);
5185 g_free(from);
5186 g_free(refer_to);
5187 g_free(referred_by);
5190 unsigned int
5191 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
5193 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
5194 struct sip_session *session;
5195 struct sip_dialog *dialog;
5197 if (state == PURPLE_NOT_TYPING)
5198 return 0;
5200 session = sipe_session_find_im(sip, who);
5201 dialog = sipe_dialog_find(session, who);
5203 if (session && dialog && dialog->is_established) {
5204 send_sip_request(gc, "INFO", who, who,
5205 "Content-Type: application/xml\r\n",
5206 SIPE_SEND_TYPING, dialog, NULL);
5208 return SIPE_TYPING_SEND_TIMEOUT;
5211 static gboolean resend_timeout(struct sipe_account_data *sip)
5213 GSList *tmp = sip->transactions;
5214 time_t currtime = time(NULL);
5215 while (tmp) {
5216 struct transaction *trans = tmp->data;
5217 tmp = tmp->next;
5218 SIPE_DEBUG_INFO("have open transaction age: %ld", (long int)currtime-trans->time);
5219 if ((currtime - trans->time > 5) && trans->retries >= 1) {
5220 /* TODO 408 */
5221 } else {
5222 if ((currtime - trans->time > 2) && trans->retries == 0) {
5223 trans->retries++;
5224 sendout_sipmsg(sip, trans->msg);
5228 return TRUE;
5231 static void do_reauthenticate_cb(struct sipe_core_private *sipe_private,
5232 SIPE_UNUSED_PARAMETER void *unused)
5234 struct sipe_account_data *sip = sipe_private->temporary;
5235 /* register again when security token expires */
5236 /* we have to start a new authentication as the security token
5237 * is almost expired by sending a not signed REGISTER message */
5238 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
5239 sipe_auth_free(&sip->registrar);
5240 sipe_auth_free(&sip->proxy);
5241 sip->registerstatus = 0;
5242 do_register(sip);
5243 sip->reauthenticate_set = FALSE;
5246 static gboolean
5247 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
5248 struct sipmsg *msg,
5249 GSList *parsed_body)
5251 gboolean found = FALSE;
5253 if (parsed_body) {
5254 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
5256 if (sipe_strequal(invitation_command, "INVITE")) {
5257 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
5258 found = TRUE;
5259 } else if (sipe_strequal(invitation_command, "CANCEL")) {
5260 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
5261 found = TRUE;
5262 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
5263 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
5264 found = TRUE;
5267 return found;
5270 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
5272 gchar *from;
5273 const gchar *contenttype;
5274 gboolean found = FALSE;
5276 from = parse_from(sipmsg_find_header(msg, "From"));
5278 if (!from) return;
5280 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
5282 contenttype = sipmsg_find_header(msg, "Content-Type");
5283 if (g_str_has_prefix(contenttype, "text/plain")
5284 || g_str_has_prefix(contenttype, "text/html")
5285 || g_str_has_prefix(contenttype, "multipart/related")
5286 || g_str_has_prefix(contenttype, "multipart/alternative"))
5288 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5289 gchar *html = get_html_message(contenttype, msg->body);
5291 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5292 if (!session) {
5293 session = sipe_session_find_im(sip, from);
5296 if (session && session->focus_uri) { /* a conference */
5297 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5298 gchar *sender = parse_from(tmp);
5299 g_free(tmp);
5300 serv_got_chat_in(sip->gc, session->chat_id, sender,
5301 PURPLE_MESSAGE_RECV, html, time(NULL));
5302 g_free(sender);
5303 } else if (session && session->is_multiparty) { /* a multiparty chat */
5304 serv_got_chat_in(sip->gc, session->chat_id, from,
5305 PURPLE_MESSAGE_RECV, html, time(NULL));
5306 } else {
5307 serv_got_im(sip->gc, from, html, 0, time(NULL));
5309 g_free(html);
5310 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5311 found = TRUE;
5313 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5314 sipe_xml *isc = sipe_xml_parse(msg->body, msg->bodylen);
5315 const sipe_xml *state;
5316 gchar *statedata;
5318 if (!isc) {
5319 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
5320 g_free(from);
5321 return;
5324 state = sipe_xml_child(isc, "state");
5326 if (!state) {
5327 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
5328 sipe_xml_free(isc);
5329 g_free(from);
5330 return;
5333 statedata = sipe_xml_data(state);
5334 if (statedata) {
5335 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5336 else serv_got_typing_stopped(sip->gc, from);
5338 g_free(statedata);
5340 sipe_xml_free(isc);
5341 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5342 found = TRUE;
5343 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5344 GSList *body = sipe_ft_parse_msg_body(msg->body);
5345 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5346 sipe_utils_nameval_free(body);
5347 if (found) {
5348 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5351 if (!found) {
5352 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5353 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5354 if (!session) {
5355 session = sipe_session_find_im(sip, from);
5357 if (session) {
5358 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5359 from);
5360 sipe_present_err(sip, session, errmsg);
5361 g_free(errmsg);
5364 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
5365 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5367 g_free(from);
5370 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5372 gchar *body;
5373 gchar *newTag;
5374 const gchar *oldHeader;
5375 gchar *newHeader;
5376 gboolean is_multiparty = FALSE;
5377 gboolean is_triggered = FALSE;
5378 gboolean was_multiparty = TRUE;
5379 gboolean just_joined = FALSE;
5380 gchar *from;
5381 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5382 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5383 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5384 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5385 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5386 GSList *end_points = NULL;
5387 char *tmp = NULL;
5388 struct sip_session *session;
5389 const gchar *ms_text_format;
5391 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
5392 g_free(tmp);
5394 /* Invitation to join conference */
5395 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5396 process_incoming_invite_conf(sip, msg);
5397 return;
5400 /* Only accept text invitations */
5401 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5402 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5403 return;
5406 // TODO There *must* be a better way to clean up the To header to add a tag...
5407 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5408 oldHeader = sipmsg_find_header(msg, "To");
5409 newTag = gentag();
5410 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5411 sipmsg_remove_header_now(msg, "To");
5412 sipmsg_add_header_now(msg, "To", newHeader);
5413 g_free(newHeader);
5415 if (end_points_hdr) {
5416 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5418 if (g_slist_length(end_points) > 2) {
5419 is_multiparty = TRUE;
5422 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5423 is_triggered = TRUE;
5424 is_multiparty = TRUE;
5427 session = sipe_session_find_chat_by_callid(sip, callid);
5428 /* Convert to multiparty */
5429 if (session && is_multiparty && !session->is_multiparty) {
5430 g_free(session->with);
5431 session->with = NULL;
5432 was_multiparty = FALSE;
5433 session->is_multiparty = TRUE;
5434 session->chat_id = rand();
5437 if (!session && is_multiparty) {
5438 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5440 /* IM session */
5441 from = parse_from(sipmsg_find_header(msg, "From"));
5442 if (!session) {
5443 session = sipe_session_find_or_add_im(sip, from);
5446 if (session) {
5447 g_free(session->callid);
5448 session->callid = g_strdup(callid);
5450 session->is_multiparty = is_multiparty;
5451 if (roster_manager) {
5452 session->roster_manager = g_strdup(roster_manager);
5456 if (is_multiparty && end_points) {
5457 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5458 GSList *entry = end_points;
5459 while (entry) {
5460 struct sip_dialog *dialog;
5461 struct sipendpoint *end_point = entry->data;
5462 entry = entry->next;
5464 if (!g_strcasecmp(from, end_point->contact) ||
5465 !g_strcasecmp(to, end_point->contact))
5466 continue;
5468 dialog = sipe_dialog_find(session, end_point->contact);
5469 if (dialog) {
5470 g_free(dialog->theirepid);
5471 dialog->theirepid = end_point->epid;
5472 end_point->epid = NULL;
5473 } else {
5474 dialog = sipe_dialog_add(session);
5476 dialog->callid = g_strdup(session->callid);
5477 dialog->with = end_point->contact;
5478 end_point->contact = NULL;
5479 dialog->theirepid = end_point->epid;
5480 end_point->epid = NULL;
5482 just_joined = TRUE;
5484 /* send triggered INVITE */
5485 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5488 g_free(to);
5491 if (end_points) {
5492 GSList *entry = end_points;
5493 while (entry) {
5494 struct sipendpoint *end_point = entry->data;
5495 entry = entry->next;
5496 g_free(end_point->contact);
5497 g_free(end_point->epid);
5498 g_free(end_point);
5500 g_slist_free(end_points);
5503 if (session) {
5504 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5505 if (dialog) {
5506 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5507 sipe_dialog_parse_routes(dialog, msg, FALSE);
5508 } else {
5509 dialog = sipe_dialog_add(session);
5511 dialog->callid = g_strdup(session->callid);
5512 dialog->with = g_strdup(from);
5513 sipe_dialog_parse(dialog, msg, FALSE);
5515 if (!dialog->ourtag) {
5516 dialog->ourtag = newTag;
5517 newTag = NULL;
5520 just_joined = TRUE;
5522 } else {
5523 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5525 g_free(newTag);
5527 if (is_multiparty && !session->conv) {
5528 gchar *chat_title = sipe_chat_get_name(callid);
5529 gchar *self = sip_uri_self(sip);
5530 /* create prpl chat */
5531 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5532 session->chat_title = g_strdup(chat_title);
5533 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5534 /* add self */
5535 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5536 self, NULL,
5537 PURPLE_CBFLAGS_NONE, FALSE);
5538 g_free(chat_title);
5539 g_free(self);
5542 if (is_multiparty && !was_multiparty) {
5543 /* add current IM counterparty to chat */
5544 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5545 sipe_dialog_first(session)->with, NULL,
5546 PURPLE_CBFLAGS_NONE, FALSE);
5549 /* add inviting party to chat */
5550 if (just_joined && session->conv) {
5551 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5552 from, NULL,
5553 PURPLE_CBFLAGS_NONE, TRUE);
5556 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5558 /* This used only in 2005 official client, not 2007 or Reuters.
5559 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5560 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5562 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5563 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5564 if (is_multiparty ||
5565 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5567 if (ms_text_format) {
5568 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5570 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5571 if (tmp) {
5572 gsize len;
5573 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5575 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5577 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5578 sipe_utils_nameval_free(parsed_body);
5579 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5581 g_free(tmp);
5583 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5585 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5586 gchar *html = get_html_message(ms_text_format, NULL);
5587 if (html) {
5588 if (is_multiparty) {
5589 serv_got_chat_in(sip->gc, session->chat_id, from,
5590 PURPLE_MESSAGE_RECV, html, time(NULL));
5591 } else {
5592 serv_got_im(sip->gc, from, html, 0, time(NULL));
5594 g_free(html);
5595 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5601 g_free(from);
5603 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5604 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5605 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5607 body = g_strdup_printf(
5608 "v=0\r\n"
5609 "o=- 0 0 IN IP4 %s\r\n"
5610 "s=session\r\n"
5611 "c=IN IP4 %s\r\n"
5612 "t=0 0\r\n"
5613 "m=%s %d sip sip:%s\r\n"
5614 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5615 sipe_backend_network_ip_address(),
5616 sipe_backend_network_ip_address(),
5617 sip->ocs2007 ? "message" : "x-ms-message",
5618 sip->realport,
5619 sip->username);
5620 send_sip_response(sip->gc, msg, 200, "OK", body);
5621 g_free(body);
5624 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5626 gchar *body;
5628 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5629 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5630 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5632 body = g_strdup_printf(
5633 "v=0\r\n"
5634 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5635 "s=session\r\n"
5636 "c=IN IP4 0.0.0.0\r\n"
5637 "t=0 0\r\n"
5638 "m=%s %d sip sip:%s\r\n"
5639 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5640 sip->ocs2007 ? "message" : "x-ms-message",
5641 sip->realport,
5642 sip->username);
5643 send_sip_response(sip->gc, msg, 200, "OK", body);
5644 g_free(body);
5647 static const char*
5648 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5650 const char *res = "NTLM";
5651 #ifdef HAVE_LIBKRB5
5652 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5653 res = "Kerberos";
5655 #else
5656 (void) sip; /* make compiler happy */
5657 #endif
5658 return res;
5661 static void sipe_connection_cleanup(struct sipe_account_data *);
5662 static void create_connection(struct sipe_account_data *, gchar *, int);
5664 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5665 SIPE_UNUSED_PARAMETER struct transaction *trans)
5667 gchar *tmp;
5668 const gchar *expires_header;
5669 int expires, i;
5670 GSList *hdr = msg->headers;
5671 struct sipnameval *elem;
5673 expires_header = sipmsg_find_header(msg, "Expires");
5674 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5675 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
5677 switch (msg->response) {
5678 case 200:
5679 if (expires == 0) {
5680 sip->registerstatus = 0;
5681 } else {
5682 const gchar *contact_hdr;
5683 gchar *gruu = NULL;
5684 gchar *epid;
5685 gchar *uuid;
5686 gchar *timeout;
5687 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5688 const char *auth_scheme;
5690 if (!sip->reregister_set) {
5691 gchar *action_name = g_strdup_printf("<%s>", "registration");
5692 sipe_schedule_action(action_name,
5693 expires,
5694 do_register_cb,
5695 NULL,
5696 SIP_TO_CORE_PRIVATE,
5697 NULL);
5698 g_free(action_name);
5699 sip->reregister_set = TRUE;
5702 sip->registerstatus = 3;
5704 if (server_hdr && !sip->server_version) {
5705 sip->server_version = g_strdup(server_hdr);
5706 g_free(default_ua);
5707 default_ua = NULL;
5710 auth_scheme = sipe_get_auth_scheme_name(sip);
5711 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5713 if (tmp) {
5714 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
5715 fill_auth(tmp, &sip->registrar);
5718 if (!sip->reauthenticate_set) {
5719 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5720 guint reauth_timeout;
5721 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5722 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5723 reauth_timeout = sip->registrar.expires - 300;
5724 } else {
5725 /* NTLM: we have to reauthenticate as our security token expires
5726 after eight hours (be five minutes early) */
5727 reauth_timeout = (8 * 3600) - 300;
5729 sipe_schedule_action(action_name,
5730 reauth_timeout,
5731 do_reauthenticate_cb,
5732 NULL,
5733 SIP_TO_CORE_PRIVATE,
5734 NULL);
5735 g_free(action_name);
5736 sip->reauthenticate_set = TRUE;
5739 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5741 epid = get_epid(sip);
5742 uuid = generateUUIDfromEPID(epid);
5743 g_free(epid);
5745 // There can be multiple Contact headers (one per location where the user is logged in) so
5746 // make sure to only get the one for this uuid
5747 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5748 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5749 if (valid_contact) {
5750 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5751 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5752 g_free(valid_contact);
5753 break;
5754 } else {
5755 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5758 g_free(uuid);
5760 g_free(sip->contact);
5761 if(gruu) {
5762 sip->contact = g_strdup_printf("<%s>", gruu);
5763 g_free(gruu);
5764 } else {
5765 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5766 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, sipe_backend_network_ip_address(), TRANSPORT_DESCRIPTOR);
5768 sip->ocs2007 = FALSE;
5769 sip->batched_support = FALSE;
5771 while(hdr)
5773 elem = hdr->data;
5774 if (sipe_strcase_equal(elem->name, "Supported")) {
5775 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5776 /* We interpret this as OCS2007+ indicator */
5777 sip->ocs2007 = TRUE;
5778 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5780 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5781 sip->batched_support = TRUE;
5782 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5785 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5786 gchar **caps = g_strsplit(elem->value,",",0);
5787 i = 0;
5788 while (caps[i]) {
5789 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5790 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5791 i++;
5793 g_strfreev(caps);
5795 hdr = g_slist_next(hdr);
5798 /* rejoin open chats to be able to use them by continue to send messages */
5799 purple_conversation_foreach(sipe_rejoin_chat);
5801 /* subscriptions */
5802 if (!sip->subscribed) { //do it just once, not every re-register
5804 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5805 (GCompareFunc)g_ascii_strcasecmp)) {
5806 sipe_subscribe_roaming_contacts(sip);
5809 /* For 2007+ it does not make sence to subscribe to:
5810 * vnd-microsoft-roaming-ACL
5811 * vnd-microsoft-provisioning (not v2)
5812 * presence.wpending
5813 * These are for backward compatibility.
5815 if (sip->ocs2007)
5817 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5818 (GCompareFunc)g_ascii_strcasecmp)) {
5819 sipe_subscribe_roaming_self(sip);
5821 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5822 (GCompareFunc)g_ascii_strcasecmp)) {
5823 sipe_subscribe_roaming_provisioning_v2(sip);
5826 /* For 2005- servers */
5827 else
5829 //sipe_options_request(sip, SIP_TO_CORE_PUBLIC->sip_domain);
5831 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5832 (GCompareFunc)g_ascii_strcasecmp)) {
5833 sipe_subscribe_roaming_acl(sip);
5835 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5836 (GCompareFunc)g_ascii_strcasecmp)) {
5837 sipe_subscribe_roaming_provisioning(sip);
5839 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5840 (GCompareFunc)g_ascii_strcasecmp)) {
5841 sipe_subscribe_presence_wpending(SIP_TO_CORE_PRIVATE,
5842 msg);
5845 /* For 2007+ we publish our initial statuses and calendar data only after
5846 * received our existing publications in sipe_process_roaming_self()
5847 * Only in this case we know versions of current publications made
5848 * on our behalf.
5850 /* For 2005- we publish our initial statuses only after
5851 * received our existing UserInfo data in response to
5852 * self subscription.
5853 * Only in this case we won't override existing UserInfo data
5854 * set earlier or by other client on our behalf.
5858 sip->subscribed = TRUE;
5861 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5862 "timeout=", ";", NULL);
5863 if (timeout != NULL) {
5864 sscanf(timeout, "%u", &sip->keepalive_timeout);
5865 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5866 sip->keepalive_timeout);
5867 g_free(timeout);
5870 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5872 break;
5873 case 301:
5875 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5877 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5878 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5879 gchar **tmp;
5880 gchar *hostname;
5881 int port = 0;
5882 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5883 int i = 1;
5885 tmp = g_strsplit(parts[0], ":", 0);
5886 hostname = g_strdup(tmp[0]);
5887 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5888 g_strfreev(tmp);
5890 while (parts[i]) {
5891 tmp = g_strsplit(parts[i], "=", 0);
5892 if (tmp[1]) {
5893 if (g_strcasecmp("transport", tmp[0]) == 0) {
5894 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5895 transport = SIPE_TRANSPORT_TCP;
5896 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5897 transport = SIPE_TRANSPORT_UDP;
5901 g_strfreev(tmp);
5902 i++;
5904 g_strfreev(parts);
5906 /* Close old connection */
5907 sipe_connection_cleanup(sip);
5909 /* Create new connection */
5910 sip->transport = transport;
5911 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5912 hostname, port, TRANSPORT_DESCRIPTOR);
5913 create_connection(sip, hostname, port);
5915 g_free(redirect);
5917 break;
5918 case 401:
5919 if (sip->registerstatus != 2) {
5920 const char *auth_scheme;
5921 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5922 if (sip->registrar.retries > 3) {
5923 sip->gc->wants_to_die = TRUE;
5924 purple_connection_error(sip->gc, _("Authentication failed"));
5925 return TRUE;
5928 auth_scheme = sipe_get_auth_scheme_name(sip);
5929 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5931 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
5932 if (!tmp) {
5933 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5934 sip->gc->wants_to_die = TRUE;
5935 purple_connection_error(sip->gc, tmp2);
5936 g_free(tmp2);
5937 return TRUE;
5939 fill_auth(tmp, &sip->registrar);
5940 sip->registerstatus = 2;
5941 if (sip->account->disconnecting) {
5942 do_register_exp(sip, 0);
5943 } else {
5944 do_register(sip);
5947 break;
5948 case 403:
5950 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5951 gchar **reason = NULL;
5952 gchar *warning;
5953 if (diagnostics != NULL) {
5954 /* Example header:
5955 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5957 reason = g_strsplit(diagnostics, "\"", 0);
5959 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5960 (reason && reason[1]) ? reason[1] : _("no reason given"));
5961 g_strfreev(reason);
5963 sip->gc->wants_to_die = TRUE;
5964 purple_connection_error(sip->gc, warning);
5965 g_free(warning);
5966 return TRUE;
5968 break;
5969 case 404:
5971 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5972 gchar *reason = NULL;
5973 gchar *warning;
5974 if (diagnostics != NULL) {
5975 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5977 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5978 diagnostics ? (reason ? reason : _("no reason given")) :
5979 _("SIP is either not enabled for the destination URI or it does not exist"));
5980 g_free(reason);
5982 sip->gc->wants_to_die = TRUE;
5983 purple_connection_error(sip->gc, warning);
5984 g_free(warning);
5985 return TRUE;
5987 break;
5988 case 503:
5989 case 504: /* Server time-out */
5991 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5992 gchar *reason = NULL;
5993 gchar *warning;
5994 if (diagnostics != NULL) {
5995 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5997 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5998 g_free(reason);
6000 sip->gc->wants_to_die = TRUE;
6001 purple_connection_error(sip->gc, warning);
6002 g_free(warning);
6003 return TRUE;
6005 break;
6007 return TRUE;
6011 * Returns 2005-style activity and Availability.
6013 * @param status Sipe statis id.
6015 static void
6016 sipe_get_act_avail_by_status_2005(const char *status,
6017 int *activity,
6018 int *availability)
6020 int avail = 300; /* online */
6021 int act = 400; /* Available */
6023 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
6024 act = 100;
6025 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
6026 // act = 150;
6027 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
6028 act = 300;
6029 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
6030 act = 400;
6031 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
6032 // act = 500;
6033 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
6034 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
6035 act = 600;
6036 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
6037 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
6038 avail = 0; /* offline */
6039 act = 100;
6040 } else {
6041 act = 400; /* Available */
6044 if (activity) *activity = act;
6045 if (availability) *availability = avail;
6049 * [MS-SIP] 2.2.1
6051 * @param activity 2005 aggregated activity. Ex.: 600
6052 * @param availablity 2005 aggregated availablity. Ex.: 300
6054 static const char *
6055 sipe_get_status_by_act_avail_2005(const int activity,
6056 const int availablity,
6057 char **activity_desc)
6059 const char *status_id = NULL;
6060 const char *act = NULL;
6062 if (activity < 150) {
6063 status_id = SIPE_STATUS_ID_AWAY;
6064 } else if (activity < 200) {
6065 //status_id = SIPE_STATUS_ID_LUNCH;
6066 status_id = SIPE_STATUS_ID_AWAY;
6067 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
6068 } else if (activity < 300) {
6069 //status_id = SIPE_STATUS_ID_IDLE;
6070 status_id = SIPE_STATUS_ID_AWAY;
6071 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6072 } else if (activity < 400) {
6073 status_id = SIPE_STATUS_ID_BRB;
6074 } else if (activity < 500) {
6075 status_id = SIPE_STATUS_ID_AVAILABLE;
6076 } else if (activity < 600) {
6077 //status_id = SIPE_STATUS_ID_ON_PHONE;
6078 status_id = SIPE_STATUS_ID_BUSY;
6079 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
6080 } else if (activity < 700) {
6081 status_id = SIPE_STATUS_ID_BUSY;
6082 } else if (activity < 800) {
6083 status_id = SIPE_STATUS_ID_AWAY;
6084 } else {
6085 status_id = SIPE_STATUS_ID_AVAILABLE;
6088 if (availablity < 100)
6089 status_id = SIPE_STATUS_ID_OFFLINE;
6091 if (activity_desc && act) {
6092 g_free(*activity_desc);
6093 *activity_desc = g_strdup(act);
6096 return status_id;
6100 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
6102 static const char*
6103 sipe_get_status_by_availability(int avail,
6104 char** activity_desc)
6106 const char *status;
6107 const char *act = NULL;
6109 if (avail < 3000) {
6110 status = SIPE_STATUS_ID_OFFLINE;
6111 } else if (avail < 4500) {
6112 status = SIPE_STATUS_ID_AVAILABLE;
6113 } else if (avail < 6000) {
6114 //status = SIPE_STATUS_ID_IDLE;
6115 status = SIPE_STATUS_ID_AVAILABLE;
6116 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6117 } else if (avail < 7500) {
6118 status = SIPE_STATUS_ID_BUSY;
6119 } else if (avail < 9000) {
6120 //status = SIPE_STATUS_ID_BUSYIDLE;
6121 status = SIPE_STATUS_ID_BUSY;
6122 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
6123 } else if (avail < 12000) {
6124 status = SIPE_STATUS_ID_DND;
6125 } else if (avail < 15000) {
6126 status = SIPE_STATUS_ID_BRB;
6127 } else if (avail < 18000) {
6128 status = SIPE_STATUS_ID_AWAY;
6129 } else {
6130 status = SIPE_STATUS_ID_OFFLINE;
6133 if (activity_desc && act) {
6134 g_free(*activity_desc);
6135 *activity_desc = g_strdup(act);
6138 return status;
6142 * Returns 2007-style availability value
6144 * @param sipe_status_id (in)
6145 * @param activity_token (out) Must be g_free()'d after use if consumed.
6147 static int
6148 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
6150 int availability;
6151 sipe_activity activity = SIPE_ACTIVITY_UNSET;
6153 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
6154 availability = 15500;
6155 if (!activity_token || !(*activity_token)) {
6156 activity = SIPE_ACTIVITY_AWAY;
6158 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
6159 availability = 12500;
6160 activity = SIPE_ACTIVITY_BRB;
6161 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
6162 availability = 9500;
6163 activity = SIPE_ACTIVITY_DND;
6164 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
6165 availability = 6500;
6166 if (!activity_token || !(*activity_token)) {
6167 activity = SIPE_ACTIVITY_BUSY;
6169 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
6170 availability = 3500;
6171 activity = SIPE_ACTIVITY_ONLINE;
6172 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
6173 availability = 0;
6174 } else {
6175 // Offline or invisible
6176 availability = 18500;
6177 activity = SIPE_ACTIVITY_OFFLINE;
6180 if (activity_token) {
6181 *activity_token = g_strdup(sipe_activity_map[activity].token);
6183 return availability;
6186 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
6188 const char *uri;
6189 sipe_xml *xn_categories;
6190 const sipe_xml *xn_category;
6191 const char *status = NULL;
6192 gboolean do_update_status = FALSE;
6193 gboolean has_note_cleaned = FALSE;
6194 gboolean has_free_busy_cleaned = FALSE;
6196 xn_categories = sipe_xml_parse(data, len);
6197 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
6199 for (xn_category = sipe_xml_child(xn_categories, "category");
6200 xn_category ;
6201 xn_category = sipe_xml_twin(xn_category) )
6203 const sipe_xml *xn_node;
6204 const char *tmp;
6205 const char *attrVar = sipe_xml_attribute(xn_category, "name");
6206 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
6207 sipe_utils_str_to_time(tmp) : 0;
6209 /* contactCard */
6210 if (sipe_strequal(attrVar, "contactCard"))
6212 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
6214 if (card) {
6215 const sipe_xml *node;
6216 /* identity - Display Name and email */
6217 node = sipe_xml_child(card, "identity");
6218 if (node) {
6219 char* display_name = sipe_xml_data(
6220 sipe_xml_child(node, "name/displayName"));
6221 char* email = sipe_xml_data(
6222 sipe_xml_child(node, "email"));
6224 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6225 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6227 g_free(display_name);
6228 g_free(email);
6230 /* company */
6231 node = sipe_xml_child(card, "company");
6232 if (node) {
6233 char* company = sipe_xml_data(node);
6234 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
6235 g_free(company);
6237 /* department */
6238 node = sipe_xml_child(card, "department");
6239 if (node) {
6240 char* department = sipe_xml_data(node);
6241 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
6242 g_free(department);
6244 /* title */
6245 node = sipe_xml_child(card, "title");
6246 if (node) {
6247 char* title = sipe_xml_data(node);
6248 sipe_update_user_info(sip, uri, TITLE_PROP, title);
6249 g_free(title);
6251 /* office */
6252 node = sipe_xml_child(card, "office");
6253 if (node) {
6254 char* office = sipe_xml_data(node);
6255 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
6256 g_free(office);
6258 /* site (url) */
6259 node = sipe_xml_child(card, "url");
6260 if (node) {
6261 char* site = sipe_xml_data(node);
6262 sipe_update_user_info(sip, uri, SITE_PROP, site);
6263 g_free(site);
6265 /* phone */
6266 for (node = sipe_xml_child(card, "phone");
6267 node;
6268 node = sipe_xml_twin(node))
6270 const char *phone_type = sipe_xml_attribute(node, "type");
6271 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
6272 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
6274 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
6276 g_free(phone);
6277 g_free(phone_display_string);
6279 /* address */
6280 for (node = sipe_xml_child(card, "address");
6281 node;
6282 node = sipe_xml_twin(node))
6284 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
6285 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
6286 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
6287 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
6288 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
6289 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
6291 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
6292 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
6293 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
6294 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
6295 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
6297 g_free(street);
6298 g_free(city);
6299 g_free(state);
6300 g_free(zipcode);
6301 g_free(country_code);
6303 break;
6308 /* note */
6309 else if (sipe_strequal(attrVar, "note"))
6311 if (uri) {
6312 struct sipe_buddy *sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri);
6314 if (!has_note_cleaned) {
6315 has_note_cleaned = TRUE;
6317 g_free(sbuddy->note);
6318 sbuddy->note = NULL;
6319 sbuddy->is_oof_note = FALSE;
6320 sbuddy->note_since = publish_time;
6322 do_update_status = TRUE;
6324 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6325 /* clean up in case no 'note' element is supplied
6326 * which indicate note removal in client
6328 g_free(sbuddy->note);
6329 sbuddy->note = NULL;
6330 sbuddy->is_oof_note = FALSE;
6331 sbuddy->note_since = publish_time;
6333 xn_node = sipe_xml_child(xn_category, "note/body");
6334 if (xn_node) {
6335 char *tmp;
6336 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6337 g_free(tmp);
6338 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6339 sbuddy->note_since = publish_time;
6341 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6342 uri, sbuddy->note ? sbuddy->note : "");
6344 /* to trigger UI refresh in case no status info is supplied in this update */
6345 do_update_status = TRUE;
6349 /* state */
6350 else if(sipe_strequal(attrVar, "state"))
6352 char *tmp;
6353 int availability;
6354 const sipe_xml *xn_availability;
6355 const sipe_xml *xn_activity;
6356 const sipe_xml *xn_meeting_subject;
6357 const sipe_xml *xn_meeting_location;
6358 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri) : NULL;
6360 xn_node = sipe_xml_child(xn_category, "state");
6361 if (!xn_node) continue;
6362 xn_availability = sipe_xml_child(xn_node, "availability");
6363 if (!xn_availability) continue;
6364 xn_activity = sipe_xml_child(xn_node, "activity");
6365 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6366 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6368 tmp = sipe_xml_data(xn_availability);
6369 availability = atoi(tmp);
6370 g_free(tmp);
6372 /* activity, meeting_subject, meeting_location */
6373 if (sbuddy) {
6374 char *tmp = NULL;
6376 /* activity */
6377 g_free(sbuddy->activity);
6378 sbuddy->activity = NULL;
6379 if (xn_activity) {
6380 const char *token = sipe_xml_attribute(xn_activity, "token");
6381 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6383 /* from token */
6384 if (!is_empty(token)) {
6385 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6387 /* from custom element */
6388 if (xn_custom) {
6389 char *custom = sipe_xml_data(xn_custom);
6391 if (!is_empty(custom)) {
6392 sbuddy->activity = custom;
6393 custom = NULL;
6395 g_free(custom);
6398 /* meeting_subject */
6399 g_free(sbuddy->meeting_subject);
6400 sbuddy->meeting_subject = NULL;
6401 if (xn_meeting_subject) {
6402 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6404 if (!is_empty(meeting_subject)) {
6405 sbuddy->meeting_subject = meeting_subject;
6406 meeting_subject = NULL;
6408 g_free(meeting_subject);
6410 /* meeting_location */
6411 g_free(sbuddy->meeting_location);
6412 sbuddy->meeting_location = NULL;
6413 if (xn_meeting_location) {
6414 char *meeting_location = sipe_xml_data(xn_meeting_location);
6416 if (!is_empty(meeting_location)) {
6417 sbuddy->meeting_location = meeting_location;
6418 meeting_location = NULL;
6420 g_free(meeting_location);
6423 status = sipe_get_status_by_availability(availability, &tmp);
6424 if (sbuddy->activity && tmp) {
6425 char *tmp2 = sbuddy->activity;
6427 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6428 g_free(tmp);
6429 g_free(tmp2);
6430 } else if (tmp) {
6431 sbuddy->activity = tmp;
6435 do_update_status = TRUE;
6437 /* calendarData */
6438 else if(sipe_strequal(attrVar, "calendarData"))
6440 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri) : NULL;
6441 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6442 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6444 if (sbuddy && xn_free_busy) {
6445 if (!has_free_busy_cleaned) {
6446 has_free_busy_cleaned = TRUE;
6448 g_free(sbuddy->cal_start_time);
6449 sbuddy->cal_start_time = NULL;
6451 g_free(sbuddy->cal_free_busy_base64);
6452 sbuddy->cal_free_busy_base64 = NULL;
6454 g_free(sbuddy->cal_free_busy);
6455 sbuddy->cal_free_busy = NULL;
6457 sbuddy->cal_free_busy_published = publish_time;
6460 if (publish_time >= sbuddy->cal_free_busy_published) {
6461 g_free(sbuddy->cal_start_time);
6462 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6464 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6465 15 : 0;
6467 g_free(sbuddy->cal_free_busy_base64);
6468 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6470 g_free(sbuddy->cal_free_busy);
6471 sbuddy->cal_free_busy = NULL;
6473 sbuddy->cal_free_busy_published = publish_time;
6475 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);
6479 if (sbuddy && xn_working_hours) {
6480 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6485 if (do_update_status) {
6486 if (!status) { /* no status category in this update, using contact's current status */
6487 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6488 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6489 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6490 status = purple_status_get_id(pstatus);
6493 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
6494 sipe_got_user_status(sip, uri, status);
6497 sipe_xml_free(xn_categories);
6500 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6502 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6503 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
6504 payload->host = g_strdup(host);
6505 payload->buddies = server;
6506 sipe_subscribe_presence_batched_routed(SIP_TO_CORE_PRIVATE,
6507 payload);
6508 sipe_subscribe_presence_batched_routed_free(payload);
6511 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6513 sipe_xml *xn_list;
6514 const sipe_xml *xn_resource;
6515 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6516 g_free, NULL);
6517 GSList *server;
6518 gchar *host;
6520 xn_list = sipe_xml_parse(data, len);
6522 for (xn_resource = sipe_xml_child(xn_list, "resource");
6523 xn_resource;
6524 xn_resource = sipe_xml_twin(xn_resource) )
6526 const char *uri, *state;
6527 const sipe_xml *xn_instance;
6529 xn_instance = sipe_xml_child(xn_resource, "instance");
6530 if (!xn_instance) continue;
6532 uri = sipe_xml_attribute(xn_resource, "uri");
6533 state = sipe_xml_attribute(xn_instance, "state");
6534 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
6536 if (strstr(state, "resubscribe")) {
6537 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
6539 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6540 gchar *user = g_strdup(uri);
6541 host = g_strdup(poolFqdn);
6542 server = g_hash_table_lookup(servers, host);
6543 server = g_slist_append(server, user);
6544 g_hash_table_insert(servers, host, server);
6545 } else {
6546 sipe_subscribe_presence_single(SIP_TO_CORE_PRIVATE,
6547 (void *) uri);
6552 /* Send out any deferred poolFqdn subscriptions */
6553 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6554 g_hash_table_destroy(servers);
6556 sipe_xml_free(xn_list);
6559 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6561 gchar *uri;
6562 gchar *getbasic;
6563 gchar *activity = NULL;
6564 sipe_xml *pidf;
6565 const sipe_xml *basicstatus = NULL, *tuple, *status;
6566 gboolean isonline = FALSE;
6567 const sipe_xml *display_name_node;
6569 pidf = sipe_xml_parse(data, len);
6570 if (!pidf) {
6571 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
6572 return;
6575 if ((tuple = sipe_xml_child(pidf, "tuple")))
6577 if ((status = sipe_xml_child(tuple, "status"))) {
6578 basicstatus = sipe_xml_child(status, "basic");
6582 if (!basicstatus) {
6583 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6584 sipe_xml_free(pidf);
6585 return;
6588 getbasic = sipe_xml_data(basicstatus);
6589 if (!getbasic) {
6590 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6591 sipe_xml_free(pidf);
6592 return;
6595 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
6596 if (strstr(getbasic, "open")) {
6597 isonline = TRUE;
6599 g_free(getbasic);
6601 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6603 display_name_node = sipe_xml_child(pidf, "display-name");
6604 if (display_name_node) {
6605 char * display_name = sipe_xml_data(display_name_node);
6607 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6608 g_free(display_name);
6611 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
6612 if ((status = sipe_xml_child(tuple, "status"))) {
6613 if ((basicstatus = sipe_xml_child(status, "activities"))) {
6614 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
6615 activity = sipe_xml_data(basicstatus);
6616 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
6622 if (isonline) {
6623 const gchar * status_id = NULL;
6624 if (activity) {
6625 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6626 status_id = SIPE_STATUS_ID_BUSY;
6627 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6628 status_id = SIPE_STATUS_ID_AWAY;
6632 if (!status_id) {
6633 status_id = SIPE_STATUS_ID_AVAILABLE;
6636 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
6637 sipe_got_user_status(sip, uri, status_id);
6638 } else {
6639 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6642 g_free(activity);
6643 g_free(uri);
6644 sipe_xml_free(pidf);
6647 /** 2005 */
6648 static void
6649 sipe_user_info_has_updated(struct sipe_account_data *sip,
6650 const sipe_xml *xn_userinfo)
6652 const sipe_xml *xn_states;
6654 g_free(sip->user_states);
6655 sip->user_states = NULL;
6656 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
6657 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
6659 /* this is a hack-around to remove added newline after inner element,
6660 * state in this case, where it shouldn't be.
6661 * After several use of sipe_xml_stringify, amount of added newlines
6662 * grows significantly.
6664 if (orig) {
6665 gchar c, *stripped = orig;
6666 while ((c = *orig++)) {
6667 if ((c != '\n') /* && (c != '\r') */) {
6668 *stripped++ = c;
6671 *stripped = '\0';
6675 /* Publish initial state if not yet.
6676 * Assuming this happens on initial responce to self subscription
6677 * so we've already updated our UserInfo.
6679 if (!sip->initial_state_published) {
6680 send_presence_soap(sip, FALSE);
6681 /* dalayed run */
6682 sipe_schedule_action("<+update-calendar>",
6683 UPDATE_CALENDAR_DELAY,
6684 (Action)sipe_core_update_calendar,
6685 NULL,
6686 SIP_TO_CORE_PRIVATE,
6687 NULL);
6691 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6693 char *activity = NULL;
6694 const char *epid;
6695 const char *status_id = NULL;
6696 const char *name;
6697 char *uri;
6698 char *self_uri = sip_uri_self(sip);
6699 int avl;
6700 int act;
6701 const char *device_name = NULL;
6702 const char *cal_start_time = NULL;
6703 const char *cal_granularity = NULL;
6704 char *cal_free_busy_base64 = NULL;
6705 struct sipe_buddy *sbuddy;
6706 const sipe_xml *node;
6707 sipe_xml *xn_presentity;
6708 const sipe_xml *xn_availability;
6709 const sipe_xml *xn_activity;
6710 const sipe_xml *xn_display_name;
6711 const sipe_xml *xn_email;
6712 const sipe_xml *xn_phone_number;
6713 const sipe_xml *xn_userinfo;
6714 const sipe_xml *xn_note;
6715 const sipe_xml *xn_oof;
6716 const sipe_xml *xn_state;
6717 const sipe_xml *xn_contact;
6718 char *note;
6719 char *free_activity;
6720 int user_avail;
6721 const char *user_avail_nil;
6722 int res_avail;
6723 time_t user_avail_since = 0;
6724 time_t activity_since = 0;
6726 /* fix for Reuters environment on Linux */
6727 if (data && strstr(data, "encoding=\"utf-16\"")) {
6728 char *tmp_data;
6729 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6730 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
6731 g_free(tmp_data);
6732 } else {
6733 xn_presentity = sipe_xml_parse(data, len);
6736 xn_availability = sipe_xml_child(xn_presentity, "availability");
6737 xn_activity = sipe_xml_child(xn_presentity, "activity");
6738 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
6739 xn_email = sipe_xml_child(xn_presentity, "email");
6740 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
6741 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
6742 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
6743 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
6744 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
6745 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
6746 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
6747 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
6748 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
6749 note = xn_note ? sipe_xml_data(xn_note) : NULL;
6751 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6752 user_avail = 0;
6753 user_avail_since = 0;
6756 free_activity = NULL;
6758 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
6759 uri = sip_uri_from_name(name);
6760 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
6761 epid = sipe_xml_attribute(xn_availability, "epid");
6762 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
6764 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6765 res_avail = sipe_get_availability_by_status(status_id, NULL);
6766 if (user_avail > res_avail) {
6767 res_avail = user_avail;
6768 status_id = sipe_get_status_by_availability(user_avail, NULL);
6771 if (xn_display_name) {
6772 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
6773 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
6774 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
6775 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
6776 char *tel_uri = sip_to_tel_uri(phone_number);
6778 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6779 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6780 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6781 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6783 g_free(tel_uri);
6784 g_free(phone_label);
6785 g_free(phone_number);
6786 g_free(email);
6787 g_free(display_name);
6790 if (xn_contact) {
6791 /* tel */
6792 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
6794 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6795 const char *phone_type = sipe_xml_attribute(node, "type");
6796 char* phone = sipe_xml_data(node);
6798 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6800 g_free(phone);
6804 /* devicePresence */
6805 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
6806 const sipe_xml *xn_device_name;
6807 const sipe_xml *xn_calendar_info;
6808 const sipe_xml *xn_state;
6809 char *state;
6811 /* deviceName */
6812 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
6813 xn_device_name = sipe_xml_child(node, "deviceName");
6814 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
6817 /* calendarInfo */
6818 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
6819 if (xn_calendar_info) {
6820 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
6822 if (cal_start_time) {
6823 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6824 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6826 if (cal_start_time_t_tmp > cal_start_time_t) {
6827 cal_start_time = cal_start_time_tmp;
6828 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6829 g_free(cal_free_busy_base64);
6830 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6832 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);
6834 } else {
6835 cal_start_time = cal_start_time_tmp;
6836 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6837 g_free(cal_free_busy_base64);
6838 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6840 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);
6844 /* state */
6845 xn_state = sipe_xml_child(node, "states/state");
6846 if (xn_state) {
6847 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
6848 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
6850 state = sipe_xml_data(xn_state);
6851 if (dev_avail_since > user_avail_since &&
6852 dev_avail >= res_avail)
6854 res_avail = dev_avail;
6855 if (!is_empty(state))
6857 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6858 g_free(activity);
6859 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6860 } else if (sipe_strequal(state, "presenting")) {
6861 g_free(activity);
6862 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6863 } else {
6864 activity = state;
6865 state = NULL;
6867 activity_since = dev_avail_since;
6869 status_id = sipe_get_status_by_availability(res_avail, &activity);
6871 g_free(state);
6875 /* oof */
6876 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6877 g_free(activity);
6878 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6879 activity_since = 0;
6882 sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri);
6883 if (sbuddy)
6885 g_free(sbuddy->activity);
6886 sbuddy->activity = activity;
6887 activity = NULL;
6889 sbuddy->activity_since = activity_since;
6891 sbuddy->user_avail = user_avail;
6892 sbuddy->user_avail_since = user_avail_since;
6894 g_free(sbuddy->note);
6895 sbuddy->note = NULL;
6896 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6898 sbuddy->is_oof_note = (xn_oof != NULL);
6900 g_free(sbuddy->device_name);
6901 sbuddy->device_name = NULL;
6902 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6904 if (!is_empty(cal_free_busy_base64)) {
6905 g_free(sbuddy->cal_start_time);
6906 sbuddy->cal_start_time = g_strdup(cal_start_time);
6908 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6910 g_free(sbuddy->cal_free_busy_base64);
6911 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6912 cal_free_busy_base64 = NULL;
6914 g_free(sbuddy->cal_free_busy);
6915 sbuddy->cal_free_busy = NULL;
6918 sbuddy->last_non_cal_status_id = status_id;
6919 g_free(sbuddy->last_non_cal_activity);
6920 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6922 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6923 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6925 sip->is_oof_note = sbuddy->is_oof_note;
6927 g_free(sip->note);
6928 sip->note = g_strdup(sbuddy->note);
6930 sip->note_since = time(NULL);
6933 g_free(sip->status);
6934 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6937 g_free(cal_free_busy_base64);
6938 g_free(activity);
6940 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
6941 sipe_got_user_status(sip, uri, status_id);
6943 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6944 sipe_user_info_has_updated(sip, xn_userinfo);
6947 g_free(note);
6948 sipe_xml_free(xn_presentity);
6949 g_free(uri);
6950 g_free(self_uri);
6953 static void sipe_presence_mime_cb(gpointer user_data,
6954 const gchar *type,
6955 const gchar *body,
6956 gsize length)
6958 if (strstr(type,"application/rlmi+xml")) {
6959 process_incoming_notify_rlmi_resub(user_data, body, length);
6960 } else if (strstr(type, "text/xml+msrtc.pidf")) {
6961 process_incoming_notify_msrtc(user_data, body, length);
6962 } else {
6963 process_incoming_notify_rlmi(user_data, body, length);
6967 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6969 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6971 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
6973 if (ctype &&
6974 (strstr(ctype, "application/rlmi+xml") ||
6975 strstr(ctype, "application/msrtc-event-categories+xml")))
6977 if (strstr(ctype, "multipart"))
6979 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sip);
6981 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6983 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6985 else if(strstr(ctype, "application/rlmi+xml"))
6987 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6990 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6992 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6994 else
6996 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
7000 static void sipe_presence_timeout_mime_cb(gpointer user_data,
7001 SIPE_UNUSED_PARAMETER const gchar *type,
7002 const gchar *body,
7003 gsize length)
7005 GSList **buddies = user_data;
7006 sipe_xml *xml = sipe_xml_parse(body, length);
7008 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
7009 const gchar *uri = sipe_xml_attribute(xml, "uri");
7010 const sipe_xml *xn_category;
7013 * automaton: presence is never expected to change
7015 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
7017 for (xn_category = sipe_xml_child(xml, "category");
7018 xn_category;
7019 xn_category = sipe_xml_twin(xn_category)) {
7020 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
7021 "contactCard")) {
7022 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
7023 if (node) {
7024 char *boolean = sipe_xml_data(node);
7025 if (sipe_strequal(boolean, "true")) {
7026 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
7027 uri);
7028 uri = NULL;
7030 g_free(boolean);
7032 break;
7036 if (uri) {
7037 *buddies = g_slist_append(*buddies, sip_uri(uri));
7041 sipe_xml_free(xml);
7044 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
7046 const char *ctype = sipmsg_find_header(msg, "Content-Type");
7047 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7049 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
7051 if (ctype &&
7052 strstr(ctype, "multipart") &&
7053 (strstr(ctype, "application/rlmi+xml") ||
7054 strstr(ctype, "application/msrtc-event-categories+xml"))) {
7055 GSList *buddies = NULL;
7057 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
7059 if (buddies) {
7060 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
7061 payload->host = g_strdup(who);
7062 payload->buddies = buddies;
7063 sipe_schedule_action(action_name,
7064 timeout,
7065 sipe_subscribe_presence_batched_routed,
7066 sipe_subscribe_presence_batched_routed_free,
7067 SIP_TO_CORE_PRIVATE,
7068 payload);
7069 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
7072 } else {
7073 sipe_schedule_action(action_name,
7074 timeout,
7075 sipe_subscribe_presence_single,
7076 g_free,
7077 SIP_TO_CORE_PRIVATE,
7078 g_strdup(who));
7079 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
7081 g_free(action_name);
7085 * Dispatcher for all incoming subscription information
7086 * whether it comes from NOTIFY, BENOTIFY requests or
7087 * piggy-backed to subscription's OK responce.
7089 * @param request whether initiated from BE/NOTIFY request or OK-response message.
7090 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
7092 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
7094 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
7095 const gchar *event = sipmsg_find_header(msg, "Event");
7096 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
7097 char *tmp;
7099 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
7100 event ? event : "",
7101 tmp = fix_newlines(msg->body));
7102 g_free(tmp);
7103 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
7105 /* implicit subscriptions */
7106 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
7107 sipe_process_imdn(sip, msg);
7110 if (event) {
7111 /* for one off subscriptions (send with Expire: 0) */
7112 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
7114 sipe_process_provisioning_v2(sip, msg);
7116 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
7118 sipe_process_provisioning(sip, msg);
7120 else if (sipe_strcase_equal(event, "presence"))
7122 sipe_process_presence(sip, msg);
7124 else if (sipe_strcase_equal(event, "registration-notify"))
7126 sipe_process_registration_notify(sip, msg);
7129 if (!subscription_state || strstr(subscription_state, "active"))
7131 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
7133 sipe_process_roaming_contacts(sip, msg);
7135 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
7137 sipe_process_roaming_self(sip, msg);
7139 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
7141 sipe_process_roaming_acl(sip, msg);
7143 else if (sipe_strcase_equal(event, "presence.wpending"))
7145 sipe_process_presence_wpending(sip, msg);
7147 else if (sipe_strcase_equal(event, "conference"))
7149 sipe_process_conference(sip, msg);
7154 /* The server sends status 'terminated' */
7155 if (subscription_state && strstr(subscription_state, "terminated") ) {
7156 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
7157 gchar *key = sipe_get_subscription_key(event, who);
7159 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
7160 g_free(who);
7162 if (g_hash_table_lookup(sip->subscriptions, key)) {
7163 g_hash_table_remove(sip->subscriptions, key);
7164 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
7167 g_free(key);
7170 if (!request && event) {
7171 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
7172 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
7173 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
7175 if (timeout) {
7176 /* 2 min ahead of expiration */
7177 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
7179 if (sipe_strcase_equal(event, "presence.wpending") &&
7180 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
7182 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
7183 sipe_schedule_action(action_name,
7184 timeout,
7185 sipe_subscribe_presence_wpending,
7186 NULL,
7187 SIP_TO_CORE_PRIVATE,
7188 NULL);
7189 g_free(action_name);
7191 else if (sipe_strcase_equal(event, "presence") &&
7192 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
7194 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
7195 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7197 if (sip->batched_support) {
7198 sipe_process_presence_timeout(sip, msg, who, timeout);
7200 else {
7201 sipe_schedule_action(action_name,
7202 timeout,
7203 sipe_subscribe_presence_single,
7204 g_free,
7205 SIP_TO_CORE_PRIVATE,
7206 g_strdup(who));
7207 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
7209 g_free(action_name);
7210 g_free(who);
7215 /* The client responses on received a NOTIFY message */
7216 if (request && !benotify)
7218 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7223 * Whether user manually changed status or
7224 * it was changed automatically due to user
7225 * became inactive/active again
7227 static gboolean
7228 sipe_is_user_state(struct sipe_account_data *sip)
7230 gboolean res;
7231 time_t now = time(NULL);
7233 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
7234 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
7236 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
7238 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
7239 return res;
7242 static void
7243 send_presence_soap0(struct sipe_account_data *sip,
7244 gboolean do_publish_calendar,
7245 gboolean do_reset_status)
7247 struct sipe_ews* ews = sip->ews;
7248 int availability = 0;
7249 int activity = 0;
7250 gchar *body;
7251 gchar *tmp;
7252 gchar *tmp2 = NULL;
7253 gchar *res_note = NULL;
7254 gchar *res_oof = NULL;
7255 const gchar *note_pub = NULL;
7256 gchar *states = NULL;
7257 gchar *calendar_data = NULL;
7258 gchar *epid = get_epid(sip);
7259 time_t now = time(NULL);
7260 gchar *since_time_str = sipe_utils_time_to_str(now);
7261 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
7262 const char *user_input;
7263 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
7265 if (oof_note && sip->note) {
7266 SIPE_DEBUG_INFO("ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
7267 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
7270 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
7272 if (!sip->initial_state_published ||
7273 do_reset_status)
7275 g_free(sip->status);
7276 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
7279 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
7281 /* Note */
7282 if (pub_oof) {
7283 note_pub = oof_note;
7284 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
7285 ews->published = TRUE;
7286 } else if (sip->note) {
7287 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
7288 g_free(sip->note);
7289 sip->note = NULL;
7290 sip->is_oof_note = FALSE;
7291 sip->note_since = 0;
7292 } else {
7293 note_pub = sip->note;
7294 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
7298 if (note_pub)
7300 /* to protocol internal plain text format */
7301 tmp = sipe_backend_markup_strip_html(note_pub);
7302 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
7303 g_free(tmp);
7306 /* User State */
7307 if (!do_reset_status) {
7308 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
7310 gchar *activity_token = NULL;
7311 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
7313 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
7314 avail_2007,
7315 since_time_str,
7316 epid,
7317 activity_token);
7318 g_free(activity_token);
7320 else /* preserve existing publication */
7322 if (sip->user_states) {
7323 states = g_strdup(sip->user_states);
7326 } else {
7327 /* do nothing - then User state will be erased */
7329 sip->initial_state_published = TRUE;
7331 /* CalendarInfo */
7332 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
7334 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7335 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7336 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
7337 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
7338 fb_start_str,
7339 free_busy_base64);
7340 g_free(fb_start_str);
7341 g_free(free_busy_base64);
7344 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
7346 /* forming resulting XML */
7347 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7348 sip->username,
7349 availability,
7350 activity,
7351 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7352 res_note ? res_note : "",
7353 res_oof ? res_oof : "",
7354 states ? states : "",
7355 calendar_data ? calendar_data : "",
7356 epid,
7357 since_time_str,
7358 since_time_str,
7359 user_input);
7360 g_free(tmp);
7361 g_free(tmp2);
7362 g_free(res_note);
7363 g_free(states);
7364 g_free(calendar_data);
7366 send_soap_request(sip, body);
7368 g_free(body);
7369 g_free(since_time_str);
7370 g_free(epid);
7373 void
7374 send_presence_soap(struct sipe_account_data *sip,
7375 gboolean do_publish_calendar)
7377 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7381 static gboolean
7382 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7383 struct sipmsg *msg,
7384 struct transaction *trans)
7386 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7388 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7389 sipe_xml *xml;
7390 const sipe_xml *node;
7391 gchar *fault_code;
7392 GHashTable *faults;
7393 int index_our;
7394 gboolean has_device_publication = FALSE;
7396 xml = sipe_xml_parse(msg->body, msg->bodylen);
7398 /* test if version mismatch fault */
7399 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
7400 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7401 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
7402 g_free(fault_code);
7403 sipe_xml_free(xml);
7404 return TRUE;
7406 g_free(fault_code);
7408 /* accumulating information about faulty versions */
7409 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7410 for (node = sipe_xml_child(xml, "details/operation");
7411 node;
7412 node = sipe_xml_twin(node))
7414 const gchar *index = sipe_xml_attribute(node, "index");
7415 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
7417 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7418 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
7420 sipe_xml_free(xml);
7422 /* here we are parsing own request to figure out what publication
7423 * referensed here only by index went wrong
7425 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
7427 /* publication */
7428 for (node = sipe_xml_child(xml, "publications/publication"),
7429 index_our = 1; /* starts with 1 - our first publication */
7430 node;
7431 node = sipe_xml_twin(node), index_our++)
7433 gchar *idx = g_strdup_printf("%d", index_our);
7434 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7435 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
7436 g_free(idx);
7438 if (sipe_strequal("device", categoryName)) {
7439 has_device_publication = TRUE;
7442 if (curVersion) { /* fault exist on this index */
7443 const gchar *container = sipe_xml_attribute(node, "container");
7444 const gchar *instance = sipe_xml_attribute(node, "instance");
7445 /* key is <category><instance><container> */
7446 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7447 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
7449 if (category) {
7450 struct sipe_publication *publication =
7451 g_hash_table_lookup(category, key);
7453 SIPE_DEBUG_INFO("key is %s", key);
7455 if (publication) {
7456 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7457 key, curVersion, publication->version);
7458 /* updating publication's version to the correct one */
7459 publication->version = atoi(curVersion);
7461 } else {
7462 /* We somehow lost this category from our publications... */
7463 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
7464 publication->category = g_strdup(categoryName);
7465 publication->instance = atoi(instance);
7466 publication->container = atoi(container);
7467 publication->version = atoi(curVersion);
7468 category = g_hash_table_new_full(g_str_hash, g_str_equal,
7469 g_free, (GDestroyNotify)free_publication);
7470 g_hash_table_insert(category, g_strdup(key), publication);
7471 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
7472 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
7474 g_free(key);
7477 sipe_xml_free(xml);
7478 g_hash_table_destroy(faults);
7480 /* rebublishing with right versions */
7481 if (has_device_publication) {
7482 send_publish_category_initial(sip);
7483 } else {
7484 send_presence_status(SIP_TO_CORE_PRIVATE, NULL);
7487 return TRUE;
7491 * Returns 'device' XML part for publication.
7492 * Must be g_free'd after use.
7494 static gchar *
7495 sipe_publish_get_category_device(struct sipe_account_data *sip)
7497 gchar *uri;
7498 gchar *doc;
7499 gchar *epid = get_epid(sip);
7500 gchar *uuid = generateUUIDfromEPID(epid);
7501 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7502 /* key is <category><instance><container> */
7503 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7504 struct sipe_publication *publication =
7505 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7507 g_free(key);
7508 g_free(epid);
7510 uri = sip_uri_self(sip);
7511 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7512 device_instance,
7513 publication ? publication->version : 0,
7514 uuid,
7515 uri,
7516 "00:00:00+01:00", /* @TODO make timezone real*/
7517 g_get_host_name()
7520 g_free(uri);
7521 g_free(uuid);
7523 return doc;
7527 * A service method - use
7528 * - send_publish_get_category_state_machine and
7529 * - send_publish_get_category_state_user instead.
7530 * Must be g_free'd after use.
7532 static gchar *
7533 sipe_publish_get_category_state(struct sipe_account_data *sip,
7534 gboolean is_user_state)
7536 int availability = sipe_get_availability_by_status(sip->status, NULL);
7537 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7538 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7539 /* key is <category><instance><container> */
7540 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7541 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7542 struct sipe_publication *publication_2 =
7543 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7544 struct sipe_publication *publication_3 =
7545 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7547 g_free(key_2);
7548 g_free(key_3);
7550 if (publication_2 && (publication_2->availability == availability))
7552 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7553 return NULL; /* nothing to update */
7556 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7557 instance,
7558 publication_2 ? publication_2->version : 0,
7559 availability,
7560 instance,
7561 publication_3 ? publication_3->version : 0,
7562 availability);
7566 * Only Busy and OOF calendar event are published.
7567 * Different instances are used for that.
7569 * Must be g_free'd after use.
7571 static gchar *
7572 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7573 struct sipe_cal_event *event,
7574 const char *uri,
7575 int cal_satus)
7577 gchar *start_time_str;
7578 int availability = 0;
7579 gchar *res;
7580 gchar *tmp = NULL;
7581 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7582 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7583 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7585 /* key is <category><instance><container> */
7586 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7587 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7588 struct sipe_publication *publication_2 =
7589 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7590 struct sipe_publication *publication_3 =
7591 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7593 g_free(key_2);
7594 g_free(key_3);
7596 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7597 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7598 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
7599 return NULL;
7602 if (event &&
7603 publication_3 &&
7604 (publication_3->availability == availability) &&
7605 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7607 g_free(tmp);
7608 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7609 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
7610 return NULL; /* nothing to update */
7612 g_free(tmp);
7614 if (event &&
7615 (event->cal_status == SIPE_CAL_BUSY ||
7616 event->cal_status == SIPE_CAL_OOF))
7618 gchar *availability_xml_str = NULL;
7619 gchar *activity_xml_str = NULL;
7621 if (event->cal_status == SIPE_CAL_BUSY) {
7622 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7625 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7626 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7627 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7628 "minAvailability=\"6500\"",
7629 "maxAvailability=\"8999\"");
7630 } else if (event->cal_status == SIPE_CAL_OOF) {
7631 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7632 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7633 "minAvailability=\"12000\"",
7634 "");
7636 start_time_str = sipe_utils_time_to_str(event->start_time);
7638 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7639 instance,
7640 publication_2 ? publication_2->version : 0,
7641 uri,
7642 start_time_str,
7643 availability_xml_str ? availability_xml_str : "",
7644 activity_xml_str ? activity_xml_str : "",
7645 event->subject ? event->subject : "",
7646 event->location ? event->location : "",
7648 instance,
7649 publication_3 ? publication_3->version : 0,
7650 uri,
7651 start_time_str,
7652 availability_xml_str ? availability_xml_str : "",
7653 activity_xml_str ? activity_xml_str : "",
7654 event->subject ? event->subject : "",
7655 event->location ? event->location : ""
7657 g_free(start_time_str);
7658 g_free(availability_xml_str);
7659 g_free(activity_xml_str);
7662 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7664 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7665 instance,
7666 publication_2 ? publication_2->version : 0,
7668 instance,
7669 publication_3 ? publication_3->version : 0
7673 return res;
7677 * Returns 'machineState' XML part for publication.
7678 * Must be g_free'd after use.
7680 static gchar *
7681 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7683 return sipe_publish_get_category_state(sip, FALSE);
7687 * Returns 'userState' XML part for publication.
7688 * Must be g_free'd after use.
7690 static gchar *
7691 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7693 return sipe_publish_get_category_state(sip, TRUE);
7697 * Returns 'note' XML part for publication.
7698 * Must be g_free'd after use.
7700 * Protocol format for Note is plain text.
7702 * @param note a note in Sipe internal HTML format
7703 * @param note_type either personal or OOF
7705 static gchar *
7706 sipe_publish_get_category_note(struct sipe_account_data *sip,
7707 const char *note, /* html */
7708 const char *note_type,
7709 time_t note_start,
7710 time_t note_end)
7712 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7713 /* key is <category><instance><container> */
7714 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7715 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7716 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7718 struct sipe_publication *publication_note_200 =
7719 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7720 struct sipe_publication *publication_note_300 =
7721 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7722 struct sipe_publication *publication_note_400 =
7723 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7725 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7726 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7727 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7728 char *res, *tmp1, *tmp2, *tmp3;
7729 char *start_time_attr;
7730 char *end_time_attr;
7732 g_free(tmp);
7733 tmp = NULL;
7734 g_free(key_note_200);
7735 g_free(key_note_300);
7736 g_free(key_note_400);
7738 /* we even need to republish empty note */
7739 if (sipe_strequal(n1, n2))
7741 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7742 g_free(n1);
7743 return NULL; /* nothing to update */
7746 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7747 g_free(tmp);
7748 tmp = NULL;
7749 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7750 g_free(tmp);
7752 if (n1) {
7753 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7754 instance,
7755 200,
7756 publication_note_200 ? publication_note_200->version : 0,
7757 note_type,
7758 start_time_attr ? start_time_attr : "",
7759 end_time_attr ? end_time_attr : "",
7760 n1);
7762 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7763 instance,
7764 300,
7765 publication_note_300 ? publication_note_300->version : 0,
7766 note_type,
7767 start_time_attr ? start_time_attr : "",
7768 end_time_attr ? end_time_attr : "",
7769 n1);
7771 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7772 instance,
7773 400,
7774 publication_note_400 ? publication_note_400->version : 0,
7775 note_type,
7776 start_time_attr ? start_time_attr : "",
7777 end_time_attr ? end_time_attr : "",
7778 n1);
7779 } else {
7780 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7781 "note",
7782 instance,
7783 200,
7784 publication_note_200 ? publication_note_200->version : 0,
7785 "static");
7786 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7787 "note",
7788 instance,
7789 300,
7790 publication_note_200 ? publication_note_200->version : 0,
7791 "static");
7792 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7793 "note",
7794 instance,
7795 400,
7796 publication_note_200 ? publication_note_200->version : 0,
7797 "static");
7799 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7801 g_free(start_time_attr);
7802 g_free(end_time_attr);
7803 g_free(tmp1);
7804 g_free(tmp2);
7805 g_free(tmp3);
7806 g_free(n1);
7808 return res;
7812 * Returns 'calendarData' XML part with WorkingHours for publication.
7813 * Must be g_free'd after use.
7815 static gchar *
7816 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7818 struct sipe_ews* ews = sip->ews;
7820 /* key is <category><instance><container> */
7821 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7822 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7823 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7824 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7825 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7826 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7828 struct sipe_publication *publication_cal_1 =
7829 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7830 struct sipe_publication *publication_cal_100 =
7831 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7832 struct sipe_publication *publication_cal_200 =
7833 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7834 struct sipe_publication *publication_cal_300 =
7835 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7836 struct sipe_publication *publication_cal_400 =
7837 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7838 struct sipe_publication *publication_cal_32000 =
7839 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7841 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7842 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7844 g_free(key_cal_1);
7845 g_free(key_cal_100);
7846 g_free(key_cal_200);
7847 g_free(key_cal_300);
7848 g_free(key_cal_400);
7849 g_free(key_cal_32000);
7851 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7852 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7853 return NULL;
7856 if (sipe_strequal(n1, n2))
7858 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7859 return NULL; /* nothing to update */
7862 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7863 /* 1 */
7864 publication_cal_1 ? publication_cal_1->version : 0,
7865 ews->email,
7866 ews->working_hours_xml_str,
7867 /* 100 - Public */
7868 publication_cal_100 ? publication_cal_100->version : 0,
7869 /* 200 - Company */
7870 publication_cal_200 ? publication_cal_200->version : 0,
7871 ews->email,
7872 ews->working_hours_xml_str,
7873 /* 300 - Team */
7874 publication_cal_300 ? publication_cal_300->version : 0,
7875 ews->email,
7876 ews->working_hours_xml_str,
7877 /* 400 - Personal */
7878 publication_cal_400 ? publication_cal_400->version : 0,
7879 ews->email,
7880 ews->working_hours_xml_str,
7881 /* 32000 - Blocked */
7882 publication_cal_32000 ? publication_cal_32000->version : 0
7887 * Returns 'calendarData' XML part with FreeBusy for publication.
7888 * Must be g_free'd after use.
7890 static gchar *
7891 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7893 struct sipe_ews* ews = sip->ews;
7894 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7895 char *fb_start_str;
7896 char *free_busy_base64;
7897 const char *st;
7898 const char *fb;
7899 char *res;
7901 /* key is <category><instance><container> */
7902 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7903 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7904 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7905 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7906 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7907 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7909 struct sipe_publication *publication_cal_1 =
7910 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7911 struct sipe_publication *publication_cal_100 =
7912 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7913 struct sipe_publication *publication_cal_200 =
7914 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7915 struct sipe_publication *publication_cal_300 =
7916 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7917 struct sipe_publication *publication_cal_400 =
7918 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7919 struct sipe_publication *publication_cal_32000 =
7920 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7922 g_free(key_cal_1);
7923 g_free(key_cal_100);
7924 g_free(key_cal_200);
7925 g_free(key_cal_300);
7926 g_free(key_cal_400);
7927 g_free(key_cal_32000);
7929 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7930 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7931 return NULL;
7934 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7935 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7937 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7938 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7940 /* we will rebuplish the same data to refresh publication time,
7941 * so if data from multiple sources, most recent will be choosen
7943 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7945 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7946 // g_free(fb_start_str);
7947 // g_free(free_busy_base64);
7948 // return NULL; /* nothing to update */
7951 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7952 /* 1 */
7953 cal_data_instance,
7954 publication_cal_1 ? publication_cal_1->version : 0,
7955 /* 100 - Public */
7956 cal_data_instance,
7957 publication_cal_100 ? publication_cal_100->version : 0,
7958 /* 200 - Company */
7959 cal_data_instance,
7960 publication_cal_200 ? publication_cal_200->version : 0,
7961 ews->email,
7962 fb_start_str,
7963 free_busy_base64,
7964 /* 300 - Team */
7965 cal_data_instance,
7966 publication_cal_300 ? publication_cal_300->version : 0,
7967 ews->email,
7968 fb_start_str,
7969 free_busy_base64,
7970 /* 400 - Personal */
7971 cal_data_instance,
7972 publication_cal_400 ? publication_cal_400->version : 0,
7973 ews->email,
7974 fb_start_str,
7975 free_busy_base64,
7976 /* 32000 - Blocked */
7977 cal_data_instance,
7978 publication_cal_32000 ? publication_cal_32000->version : 0
7981 g_free(fb_start_str);
7982 g_free(free_busy_base64);
7983 return res;
7986 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7988 gchar *uri;
7989 gchar *doc;
7990 gchar *tmp;
7991 gchar *hdr;
7993 uri = sip_uri_self(sip);
7994 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7995 uri,
7996 publications);
7998 tmp = get_contact(sip);
7999 hdr = g_strdup_printf("Contact: %s\r\n"
8000 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
8002 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
8004 g_free(tmp);
8005 g_free(hdr);
8006 g_free(uri);
8007 g_free(doc);
8010 static void
8011 send_publish_category_initial(struct sipe_account_data *sip)
8013 gchar *pub_device = sipe_publish_get_category_device(sip);
8014 gchar *pub_machine;
8015 gchar *publications;
8017 g_free(sip->status);
8018 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
8020 pub_machine = sipe_publish_get_category_state_machine(sip);
8021 publications = g_strdup_printf("%s%s",
8022 pub_device,
8023 pub_machine ? pub_machine : "");
8024 g_free(pub_device);
8025 g_free(pub_machine);
8027 send_presence_publish(sip, publications);
8028 g_free(publications);
8031 static void
8032 send_presence_category_publish(struct sipe_account_data *sip)
8034 gchar *pub_state = sipe_is_user_state(sip) ?
8035 sipe_publish_get_category_state_user(sip) :
8036 sipe_publish_get_category_state_machine(sip);
8037 gchar *pub_note = sipe_publish_get_category_note(sip,
8038 sip->note,
8039 sip->is_oof_note ? "OOF" : "personal",
8042 gchar *publications;
8044 if (!pub_state && !pub_note) {
8045 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
8046 return;
8049 publications = g_strdup_printf("%s%s",
8050 pub_state ? pub_state : "",
8051 pub_note ? pub_note : "");
8053 g_free(pub_state);
8054 g_free(pub_note);
8056 send_presence_publish(sip, publications);
8057 g_free(publications);
8061 * Publishes self status
8062 * based on own calendar information.
8064 * For 2007+
8066 void
8067 publish_calendar_status_self(struct sipe_core_private *sipe_private,
8068 SIPE_UNUSED_PARAMETER void *unused)
8070 struct sipe_account_data *sip = sipe_private->temporary;
8071 struct sipe_cal_event* event = NULL;
8072 gchar *pub_cal_working_hours = NULL;
8073 gchar *pub_cal_free_busy = NULL;
8074 gchar *pub_calendar = NULL;
8075 gchar *pub_calendar2 = NULL;
8076 gchar *pub_oof_note = NULL;
8077 const gchar *oof_note;
8078 time_t oof_start = 0;
8079 time_t oof_end = 0;
8081 if (!sip->ews) {
8082 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
8083 return;
8086 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
8087 if (sip->ews->cal_events) {
8088 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
8091 if (!event) {
8092 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
8093 } else {
8094 char *desc = sipe_cal_event_describe(event);
8095 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
8096 g_free(desc);
8099 /* Logic
8100 if OOF
8101 OOF publish, Busy clean
8102 ilse if Busy
8103 OOF clean, Busy publish
8104 else
8105 OOF clean, Busy clean
8107 if (event && event->cal_status == SIPE_CAL_OOF) {
8108 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
8109 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8110 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
8111 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8112 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
8113 } else {
8114 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8115 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8118 oof_note = sipe_ews_get_oof_note(sip->ews);
8119 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
8120 oof_start = sip->ews->oof_start;
8121 oof_end = sip->ews->oof_end;
8123 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
8125 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
8126 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
8128 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
8129 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
8130 } else {
8131 gchar *publications = g_strdup_printf("%s%s%s%s%s",
8132 pub_cal_working_hours ? pub_cal_working_hours : "",
8133 pub_cal_free_busy ? pub_cal_free_busy : "",
8134 pub_calendar ? pub_calendar : "",
8135 pub_calendar2 ? pub_calendar2 : "",
8136 pub_oof_note ? pub_oof_note : "");
8138 send_presence_publish(sip, publications);
8139 g_free(publications);
8142 g_free(pub_cal_working_hours);
8143 g_free(pub_cal_free_busy);
8144 g_free(pub_calendar);
8145 g_free(pub_calendar2);
8146 g_free(pub_oof_note);
8148 /* repeat scheduling */
8149 sipe_sched_calendar_status_self_publish(sip, time(NULL));
8152 static void send_presence_status(struct sipe_core_private *sipe_private,
8153 SIPE_UNUSED_PARAMETER void *unused)
8155 struct sipe_account_data *sip = sipe_private->temporary;
8156 PurpleStatus * status = purple_account_get_active_status(sip->account);
8158 if (!status) return;
8160 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
8161 purple_status_get_id(status) ? purple_status_get_id(status) : "",
8162 sipe_is_user_state(sip) ? "USER" : "MACHINE");
8164 if (sip->ocs2007) {
8165 send_presence_category_publish(sip);
8166 } else {
8167 send_presence_soap(sip, FALSE);
8171 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
8173 gboolean found = FALSE;
8174 const char *method = msg->method ? msg->method : "NOT FOUND";
8175 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
8176 if (msg->response == 0) { /* request */
8177 if (sipe_strequal(method, "MESSAGE")) {
8178 process_incoming_message(sip, msg);
8179 found = TRUE;
8180 } else if (sipe_strequal(method, "NOTIFY")) {
8181 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
8182 process_incoming_notify(sip, msg, TRUE, FALSE);
8183 found = TRUE;
8184 } else if (sipe_strequal(method, "BENOTIFY")) {
8185 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
8186 process_incoming_notify(sip, msg, TRUE, TRUE);
8187 found = TRUE;
8188 } else if (sipe_strequal(method, "INVITE")) {
8189 process_incoming_invite(sip, msg);
8190 found = TRUE;
8191 } else if (sipe_strequal(method, "REFER")) {
8192 process_incoming_refer(sip, msg);
8193 found = TRUE;
8194 } else if (sipe_strequal(method, "OPTIONS")) {
8195 process_incoming_options(sip, msg);
8196 found = TRUE;
8197 } else if (sipe_strequal(method, "INFO")) {
8198 process_incoming_info(sip, msg);
8199 found = TRUE;
8200 } else if (sipe_strequal(method, "ACK")) {
8201 // ACK's don't need any response
8202 found = TRUE;
8203 } else if (sipe_strequal(method, "SUBSCRIBE")) {
8204 // LCS 2005 sends us these - just respond 200 OK
8205 found = TRUE;
8206 send_sip_response(sip->gc, msg, 200, "OK", NULL);
8207 } else if (sipe_strequal(method, "BYE")) {
8208 process_incoming_bye(sip, msg);
8209 found = TRUE;
8210 } else {
8211 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
8213 } else { /* response */
8214 struct transaction *trans = transactions_find(sip, msg);
8215 if (trans) {
8216 if (msg->response == 407) {
8217 gchar *resend, *auth;
8218 const gchar *ptmp;
8220 if (sip->proxy.retries > 30) return;
8221 sip->proxy.retries++;
8222 /* do proxy authentication */
8224 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
8226 fill_auth(ptmp, &sip->proxy);
8227 auth = auth_header(sip, &sip->proxy, trans->msg);
8228 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
8229 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
8230 g_free(auth);
8231 resend = sipmsg_to_string(trans->msg);
8232 /* resend request */
8233 sendout_pkt(sip->gc, resend);
8234 g_free(resend);
8235 } else {
8236 if (msg->response < 200) {
8237 /* ignore provisional response */
8238 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
8239 } else {
8240 sip->proxy.retries = 0;
8241 if (sipe_strequal(trans->msg->method, "REGISTER")) {
8242 if (msg->response == 401)
8244 sip->registrar.retries++;
8246 else
8248 sip->registrar.retries = 0;
8250 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
8251 } else {
8252 if (msg->response == 401) {
8253 gchar *resend, *auth, *ptmp;
8254 const char* auth_scheme;
8256 if (sip->registrar.retries > 4) return;
8257 sip->registrar.retries++;
8259 auth_scheme = sipe_get_auth_scheme_name(sip);
8260 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
8262 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
8263 if (!ptmp) {
8264 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
8265 sip->gc->wants_to_die = TRUE;
8266 purple_connection_error(sip->gc, tmp2);
8267 g_free(tmp2);
8268 return;
8271 fill_auth(ptmp, &sip->registrar);
8272 auth = auth_header(sip, &sip->registrar, trans->msg);
8273 sipmsg_remove_header_now(trans->msg, "Authorization");
8274 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
8275 g_free(auth);
8276 resend = sipmsg_to_string(trans->msg);
8277 /* resend request */
8278 sendout_pkt(sip->gc, resend);
8279 g_free(resend);
8283 if (trans->callback) {
8284 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
8285 /* call the callback to process response*/
8286 (trans->callback)(sip, msg, trans);
8289 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
8290 transactions_remove(sip, trans);
8294 found = TRUE;
8295 } else {
8296 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
8299 if (!found) {
8300 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
8304 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
8306 char *cur;
8307 char *dummy;
8308 char *tmp;
8309 struct sipmsg *msg;
8310 int restlen;
8311 cur = conn->inbuf;
8313 /* according to the RFC remove CRLF at the beginning */
8314 while (*cur == '\r' || *cur == '\n') {
8315 cur++;
8317 if (cur != conn->inbuf) {
8318 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
8319 conn->inbufused = strlen(conn->inbuf);
8322 /* Received a full Header? */
8323 sip->processing_input = TRUE;
8324 while (sip->processing_input &&
8325 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
8326 time_t currtime = time(NULL);
8327 cur += 2;
8328 cur[0] = '\0';
8329 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
8330 g_free(tmp);
8331 msg = sipmsg_parse_header(conn->inbuf);
8332 cur[0] = '\r';
8333 cur += 2;
8334 restlen = conn->inbufused - (cur - conn->inbuf);
8335 if (msg && restlen >= msg->bodylen) {
8336 dummy = g_malloc(msg->bodylen + 1);
8337 memcpy(dummy, cur, msg->bodylen);
8338 dummy[msg->bodylen] = '\0';
8339 msg->body = dummy;
8340 cur += msg->bodylen;
8341 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
8342 conn->inbufused = strlen(conn->inbuf);
8343 } else {
8344 if (msg){
8345 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen, msg->bodylen, (int)strlen(conn->inbuf));
8346 sipmsg_free(msg);
8348 return;
8351 /*if (msg->body) {
8352 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8355 // Verify the signature before processing it
8356 if (sip->registrar.gssapi_context) {
8357 struct sipmsg_breakdown msgbd;
8358 gchar *signature_input_str;
8359 gchar *rspauth;
8360 msgbd.msg = msg;
8361 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8362 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8364 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8366 if (rspauth != NULL) {
8367 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8368 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8369 process_input_message(sip, msg);
8370 } else {
8371 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8372 purple_connection_error(sip->gc, _("Invalid message signature received"));
8373 sip->gc->wants_to_die = TRUE;
8375 } else if (msg->response == 401) {
8376 purple_connection_error(sip->gc, _("Authentication failed"));
8377 sip->gc->wants_to_die = TRUE;
8379 g_free(signature_input_str);
8381 g_free(rspauth);
8382 sipmsg_breakdown_free(&msgbd);
8383 } else {
8384 process_input_message(sip, msg);
8387 sipmsg_free(msg);
8391 static void sipe_udp_process(gpointer data, gint source,
8392 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8394 PurpleConnection *gc = data;
8395 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
8396 int len;
8398 static char buffer[65536];
8399 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8400 time_t currtime = time(NULL);
8401 struct sipmsg *msg;
8402 buffer[len] = '\0';
8403 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), buffer);
8404 msg = sipmsg_parse_msg(buffer);
8405 if (msg) process_input_message(sip, msg);
8409 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8411 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
8412 PurpleSslConnection *gsc = sip->gsc;
8414 SIPE_DEBUG_ERROR("%s", debug);
8415 purple_connection_error(gc, msg);
8417 /* Invalidate this connection. Next send will open a new one */
8418 if (gsc) {
8419 connection_remove(sip, gsc->fd);
8420 purple_ssl_close(gsc);
8422 sip->gsc = NULL;
8423 sip->fd = -1;
8426 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8427 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8429 PurpleConnection *gc = data;
8430 struct sipe_account_data *sip;
8431 struct sip_connection *conn;
8432 int readlen, len;
8433 gboolean firstread = TRUE;
8435 /* NOTE: This check *IS* necessary */
8436 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8437 purple_ssl_close(gsc);
8438 return;
8441 sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
8442 conn = connection_find(sip, gsc->fd);
8443 if (conn == NULL) {
8444 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
8445 gc->wants_to_die = TRUE;
8446 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8447 return;
8450 /* Read all available data from the SSL connection */
8451 do {
8452 /* Increase input buffer size as needed */
8453 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8454 conn->inbuflen += SIMPLE_BUF_INC;
8455 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8456 SIPE_DEBUG_INFO("sipe_input_cb_ssl: new input buffer length %d", conn->inbuflen);
8459 /* Try to read as much as there is space left in the buffer */
8460 readlen = conn->inbuflen - conn->inbufused - 1;
8461 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8463 if (len < 0 && errno == EAGAIN) {
8464 /* Try again later */
8465 return;
8466 } else if (len < 0) {
8467 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8468 return;
8469 } else if (firstread && (len == 0)) {
8470 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8471 return;
8474 conn->inbufused += len;
8475 firstread = FALSE;
8477 /* Equivalence indicates that there is possibly more data to read */
8478 } while (len == readlen);
8480 conn->inbuf[conn->inbufused] = '\0';
8481 process_input(sip, conn);
8485 static void sipe_input_cb(gpointer data, gint source,
8486 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8488 PurpleConnection *gc = data;
8489 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
8490 int len;
8491 struct sip_connection *conn = connection_find(sip, source);
8492 if (!conn) {
8493 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found!");
8494 return;
8497 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8498 conn->inbuflen += SIMPLE_BUF_INC;
8499 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8502 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8504 if (len < 0 && errno == EAGAIN)
8505 return;
8506 else if (len <= 0) {
8507 SIPE_DEBUG_INFO_NOFORMAT("sipe_input_cb: read error");
8508 connection_remove(sip, source);
8509 if (sip->fd == source) sip->fd = -1;
8510 return;
8513 conn->inbufused += len;
8514 conn->inbuf[conn->inbufused] = '\0';
8516 process_input(sip, conn);
8519 /* Callback for new connections on incoming TCP port */
8520 static void sipe_newconn_cb(gpointer data, gint source,
8521 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8523 PurpleConnection *gc = data;
8524 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
8525 struct sip_connection *conn;
8527 int newfd = accept(source, NULL, NULL);
8529 conn = connection_create(sip, newfd);
8531 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8534 static void login_cb(gpointer data, gint source,
8535 SIPE_UNUSED_PARAMETER const gchar *error_message)
8537 PurpleConnection *gc = data;
8538 struct sipe_account_data *sip;
8539 struct sip_connection *conn;
8541 if (!PURPLE_CONNECTION_IS_VALID(gc))
8543 if (source >= 0)
8544 close(source);
8545 return;
8548 if (source < 0) {
8549 purple_connection_error(gc, _("Could not connect"));
8550 return;
8553 sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
8554 sip->fd = source;
8555 sip->last_keepalive = time(NULL);
8557 conn = connection_create(sip, source);
8559 do_register(sip);
8561 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8564 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8565 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8567 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8568 if (sip == NULL) return;
8570 do_register(sip);
8573 static guint sipe_ht_hash_nick(const char *nick)
8575 char *lc = g_utf8_strdown(nick, -1);
8576 guint bucket = g_str_hash(lc);
8577 g_free(lc);
8579 return bucket;
8582 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8584 char *nick1_norm = NULL;
8585 char *nick2_norm = NULL;
8586 gboolean equal;
8588 if (nick1 == NULL && nick2 == NULL) return TRUE;
8589 if (nick1 == NULL || nick2 == NULL ||
8590 !g_utf8_validate(nick1, -1, NULL) ||
8591 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
8593 nick1_norm = g_utf8_casefold(nick1, -1);
8594 nick2_norm = g_utf8_casefold(nick2, -1);
8595 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
8596 g_free(nick2_norm);
8597 g_free(nick1_norm);
8599 return equal;
8602 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8604 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8606 sip->listen_data = NULL;
8608 if (listenfd == -1) {
8609 purple_connection_error(sip->gc, _("Could not create listen socket"));
8610 return;
8613 sip->fd = listenfd;
8615 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8616 sip->listenfd = sip->fd;
8618 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8620 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8621 do_register(sip);
8624 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8625 SIPE_UNUSED_PARAMETER const char *error_message)
8627 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8629 sip->query_data = NULL;
8631 if (!hosts || !hosts->data) {
8632 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8633 return;
8636 hosts = g_slist_remove(hosts, hosts->data);
8637 g_free(sip->serveraddr);
8638 sip->serveraddr = hosts->data;
8639 hosts = g_slist_remove(hosts, hosts->data);
8640 while (hosts) {
8641 void *tmp = hosts->data;
8642 hosts = g_slist_remove(hosts, tmp);
8643 hosts = g_slist_remove(hosts, tmp);
8644 g_free(tmp);
8647 /* create socket for incoming connections */
8648 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8649 sipe_udp_host_resolved_listen_cb, sip);
8650 if (sip->listen_data == NULL) {
8651 purple_connection_error(sip->gc, _("Could not create listen socket"));
8652 return;
8656 struct sipe_service_data {
8657 const char *service;
8658 const char *transport;
8659 sipe_transport_type type;
8662 static const struct sipe_service_data *current_service = NULL;
8664 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8665 PurpleSslErrorType error,
8666 gpointer data)
8668 PurpleConnection *gc = data;
8669 struct sipe_account_data *sip;
8671 /* If the connection is already disconnected, we don't need to do anything else */
8672 if (!PURPLE_CONNECTION_IS_VALID(gc))
8673 return;
8675 sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
8676 current_service = sip->service_data;
8677 if (current_service) {
8678 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8679 current_service->transport ? current_service->transport : "NULL",
8680 current_service->service ? current_service->service : "NULL");
8683 sip->fd = -1;
8684 sip->gsc = NULL;
8686 switch(error) {
8687 case PURPLE_SSL_CONNECT_FAILED:
8688 purple_connection_error(gc, _("Connection failed"));
8689 break;
8690 case PURPLE_SSL_HANDSHAKE_FAILED:
8691 purple_connection_error(gc, _("SSL handshake failed"));
8692 break;
8693 case PURPLE_SSL_CERTIFICATE_INVALID:
8694 purple_connection_error(gc, _("SSL certificate invalid"));
8695 break;
8699 static void
8700 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8702 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8703 PurpleProxyConnectData *connect_data;
8705 sip->listen_data = NULL;
8707 sip->listenfd = listenfd;
8708 if (sip->listenfd == -1) {
8709 purple_connection_error(sip->gc, _("Could not create listen socket"));
8710 return;
8713 SIPE_DEBUG_INFO("listenfd: %d", sip->listenfd);
8714 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8715 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8716 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8717 sipe_newconn_cb, sip->gc);
8718 SIPE_DEBUG_INFO("connecting to %s port %d",
8719 sip->realhostname, sip->realport);
8720 /* open tcp connection to the server */
8721 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8722 sip->realport, login_cb, sip->gc);
8724 if (connect_data == NULL) {
8725 purple_connection_error(sip->gc, _("Could not create socket"));
8729 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8731 PurpleAccount *account = sip->account;
8732 PurpleConnection *gc = sip->gc;
8734 if (port == 0) {
8735 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8738 sip->realhostname = hostname;
8739 sip->realport = port;
8741 SIPE_DEBUG_INFO("create_connection - hostname: %s port: %d",
8742 hostname, port);
8744 /* TODO: is there a good default grow size? */
8745 if (sip->transport != SIPE_TRANSPORT_UDP)
8746 sip->txbuf = purple_circ_buffer_new(0);
8748 if (sip->transport == SIPE_TRANSPORT_TLS) {
8749 /* SSL case */
8750 if (!sip->has_ssl) {
8751 gc->wants_to_die = TRUE;
8752 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8753 return;
8756 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
8758 sip->gsc = purple_ssl_connect(account, hostname, port,
8759 login_cb_ssl, sipe_ssl_connect_failure, gc);
8760 if (sip->gsc == NULL) {
8761 purple_connection_error(gc, _("Could not create SSL context"));
8762 return;
8764 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8765 /* UDP case */
8766 SIPE_DEBUG_INFO_NOFORMAT("using UDP");
8768 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8769 if (sip->query_data == NULL) {
8770 purple_connection_error(gc, _("Could not resolve hostname"));
8772 } else {
8773 /* TCP case */
8774 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
8775 /* create socket for incoming connections */
8776 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8777 sipe_tcp_connect_listen_cb, sip);
8778 if (sip->listen_data == NULL) {
8779 purple_connection_error(gc, _("Could not create listen socket"));
8780 return;
8785 /* Service list for autodection */
8786 static const struct sipe_service_data service_autodetect[] = {
8787 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8788 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8789 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8790 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8791 { NULL, NULL, 0 }
8794 /* Service list for SSL/TLS */
8795 static const struct sipe_service_data service_tls[] = {
8796 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8797 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8798 { NULL, NULL, 0 }
8801 /* Service list for TCP */
8802 static const struct sipe_service_data service_tcp[] = {
8803 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8804 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8805 { NULL, NULL, 0 }
8808 /* Service list for UDP */
8809 static const struct sipe_service_data service_udp[] = {
8810 { "sip", "udp", SIPE_TRANSPORT_UDP },
8811 { NULL, NULL, 0 }
8814 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8815 static void resolve_next_service(struct sipe_account_data *sip,
8816 const struct sipe_service_data *start)
8818 if (start) {
8819 sip->service_data = start;
8820 } else {
8821 sip->service_data++;
8822 if (sip->service_data->service == NULL) {
8823 gchar *hostname;
8824 /* Try connecting to the SIP hostname directly */
8825 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8826 if (sip->auto_transport) {
8827 // If SSL is supported, default to using it; OCS servers aren't configured
8828 // by default to accept TCP
8829 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8830 sip->transport = sip->has_ssl ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8831 SIPE_DEBUG_INFO_NOFORMAT("set transport type..");
8834 hostname = g_strdup(SIP_TO_CORE_PUBLIC->sip_domain);
8835 create_connection(sip, hostname, 0);
8836 return;
8840 /* Try to resolve next service */
8841 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8842 sip->service_data->transport,
8843 SIP_TO_CORE_PUBLIC->sip_domain,
8844 srvresolved, sip);
8847 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8849 struct sipe_account_data *sip = data;
8851 sip->srv_query_data = NULL;
8853 /* find the host to connect to */
8854 if (results) {
8855 gchar *hostname = g_strdup(resp->hostname);
8856 int port = resp->port;
8857 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8858 hostname, port);
8859 g_free(resp);
8861 sip->transport = sip->service_data->type;
8863 create_connection(sip, hostname, port);
8864 } else {
8865 resolve_next_service(sip, NULL);
8869 /* temporary function */
8870 void sipe_purple_setup(struct sipe_core_public *sipe_public,
8871 PurpleConnection *gc,
8872 PurpleAccount *account)
8874 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
8875 sip->gc = gc;
8876 sip->account = account;
8879 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
8880 const gchar *login_domain,
8881 const gchar *login_account,
8882 const gchar *password,
8883 const gchar *email,
8884 const gchar **errmsg)
8886 struct sipe_core_private *sipe_private;
8887 struct sipe_account_data *sip;
8888 gchar **user_domain;
8890 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
8892 /* ensure that sign-in name doesn't contain invalid characters */
8893 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
8894 *errmsg = _("SIP Exchange user name contains invalid characters");
8895 return NULL;
8898 /* ensure that sign-in name format is name@domain */
8899 if (!strchr(signin_name, '@') ||
8900 g_str_has_prefix(signin_name, "@") ||
8901 g_str_has_suffix(signin_name, "@")) {
8902 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
8903 return NULL;
8906 /* ensure that email format is name@domain (if provided) */
8907 if (!is_empty(email) &&
8908 (!strchr(email, '@') ||
8909 g_str_has_prefix(email, "@") ||
8910 g_str_has_suffix(email, "@")))
8912 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
8913 return NULL;
8916 /* ensure that user name doesn't contain spaces */
8917 user_domain = g_strsplit(signin_name, "@", 2);
8918 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
8919 if (strchr(user_domain[0], ' ') != NULL) {
8920 g_strfreev(user_domain);
8921 *errmsg = _("SIP Exchange user name contains whitespace");
8922 return NULL;
8925 sipe_private = g_new0(struct sipe_core_private, 1);
8926 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
8927 sip->public = (struct sipe_core_public *)sipe_private;
8928 sip->private = sipe_private;
8929 sip->reregister_set = FALSE;
8930 sip->reauthenticate_set = FALSE;
8931 sip->subscribed = FALSE;
8932 sip->subscribed_buddies = FALSE;
8933 sip->initial_state_published = FALSE;
8934 sip->username = g_strdup(signin_name);
8935 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
8936 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
8937 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
8938 sip->password = g_strdup(password);
8939 sipe_private->public.sip_name = g_strdup(user_domain[0]);
8940 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
8941 g_strfreev(user_domain);
8943 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8944 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8945 g_free, (GDestroyNotify)g_hash_table_destroy);
8946 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8947 g_free, (GDestroyNotify)sipe_subscription_free);
8948 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8949 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8951 return((struct sipe_core_public *)sipe_private);
8954 void sipe_core_connect(struct sipe_core_public *sipe_public,
8955 sipe_transport_type transport,
8956 const gchar *server,
8957 const gchar *port,
8958 gboolean has_ssl)
8960 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
8962 sip->auto_transport = FALSE;
8963 sip->has_ssl = has_ssl;
8964 if (server) {
8965 /* Use user specified server[:port] */
8966 int port_number = 0;
8968 if (port)
8969 port_number = atoi(port);
8971 SIPE_DEBUG_INFO("sipe_core_connect: user specified SIP server %s:%d",
8972 server, port_number);
8974 sip->transport = transport;
8975 create_connection(sip, g_strdup(server), port_number);
8976 } else {
8977 /* Server auto-discovery */
8978 switch (transport) {
8979 case SIPE_TRANSPORT_AUTO:
8980 sip->auto_transport = TRUE;
8981 if (current_service &&
8982 current_service->transport != NULL &&
8983 current_service->service != NULL) {
8984 current_service++;
8985 resolve_next_service(sip, current_service);
8986 } else {
8987 resolve_next_service(sip, has_ssl ? service_autodetect : service_tcp);
8989 break;
8990 case SIPE_TRANSPORT_TLS:
8991 resolve_next_service(sip, service_tls);
8992 break;
8993 case SIPE_TRANSPORT_TCP:
8994 resolve_next_service(sip, service_tcp);
8995 break;
8996 case SIPE_TRANSPORT_UDP:
8997 resolve_next_service(sip, service_udp);
8998 break;
9003 static void sipe_connection_cleanup(struct sipe_account_data *sip)
9005 struct sipe_core_private *sipe_private = SIP_TO_CORE_PRIVATE;
9007 connection_free_all(sip);
9009 g_free(sip->epid);
9010 sip->epid = NULL;
9012 if (sip->query_data != NULL)
9013 purple_dnsquery_destroy(sip->query_data);
9014 sip->query_data = NULL;
9016 if (sip->srv_query_data != NULL)
9017 purple_srv_cancel(sip->srv_query_data);
9018 sip->srv_query_data = NULL;
9020 if (sip->listen_data != NULL)
9021 purple_network_listen_cancel(sip->listen_data);
9022 sip->listen_data = NULL;
9024 if (sip->gsc != NULL)
9025 purple_ssl_close(sip->gsc);
9026 sip->gsc = NULL;
9028 sipe_auth_free(&sip->registrar);
9029 sipe_auth_free(&sip->proxy);
9031 if (sip->txbuf)
9032 purple_circ_buffer_destroy(sip->txbuf);
9033 sip->txbuf = NULL;
9035 g_free(sip->realhostname);
9036 sip->realhostname = NULL;
9038 g_free(sip->server_version);
9039 sip->server_version = NULL;
9041 if (sip->listenpa)
9042 purple_input_remove(sip->listenpa);
9043 sip->listenpa = 0;
9044 if (sip->tx_handler)
9045 purple_input_remove(sip->tx_handler);
9046 sip->tx_handler = 0;
9047 if (sip->resendtimeout)
9048 purple_timeout_remove(sip->resendtimeout);
9049 sip->resendtimeout = 0;
9050 if (sipe_private->timeouts) {
9051 GSList *entry = sipe_private->timeouts;
9052 while (entry) {
9053 struct scheduled_action *sched_action = entry->data;
9054 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
9055 purple_timeout_remove(sched_action->timeout_handler);
9056 if (sched_action->destroy) {
9057 (*sched_action->destroy)(sched_action->payload);
9059 g_free(sched_action->name);
9060 g_free(sched_action);
9061 entry = entry->next;
9064 g_slist_free(sipe_private->timeouts);
9066 if (sip->allow_events) {
9067 GSList *entry = sip->allow_events;
9068 while (entry) {
9069 g_free(entry->data);
9070 entry = entry->next;
9073 g_slist_free(sip->allow_events);
9075 if (sip->containers) {
9076 GSList *entry = sip->containers;
9077 while (entry) {
9078 free_container((struct sipe_container *)entry->data);
9079 entry = entry->next;
9082 g_slist_free(sip->containers);
9084 if (sip->contact)
9085 g_free(sip->contact);
9086 sip->contact = NULL;
9087 if (sip->regcallid)
9088 g_free(sip->regcallid);
9089 sip->regcallid = NULL;
9091 if (sip->serveraddr)
9092 g_free(sip->serveraddr);
9093 sip->serveraddr = NULL;
9095 if (sip->focus_factory_uri)
9096 g_free(sip->focus_factory_uri);
9097 sip->focus_factory_uri = NULL;
9099 sip->fd = -1;
9100 sip->processing_input = FALSE;
9102 if (sip->ews) {
9103 sipe_ews_free(sip->ews);
9105 sip->ews = NULL;
9109 * A callback for g_hash_table_foreach_remove
9111 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
9112 SIPE_UNUSED_PARAMETER gpointer user_data)
9114 sipe_free_buddy((struct sipe_buddy *) buddy);
9116 /* We must return TRUE as the key/value have already been deleted */
9117 return(TRUE);
9120 void sipe_close(PurpleConnection *gc)
9122 struct sipe_core_private *sipe_private = gc->proto_data;
9124 if (sipe_private) {
9125 struct sipe_account_data *sip = sipe_private->temporary;
9127 /* leave all conversations */
9128 sipe_session_close_all(sip);
9129 sipe_session_remove_all(sip);
9131 if (sip->csta) {
9132 sip_csta_close(sip);
9135 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
9136 /* unsubscribe all */
9137 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
9139 /* unregister */
9140 do_register_exp(sip, 0);
9143 sipe_connection_cleanup(sip);
9144 g_free(sipe_private->public.sip_name);
9145 g_free(sipe_private->public.sip_domain);
9146 g_free(sip->username);
9147 g_free(sip->email);
9148 g_free(sip->password);
9149 g_free(sip->authdomain);
9150 g_free(sip->authuser);
9151 g_free(sip->status);
9152 g_free(sip->note);
9153 g_free(sip->user_states);
9155 g_hash_table_foreach_steal(SIP_TO_CORE_PRIVATE->buddies, sipe_buddy_remove, NULL);
9156 g_hash_table_destroy(SIP_TO_CORE_PRIVATE->buddies);
9157 g_hash_table_destroy(sip->our_publications);
9158 g_hash_table_destroy(sip->user_state_publications);
9159 g_hash_table_destroy(sip->subscriptions);
9160 g_hash_table_destroy(sip->filetransfers);
9162 if (sip->groups) {
9163 GSList *entry = sip->groups;
9164 while (entry) {
9165 struct sipe_group *group = entry->data;
9166 g_free(group->name);
9167 g_free(group);
9168 entry = entry->next;
9171 g_slist_free(sip->groups);
9173 if (sip->our_publication_keys) {
9174 GSList *entry = sip->our_publication_keys;
9175 while (entry) {
9176 g_free(entry->data);
9177 entry = entry->next;
9180 g_slist_free(sip->our_publication_keys);
9182 while (sip->transactions)
9183 transactions_remove(sip, sip->transactions->data);
9184 g_free(sip);
9186 g_free(gc->proto_data);
9187 gc->proto_data = NULL;
9190 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
9191 SIPE_UNUSED_PARAMETER void *user_data)
9193 PurpleAccount *acct = purple_connection_get_account(gc);
9194 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
9195 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
9196 if (conv == NULL)
9197 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
9198 purple_conversation_present(conv);
9199 g_free(id);
9202 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
9203 SIPE_UNUSED_PARAMETER void *user_data)
9206 purple_blist_request_add_buddy(purple_connection_get_account(gc),
9207 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
9210 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
9211 SIPE_UNUSED_PARAMETER struct transaction *trans)
9213 PurpleNotifySearchResults *results;
9214 PurpleNotifySearchColumn *column;
9215 sipe_xml *searchResults;
9216 const sipe_xml *mrow;
9217 int match_count = 0;
9218 gboolean more = FALSE;
9219 gchar *secondary;
9221 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
9223 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
9224 if (!searchResults) {
9225 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
9226 return FALSE;
9229 results = purple_notify_searchresults_new();
9231 if (results == NULL) {
9232 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
9233 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
9235 sipe_xml_free(searchResults);
9236 return FALSE;
9239 column = purple_notify_searchresults_column_new(_("User name"));
9240 purple_notify_searchresults_column_add(results, column);
9242 column = purple_notify_searchresults_column_new(_("Name"));
9243 purple_notify_searchresults_column_add(results, column);
9245 column = purple_notify_searchresults_column_new(_("Company"));
9246 purple_notify_searchresults_column_add(results, column);
9248 column = purple_notify_searchresults_column_new(_("Country"));
9249 purple_notify_searchresults_column_add(results, column);
9251 column = purple_notify_searchresults_column_new(_("Email"));
9252 purple_notify_searchresults_column_add(results, column);
9254 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
9255 GList *row = NULL;
9257 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
9258 row = g_list_append(row, g_strdup(uri_parts[1]));
9259 g_strfreev(uri_parts);
9261 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
9262 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
9263 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
9264 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
9266 purple_notify_searchresults_row_add(results, row);
9267 match_count++;
9270 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
9271 char *data = sipe_xml_data(mrow);
9272 more = (g_strcasecmp(data, "true") == 0);
9273 g_free(data);
9276 secondary = g_strdup_printf(
9277 dngettext(PACKAGE_NAME,
9278 "Found %d contact%s:",
9279 "Found %d contacts%s:", match_count),
9280 match_count, more ? _(" (more matched your query)") : "");
9282 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
9283 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
9284 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
9286 g_free(secondary);
9287 sipe_xml_free(searchResults);
9288 return TRUE;
9291 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
9293 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
9294 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
9295 unsigned i = 0;
9297 if (!attrs) return;
9299 do {
9300 PurpleRequestField *field = entries->data;
9301 const char *id = purple_request_field_get_id(field);
9302 const char *value = purple_request_field_string_get_value(field);
9304 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
9306 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
9307 } while ((entries = g_list_next(entries)) != NULL);
9308 attrs[i] = NULL;
9310 if (i > 0) {
9311 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
9312 gchar *domain_uri = sip_uri_from_name(SIP_TO_CORE_PUBLIC->sip_domain);
9313 gchar *query = g_strjoinv(NULL, attrs);
9314 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
9315 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
9316 send_soap_request_with_cb(sip, domain_uri, body,
9317 (TransCallback) process_search_contact_response, NULL);
9318 g_free(domain_uri);
9319 g_free(body);
9320 g_free(query);
9323 g_strfreev(attrs);
9326 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9327 gpointer value,
9328 GString* str)
9330 struct sipe_publication *publication = value;
9332 g_string_append_printf( str,
9333 SIPE_PUB_XML_PUBLICATION_CLEAR,
9334 publication->category,
9335 publication->instance,
9336 publication->container,
9337 publication->version,
9338 "static");
9341 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
9343 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
9344 if (sip->ocs2007) /* 2007+ */
9346 GString* str = g_string_new(NULL);
9347 gchar *publications;
9349 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9350 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
9351 return;
9354 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9355 publications = g_string_free(str, FALSE);
9357 send_presence_publish(sip, publications);
9358 g_free(publications);
9360 else /* 2005 */
9362 send_presence_soap0(sip, FALSE, TRUE);
9366 /** for Access levels menu */
9367 #define INDENT_FMT " %s"
9369 /** Member is directly placed to access level container.
9370 * For example SIP URI of user is in the container.
9372 #define INDENT_MARKED_FMT "* %s"
9374 /** Member is indirectly belong to access level container.
9375 * For example 'sameEnterprise' is in the container and user
9376 * belongs to that same enterprise.
9378 #define INDENT_MARKED_INHERITED_FMT "= %s"
9380 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
9381 const gchar *name,
9382 const gchar *status_name,
9383 gboolean is_online)
9385 gchar *note = NULL;
9386 gboolean is_oof_note = FALSE;
9387 gchar *activity = NULL;
9388 gchar *calendar = NULL;
9389 gchar *meeting_subject = NULL;
9390 gchar *meeting_location = NULL;
9391 gchar *access_text = NULL;
9392 GSList *info = NULL;
9394 #define SIPE_ADD_BUDDY_INFO(l, t) \
9396 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
9397 sbi->label = (l); \
9398 sbi->text = (t); \
9399 info = g_slist_append(info, sbi); \
9402 if (sipe_public) { //happens on pidgin exit
9403 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
9404 struct sipe_buddy *sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, name);
9405 if (sbuddy) {
9406 note = sbuddy->note;
9407 is_oof_note = sbuddy->is_oof_note;
9408 activity = sbuddy->activity;
9409 calendar = sipe_cal_get_description(sbuddy);
9410 meeting_subject = sbuddy->meeting_subject;
9411 meeting_location = sbuddy->meeting_location;
9413 if (sip && sip->ocs2007) {
9414 gboolean is_group_access = FALSE;
9415 const int container_id = sipe_find_access_level(sip, "user", sipe_get_no_sip_uri(name), &is_group_access);
9416 const char *access_level = sipe_get_access_level_name(container_id);
9417 access_text = is_group_access ?
9418 g_strdup(access_level) :
9419 g_strdup_printf(INDENT_MARKED_FMT, access_level);
9423 //Layout
9424 if (is_online)
9426 gchar *status_str = g_strdup(activity ? activity : status_name);
9428 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
9430 if (is_online && !is_empty(calendar))
9432 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
9433 calendar = NULL;
9435 g_free(calendar);
9436 if (!is_empty(meeting_location))
9438 SIPE_ADD_BUDDY_INFO(_("Meeting in"), g_strdup(meeting_location));
9440 if (!is_empty(meeting_subject))
9442 SIPE_ADD_BUDDY_INFO(_("Meeting about"), g_strdup(meeting_subject));
9444 if (note)
9446 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
9447 SIPE_ADD_BUDDY_INFO(is_oof_note ? _("Out of office note") : _("Note"),
9448 g_strdup_printf("<i>%s</i>", note));
9450 if (access_text) {
9451 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
9454 return(info);
9457 static PurpleBuddy *
9458 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9460 PurpleBuddy *clone;
9461 const gchar *server_alias, *email;
9462 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9464 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9466 purple_blist_add_buddy(clone, NULL, group, NULL);
9468 server_alias = purple_buddy_get_server_alias(buddy);
9469 if (server_alias) {
9470 purple_blist_server_alias_buddy(clone, server_alias);
9473 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9474 if (email) {
9475 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9478 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9479 //for UI to update;
9480 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9481 return clone;
9484 static void
9485 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9487 PurpleBuddy *buddy, *b;
9488 PurpleConnection *gc;
9489 PurpleGroup * group = purple_find_group(group_name);
9491 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9493 buddy = (PurpleBuddy *)node;
9495 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
9496 gc = purple_account_get_connection(buddy->account);
9498 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9499 if (!b){
9500 purple_blist_add_buddy_clone(group, buddy);
9503 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9506 static void
9507 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9509 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9511 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
9513 /* 2007+ conference */
9514 if (sip->ocs2007)
9516 sipe_conf_add(sip, buddy->name);
9518 else /* 2005- multiparty chat */
9520 gchar *self = sip_uri_self(sip);
9521 struct sip_session *session;
9523 session = sipe_session_add_chat(sip);
9524 session->chat_title = sipe_chat_get_name(session->callid);
9525 session->roster_manager = g_strdup(self);
9527 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9528 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9529 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9530 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9532 g_free(self);
9536 static gboolean
9537 sipe_is_election_finished(struct sip_session *session)
9539 gboolean res = TRUE;
9541 SIPE_DIALOG_FOREACH {
9542 if (dialog->election_vote == 0) {
9543 res = FALSE;
9544 break;
9546 } SIPE_DIALOG_FOREACH_END;
9548 if (res) {
9549 session->is_voting_in_progress = FALSE;
9551 return res;
9554 static void
9555 sipe_election_start(struct sipe_account_data *sip,
9556 struct sip_session *session)
9558 int election_timeout;
9560 if (session->is_voting_in_progress) {
9561 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
9562 return;
9563 } else {
9564 session->is_voting_in_progress = TRUE;
9566 session->bid = rand();
9568 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
9570 SIPE_DIALOG_FOREACH {
9571 /* reset election_vote for each chat participant */
9572 dialog->election_vote = 0;
9574 /* send RequestRM to each chat participant*/
9575 sipe_send_election_request_rm(sip, dialog, session->bid);
9576 } SIPE_DIALOG_FOREACH_END;
9578 election_timeout = 15; /* sec */
9579 sipe_schedule_action("<+election-result>",
9580 election_timeout,
9581 sipe_election_result,
9582 NULL,
9583 SIP_TO_CORE_PRIVATE,
9584 session);
9588 * @param who a URI to whom to invite to chat
9590 void
9591 sipe_invite_to_chat(struct sipe_account_data *sip,
9592 struct sip_session *session,
9593 const gchar *who)
9595 /* a conference */
9596 if (session->focus_uri)
9598 sipe_invite_conf(sip, session, who);
9600 else /* a multi-party chat */
9602 gchar *self = sip_uri_self(sip);
9603 if (session->roster_manager) {
9604 if (sipe_strcase_equal(session->roster_manager, self)) {
9605 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9606 } else {
9607 sipe_refer(sip, session, who);
9609 } else {
9610 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
9612 session->pending_invite_queue = slist_insert_unique_sorted(
9613 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9615 sipe_election_start(sip, session);
9617 g_free(self);
9621 void
9622 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9623 struct sip_session *session)
9625 gchar *invitee;
9626 GSList *entry = session->pending_invite_queue;
9628 while (entry) {
9629 invitee = entry->data;
9630 sipe_invite_to_chat(sip, session, invitee);
9631 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9632 g_free(invitee);
9636 static void
9637 sipe_election_result(struct sipe_core_private *sipe_private,
9638 void *sess)
9640 struct sipe_account_data *sip = sipe_private->temporary;
9641 struct sip_session *session = (struct sip_session *)sess;
9642 gchar *rival;
9643 gboolean has_won = TRUE;
9645 if (session->roster_manager) {
9646 SIPE_DEBUG_INFO(
9647 "sipe_election_result: RM has already been elected in the meantime. It is %s",
9648 session->roster_manager);
9649 return;
9652 session->is_voting_in_progress = FALSE;
9654 SIPE_DIALOG_FOREACH {
9655 if (dialog->election_vote < 0) {
9656 has_won = FALSE;
9657 rival = dialog->with;
9658 break;
9660 } SIPE_DIALOG_FOREACH_END;
9662 if (has_won) {
9663 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
9665 session->roster_manager = sip_uri_self(sip);
9667 SIPE_DIALOG_FOREACH {
9668 /* send SetRM to each chat participant*/
9669 sipe_send_election_set_rm(sip, dialog);
9670 } SIPE_DIALOG_FOREACH_END;
9671 } else {
9672 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
9674 session->bid = 0;
9676 sipe_process_pending_invite_queue(sip, session);
9680 * For 2007+ conference only.
9682 static void
9683 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9685 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9686 struct sip_session *session;
9688 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
9689 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
9691 session = sipe_session_find_chat_by_title(sip, chat_title);
9693 sipe_conf_modify_user_role(sip, session, buddy->name);
9697 * For 2007+ conference only.
9699 static void
9700 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9702 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9703 struct sip_session *session;
9705 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
9706 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
9708 session = sipe_session_find_chat_by_title(sip, chat_title);
9710 sipe_conf_delete_user(sip, session, buddy->name);
9713 static void
9714 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9716 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9717 struct sip_session *session;
9719 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
9720 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
9722 session = sipe_session_find_chat_by_title(sip, chat_title);
9724 sipe_invite_to_chat(sip, session, buddy->name);
9727 static void
9728 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9730 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9732 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
9733 if (phone) {
9734 char *tel_uri = sip_to_tel_uri(phone);
9736 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
9737 sip_csta_make_call(sip, tel_uri);
9739 g_free(tel_uri);
9743 static void
9744 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
9746 /** Translators: replace with URL to localized page
9747 * If it doesn't exist copy the original URL */
9748 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
9751 static void
9752 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9754 const gchar *email;
9755 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
9757 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9758 if (email)
9760 char *command_line = g_strdup_printf(
9761 #ifdef _WIN32
9762 "cmd /c start"
9763 #else
9764 "xdg-email"
9765 #endif
9766 " mailto:%s", email);
9767 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
9769 g_spawn_command_line_async(command_line, NULL);
9770 g_free(command_line);
9772 else
9774 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
9778 static void
9779 sipe_buddy_menu_access_level_cb(SIPE_UNUSED_PARAMETER PurpleBuddy *buddy,
9780 struct sipe_container *container)
9782 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9783 struct sipe_container_member *member;
9785 if (!container || !container->members) return;
9787 member = ((struct sipe_container_member *)container->members->data);
9789 if (!member->type) return;
9791 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
9792 container->id, member->type, member->value ? member->value : "");
9794 sipe_change_access_level(sip, container->id, member->type, member->value);
9797 static GList *
9798 sipe_get_access_control_menu(struct sipe_account_data *sip,
9799 const char* uri);
9802 * A menu which appear when right-clicking on buddy in contact list.
9804 GList *
9805 sipe_buddy_menu(PurpleBuddy *buddy)
9807 PurpleBlistNode *g_node;
9808 PurpleGroup *group, *gr_parent;
9809 PurpleMenuAction *act;
9810 GList *menu = NULL;
9811 GList *menu_groups = NULL;
9812 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9813 const char *email;
9814 const char *phone;
9815 const char *phone_disp_str;
9816 gchar *self = sip_uri_self(sip);
9818 SIPE_SESSION_FOREACH {
9819 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9821 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9823 PurpleConvChatBuddyFlags flags;
9824 PurpleConvChatBuddyFlags flags_us;
9826 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9827 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9828 if (session->focus_uri
9829 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9830 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9832 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9833 act = purple_menu_action_new(label,
9834 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9835 session->chat_title, NULL);
9836 g_free(label);
9837 menu = g_list_prepend(menu, act);
9840 if (session->focus_uri
9841 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9843 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9844 act = purple_menu_action_new(label,
9845 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9846 session->chat_title, NULL);
9847 g_free(label);
9848 menu = g_list_prepend(menu, act);
9851 else
9853 if (!session->focus_uri
9854 || (session->focus_uri && !session->locked))
9856 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9857 act = purple_menu_action_new(label,
9858 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9859 session->chat_title, NULL);
9860 g_free(label);
9861 menu = g_list_prepend(menu, act);
9865 } SIPE_SESSION_FOREACH_END;
9867 act = purple_menu_action_new(_("New chat"),
9868 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9869 NULL, NULL);
9870 menu = g_list_prepend(menu, act);
9872 if (sip->csta && !sip->csta->line_status) {
9873 gchar *tmp = NULL;
9874 /* work phone */
9875 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9876 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9877 if (phone) {
9878 gchar *label = g_strdup_printf(_("Work %s"),
9879 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9880 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9881 g_free(tmp);
9882 tmp = NULL;
9883 g_free(label);
9884 menu = g_list_prepend(menu, act);
9887 /* mobile phone */
9888 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9889 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9890 if (phone) {
9891 gchar *label = g_strdup_printf(_("Mobile %s"),
9892 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9893 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9894 g_free(tmp);
9895 tmp = NULL;
9896 g_free(label);
9897 menu = g_list_prepend(menu, act);
9900 /* home phone */
9901 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9902 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9903 if (phone) {
9904 gchar *label = g_strdup_printf(_("Home %s"),
9905 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9906 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9907 g_free(tmp);
9908 tmp = NULL;
9909 g_free(label);
9910 menu = g_list_prepend(menu, act);
9913 /* other phone */
9914 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9915 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9916 if (phone) {
9917 gchar *label = g_strdup_printf(_("Other %s"),
9918 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9919 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9920 g_free(tmp);
9921 tmp = NULL;
9922 g_free(label);
9923 menu = g_list_prepend(menu, act);
9926 /* custom1 phone */
9927 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9928 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9929 if (phone) {
9930 gchar *label = g_strdup_printf(_("Custom1 %s"),
9931 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9932 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9933 g_free(tmp);
9934 tmp = NULL;
9935 g_free(label);
9936 menu = g_list_prepend(menu, act);
9940 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9941 if (email) {
9942 act = purple_menu_action_new(_("Send email..."),
9943 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9944 NULL, NULL);
9945 menu = g_list_prepend(menu, act);
9948 /* Access Level */
9949 if (sip->ocs2007) {
9950 GList *menu_access_levels = sipe_get_access_control_menu(sip, buddy->name);
9952 act = purple_menu_action_new(_("Access level"),
9953 NULL,
9954 NULL, menu_access_levels);
9955 menu = g_list_prepend(menu, act);
9958 /* Copy to */
9959 gr_parent = purple_buddy_get_group(buddy);
9960 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9961 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9962 continue;
9964 group = (PurpleGroup *)g_node;
9965 if (group == gr_parent)
9966 continue;
9968 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9969 continue;
9971 act = purple_menu_action_new(purple_group_get_name(group),
9972 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9973 group->name, NULL);
9974 menu_groups = g_list_prepend(menu_groups, act);
9976 menu_groups = g_list_reverse(menu_groups);
9978 act = purple_menu_action_new(_("Copy to"),
9979 NULL,
9980 NULL, menu_groups);
9981 menu = g_list_prepend(menu, act);
9983 menu = g_list_reverse(menu);
9985 g_free(self);
9986 return menu;
9989 static void
9990 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
9992 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
9993 const char *domain = purple_request_fields_get_string(fields, "access_domain");
9994 int index = purple_request_fields_get_choice(fields, "container_id");
9995 /* move Blocked first */
9996 int i = (index == 4) ? 0 : index + 1;
9997 int container_id = containers[i];
9999 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
10001 sipe_change_access_level(sip, container_id, "domain", domain);
10004 static void
10005 sipe_ask_access_domain(struct sipe_account_data *sip)
10007 PurpleAccount *account = sip->account;
10008 PurpleConnection *gc = sip->gc;
10009 PurpleRequestFields *fields;
10010 PurpleRequestFieldGroup *g;
10011 PurpleRequestField *f;
10013 fields = purple_request_fields_new();
10015 g = purple_request_field_group_new(NULL);
10016 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
10017 purple_request_field_set_required(f, TRUE);
10018 purple_request_field_group_add_field(g, f);
10020 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
10021 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
10022 purple_request_field_choice_add(f, _("Team"));
10023 purple_request_field_choice_add(f, _("Company"));
10024 purple_request_field_choice_add(f, _("Public"));
10025 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
10026 purple_request_field_choice_set_default_value(f, 3); /* index */
10027 purple_request_field_set_required(f, TRUE);
10028 purple_request_field_group_add_field(g, f);
10030 purple_request_fields_add_group(fields, g);
10032 purple_request_fields(gc, _("Add new domain"),
10033 _("Add new domain"), NULL, fields,
10034 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
10035 _("Cancel"), NULL,
10036 account, NULL, NULL, gc);
10039 static void
10040 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
10042 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA);
10045 static GList *
10046 sipe_get_access_levels_menu(struct sipe_account_data *sip,
10047 const char* member_type,
10048 const char* member_value,
10049 const gboolean extra_menu)
10051 GList *menu_access_levels = NULL;
10052 unsigned int i;
10053 char *menu_name;
10054 PurpleMenuAction *act;
10055 struct sipe_container *container;
10056 struct sipe_container_member *member;
10057 gboolean is_group_access = FALSE;
10058 int container_id = sipe_find_access_level(sip, member_type, member_value, &is_group_access);
10060 for (i = 1; i <= CONTAINERS_LEN; i++) {
10061 /* to put Blocked level last in menu list.
10062 * Blocked should remaim in the first place in the containers[] array.
10064 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
10065 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
10067 container = g_new0(struct sipe_container, 1);
10068 member = g_new0(struct sipe_container_member, 1);
10069 container->id = containers[j];
10070 container->members = g_slist_append(container->members, member);
10071 member->type = g_strdup(member_type);
10072 member->value = g_strdup(member_value);
10074 /* current container/access level */
10075 if (((int)containers[j]) == container_id) {
10076 menu_name = is_group_access ?
10077 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
10078 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
10079 } else {
10080 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
10083 act = purple_menu_action_new(menu_name,
10084 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
10085 container, NULL);
10086 g_free(menu_name);
10087 menu_access_levels = g_list_prepend(menu_access_levels, act);
10090 if (extra_menu && (container_id >= 0)) {
10091 /* separator */
10092 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
10093 menu_access_levels = g_list_prepend(menu_access_levels, act);
10095 if (!is_group_access) {
10096 container = g_new0(struct sipe_container, 1);
10097 member = g_new0(struct sipe_container_member, 1);
10098 container->id = -1;
10099 container->members = g_slist_append(container->members, member);
10100 member->type = g_strdup(member_type);
10101 member->value = g_strdup(member_value);
10103 /* Translators: remove (clear) previously assigned access level */
10104 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
10105 act = purple_menu_action_new(menu_name,
10106 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
10107 container, NULL);
10108 g_free(menu_name);
10109 menu_access_levels = g_list_prepend(menu_access_levels, act);
10113 menu_access_levels = g_list_reverse(menu_access_levels);
10114 return menu_access_levels;
10117 static GList *
10118 sipe_get_access_groups_menu(struct sipe_account_data *sip)
10120 GList *menu_access_groups = NULL;
10121 PurpleMenuAction *act;
10122 GSList *access_domains = NULL;
10123 GSList *entry;
10124 char *menu_name;
10125 char *domain;
10127 act = purple_menu_action_new(_("People in my company"),
10128 NULL,
10129 NULL, sipe_get_access_levels_menu(sip, "sameEnterprise", NULL, FALSE));
10130 menu_access_groups = g_list_prepend(menu_access_groups, act);
10132 /* this is original name, don't edit */
10133 act = purple_menu_action_new(_("People in domains connected with my company"),
10134 NULL,
10135 NULL, sipe_get_access_levels_menu(sip, "federated", NULL, FALSE));
10136 menu_access_groups = g_list_prepend(menu_access_groups, act);
10138 act = purple_menu_action_new(_("People in public domains"),
10139 NULL,
10140 NULL, sipe_get_access_levels_menu(sip, "publicCloud", NULL, TRUE));
10141 menu_access_groups = g_list_prepend(menu_access_groups, act);
10143 access_domains = sipe_get_access_domains(sip);
10144 entry = access_domains;
10145 while (entry) {
10146 domain = entry->data;
10148 menu_name = g_strdup_printf(_("People at %s"), domain);
10149 act = purple_menu_action_new(menu_name,
10150 NULL,
10151 NULL, sipe_get_access_levels_menu(sip, "domain", g_strdup(domain), TRUE));
10152 menu_access_groups = g_list_prepend(menu_access_groups, act);
10153 g_free(menu_name);
10155 entry = entry->next;
10158 /* separator */
10159 /* People in domains connected with my company */
10160 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
10161 menu_access_groups = g_list_prepend(menu_access_groups, act);
10163 act = purple_menu_action_new(_("Add new domain..."),
10164 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
10165 NULL, NULL);
10166 menu_access_groups = g_list_prepend(menu_access_groups, act);
10168 menu_access_groups = g_list_reverse(menu_access_groups);
10170 return menu_access_groups;
10173 static GList *
10174 sipe_get_access_control_menu(struct sipe_account_data *sip,
10175 const char* uri)
10177 GList *menu_access_levels = NULL;
10178 GList *menu_access_groups = NULL;
10179 char *menu_name;
10180 PurpleMenuAction *act;
10182 menu_access_levels = sipe_get_access_levels_menu(sip, "user", sipe_get_no_sip_uri(uri), TRUE);
10184 menu_access_groups = sipe_get_access_groups_menu(sip);
10186 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
10187 act = purple_menu_action_new(menu_name,
10188 NULL,
10189 NULL, menu_access_groups);
10190 g_free(menu_name);
10191 menu_access_levels = g_list_append(menu_access_levels, act);
10193 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
10194 act = purple_menu_action_new(menu_name,
10195 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
10196 NULL, NULL);
10197 g_free(menu_name);
10198 menu_access_levels = g_list_append(menu_access_levels, act);
10200 return menu_access_levels;
10203 static void
10204 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
10206 struct sipe_account_data *sip = PURPLE_CHAT_TO_SIPE_ACCOUNT_DATA;
10207 struct sip_session *session;
10209 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10210 sipe_conf_modify_conference_lock(sip, session, locked);
10213 static void
10214 sipe_chat_menu_unlock_cb(PurpleChat *chat)
10216 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
10217 sipe_conf_modify_lock(chat, FALSE);
10220 static void
10221 sipe_chat_menu_lock_cb(PurpleChat *chat)
10223 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
10224 sipe_conf_modify_lock(chat, TRUE);
10227 GList *
10228 sipe_chat_menu(PurpleChat *chat)
10230 PurpleMenuAction *act;
10231 PurpleConvChatBuddyFlags flags_us;
10232 GList *menu = NULL;
10233 struct sipe_account_data *sip = PURPLE_CHAT_TO_SIPE_ACCOUNT_DATA;
10234 struct sip_session *session;
10235 gchar *self;
10237 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10238 if (!session) return NULL;
10240 self = sip_uri_self(sip);
10241 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
10243 if (session->focus_uri
10244 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
10246 if (session->locked) {
10247 act = purple_menu_action_new(_("Unlock"),
10248 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
10249 NULL, NULL);
10250 menu = g_list_prepend(menu, act);
10251 } else {
10252 act = purple_menu_action_new(_("Lock"),
10253 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
10254 NULL, NULL);
10255 menu = g_list_prepend(menu, act);
10259 menu = g_list_reverse(menu);
10261 g_free(self);
10262 return menu;
10265 static gboolean
10266 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
10268 char *uri = trans->payload->data;
10270 PurpleNotifyUserInfo *info;
10271 PurpleBuddy *pbuddy = NULL;
10272 struct sipe_buddy *sbuddy;
10273 const char *alias = NULL;
10274 char *device_name = NULL;
10275 char *server_alias = NULL;
10276 char *phone_number = NULL;
10277 char *email = NULL;
10278 const char *site;
10279 char *first_name = NULL;
10280 char *last_name = NULL;
10282 if (!sip) return FALSE;
10284 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sip->username);
10286 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
10287 alias = purple_buddy_get_local_alias(pbuddy);
10289 //will query buddy UA's capabilities and send answer to log
10290 sipe_options_request(sip, uri);
10292 sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri);
10293 if (sbuddy) {
10294 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
10297 info = purple_notify_user_info_new();
10299 if (msg->response != 200) {
10300 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg->response);
10301 } else {
10302 sipe_xml *searchResults;
10303 const sipe_xml *mrow;
10305 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
10306 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
10307 if (!searchResults) {
10308 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
10309 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
10310 const char *value;
10311 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
10312 email = g_strdup(sipe_xml_attribute(mrow, "email"));
10313 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
10315 /* For 2007 system we will take this from ContactCard -
10316 * it has cleaner tel: URIs at least
10318 if (!sip->ocs2007) {
10319 char *tel_uri = sip_to_tel_uri(phone_number);
10320 /* trims its parameters, so call first */
10321 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
10322 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
10323 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
10324 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
10325 g_free(tel_uri);
10328 if (server_alias && strlen(server_alias) > 0) {
10329 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10331 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
10332 purple_notify_user_info_add_pair(info, _("Job title"), value);
10334 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
10335 purple_notify_user_info_add_pair(info, _("Office"), value);
10337 if (phone_number && strlen(phone_number) > 0) {
10338 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
10340 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
10341 purple_notify_user_info_add_pair(info, _("Company"), value);
10343 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
10344 purple_notify_user_info_add_pair(info, _("City"), value);
10346 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
10347 purple_notify_user_info_add_pair(info, _("State"), value);
10349 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
10350 purple_notify_user_info_add_pair(info, _("Country"), value);
10352 if (email && strlen(email) > 0) {
10353 purple_notify_user_info_add_pair(info, _("Email address"), email);
10357 sipe_xml_free(searchResults);
10360 purple_notify_user_info_add_section_break(info);
10362 if (is_empty(server_alias)) {
10363 g_free(server_alias);
10364 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
10365 if (server_alias) {
10366 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10370 /* present alias if it differs from server alias */
10371 if (alias && !sipe_strequal(alias, server_alias))
10373 purple_notify_user_info_add_pair(info, _("Alias"), alias);
10376 if (is_empty(email)) {
10377 g_free(email);
10378 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
10379 if (email) {
10380 purple_notify_user_info_add_pair(info, _("Email address"), email);
10384 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
10385 if (site) {
10386 purple_notify_user_info_add_pair(info, _("Site"), site);
10389 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
10390 if (first_name && last_name) {
10391 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
10393 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
10394 g_free(link);
10396 g_free(first_name);
10397 g_free(last_name);
10399 if (device_name) {
10400 purple_notify_user_info_add_pair(info, _("Device"), device_name);
10403 /* show a buddy's user info in a nice dialog box */
10404 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
10405 uri, /* buddy's URI */
10406 info, /* body */
10407 NULL, /* callback called when dialog closed */
10408 NULL); /* userdata for callback */
10410 g_free(phone_number);
10411 g_free(server_alias);
10412 g_free(email);
10413 g_free(device_name);
10415 return TRUE;
10419 * AD search first, LDAP based
10421 void sipe_get_info(PurpleConnection *gc, const char *username)
10423 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
10424 gchar *domain_uri = sip_uri_from_name(SIP_TO_CORE_PUBLIC->sip_domain);
10425 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
10426 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
10427 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
10429 payload->destroy = g_free;
10430 payload->data = g_strdup(username);
10432 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
10433 send_soap_request_with_cb(sip, domain_uri, body,
10434 (TransCallback) process_get_info_response, payload);
10435 g_free(domain_uri);
10436 g_free(body);
10437 g_free(row);
10441 Local Variables:
10442 mode: c
10443 c-file-style: "bsd"
10444 indent-tabs-mode: t
10445 tab-width: 8
10446 End: