l10n: Updates to Portuguese (Brazilian) (pt_BR) translation
[siplcs.git] / src / core / sipe.c
blob45a901566d218ddbde7922f81eab8d5a8ff2b8ec
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #ifdef _WIN32
41 #include "win32dep.h" /* for LOCALEDIR */
42 #ifdef _DLL
43 #define _WS2TCPIP_H_
44 #define _WINSOCK2API_
45 #define _LIBC_INTERNAL_
46 #endif /* _DLL */
47 /* for network */
48 #include "libc_interface.h"
49 #else
50 #include <sys/types.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #endif /* _WIN32 */
55 #include <time.h>
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <errno.h>
59 #include <string.h>
60 #include <unistd.h>
62 #include <glib.h>
63 #ifdef HAVE_GMIME
64 #include <gmime/gmime.h>
65 #endif
67 #include "sipe-common.h"
69 #include "account.h"
70 #include "blist.h"
71 #include "connection.h"
72 #include "conversation.h"
73 #include "core.h"
74 #include "circbuffer.h"
75 #include "dnsquery.h"
76 #include "dnssrv.h"
77 #include "ft.h"
78 #include "network.h"
79 #include "notify.h"
80 #include "plugin.h"
81 #include "privacy.h"
82 #include "request.h"
83 #include "savedstatuses.h"
84 #include "sslconn.h"
85 #include "version.h"
87 #include "core-depurple.h" /* Temporary for the core de-purple transition */
89 #include "sipmsg.h"
90 #include "sip-csta.h"
91 #include "sip-sec.h"
92 #include "sipe-backend.h"
93 #include "sipe-cal.h"
94 #include "sipe-chat.h"
95 #include "sipe-conf.h"
96 #include "sipe-core.h"
97 #include "sipe-dialog.h"
98 #include "sipe-ews.h"
99 #include "sipe-ft.h"
100 #include "sipe-mime.h"
101 #include "sipe-nls.h"
102 #include "sipe-session.h"
103 #include "sipe-sign.h"
104 #include "sipe-utils.h"
105 #include "sipe-xml.h"
106 #include "http-conn.h"
107 #include "uuid.h"
108 #include "sipe.h"
110 /* Backward compatibility when compiling against 2.4.x API */
111 #if !PURPLE_VERSION_CHECK(2,5,0)
112 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
113 #endif
115 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
117 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
118 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
120 /* Keep in sync with sipe_transport_type! */
121 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
122 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
124 /* Status identifiers (see also: sipe_status_types()) */
125 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
126 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
127 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
128 /* PURPLE_STATUS_UNAVAILABLE: */
129 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
130 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
131 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
132 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
133 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
134 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
135 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
136 /* PURPLE_STATUS_AWAY: */
137 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
138 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
139 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
140 /** Reuters status (user settable) */
141 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
142 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
143 /* ??? PURPLE_STATUS_MOBILE */
144 /* ??? PURPLE_STATUS_TUNE */
146 /* Status attributes (see also sipe_status_types() */
147 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
149 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
151 static struct sipe_activity_map_struct
153 sipe_activity type;
154 const char *token;
155 const char *desc;
156 const char *status_id;
158 } const sipe_activity_map[] =
160 /* This has nothing to do with Availability numbers, like 3500 (online).
161 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
163 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
164 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
165 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
166 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
167 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
168 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
169 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
170 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
171 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
172 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
173 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
174 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
175 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
176 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
177 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
179 /** @param x is sipe_activity */
180 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
183 /* Action name templates */
184 #define ACTION_NAME_PRESENCE "<presence><%s>"
186 static sipe_activity
187 sipe_get_activity_by_token(const char *token)
189 int i;
191 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
193 if (sipe_strequal(token, sipe_activity_map[i].token))
194 return sipe_activity_map[i].type;
197 return sipe_activity_map[0].type;
200 static const char *
201 sipe_get_activity_desc_by_token(const char *token)
203 if (!token) return NULL;
205 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
208 /** Allows to send typed messages from chat window again after account reinstantiation. */
209 static void
210 sipe_rejoin_chat(PurpleConversation *conv)
212 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
213 PURPLE_CONV_CHAT(conv)->left)
215 PURPLE_CONV_CHAT(conv)->left = FALSE;
216 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
220 static char *genbranch()
222 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
223 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
224 rand() & 0xFFFF, rand() & 0xFFFF);
228 static char *default_ua = NULL;
229 static const char*
230 sipe_get_useragent(struct sipe_account_data *sip)
232 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
233 if (is_empty(useragent)) {
234 if (!default_ua) {
235 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
236 /* ref: lzodefs.h */
237 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
238 #define SIPE_TARGET_PLATFORM "linux"
239 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
240 #define SIPE_TARGET_PLATFORM "bsd"
241 #elif defined(__APPLE__) || defined(__MACOS__)
242 #define SIPE_TARGET_PLATFORM "macosx"
243 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
244 #define SIPE_TARGET_PLATFORM "aix"
245 #elif defined(__solaris__) || defined(__sun)
246 #define SIPE_TARGET_PLATFORM "sun"
247 #elif defined(_WIN32)
248 #define SIPE_TARGET_PLATFORM "win"
249 #elif defined(__CYGWIN__)
250 #define SIPE_TARGET_PLATFORM "cygwin"
251 #elif defined(__hpux__)
252 #define SIPE_TARGET_PLATFORM "hpux"
253 #elif defined(__sgi__)
254 #define SIPE_TARGET_PLATFORM "irix"
255 #else
256 #define SIPE_TARGET_PLATFORM "unknown"
257 #endif
259 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
260 #define SIPE_TARGET_ARCH "x86_64"
261 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
262 #define SIPE_TARGET_ARCH "i386"
263 #elif defined(__ppc64__)
264 #define SIPE_TARGET_ARCH "ppc64"
265 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
266 #define SIPE_TARGET_ARCH "ppc"
267 #elif defined(__hppa__) || defined(__hppa)
268 #define SIPE_TARGET_ARCH "hppa"
269 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
270 #define SIPE_TARGET_ARCH "mips"
271 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
272 #define SIPE_TARGET_ARCH "s390"
273 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
274 #define SIPE_TARGET_ARCH "sparc"
275 #elif defined(__arm__)
276 #define SIPE_TARGET_ARCH "arm"
277 #else
278 #define SIPE_TARGET_ARCH "other"
279 #endif
281 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
282 purple_core_get_version(),
283 sip->server_version ? sip->server_version : "");
285 useragent = default_ua;
287 return useragent;
290 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
291 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
293 return "sipe";
296 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
298 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
299 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
300 gpointer data);
302 static void sipe_close(PurpleConnection *gc);
304 static void send_presence_status(struct sipe_account_data *sip);
306 static void sendout_pkt(PurpleConnection *gc, const char *buf);
308 static void sipe_keep_alive(PurpleConnection *gc)
310 struct sipe_account_data *sip = gc->proto_data;
311 if (sip->transport == SIPE_TRANSPORT_UDP) {
312 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
313 gchar buf[2] = {0, 0};
314 SIPE_DEBUG_INFO_NOFORMAT("sending keep alive");
315 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
316 } else {
317 time_t now = time(NULL);
318 if ((sip->keepalive_timeout > 0) &&
319 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
320 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
322 SIPE_DEBUG_INFO("sending keep alive %d", sip->keepalive_timeout);
323 sendout_pkt(gc, "\r\n\r\n");
324 sip->last_keepalive = now;
329 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
331 struct sip_connection *ret = NULL;
332 GSList *entry = sip->openconns;
333 while (entry) {
334 ret = entry->data;
335 if (ret->fd == fd) return ret;
336 entry = entry->next;
338 return NULL;
341 static void sipe_auth_free(struct sip_auth *auth)
343 g_free(auth->opaque);
344 auth->opaque = NULL;
345 g_free(auth->realm);
346 auth->realm = NULL;
347 g_free(auth->target);
348 auth->target = NULL;
349 auth->version = 0;
350 auth->type = AUTH_TYPE_UNSET;
351 auth->retries = 0;
352 auth->expires = 0;
353 g_free(auth->gssapi_data);
354 auth->gssapi_data = NULL;
355 sip_sec_destroy_context(auth->gssapi_context);
356 auth->gssapi_context = NULL;
359 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
361 struct sip_connection *ret = g_new0(struct sip_connection, 1);
362 ret->fd = fd;
363 sip->openconns = g_slist_append(sip->openconns, ret);
364 return ret;
367 static void connection_remove(struct sipe_account_data *sip, int fd)
369 struct sip_connection *conn = connection_find(sip, fd);
370 if (conn) {
371 sip->openconns = g_slist_remove(sip->openconns, conn);
372 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
373 g_free(conn->inbuf);
374 g_free(conn);
378 static void connection_free_all(struct sipe_account_data *sip)
380 struct sip_connection *ret = NULL;
381 GSList *entry = sip->openconns;
382 while (entry) {
383 ret = entry->data;
384 connection_remove(sip, ret->fd);
385 entry = sip->openconns;
389 static void
390 sipe_make_signature(struct sipe_account_data *sip,
391 struct sipmsg *msg);
393 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
395 const char *authuser = sip->authuser;
396 gchar *ret;
398 if (!authuser || strlen(authuser) < 1) {
399 authuser = sip->username;
402 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
403 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
404 gchar *version_str;
406 // If we have a signature for the message, include that
407 if (msg->signature) {
408 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);
411 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
412 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
413 gchar *gssapi_data;
414 gchar *opaque;
415 gchar *sign_str = NULL;
417 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
418 &(auth->expires),
419 auth->type,
420 purple_account_get_bool(sip->account, "sso", TRUE),
421 sip->authdomain ? sip->authdomain : "",
422 authuser,
423 sip->password,
424 auth->target,
425 auth->gssapi_data);
426 if (!gssapi_data || !auth->gssapi_context) {
427 sip->gc->wants_to_die = TRUE;
428 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
429 return NULL;
432 if (auth->version > 3) {
433 sipe_make_signature(sip, msg);
434 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
435 msg->rand, msg->num, msg->signature);
436 } else {
437 sign_str = g_strdup("");
440 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
441 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
442 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);
443 g_free(opaque);
444 g_free(gssapi_data);
445 g_free(version_str);
446 g_free(sign_str);
447 return ret;
450 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
451 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
452 g_free(version_str);
453 return ret;
455 } else { /* Digest */
456 gchar *string;
457 gchar *hex_digest;
458 guchar digest[SIPE_DIGEST_MD5_LENGTH];
460 /* Calculate new session key */
461 if (!auth->opaque) {
462 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth->gssapi_data, auth->realm);
463 if (sip->password) {
465 * Calculate a session key for HTTP MD5 Digest authentation
467 * See RFC 2617 for more information.
469 string = g_strdup_printf("%s:%s:%s",
470 authuser,
471 auth->realm,
472 sip->password);
473 sipe_backend_digest_md5((guchar *)string, strlen(string), digest);
474 g_free(string);
475 auth->opaque = buff_to_hex_str(digest, sizeof(digest));
480 * Calculate a response for HTTP MD5 Digest authentication
482 * See RFC 2617 for more information.
484 string = g_strdup_printf("%s:%s", msg->method, msg->target);
485 sipe_backend_digest_md5((guchar *)string, strlen(string), digest);
486 g_free(string);
488 hex_digest = buff_to_hex_str(digest, sizeof(digest));
489 string = g_strdup_printf("%s:%s:%s", auth->opaque, auth->gssapi_data, hex_digest);
490 g_free(hex_digest);
491 sipe_backend_digest_md5((guchar *)string, strlen(string), digest);
492 g_free(string);
494 hex_digest = buff_to_hex_str(digest, sizeof(digest));
495 SIPE_DEBUG_INFO("Digest response %s", hex_digest);
496 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);
497 g_free(hex_digest);
498 return ret;
502 static char *parse_attribute(const char *attrname, const char *source)
504 const char *tmp, *tmp2;
505 char *retval = NULL;
506 int len = strlen(attrname);
508 if (g_str_has_prefix(source, attrname)) {
509 tmp = source + len;
510 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
511 if (tmp2)
512 retval = g_strndup(tmp, tmp2 - tmp);
513 else
514 retval = g_strdup(tmp);
517 return retval;
520 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
522 int i;
523 gchar **parts;
525 if (!hdr) {
526 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
527 return;
530 if (!g_strncasecmp(hdr, "NTLM", 4)) {
531 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
532 auth->type = AUTH_TYPE_NTLM;
533 hdr += 5;
534 auth->nc = 1;
535 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
536 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
537 auth->type = AUTH_TYPE_KERBEROS;
538 hdr += 9;
539 auth->nc = 3;
540 } else {
541 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
542 auth->type = AUTH_TYPE_DIGEST;
543 hdr += 7;
546 parts = g_strsplit(hdr, "\", ", 0);
547 for (i = 0; parts[i]; i++) {
548 char *tmp;
550 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
552 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
553 g_free(auth->gssapi_data);
554 auth->gssapi_data = tmp;
556 if (auth->type == AUTH_TYPE_NTLM) {
557 /* NTLM module extracts nonce from gssapi-data */
558 auth->nc = 3;
561 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
562 /* Only used with AUTH_TYPE_DIGEST */
563 g_free(auth->gssapi_data);
564 auth->gssapi_data = tmp;
565 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
566 g_free(auth->opaque);
567 auth->opaque = tmp;
568 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
569 g_free(auth->realm);
570 auth->realm = tmp;
572 if (auth->type == AUTH_TYPE_DIGEST) {
573 /* Throw away old session key */
574 g_free(auth->opaque);
575 auth->opaque = NULL;
576 auth->nc = 1;
578 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
579 g_free(auth->target);
580 auth->target = tmp;
581 } else if ((tmp = parse_attribute("version=", parts[i]))) {
582 auth->version = atoi(tmp);
583 g_free(tmp);
585 // uncomment to revert to previous functionality if version 3+ does not work.
586 // auth->version = 2;
588 g_strfreev(parts);
590 return;
593 static void sipe_canwrite_cb(gpointer data,
594 SIPE_UNUSED_PARAMETER gint source,
595 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
597 PurpleConnection *gc = data;
598 struct sipe_account_data *sip = gc->proto_data;
599 gsize max_write;
600 gssize written;
602 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
604 if (max_write == 0) {
605 if (sip->tx_handler != 0){
606 purple_input_remove(sip->tx_handler);
607 sip->tx_handler = 0;
609 return;
612 written = write(sip->fd, sip->txbuf->outptr, max_write);
614 if (written < 0 && errno == EAGAIN)
615 written = 0;
616 else if (written <= 0) {
617 /*TODO: do we really want to disconnect on a failure to write?*/
618 purple_connection_error(gc, _("Could not write"));
619 return;
622 purple_circ_buffer_mark_read(sip->txbuf, written);
625 static void sipe_canwrite_cb_ssl(gpointer data,
626 SIPE_UNUSED_PARAMETER gint src,
627 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
629 PurpleConnection *gc = data;
630 struct sipe_account_data *sip = gc->proto_data;
631 gsize max_write;
632 gssize written;
634 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
636 if (max_write == 0) {
637 if (sip->tx_handler != 0) {
638 purple_input_remove(sip->tx_handler);
639 sip->tx_handler = 0;
640 return;
644 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
646 if (written < 0 && errno == EAGAIN)
647 written = 0;
648 else if (written <= 0) {
649 /*TODO: do we really want to disconnect on a failure to write?*/
650 purple_connection_error(gc, _("Could not write"));
651 return;
654 purple_circ_buffer_mark_read(sip->txbuf, written);
657 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
659 static void send_later_cb(gpointer data, gint source,
660 SIPE_UNUSED_PARAMETER const gchar *error)
662 PurpleConnection *gc = data;
663 struct sipe_account_data *sip;
664 struct sip_connection *conn;
666 if (!PURPLE_CONNECTION_IS_VALID(gc))
668 if (source >= 0)
669 close(source);
670 return;
673 if (source < 0) {
674 purple_connection_error(gc, _("Could not connect"));
675 return;
678 sip = gc->proto_data;
679 sip->fd = source;
680 sip->connecting = FALSE;
681 sip->last_keepalive = time(NULL);
683 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
685 /* If there is more to write now, we need to register a handler */
686 if (sip->txbuf->bufused > 0)
687 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
689 conn = connection_create(sip, source);
690 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
693 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
695 struct sipe_account_data *sip;
697 if (!PURPLE_CONNECTION_IS_VALID(gc))
699 if (gsc) purple_ssl_close(gsc);
700 return NULL;
703 sip = gc->proto_data;
704 sip->fd = gsc->fd;
705 sip->gsc = gsc;
706 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
707 sip->connecting = FALSE;
708 sip->last_keepalive = time(NULL);
710 connection_create(sip, gsc->fd);
712 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
714 return sip;
717 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
718 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
720 PurpleConnection *gc = data;
721 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
722 if (sip == NULL) return;
724 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
726 /* If there is more to write now */
727 if (sip->txbuf->bufused > 0) {
728 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
733 static void sendlater(PurpleConnection *gc, const char *buf)
735 struct sipe_account_data *sip = gc->proto_data;
737 if (!sip->connecting) {
738 SIPE_DEBUG_INFO("connecting to %s port %d", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
739 if (sip->transport == SIPE_TRANSPORT_TLS){
740 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
741 } else {
742 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
743 purple_connection_error(gc, _("Could not create socket"));
746 sip->connecting = TRUE;
749 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
750 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
752 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
755 static void sendout_pkt(PurpleConnection *gc, const char *buf)
757 struct sipe_account_data *sip = gc->proto_data;
758 time_t currtime = time(NULL);
759 int writelen = strlen(buf);
760 char *tmp;
762 SIPE_DEBUG_INFO("sending - %s######\n%s######", ctime(&currtime), tmp = fix_newlines(buf));
763 g_free(tmp);
764 if (sip->transport == SIPE_TRANSPORT_UDP) {
765 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
766 SIPE_DEBUG_INFO_NOFORMAT("could not send packet");
768 } else {
769 int ret;
770 if (sip->fd < 0) {
771 sendlater(gc, buf);
772 return;
775 if (sip->tx_handler) {
776 ret = -1;
777 errno = EAGAIN;
778 } else{
779 if (sip->gsc){
780 ret = purple_ssl_write(sip->gsc, buf, writelen);
781 }else{
782 ret = write(sip->fd, buf, writelen);
786 if (ret < 0 && errno == EAGAIN)
787 ret = 0;
788 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
789 sendlater(gc, buf);
790 return;
793 if (ret < writelen) {
794 if (!sip->tx_handler){
795 if (sip->gsc){
796 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
798 else{
799 sip->tx_handler = purple_input_add(sip->fd,
800 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
801 gc);
805 /* XXX: is it OK to do this? You might get part of a request sent
806 with part of another. */
807 if (sip->txbuf->bufused > 0)
808 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
810 purple_circ_buffer_append(sip->txbuf, buf + ret,
811 writelen - ret);
816 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
818 sendout_pkt(gc, buf);
819 return len;
822 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
824 GSList *tmp = msg->headers;
825 gchar *name;
826 gchar *value;
827 GString *outstr = g_string_new("");
828 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
829 while (tmp) {
830 name = ((struct sipnameval*) (tmp->data))->name;
831 value = ((struct sipnameval*) (tmp->data))->value;
832 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
833 tmp = g_slist_next(tmp);
835 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
836 sendout_pkt(sip->gc, outstr->str);
837 g_string_free(outstr, TRUE);
840 static void
841 sipe_make_signature(struct sipe_account_data *sip,
842 struct sipmsg *msg)
844 if (sip->registrar.gssapi_context) {
845 struct sipmsg_breakdown msgbd;
846 gchar *signature_input_str;
847 msgbd.msg = msg;
848 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
849 msgbd.rand = g_strdup_printf("%08x", g_random_int());
850 sip->registrar.ntlm_num++;
851 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
852 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
853 if (signature_input_str != NULL) {
854 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
855 msg->signature = signature_hex;
856 msg->rand = g_strdup(msgbd.rand);
857 msg->num = g_strdup(msgbd.num);
858 g_free(signature_input_str);
860 sipmsg_breakdown_free(&msgbd);
864 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
866 gchar * buf;
868 if (sip->registrar.type == AUTH_TYPE_UNSET) {
869 return;
872 sipe_make_signature(sip, msg);
874 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
875 buf = auth_header(sip, &sip->registrar, msg);
876 if (buf) {
877 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
879 g_free(buf);
880 } 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")) {
881 sip->registrar.nc = 3;
882 sip->registrar.type = AUTH_TYPE_NTLM;
883 #ifdef HAVE_LIBKRB5
884 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
885 sip->registrar.type = AUTH_TYPE_KERBEROS;
887 #endif
890 buf = auth_header(sip, &sip->registrar, msg);
891 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
892 g_free(buf);
893 } else {
894 SIPE_DEBUG_INFO("not adding auth header to msg w/ method %s", method);
898 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
899 const char *text, const char *body)
901 gchar *name;
902 gchar *value;
903 GString *outstr = g_string_new("");
904 struct sipe_account_data *sip = gc->proto_data;
905 gchar *contact;
906 GSList *tmp;
907 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
909 /* Can return NULL! */
910 contact = get_contact(sip);
911 if (contact) {
912 sipmsg_add_header(msg, "Contact", contact);
913 g_free(contact);
916 if (body) {
917 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
918 sipmsg_add_header(msg, "Content-Length", len);
919 g_free(len);
920 } else {
921 sipmsg_add_header(msg, "Content-Length", "0");
924 msg->response = code;
926 sipmsg_strip_headers(msg, keepers);
927 sipmsg_merge_new_headers(msg);
928 sign_outgoing_message(msg, sip, msg->method);
930 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
931 tmp = msg->headers;
932 while (tmp) {
933 name = ((struct sipnameval*) (tmp->data))->name;
934 value = ((struct sipnameval*) (tmp->data))->value;
936 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
937 tmp = g_slist_next(tmp);
939 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
940 sendout_pkt(gc, outstr->str);
941 g_string_free(outstr, TRUE);
944 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
946 if (sip->transactions) {
947 sip->transactions = g_slist_remove(sip->transactions, trans);
948 SIPE_DEBUG_INFO("sip->transactions count:%d after removal", g_slist_length(sip->transactions));
950 if (trans->msg) sipmsg_free(trans->msg);
951 if (trans->payload) {
952 (*trans->payload->destroy)(trans->payload->data);
953 g_free(trans->payload);
955 g_free(trans->key);
956 g_free(trans);
960 static struct transaction *
961 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
963 const gchar *call_id;
964 const gchar *cseq;
965 struct transaction *trans = g_new0(struct transaction, 1);
967 trans->time = time(NULL);
968 trans->msg = (struct sipmsg *)msg;
969 call_id = sipmsg_find_header(trans->msg, "Call-ID");
970 cseq = sipmsg_find_header(trans->msg, "CSeq");
971 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
972 trans->callback = callback;
973 sip->transactions = g_slist_append(sip->transactions, trans);
974 SIPE_DEBUG_INFO("sip->transactions count:%d after addition", g_slist_length(sip->transactions));
975 return trans;
978 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
980 struct transaction *trans;
981 GSList *transactions = sip->transactions;
982 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
983 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
984 gchar *key;
986 if (!call_id || !cseq) {
987 SIPE_DEBUG_ERROR_NOFORMAT("transaction_find: no Call-ID or CSeq!");
988 return NULL;
991 key = g_strdup_printf("<%s><%s>", call_id, cseq);
992 while (transactions) {
993 trans = transactions->data;
994 if (!g_strcasecmp(trans->key, key)) {
995 g_free(key);
996 return trans;
998 transactions = transactions->next;
1001 g_free(key);
1002 return NULL;
1005 struct transaction *
1006 send_sip_request(PurpleConnection *gc, const gchar *method,
1007 const gchar *url, const gchar *to, const gchar *addheaders,
1008 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
1010 struct sipe_account_data *sip = gc->proto_data;
1011 const char *addh = "";
1012 char *buf;
1013 struct sipmsg *msg;
1014 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
1015 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
1016 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
1017 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
1018 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
1019 gchar *route = g_strdup("");
1020 gchar *epid = get_epid(sip);
1021 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
1022 struct transaction *trans = NULL;
1024 if (dialog && dialog->routes)
1026 GSList *iter = dialog->routes;
1028 while(iter)
1030 char *tmp = route;
1031 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1032 g_free(tmp);
1033 iter = g_slist_next(iter);
1037 if (!ourtag && !dialog) {
1038 ourtag = gentag();
1041 if (sipe_strequal(method, "REGISTER")) {
1042 if (sip->regcallid) {
1043 g_free(callid);
1044 callid = g_strdup(sip->regcallid);
1045 } else {
1046 sip->regcallid = g_strdup(callid);
1048 cseq = ++sip->cseq;
1051 if (addheaders) addh = addheaders;
1053 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1054 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1055 "From: <sip:%s>%s%s;epid=%s\r\n"
1056 "To: <%s>%s%s%s%s\r\n"
1057 "Max-Forwards: 70\r\n"
1058 "CSeq: %d %s\r\n"
1059 "User-Agent: %s\r\n"
1060 "Call-ID: %s\r\n"
1061 "%s%s"
1062 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1063 method,
1064 dialog && dialog->request ? dialog->request : url,
1065 TRANSPORT_DESCRIPTOR,
1066 sipe_backend_network_ip_address(),
1067 sip->listenport,
1068 branch ? ";branch=" : "",
1069 branch ? branch : "",
1070 sip->username,
1071 ourtag ? ";tag=" : "",
1072 ourtag ? ourtag : "",
1073 epid,
1075 theirtag ? ";tag=" : "",
1076 theirtag ? theirtag : "",
1077 theirepid ? ";epid=" : "",
1078 theirepid ? theirepid : "",
1079 cseq,
1080 method,
1081 sipe_get_useragent(sip),
1082 callid,
1083 route,
1084 addh,
1085 body ? (gsize) strlen(body) : 0,
1086 body ? body : "");
1089 //printf ("parsing msg buf:\n%s\n\n", buf);
1090 msg = sipmsg_parse_msg(buf);
1092 g_free(buf);
1093 g_free(ourtag);
1094 g_free(theirtag);
1095 g_free(theirepid);
1096 g_free(branch);
1097 g_free(callid);
1098 g_free(route);
1099 g_free(epid);
1101 sign_outgoing_message (msg, sip, method);
1103 buf = sipmsg_to_string (msg);
1105 /* add to ongoing transactions */
1106 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1107 if (!sipe_strequal(method, "ACK")) {
1108 trans = transactions_add_buf(sip, msg, tc);
1109 } else {
1110 sipmsg_free(msg);
1112 sendout_pkt(gc, buf);
1113 g_free(buf);
1115 return trans;
1119 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1121 static void
1122 send_soap_request_with_cb(struct sipe_account_data *sip,
1123 gchar *from0,
1124 gchar *body,
1125 TransCallback callback,
1126 struct transaction_payload *payload)
1128 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1129 gchar *contact = get_contact(sip);
1130 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1131 "Content-Type: application/SOAP+xml\r\n",contact);
1133 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1134 trans->payload = payload;
1136 g_free(from);
1137 g_free(contact);
1138 g_free(hdr);
1141 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1143 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1146 static char *get_contact_register(struct sipe_account_data *sip)
1148 char *epid = get_epid(sip);
1149 char *uuid = generateUUIDfromEPID(epid);
1150 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);
1151 g_free(uuid);
1152 g_free(epid);
1153 return(buf);
1156 static void do_register_exp(struct sipe_account_data *sip, int expire)
1158 char *uri;
1159 char *expires;
1160 char *to;
1161 char *contact;
1162 char *hdr;
1164 if (!sip->sipdomain) return;
1166 uri = sip_uri_from_name(sip->sipdomain);
1167 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1168 to = sip_uri_self(sip);
1169 contact = get_contact_register(sip);
1170 hdr = g_strdup_printf("Contact: %s\r\n"
1171 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1172 "Event: registration\r\n"
1173 "Allow-Events: presence\r\n"
1174 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1175 "%s", contact, expires);
1176 g_free(contact);
1177 g_free(expires);
1179 sip->registerstatus = 1;
1181 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1182 process_register_response);
1184 g_free(hdr);
1185 g_free(uri);
1186 g_free(to);
1189 static void do_register_cb(struct sipe_account_data *sip,
1190 SIPE_UNUSED_PARAMETER void *unused)
1192 do_register_exp(sip, -1);
1193 sip->reregister_set = FALSE;
1196 static void do_register(struct sipe_account_data *sip)
1198 do_register_exp(sip, -1);
1202 * Returns pointer to URI without sip: prefix if any
1204 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
1205 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
1207 * Doesn't allocate memory
1209 static const char *
1210 sipe_get_no_sip_uri(const char *sip_uri)
1212 const char *prefix = "sip:";
1213 if (!sip_uri) return NULL;
1215 if (g_str_has_prefix(sip_uri, prefix)) {
1216 return (sip_uri+strlen(prefix));
1217 } else {
1218 return sip_uri;
1222 static void
1223 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1225 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1226 send_soap_request(sip, body);
1227 g_free(body);
1230 static void
1231 sipe_change_access_level(struct sipe_account_data *sip,
1232 const int container_id,
1233 const gchar *type,
1234 const gchar *value);
1236 static void
1237 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1239 if (allow) {
1240 SIPE_DEBUG_INFO("Authorizing contact %s", who);
1241 } else {
1242 SIPE_DEBUG_INFO("Blocking contact %s", who);
1245 if (sip->ocs2007) {
1246 sipe_change_access_level(sip, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
1247 } else {
1248 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1252 static
1253 void sipe_auth_user_cb(void * data)
1255 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1256 if (!job) return;
1258 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1259 g_free(job);
1262 static
1263 void sipe_deny_user_cb(void * data)
1265 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1266 if (!job) return;
1268 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1269 g_free(job);
1272 static void
1273 sipe_add_permit(PurpleConnection *gc, const char *name)
1275 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1277 sipe_contact_allow_deny(sip, name, TRUE);
1280 static void
1281 sipe_add_deny(PurpleConnection *gc, const char *name)
1283 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1284 sipe_contact_allow_deny(sip, name, FALSE);
1287 /*static void
1288 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1290 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1291 sipe_contact_set_acl(sip, name, "");
1294 /** @applicable: 2005-
1296 static void
1297 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1299 sipe_xml *watchers;
1300 const sipe_xml *watcher;
1301 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1302 if (msg->response != 0 && msg->response != 200) return;
1304 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1306 watchers = sipe_xml_parse(msg->body, msg->bodylen);
1307 if (!watchers) return;
1309 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
1310 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
1311 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
1312 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1314 // TODO pull out optional displayName to pass as alias
1315 if (remote_user) {
1316 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1317 job->who = remote_user;
1318 job->sip = sip;
1319 purple_account_request_authorization(
1320 sip->account,
1321 remote_user,
1322 _("you"), /* id */
1323 alias,
1324 NULL, /* message */
1325 on_list,
1326 sipe_auth_user_cb,
1327 sipe_deny_user_cb,
1328 (void *) job);
1333 sipe_xml_free(watchers);
1334 return;
1337 static void
1338 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1340 PurpleGroup * purple_group = purple_find_group(group->name);
1341 if (!purple_group) {
1342 purple_group = purple_group_new(group->name);
1343 purple_blist_add_group(purple_group, NULL);
1346 if (purple_group) {
1347 group->purple_group = purple_group;
1348 sip->groups = g_slist_append(sip->groups, group);
1349 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
1350 } else {
1351 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
1355 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1357 struct sipe_group *group;
1358 GSList *entry;
1359 if (sip == NULL) {
1360 return NULL;
1363 entry = sip->groups;
1364 while (entry) {
1365 group = entry->data;
1366 if (group->id == id) {
1367 return group;
1369 entry = entry->next;
1371 return NULL;
1374 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1376 struct sipe_group *group;
1377 GSList *entry;
1378 if (!sip || !name) {
1379 return NULL;
1382 entry = sip->groups;
1383 while (entry) {
1384 group = entry->data;
1385 if (sipe_strequal(group->name, name)) {
1386 return group;
1388 entry = entry->next;
1390 return NULL;
1393 static void
1394 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1396 gchar *body;
1397 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
1398 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1399 send_soap_request(sip, body);
1400 g_free(body);
1401 g_free(group->name);
1402 group->name = g_strdup(name);
1406 * Only appends if no such value already stored.
1407 * Like Set in Java.
1409 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1410 GSList * res = list;
1411 if (!g_slist_find_custom(list, data, func)) {
1412 res = g_slist_insert_sorted(list, data, func);
1414 return res;
1417 static int
1418 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1419 return group1->id - group2->id;
1423 * Returns string like "2 4 7 8" - group ids buddy belong to.
1425 static gchar *
1426 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1427 int i = 0;
1428 gchar *res;
1429 //creating array from GList, converting int to gchar*
1430 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1431 GSList *entry = buddy->groups;
1433 if (!ids_arr) return NULL;
1435 while (entry) {
1436 struct sipe_group * group = entry->data;
1437 ids_arr[i] = g_strdup_printf("%d", group->id);
1438 entry = entry->next;
1439 i++;
1441 ids_arr[i] = NULL;
1442 res = g_strjoinv(" ", ids_arr);
1443 g_strfreev(ids_arr);
1444 return res;
1448 * Sends buddy update to server
1450 static void
1451 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1453 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1454 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1456 if (buddy && purple_buddy) {
1457 const char *alias = purple_buddy_get_alias(purple_buddy);
1458 gchar *groups = sipe_get_buddy_groups_string(buddy);
1459 if (groups) {
1460 gchar *body;
1461 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
1463 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1464 alias, groups, "true", buddy->name, sip->contacts_delta++
1466 send_soap_request(sip, body);
1467 g_free(groups);
1468 g_free(body);
1473 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1475 if (msg->response == 200) {
1476 struct sipe_group *group;
1477 struct group_user_context *ctx = trans->payload->data;
1478 sipe_xml *xml;
1479 const sipe_xml *node;
1480 char *group_id;
1481 struct sipe_buddy *buddy;
1483 xml = sipe_xml_parse(msg->body, msg->bodylen);
1484 if (!xml) {
1485 return FALSE;
1488 node = sipe_xml_child(xml, "Body/addGroup/groupID");
1489 if (!node) {
1490 sipe_xml_free(xml);
1491 return FALSE;
1494 group_id = sipe_xml_data(node);
1495 if (!group_id) {
1496 sipe_xml_free(xml);
1497 return FALSE;
1500 group = g_new0(struct sipe_group, 1);
1501 group->id = (int)g_ascii_strtod(group_id, NULL);
1502 g_free(group_id);
1503 group->name = g_strdup(ctx->group_name);
1505 sipe_group_add(sip, group);
1507 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1508 if (buddy) {
1509 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1512 sipe_group_set_user(sip, ctx->user_name);
1514 sipe_xml_free(xml);
1515 return TRUE;
1517 return FALSE;
1520 static void sipe_group_context_destroy(gpointer data)
1522 struct group_user_context *ctx = data;
1523 g_free(ctx->group_name);
1524 g_free(ctx->user_name);
1525 g_free(ctx);
1528 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1530 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1531 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1532 gchar *body;
1533 ctx->group_name = g_strdup(name);
1534 ctx->user_name = g_strdup(who);
1535 payload->destroy = sipe_group_context_destroy;
1536 payload->data = ctx;
1538 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1539 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1540 g_free(body);
1544 * Data structure for scheduled actions
1547 struct scheduled_action {
1549 * Name of action.
1550 * Format is <Event>[<Data>...]
1551 * Example: <presence><sip:user@domain.com> or <registration>
1553 gchar *name;
1554 guint timeout_handler;
1555 gboolean repetitive;
1556 Action action;
1557 GDestroyNotify destroy;
1558 struct sipe_account_data *sip;
1559 void *payload;
1563 * A timer callback
1564 * Should return FALSE if repetitive action is not needed
1566 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1568 gboolean ret;
1569 SIPE_DEBUG_INFO_NOFORMAT("sipe_scheduled_exec: executing");
1570 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1571 SIPE_DEBUG_INFO("sip->timeouts count:%d after removal", g_slist_length(sched_action->sip->timeouts));
1572 (sched_action->action)(sched_action->sip, sched_action->payload);
1573 ret = sched_action->repetitive;
1574 if (sched_action->destroy) {
1575 (*sched_action->destroy)(sched_action->payload);
1577 g_free(sched_action->name);
1578 g_free(sched_action);
1579 return ret;
1583 * Kills action timer effectively cancelling
1584 * scheduled action
1586 * @param name of action
1588 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1590 GSList *entry;
1592 if (!sip->timeouts || !name) return;
1594 entry = sip->timeouts;
1595 while (entry) {
1596 struct scheduled_action *sched_action = entry->data;
1597 if(sipe_strequal(sched_action->name, name)) {
1598 GSList *to_delete = entry;
1599 entry = entry->next;
1600 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1601 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
1602 purple_timeout_remove(sched_action->timeout_handler);
1603 if (sched_action->destroy) {
1604 (*sched_action->destroy)(sched_action->payload);
1606 g_free(sched_action->name);
1607 g_free(sched_action);
1608 } else {
1609 entry = entry->next;
1614 static void
1615 sipe_schedule_action0(const gchar *name,
1616 int timeout,
1617 gboolean isSeconds,
1618 Action action,
1619 GDestroyNotify destroy,
1620 struct sipe_account_data *sip,
1621 void *payload)
1623 struct scheduled_action *sched_action;
1625 /* Make sure each action only exists once */
1626 sipe_cancel_scheduled_action(sip, name);
1628 SIPE_DEBUG_INFO("scheduling action %s timeout:%d(%s)", name, timeout, isSeconds ? "sec" : "msec");
1629 sched_action = g_new0(struct scheduled_action, 1);
1630 sched_action->repetitive = FALSE;
1631 sched_action->name = g_strdup(name);
1632 sched_action->action = action;
1633 sched_action->destroy = destroy;
1634 sched_action->sip = sip;
1635 sched_action->payload = payload;
1636 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1637 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1638 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1639 SIPE_DEBUG_INFO("sip->timeouts count:%d after addition", g_slist_length(sip->timeouts));
1642 void
1643 sipe_schedule_action(const gchar *name,
1644 int timeout,
1645 Action action,
1646 GDestroyNotify destroy,
1647 struct sipe_account_data *sip,
1648 void *payload)
1650 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1654 * Same as sipe_schedule_action() but timeout is in milliseconds.
1656 static void
1657 sipe_schedule_action_msec(const gchar *name,
1658 int timeout,
1659 Action action,
1660 GDestroyNotify destroy,
1661 struct sipe_account_data *sip,
1662 void *payload)
1664 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1667 static void
1668 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1669 time_t calculate_from);
1671 static int
1672 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1674 static const char*
1675 sipe_get_status_by_availability(int avail,
1676 char** activity);
1678 static void
1679 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1680 const char *status_id,
1681 const char *message,
1682 time_t do_not_publish[]);
1684 static void
1685 sipe_apply_calendar_status(struct sipe_account_data *sip,
1686 struct sipe_buddy *sbuddy,
1687 const char *status_id)
1689 time_t cal_avail_since;
1690 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1691 int avail;
1692 gchar *self_uri;
1694 if (!sbuddy) return;
1696 if (cal_status < SIPE_CAL_NO_DATA) {
1697 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
1698 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1701 /* scheduled Cal update call */
1702 if (!status_id) {
1703 status_id = sbuddy->last_non_cal_status_id;
1704 g_free(sbuddy->activity);
1705 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1708 if (!status_id) {
1709 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
1710 sbuddy->name ? sbuddy->name : "" );
1711 return;
1714 /* adjust to calendar status */
1715 if (cal_status != SIPE_CAL_NO_DATA) {
1716 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1718 if (cal_status == SIPE_CAL_BUSY
1719 && cal_avail_since > sbuddy->user_avail_since
1720 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1722 status_id = SIPE_STATUS_ID_BUSY;
1723 g_free(sbuddy->activity);
1724 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1726 avail = sipe_get_availability_by_status(status_id, NULL);
1728 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1729 if (cal_avail_since > sbuddy->activity_since) {
1730 if (cal_status == SIPE_CAL_OOF
1731 && avail >= 15000) /* 12000 in 2007 */
1733 g_free(sbuddy->activity);
1734 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1739 /* then set status_id actually */
1740 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
1741 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1743 /* set our account state to the one in roaming (including calendar info) */
1744 self_uri = sip_uri_self(sip);
1745 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1746 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1747 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1750 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
1751 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1753 g_free(self_uri);
1756 static void
1757 sipe_got_user_status(struct sipe_account_data *sip,
1758 const char* uri,
1759 const char *status_id)
1761 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1763 if (!sbuddy) return;
1765 /* Check if on 2005 system contact's calendar,
1766 * then set/preserve it.
1768 if (!sip->ocs2007) {
1769 sipe_apply_calendar_status(sip, sbuddy, status_id);
1770 } else {
1771 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1775 static void
1776 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1777 struct sipe_buddy *sbuddy,
1778 struct sipe_account_data *sip)
1780 sipe_apply_calendar_status(sip, sbuddy, NULL);
1784 * Updates contact's status
1785 * based on their calendar information.
1787 * Applicability: 2005 systems
1789 static void
1790 update_calendar_status(struct sipe_account_data *sip)
1792 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
1793 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1795 /* repeat scheduling */
1796 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1800 * Schedules process of contacts' status update
1801 * based on their calendar information.
1802 * Should be scheduled to the beginning of every
1803 * 15 min interval, like:
1804 * 13:00, 13:15, 13:30, 13:45, etc.
1806 * Applicability: 2005 systems
1808 static void
1809 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1810 time_t calculate_from)
1812 int interval = 15*60;
1813 /** start of the beginning of closest 15 min interval. */
1814 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1816 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
1817 asctime(localtime(&calculate_from)));
1818 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
1819 asctime(localtime(&next_start)));
1821 sipe_schedule_action("<+2005-cal-status>",
1822 (int)(next_start - time(NULL)),
1823 (Action)update_calendar_status,
1824 NULL,
1825 sip,
1826 NULL);
1830 * Schedules process of self status publish
1831 * based on own calendar information.
1832 * Should be scheduled to the beginning of every
1833 * 15 min interval, like:
1834 * 13:00, 13:15, 13:30, 13:45, etc.
1836 * Applicability: 2007+ systems
1838 static void
1839 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1840 time_t calculate_from)
1842 int interval = 5*60;
1843 /** start of the beginning of closest 5 min interval. */
1844 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1846 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1847 asctime(localtime(&calculate_from)));
1848 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1849 asctime(localtime(&next_start)));
1851 sipe_schedule_action("<+2007-cal-status>",
1852 (int)(next_start - time(NULL)),
1853 (Action)publish_calendar_status_self,
1854 NULL,
1855 sip,
1856 NULL);
1859 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1861 /** Should be g_free()'d
1863 static gchar *
1864 sipe_get_subscription_key(const gchar *event,
1865 const gchar *with)
1867 gchar *key = NULL;
1869 if (is_empty(event)) return NULL;
1871 if (event && sipe_strcase_equal(event, "presence")) {
1872 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1873 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1875 /* @TODO drop participated buddies' just_added flag */
1876 } else if (event) {
1877 /* Subscription is identified by <event> key */
1878 key = g_strdup_printf("<%s>", event);
1881 return key;
1884 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1885 SIPE_UNUSED_PARAMETER struct transaction *trans)
1887 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1888 const gchar *event = sipmsg_find_header(msg, "Event");
1889 gchar *key;
1891 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1892 if (!event) {
1893 struct sipmsg *request_msg = trans->msg;
1894 event = sipmsg_find_header(request_msg, "Event");
1897 key = sipe_get_subscription_key(event, with);
1899 /* 200 OK; 481 Call Leg Does Not Exist */
1900 if (key && (msg->response == 200 || msg->response == 481)) {
1901 if (g_hash_table_lookup(sip->subscriptions, key)) {
1902 g_hash_table_remove(sip->subscriptions, key);
1903 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
1907 /* create/store subscription dialog if not yet */
1908 if (msg->response == 200) {
1909 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1910 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1912 if (key) {
1913 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1914 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1916 subscription->dialog.callid = g_strdup(callid);
1917 subscription->dialog.cseq = atoi(cseq);
1918 subscription->dialog.with = g_strdup(with);
1919 subscription->event = g_strdup(event);
1920 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1922 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key);
1925 g_free(cseq);
1928 g_free(key);
1929 g_free(with);
1931 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1933 process_incoming_notify(sip, msg, FALSE, FALSE);
1935 return TRUE;
1938 static void sipe_subscribe_resource_uri(const char *name,
1939 SIPE_UNUSED_PARAMETER gpointer value,
1940 gchar **resources_uri)
1942 gchar *tmp = *resources_uri;
1943 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1944 g_free(tmp);
1947 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1949 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1950 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1951 gchar *tmp = *resources_uri;
1953 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1955 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1956 g_free(tmp);
1960 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1961 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1962 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1963 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1964 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1967 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1969 gchar *key;
1970 gchar *contact = get_contact(sip);
1971 gchar *request;
1972 gchar *content;
1973 gchar *require = "";
1974 gchar *accept = "";
1975 gchar *autoextend = "";
1976 gchar *content_type;
1977 struct sip_dialog *dialog;
1979 if (sip->ocs2007) {
1980 require = ", categoryList";
1981 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1982 content_type = "application/msrtc-adrl-categorylist+xml";
1983 content = g_strdup_printf(
1984 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1985 "<action name=\"subscribe\" id=\"63792024\">\n"
1986 "<adhocList>\n%s</adhocList>\n"
1987 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1988 "<category name=\"calendarData\"/>\n"
1989 "<category name=\"contactCard\"/>\n"
1990 "<category name=\"note\"/>\n"
1991 "<category name=\"state\"/>\n"
1992 "</categoryList>\n"
1993 "</action>\n"
1994 "</batchSub>", sip->username, resources_uri);
1995 } else {
1996 autoextend = "Supported: com.microsoft.autoextend\r\n";
1997 content_type = "application/adrl+xml";
1998 content = g_strdup_printf(
1999 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
2000 "<create xmlns=\"\">\n%s</create>\n"
2001 "</adhoclist>\n", sip->username, sip->username, resources_uri);
2003 g_free(resources_uri);
2005 request = g_strdup_printf(
2006 "Require: adhoclist%s\r\n"
2007 "Supported: eventlist\r\n"
2008 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
2009 "Supported: ms-piggyback-first-notify\r\n"
2010 "%sSupported: ms-benotify\r\n"
2011 "Proxy-Require: ms-benotify\r\n"
2012 "Event: presence\r\n"
2013 "Content-Type: %s\r\n"
2014 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
2015 g_free(contact);
2017 /* subscribe to buddy presence */
2018 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2019 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2020 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2021 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2023 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2025 g_free(content);
2026 g_free(to);
2027 g_free(request);
2028 g_free(key);
2031 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
2032 SIPE_UNUSED_PARAMETER void *unused)
2034 gchar *to = sip_uri_self(sip);
2035 gchar *resources_uri = g_strdup("");
2036 if (sip->ocs2007) {
2037 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
2038 } else {
2039 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
2042 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
2045 struct presence_batched_routed {
2046 gchar *host;
2047 GSList *buddies;
2050 static void sipe_subscribe_presence_batched_routed_free(void *payload)
2052 struct presence_batched_routed *data = payload;
2053 GSList *buddies = data->buddies;
2054 while (buddies) {
2055 g_free(buddies->data);
2056 buddies = buddies->next;
2058 g_slist_free(data->buddies);
2059 g_free(data->host);
2060 g_free(payload);
2063 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
2065 struct presence_batched_routed *data = payload;
2066 GSList *buddies = data->buddies;
2067 gchar *resources_uri = g_strdup("");
2068 while (buddies) {
2069 gchar *tmp = resources_uri;
2070 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2071 g_free(tmp);
2072 buddies = buddies->next;
2074 sipe_subscribe_presence_batched_to(sip, resources_uri,
2075 g_strdup(data->host));
2079 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2080 * The user sends a single SUBSCRIBE request to the subscribed contact.
2081 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2085 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2088 gchar *key;
2089 gchar *to = sip_uri((char *)buddy_name);
2090 gchar *tmp = get_contact(sip);
2091 gchar *request;
2092 gchar *content = NULL;
2093 gchar *autoextend = "";
2094 gchar *content_type = "";
2095 struct sip_dialog *dialog;
2096 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2097 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2099 if (sbuddy) sbuddy->just_added = FALSE;
2101 if (sip->ocs2007) {
2102 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2103 } else {
2104 autoextend = "Supported: com.microsoft.autoextend\r\n";
2107 request = g_strdup_printf(
2108 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2109 "Supported: ms-piggyback-first-notify\r\n"
2110 "%s%sSupported: ms-benotify\r\n"
2111 "Proxy-Require: ms-benotify\r\n"
2112 "Event: presence\r\n"
2113 "Contact: %s\r\n", autoextend, content_type, tmp);
2115 if (sip->ocs2007) {
2116 content = g_strdup_printf(
2117 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2118 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2119 "<resource uri=\"%s\"%s\n"
2120 "</adhocList>\n"
2121 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2122 "<category name=\"calendarData\"/>\n"
2123 "<category name=\"contactCard\"/>\n"
2124 "<category name=\"note\"/>\n"
2125 "<category name=\"state\"/>\n"
2126 "</categoryList>\n"
2127 "</action>\n"
2128 "</batchSub>", sip->username, to, context);
2131 g_free(tmp);
2133 /* subscribe to buddy presence */
2134 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2135 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2136 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2137 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2139 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2141 g_free(content);
2142 g_free(to);
2143 g_free(request);
2144 g_free(key);
2147 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2149 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
2151 if (!purple_status_is_active(status))
2152 return;
2154 if (account->gc) {
2155 struct sipe_account_data *sip = account->gc->proto_data;
2157 if (sip) {
2158 gchar *action_name;
2159 gchar *tmp;
2160 time_t now = time(NULL);
2161 const char *status_id = purple_status_get_id(status);
2162 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2163 sipe_activity activity = sipe_get_activity_by_token(status_id);
2164 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2166 /* when other point of presence clears note, but we are keeping
2167 * state if OOF note.
2169 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2170 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
2171 do_not_publish = FALSE;
2174 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
2175 status_id, (int)sip->do_not_publish[activity], (int)now);
2177 sip->do_not_publish[activity] = 0;
2178 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
2179 status_id, (int)sip->do_not_publish[activity]);
2181 if (do_not_publish)
2183 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
2184 return;
2187 g_free(sip->status);
2188 sip->status = g_strdup(status_id);
2190 /* hack to escape apostrof before comparison */
2191 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
2193 /* this will preserve OOF flag as well */
2194 if (!sipe_strequal(tmp, sip->note)) {
2195 sip->is_oof_note = FALSE;
2196 g_free(sip->note);
2197 sip->note = g_strdup(note);
2198 sip->note_since = time(NULL);
2200 g_free(tmp);
2202 /* schedule 2 sec to capture idle flag */
2203 action_name = g_strdup_printf("<%s>", "+set-status");
2204 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2205 g_free(action_name);
2209 static void
2210 sipe_set_idle(PurpleConnection * gc,
2211 int interval)
2213 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
2215 if (gc) {
2216 struct sipe_account_data *sip = gc->proto_data;
2218 if (sip) {
2219 sip->idle_switch = time(NULL);
2220 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2225 static void
2226 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2227 SIPE_UNUSED_PARAMETER const char *alias)
2229 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2230 sipe_group_set_user(sip, name);
2233 static void
2234 sipe_group_buddy(PurpleConnection *gc,
2235 const char *who,
2236 const char *old_group_name,
2237 const char *new_group_name)
2239 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2240 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2241 struct sipe_group * old_group = NULL;
2242 struct sipe_group * new_group;
2244 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
2245 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2247 if(!buddy) { // buddy not in roaming list
2248 return;
2251 if (old_group_name) {
2252 old_group = sipe_group_find_by_name(sip, old_group_name);
2254 new_group = sipe_group_find_by_name(sip, new_group_name);
2256 if (old_group) {
2257 buddy->groups = g_slist_remove(buddy->groups, old_group);
2258 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
2261 if (!new_group) {
2262 sipe_group_create(sip, new_group_name, who);
2263 } else {
2264 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2265 sipe_group_set_user(sip, who);
2269 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2271 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2273 /* libpurple can call us with undefined buddy or group */
2274 if (buddy && group) {
2275 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2277 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2278 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2279 purple_blist_rename_buddy(buddy, buddy_name);
2280 g_free(buddy_name);
2282 /* Prepend sip: if needed */
2283 if (!g_str_has_prefix(buddy->name, "sip:")) {
2284 gchar *buf = sip_uri_from_name(buddy->name);
2285 purple_blist_rename_buddy(buddy, buf);
2286 g_free(buf);
2289 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2290 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2291 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
2292 b->name = g_strdup(buddy->name);
2293 b->just_added = TRUE;
2294 g_hash_table_insert(sip->buddies, b->name, b);
2295 sipe_group_buddy(gc, b->name, NULL, group->name);
2296 /* @TODO should go to callback */
2297 sipe_subscribe_presence_single(sip, b->name);
2298 } else {
2299 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
2304 static void sipe_free_buddy(struct sipe_buddy *buddy)
2306 #ifndef _WIN32
2308 * We are calling g_hash_table_foreach_steal(). That means that no
2309 * key/value deallocation functions are called. Therefore the glib
2310 * hash code does not touch the key (buddy->name) or value (buddy)
2311 * of the to-be-deleted hash node at all. It follows that we
2313 * - MUST free the memory for the key ourselves and
2314 * - ARE allowed to do it in this function
2316 * Conclusion: glib must be broken on the Windows platform if sipe
2317 * crashes with SIGTRAP when closing. You'll have to live
2318 * with the memory leak until this is fixed.
2320 g_free(buddy->name);
2321 #endif
2322 g_free(buddy->activity);
2323 g_free(buddy->meeting_subject);
2324 g_free(buddy->meeting_location);
2325 g_free(buddy->note);
2327 g_free(buddy->cal_start_time);
2328 g_free(buddy->cal_free_busy_base64);
2329 g_free(buddy->cal_free_busy);
2330 g_free(buddy->last_non_cal_activity);
2332 sipe_cal_free_working_hours(buddy->cal_working_hours);
2334 g_free(buddy->device_name);
2335 g_slist_free(buddy->groups);
2336 g_free(buddy);
2340 * Unassociates buddy from group first.
2341 * Then see if no groups left, removes buddy completely.
2342 * Otherwise updates buddy groups on server.
2344 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2346 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2347 struct sipe_buddy *b;
2348 struct sipe_group *g = NULL;
2350 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2351 if (!buddy) return;
2353 b = g_hash_table_lookup(sip->buddies, buddy->name);
2354 if (!b) return;
2356 if (group) {
2357 g = sipe_group_find_by_name(sip, group->name);
2360 if (g) {
2361 b->groups = g_slist_remove(b->groups, g);
2362 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
2365 if (g_slist_length(b->groups) < 1) {
2366 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2367 sipe_cancel_scheduled_action(sip, action_name);
2368 g_free(action_name);
2370 g_hash_table_remove(sip->buddies, buddy->name);
2372 if (b->name) {
2373 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2374 send_soap_request(sip, body);
2375 g_free(body);
2378 sipe_free_buddy(b);
2379 } else {
2380 //updates groups on server
2381 sipe_group_set_user(sip, b->name);
2386 static void
2387 sipe_rename_group(PurpleConnection *gc,
2388 const char *old_name,
2389 PurpleGroup *group,
2390 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2392 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2393 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2394 if (s_group) {
2395 sipe_group_rename(sip, s_group, group->name);
2396 } else {
2397 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
2401 static void
2402 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2404 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2405 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2406 if (s_group) {
2407 gchar *body;
2408 SIPE_DEBUG_INFO("Deleting group %s", group->name);
2409 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2410 send_soap_request(sip, body);
2411 g_free(body);
2413 sip->groups = g_slist_remove(sip->groups, s_group);
2414 g_free(s_group->name);
2415 g_free(s_group);
2416 } else {
2417 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
2421 /** All statuses need message attribute to pass Note */
2422 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2424 PurpleStatusType *type;
2425 GList *types = NULL;
2427 /* Macros to reduce code repetition.
2428 Translators: noun */
2429 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2430 prim, id, name, \
2431 TRUE, user, FALSE, \
2432 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2433 NULL); \
2434 types = g_list_append(types, type);
2436 /* Online */
2437 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2438 NULL,
2439 NULL,
2440 TRUE);
2442 /* Busy */
2443 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2444 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2445 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2446 TRUE);
2448 /* Do Not Disturb */
2449 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2450 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2451 NULL,
2452 TRUE);
2454 /* Away */
2455 /* Goes first in the list as
2456 * purple picks the first status with the AWAY type
2457 * for idle.
2459 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2460 NULL,
2461 NULL,
2462 TRUE);
2464 /* Be Right Back */
2465 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2466 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2467 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2468 TRUE);
2470 /* Appear Offline */
2471 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2472 NULL,
2473 NULL,
2474 TRUE);
2476 /* Offline */
2477 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2478 NULL,
2479 NULL,
2480 TRUE);
2481 types = g_list_append(types, type);
2483 return types;
2487 * A callback for g_hash_table_foreach
2489 static void
2490 sipe_buddy_subscribe_cb(char *buddy_name,
2491 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2492 struct sipe_account_data *sip)
2494 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2495 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2496 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2497 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2499 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2500 g_free(action_name);
2504 * Removes entries from purple buddy list
2505 * that does not correspond ones in the roaming contact list.
2507 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2508 GSList *buddies = purple_find_buddies(sip->account, NULL);
2509 GSList *entry = buddies;
2510 struct sipe_buddy *buddy;
2511 PurpleBuddy *b;
2512 PurpleGroup *g;
2514 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
2515 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sip->buddies));
2516 while (entry) {
2517 b = entry->data;
2518 g = purple_buddy_get_group(b);
2519 buddy = g_hash_table_lookup(sip->buddies, b->name);
2520 if(buddy) {
2521 gboolean in_sipe_groups = FALSE;
2522 GSList *entry2 = buddy->groups;
2523 while (entry2) {
2524 struct sipe_group *group = entry2->data;
2525 if (sipe_strequal(group->name, g->name)) {
2526 in_sipe_groups = TRUE;
2527 break;
2529 entry2 = entry2->next;
2531 if(!in_sipe_groups) {
2532 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
2533 purple_blist_remove_buddy(b);
2535 } else {
2536 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
2537 purple_blist_remove_buddy(b);
2539 entry = entry->next;
2541 g_slist_free(buddies);
2544 static int
2545 sipe_find_access_level(struct sipe_account_data *sip,
2546 const gchar *type,
2547 const gchar *value,
2548 gboolean *is_group_access);
2550 static void
2551 sipe_refresh_blocked_status_cb(char *buddy_name,
2552 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2553 struct sipe_account_data *sip)
2555 int container_id = sipe_find_access_level(sip, "user", buddy_name, NULL);
2556 gboolean blocked = (container_id == 32000);
2557 gboolean blocked_in_blist = !purple_privacy_check(sip->account, buddy_name);
2559 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
2560 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
2562 if (blocked != blocked_in_blist) {
2563 if (blocked) {
2564 purple_privacy_deny_add(sip->account, buddy_name, TRUE);
2565 } else {
2566 purple_privacy_deny_remove(sip->account, buddy_name, TRUE);
2569 /* stupid workaround to make pidgin re-render screen to reflect our changes */
2571 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, buddy_name);
2572 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
2573 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
2575 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
2576 sipe_got_user_status(sip, buddy_name, purple_status_get_id(pstatus));
2582 static void
2583 sipe_refresh_blocked_status(struct sipe_account_data *sip)
2585 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_refresh_blocked_status_cb , (gpointer)sip);
2588 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2590 int len = msg->bodylen;
2592 const gchar *tmp = sipmsg_find_header(msg, "Event");
2593 const sipe_xml *item;
2594 sipe_xml *isc;
2595 const gchar *contacts_delta;
2596 const sipe_xml *group_node;
2597 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2598 return FALSE;
2601 /* Convert the contact from XML to Purple Buddies */
2602 isc = sipe_xml_parse(msg->body, len);
2603 if (!isc) {
2604 return FALSE;
2607 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
2608 if (contacts_delta) {
2609 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2612 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
2614 /* Parse groups */
2615 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
2616 struct sipe_group * group = g_new0(struct sipe_group, 1);
2617 const char *name = sipe_xml_attribute(group_node, "name");
2619 if (g_str_has_prefix(name, "~")) {
2620 name = _("Other Contacts");
2622 group->name = g_strdup(name);
2623 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
2625 sipe_group_add(sip, group);
2628 // Make sure we have at least one group
2629 if (g_slist_length(sip->groups) == 0) {
2630 struct sipe_group * group = g_new0(struct sipe_group, 1);
2631 PurpleGroup *purple_group;
2632 group->name = g_strdup(_("Other Contacts"));
2633 group->id = 1;
2634 purple_group = purple_group_new(group->name);
2635 purple_blist_add_group(purple_group, NULL);
2636 sip->groups = g_slist_append(sip->groups, group);
2639 /* Parse contacts */
2640 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
2641 const gchar *uri = sipe_xml_attribute(item, "uri");
2642 const gchar *name = sipe_xml_attribute(item, "name");
2643 gchar *buddy_name;
2644 struct sipe_buddy *buddy = NULL;
2645 gchar *tmp;
2646 gchar **item_groups;
2647 int i = 0;
2649 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2650 tmp = sip_uri_from_name(uri);
2651 buddy_name = g_ascii_strdown(tmp, -1);
2652 g_free(tmp);
2654 /* assign to group Other Contacts if nothing else received */
2655 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
2656 if(is_empty(tmp)) {
2657 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2658 g_free(tmp);
2659 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2661 item_groups = g_strsplit(tmp, " ", 0);
2662 g_free(tmp);
2664 while (item_groups[i]) {
2665 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2667 // If couldn't find the right group for this contact, just put them in the first group we have
2668 if (group == NULL && g_slist_length(sip->groups) > 0) {
2669 group = sip->groups->data;
2672 if (group != NULL) {
2673 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2674 if (!b){
2675 b = purple_buddy_new(sip->account, buddy_name, uri);
2676 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2678 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
2681 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2682 if (name != NULL && strlen(name) != 0) {
2683 purple_blist_alias_buddy(b, name);
2685 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
2689 if (!buddy) {
2690 buddy = g_new0(struct sipe_buddy, 1);
2691 buddy->name = g_strdup(b->name);
2692 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2695 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2697 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
2698 } else {
2699 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2700 name);
2703 i++;
2704 } // while, contact groups
2705 g_strfreev(item_groups);
2706 g_free(buddy_name);
2708 } // for, contacts
2710 sipe_cleanup_local_blist(sip);
2712 /* Add self-contact if not there yet. 2005 systems. */
2713 /* This will resemble subscription to roaming_self in 2007 systems */
2714 if (!sip->ocs2007) {
2715 gchar *self_uri = sip_uri_self(sip);
2716 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2718 if (!buddy) {
2719 buddy = g_new0(struct sipe_buddy, 1);
2720 buddy->name = g_strdup(self_uri);
2721 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2723 g_free(self_uri);
2726 sipe_xml_free(isc);
2728 /* subscribe to buddies */
2729 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2730 if (sip->batched_support) {
2731 sipe_subscribe_presence_batched(sip, NULL);
2732 } else {
2733 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2735 sip->subscribed_buddies = TRUE;
2737 /* for 2005 systems schedule contacts' status update
2738 * based on their calendar information
2740 if (!sip->ocs2007) {
2741 sipe_sched_calendar_status_update(sip, time(NULL));
2744 return 0;
2748 * Subscribe roaming contacts
2750 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2752 gchar *to = sip_uri_self(sip);
2753 gchar *tmp = get_contact(sip);
2754 gchar *hdr = g_strdup_printf(
2755 "Event: vnd-microsoft-roaming-contacts\r\n"
2756 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2757 "Supported: com.microsoft.autoextend\r\n"
2758 "Supported: ms-benotify\r\n"
2759 "Proxy-Require: ms-benotify\r\n"
2760 "Supported: ms-piggyback-first-notify\r\n"
2761 "Contact: %s\r\n", tmp);
2762 g_free(tmp);
2764 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2765 g_free(to);
2766 g_free(hdr);
2769 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2770 SIPE_UNUSED_PARAMETER void *unused)
2772 gchar *key;
2773 struct sip_dialog *dialog;
2774 gchar *to = sip_uri_self(sip);
2775 gchar *tmp = get_contact(sip);
2776 gchar *hdr = g_strdup_printf(
2777 "Event: presence.wpending\r\n"
2778 "Accept: text/xml+msrtc.wpending\r\n"
2779 "Supported: com.microsoft.autoextend\r\n"
2780 "Supported: ms-benotify\r\n"
2781 "Proxy-Require: ms-benotify\r\n"
2782 "Supported: ms-piggyback-first-notify\r\n"
2783 "Contact: %s\r\n", tmp);
2784 g_free(tmp);
2786 /* Subscription is identified by <event> key */
2787 key = g_strdup_printf("<%s>", "presence.wpending");
2788 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2789 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2791 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2793 g_free(to);
2794 g_free(hdr);
2795 g_free(key);
2799 * Fires on deregistration event initiated by server.
2800 * [MS-SIPREGE] SIP extension.
2803 // 2007 Example
2805 // Content-Type: text/registration-event
2806 // subscription-state: terminated;expires=0
2807 // ms-diagnostics-public: 4141;reason="User disabled"
2809 // deregistered;event=rejected
2811 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2813 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2814 gchar *event = NULL;
2815 gchar *reason = NULL;
2816 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2817 gchar *warning;
2819 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2820 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2822 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2823 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2824 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2825 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2826 } else {
2827 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2828 return;
2831 if (diagnostics != NULL) {
2832 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2833 } else { // for LCS2005
2834 int error_id = 0;
2835 if (event && sipe_strcase_equal(event, "unregistered")) {
2836 error_id = 4140; // [MS-SIPREGE]
2837 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2838 reason = g_strdup(_("you are already signed in at another location"));
2839 } else if (event && sipe_strcase_equal(event, "rejected")) {
2840 error_id = 4141;
2841 reason = g_strdup(_("user disabled")); // [MS-OCER]
2842 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2843 error_id = 4142;
2844 reason = g_strdup(_("user moved")); // [MS-OCER]
2847 g_free(event);
2848 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2849 g_free(reason);
2851 sip->gc->wants_to_die = TRUE;
2852 purple_connection_error(sip->gc, warning);
2853 g_free(warning);
2857 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2859 sipe_xml *xn_provision_group_list;
2860 const sipe_xml *node;
2862 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
2864 /* provisionGroup */
2865 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
2866 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
2867 g_free(sip->focus_factory_uri);
2868 sip->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
2869 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2870 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2871 break;
2874 sipe_xml_free(xn_provision_group_list);
2877 /** for 2005 system */
2878 static void
2879 sipe_process_provisioning(struct sipe_account_data *sip,
2880 struct sipmsg *msg)
2882 sipe_xml *xn_provision;
2883 const sipe_xml *node;
2885 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
2886 if ((node = sipe_xml_child(xn_provision, "user"))) {
2887 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
2888 if ((node = sipe_xml_child(node, "line"))) {
2889 const gchar *line_uri = sipe_xml_attribute(node, "uri");
2890 const gchar *server = sipe_xml_attribute(node, "server");
2891 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2892 sip_csta_open(sip, line_uri, server);
2895 sipe_xml_free(xn_provision);
2898 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2900 const gchar *contacts_delta;
2901 sipe_xml *xml;
2903 xml = sipe_xml_parse(msg->body, msg->bodylen);
2904 if (!xml)
2906 return;
2909 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
2910 if (contacts_delta)
2912 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2915 sipe_xml_free(xml);
2918 static void
2919 free_container_member(struct sipe_container_member *member)
2921 if (!member) return;
2923 g_free(member->type);
2924 g_free(member->value);
2925 g_free(member);
2928 static void
2929 free_container(struct sipe_container *container)
2931 GSList *entry;
2933 if (!container) return;
2935 entry = container->members;
2936 while (entry) {
2937 void *data = entry->data;
2938 entry = g_slist_remove(entry, data);
2939 free_container_member((struct sipe_container_member *)data);
2941 g_free(container);
2944 static void
2945 sipe_send_container_members_prepare(const guint container_id,
2946 const guint container_version,
2947 const gchar *action,
2948 const gchar *type,
2949 const gchar *value,
2950 char **container_xmls)
2952 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2953 gchar *body;
2955 if (!container_xmls) return;
2957 body = g_strdup_printf(
2958 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
2959 container_id,
2960 container_version,
2961 action,
2962 type,
2963 value_str);
2964 g_free(value_str);
2966 if ((*container_xmls) == NULL) {
2967 *container_xmls = body;
2968 } else {
2969 char *tmp = *container_xmls;
2971 *container_xmls = g_strconcat(*container_xmls, body, NULL);
2972 g_free(tmp);
2973 g_free(body);
2977 static void
2978 sipe_send_set_container_members(struct sipe_account_data *sip,
2979 char *container_xmls)
2981 gchar *self;
2982 gchar *contact;
2983 gchar *hdr;
2984 gchar *body;
2986 if (!container_xmls) return;
2988 self = sip_uri_self(sip);
2989 body = g_strdup_printf(
2990 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2991 "%s"
2992 "</setContainerMembers>",
2993 container_xmls);
2995 contact = get_contact(sip);
2996 hdr = g_strdup_printf("Contact: %s\r\n"
2997 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2998 g_free(contact);
3000 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
3002 g_free(hdr);
3003 g_free(body);
3004 g_free(self);
3008 * Finds locally stored MS-PRES container member
3010 static struct sipe_container_member *
3011 sipe_find_container_member(struct sipe_container *container,
3012 const gchar *type,
3013 const gchar *value)
3015 struct sipe_container_member *member;
3016 GSList *entry;
3018 if (container == NULL || type == NULL) {
3019 return NULL;
3022 entry = container->members;
3023 while (entry) {
3024 member = entry->data;
3025 if (sipe_strcase_equal(member->type, type) &&
3026 sipe_strcase_equal(member->value, value))
3028 return member;
3030 entry = entry->next;
3032 return NULL;
3036 * Finds locally stored MS-PRES container by id
3038 static struct sipe_container *
3039 sipe_find_container(struct sipe_account_data *sip,
3040 guint id)
3042 struct sipe_container *container;
3043 GSList *entry;
3045 if (sip == NULL) {
3046 return NULL;
3049 entry = sip->containers;
3050 while (entry) {
3051 container = entry->data;
3052 if (id == container->id) {
3053 return container;
3055 entry = entry->next;
3057 return NULL;
3060 static GSList *
3061 sipe_get_access_domains(struct sipe_account_data *sip)
3063 struct sipe_container *container;
3064 struct sipe_container_member *member;
3065 GSList *entry;
3066 GSList *entry2;
3067 GSList *res = NULL;
3069 if (!sip) return NULL;
3071 entry = sip->containers;
3072 while (entry) {
3073 container = entry->data;
3075 entry2 = container->members;
3076 while (entry2) {
3077 member = entry2->data;
3078 if (sipe_strcase_equal(member->type, "domain"))
3080 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
3082 entry2 = entry2->next;
3084 entry = entry->next;
3086 return res;
3090 * Returns pointer to domain part in provided Email URL
3092 * @param email an email URL. Example: first.last@hq.company.com
3093 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
3095 * Doesn't allocate memory
3097 static const char *
3098 sipe_get_domain(const char *email)
3100 char *tmp;
3102 if (!email) return NULL;
3104 tmp = strstr(email, "@");
3106 if (tmp && ((tmp+1) < (email + strlen(email)))) {
3107 return tmp+1;
3108 } else {
3109 return NULL;
3114 /* @TODO: replace with binary search for faster access? */
3115 /** source: http://support.microsoft.com/kb/897567 */
3116 static const char * const public_domains [] = {
3117 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
3118 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
3119 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
3120 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
3121 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
3122 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
3123 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
3124 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
3125 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
3126 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
3127 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
3128 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
3129 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
3130 "yahoo.com",
3131 NULL};
3133 static gboolean
3134 sipe_is_public_domain(const char *domain)
3136 int i = 0;
3137 while (public_domains[i]) {
3138 if (sipe_strcase_equal(public_domains[i], domain)) {
3139 return TRUE;
3141 i++;
3143 return FALSE;
3147 * Access Levels
3148 * 32000 - Blocked
3149 * 400 - Personal
3150 * 300 - Team
3151 * 200 - Company
3152 * 100 - Public
3154 static const char *
3155 sipe_get_access_level_name(int container_id)
3157 switch(container_id) {
3158 case 32000: return _("Blocked");
3159 case 400: return _("Personal");
3160 case 300: return _("Team");
3161 case 200: return _("Company");
3162 case 100: return _("Public");
3164 return _("Unknown");
3167 static const guint containers[] = {32000, 400, 300, 200, 100};
3168 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
3171 static int
3172 sipe_find_member_access_level(struct sipe_account_data *sip,
3173 const gchar *type,
3174 const gchar *value)
3176 unsigned int i = 0;
3177 const gchar *value_mod = value;
3179 if (!type) return -1;
3181 if (sipe_strequal("user", type)) {
3182 value_mod = sipe_get_no_sip_uri(value);
3185 for (i = 0; i < CONTAINERS_LEN; i++) {
3186 struct sipe_container_member *member;
3187 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3188 if (!container) continue;
3190 member = sipe_find_container_member(container, type, value_mod);
3191 if (member) return containers[i];
3194 return -1;
3197 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
3198 static int
3199 sipe_find_access_level(struct sipe_account_data *sip,
3200 const gchar *type,
3201 const gchar *value,
3202 gboolean *is_group_access)
3204 int container_id = -1;
3206 if (sipe_strequal("user", type)) {
3207 const char *domain;
3208 const char *no_sip_uri = sipe_get_no_sip_uri(value);
3210 container_id = sipe_find_member_access_level(sip, "user", no_sip_uri);
3211 if (container_id >= 0) {
3212 if (is_group_access) *is_group_access = FALSE;
3213 return container_id;
3216 domain = sipe_get_domain(no_sip_uri);
3217 container_id = sipe_find_member_access_level(sip, "domain", domain);
3218 if (container_id >= 0) {
3219 if (is_group_access) *is_group_access = TRUE;
3220 return container_id;
3223 container_id = sipe_find_member_access_level(sip, "sameEnterprise", NULL);
3224 if ((container_id >= 0) && sipe_strcase_equal(sip->sipdomain, domain)) {
3225 if (is_group_access) *is_group_access = TRUE;
3226 return container_id;
3229 container_id = sipe_find_member_access_level(sip, "publicCloud", NULL);
3230 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
3231 if (is_group_access) *is_group_access = TRUE;
3232 return container_id;
3235 container_id = sipe_find_member_access_level(sip, "everyone", NULL);
3236 if ((container_id >= 0)) {
3237 if (is_group_access) *is_group_access = TRUE;
3238 return container_id;
3240 } else {
3241 container_id = sipe_find_member_access_level(sip, type, value);
3242 if (is_group_access) *is_group_access = FALSE;
3245 return container_id;
3249 * @param container_id a new access level. If -1 then current access level
3250 * is just removed (I.e. the member is removed from all containers).
3251 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
3252 * @param value a value for member. E.g. SIP URI for "user" member type.
3254 static void
3255 sipe_change_access_level(struct sipe_account_data *sip,
3256 const int container_id,
3257 const gchar *type,
3258 const gchar *value)
3260 unsigned int i;
3261 int current_container_id = -1;
3262 char *container_xmls = NULL;
3264 /* for each container: find/delete */
3265 for (i = 0; i < CONTAINERS_LEN; i++) {
3266 struct sipe_container_member *member;
3267 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3269 if (!container) continue;
3271 member = sipe_find_container_member(container, type, value);
3272 if (member) {
3273 current_container_id = containers[i];
3274 /* delete/publish current access level */
3275 if (container_id < 0 || container_id != current_container_id) {
3276 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
3277 /* remove member from our cache, to be able to recalculate AL below */
3278 container->members = g_slist_remove(container->members, member);
3279 current_container_id = -1;
3284 /* recalculate AL below */
3285 current_container_id = sipe_find_access_level(sip, type, value, NULL);
3287 /* assign/publish new access level */
3288 if (container_id != current_container_id && container_id >= 0) {
3289 struct sipe_container *container = sipe_find_container(sip, container_id);
3290 guint version = container ? container->version : 0;
3292 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
3295 if (container_xmls) {
3296 sipe_send_set_container_members(sip, container_xmls);
3298 g_free(container_xmls);
3301 static void
3302 free_publication(struct sipe_publication *publication)
3304 g_free(publication->category);
3305 g_free(publication->cal_event_hash);
3306 g_free(publication->note);
3308 g_free(publication->working_hours_xml_str);
3309 g_free(publication->fb_start_str);
3310 g_free(publication->free_busy_base64);
3312 g_free(publication);
3315 /* key is <category><instance><container> */
3316 static gboolean
3317 sipe_is_our_publication(struct sipe_account_data *sip,
3318 const gchar *key)
3320 GSList *entry;
3322 /* filling keys for our publications if not yet cached */
3323 if (!sip->our_publication_keys) {
3324 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
3325 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
3326 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
3327 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
3328 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
3329 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
3330 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
3332 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
3333 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
3334 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
3335 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
3336 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
3337 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
3338 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
3339 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
3340 SIPE_DEBUG_INFO("\tNote : %u", 0);
3341 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
3343 /* device */
3344 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3345 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
3347 /* state:machineState */
3348 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3349 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
3350 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3351 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3353 /* state:userState */
3354 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3355 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3356 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3357 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3359 /* state:calendarState */
3360 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3361 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3362 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3363 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3365 /* state:calendarState OOF */
3366 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3367 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3368 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3369 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3371 /* note */
3372 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3373 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3374 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3375 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3376 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3377 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3379 /* note OOF */
3380 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3381 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3382 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3383 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3384 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3385 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3387 /* calendarData:WorkingHours */
3388 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3389 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3390 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3391 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3392 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3393 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3394 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3395 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3396 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3397 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3398 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3399 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3401 /* calendarData:FreeBusy */
3402 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3403 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3404 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3405 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3406 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3407 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3408 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3409 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3410 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3411 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3412 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3413 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3415 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3416 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3419 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3421 entry = sip->our_publication_keys;
3422 while (entry) {
3423 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3424 if (sipe_strequal(entry->data, key)) {
3425 return TRUE;
3427 entry = entry->next;
3429 return FALSE;
3432 /** Property names to store in blist.xml */
3433 #define ALIAS_PROP "alias"
3434 #define EMAIL_PROP "email"
3435 #define PHONE_PROP "phone"
3436 #define PHONE_DISPLAY_PROP "phone-display"
3437 #define PHONE_MOBILE_PROP "phone-mobile"
3438 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3439 #define PHONE_HOME_PROP "phone-home"
3440 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3441 #define PHONE_OTHER_PROP "phone-other"
3442 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3443 #define PHONE_CUSTOM1_PROP "phone-custom1"
3444 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3445 #define SITE_PROP "site"
3446 #define COMPANY_PROP "company"
3447 #define DEPARTMENT_PROP "department"
3448 #define TITLE_PROP "title"
3449 #define OFFICE_PROP "office"
3450 /** implies work address */
3451 #define ADDRESS_STREET_PROP "address-street"
3452 #define ADDRESS_CITY_PROP "address-city"
3453 #define ADDRESS_STATE_PROP "address-state"
3454 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3455 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3458 * Tries to figure out user first and last name
3459 * based on Display Name and email properties.
3461 * Allocates memory - must be g_free()'d
3463 * Examples to parse:
3464 * First Last
3465 * First Last - Company Name
3466 * Last, First
3467 * Last, First M.
3468 * Last, First (C)(STP) (Company)
3469 * first.last@company.com (preprocessed as "first last")
3470 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3472 * Unusable examples:
3473 * user@company.com (preprocessed as "user")
3474 * first.m.last@company.com (preprocessed as "first m last")
3475 * user.company.com@reuters.net (preprocessed as "user company com")
3477 static void
3478 sipe_get_first_last_names(struct sipe_account_data *sip,
3479 const char *uri,
3480 char **first_name,
3481 char **last_name)
3483 PurpleBuddy *p_buddy;
3484 char *display_name;
3485 const char *email;
3486 const char *first, *last;
3487 char *tmp;
3488 char **parts;
3489 gboolean has_comma = FALSE;
3491 if (!sip || !uri) return;
3493 p_buddy = purple_find_buddy(sip->account, uri);
3495 if (!p_buddy) return;
3497 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3498 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3500 if (!display_name && !email) return;
3502 /* if no display name, make "first last anything_else" out of email */
3503 if (email && !display_name) {
3504 display_name = g_strndup(email, strstr(email, "@") - email);
3505 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
3506 g_free(tmp);
3509 if (display_name) {
3510 has_comma = (strstr(display_name, ",") != NULL);
3511 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
3512 g_free(tmp);
3513 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
3514 g_free(tmp);
3517 parts = g_strsplit(display_name, " ", 0);
3519 if (!parts[0] || !parts[1]) {
3520 g_free(display_name);
3521 g_strfreev(parts);
3522 return;
3525 if (has_comma) {
3526 last = parts[0];
3527 first = parts[1];
3528 } else {
3529 first = parts[0];
3530 last = parts[1];
3533 if (first_name) {
3534 *first_name = g_strstrip(g_strdup(first));
3537 if (last_name) {
3538 *last_name = g_strstrip(g_strdup(last));
3541 g_free(display_name);
3542 g_strfreev(parts);
3546 * Update user information
3548 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3549 * @param property_name
3550 * @param property_value may be modified to strip white space
3552 static void
3553 sipe_update_user_info(struct sipe_account_data *sip,
3554 const char *uri,
3555 const char *property_name,
3556 char *property_value)
3558 GSList *buddies, *entry;
3560 if (!property_name || strlen(property_name) == 0) return;
3562 if (property_value)
3563 property_value = g_strstrip(property_value);
3565 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3566 while (entry) {
3567 const char *prop_str;
3568 const char *server_alias;
3569 PurpleBuddy *p_buddy = entry->data;
3571 /* for Display Name */
3572 if (sipe_strequal(property_name, ALIAS_PROP)) {
3573 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3574 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
3575 purple_blist_alias_buddy(p_buddy, property_value);
3578 server_alias = purple_buddy_get_server_alias(p_buddy);
3579 if (!is_empty(property_value) &&
3580 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3582 purple_blist_server_alias_buddy(p_buddy, property_value);
3585 /* for other properties */
3586 else {
3587 if (!is_empty(property_value)) {
3588 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3589 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3590 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3595 entry = entry->next;
3597 g_slist_free(buddies);
3601 * Update user phone
3602 * Suitable for both 2005 and 2007 systems.
3604 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3605 * @param phone_type
3606 * @param phone may be modified to strip white space
3607 * @param phone_display_string may be modified to strip white space
3609 static void
3610 sipe_update_user_phone(struct sipe_account_data *sip,
3611 const char *uri,
3612 const gchar *phone_type,
3613 gchar *phone,
3614 gchar *phone_display_string)
3616 const char *phone_node = PHONE_PROP; /* work phone by default */
3617 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3619 if(!phone || strlen(phone) == 0) return;
3621 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3622 phone_node = PHONE_MOBILE_PROP;
3623 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3624 } else if (sipe_strequal(phone_type, "home")) {
3625 phone_node = PHONE_HOME_PROP;
3626 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3627 } else if (sipe_strequal(phone_type, "other")) {
3628 phone_node = PHONE_OTHER_PROP;
3629 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3630 } else if (sipe_strequal(phone_type, "custom1")) {
3631 phone_node = PHONE_CUSTOM1_PROP;
3632 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3635 sipe_update_user_info(sip, uri, phone_node, phone);
3636 if (phone_display_string) {
3637 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3641 void
3642 sipe_core_update_calendar(struct sipe_account_data *sip)
3644 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3646 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: started.");
3648 if (sipe_strequal(calendar, "EXCH")) {
3649 sipe_ews_update_calendar(sip);
3652 /* schedule repeat */
3653 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_core_update_calendar, NULL, sip, NULL);
3655 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: finished.");
3659 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3660 * by using standard Purple's means of signals and saved statuses.
3662 * Thus all UI elements get updated: Status Button with Note, docklet.
3663 * This is ablolutely important as both our status and note can come
3664 * inbound (roaming) or be updated programmatically (e.g. based on our
3665 * calendar data).
3667 static void
3668 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3669 const char *status_id,
3670 const char *message,
3671 time_t do_not_publish[])
3673 PurpleStatus *status = purple_account_get_active_status(account);
3674 gboolean changed = TRUE;
3676 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3677 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3679 changed = FALSE;
3682 if (purple_savedstatus_is_idleaway()) {
3683 changed = FALSE;
3686 if (changed) {
3687 PurpleSavedStatus *saved_status;
3688 const PurpleStatusType *acct_status_type =
3689 purple_status_type_find_with_id(account->status_types, status_id);
3690 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3691 sipe_activity activity = sipe_get_activity_by_token(status_id);
3693 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3694 if (saved_status) {
3695 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3698 /* If this type+message is unique then create a new transient saved status
3699 * Ref: gtkstatusbox.c
3701 if (!saved_status) {
3702 GList *tmp;
3703 GList *active_accts = purple_accounts_get_all_active();
3705 saved_status = purple_savedstatus_new(NULL, primitive);
3706 purple_savedstatus_set_message(saved_status, message);
3708 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3709 purple_savedstatus_set_substatus(saved_status,
3710 (PurpleAccount *)tmp->data, acct_status_type, message);
3712 g_list_free(active_accts);
3715 do_not_publish[activity] = time(NULL);
3716 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3717 status_id, (int)do_not_publish[activity]);
3719 /* Set the status for each account */
3720 purple_savedstatus_activate(saved_status);
3724 struct hash_table_delete_payload {
3725 GHashTable *hash_table;
3726 guint container;
3729 static void
3730 sipe_remove_category_container_publications_cb(const char *name,
3731 struct sipe_publication *publication,
3732 struct hash_table_delete_payload *payload)
3734 if (publication->container == payload->container) {
3735 g_hash_table_remove(payload->hash_table, name);
3738 static void
3739 sipe_remove_category_container_publications(GHashTable *our_publications,
3740 const char *category,
3741 guint container)
3743 struct hash_table_delete_payload payload;
3744 payload.hash_table = g_hash_table_lookup(our_publications, category);
3746 if (!payload.hash_table) return;
3748 payload.container = container;
3749 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3752 static void
3753 send_publish_category_initial(struct sipe_account_data *sip);
3756 * When we receive some self (BE) NOTIFY with a new subscriber
3757 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3760 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3762 gchar *contact;
3763 gchar *to;
3764 sipe_xml *xml;
3765 const sipe_xml *node;
3766 const sipe_xml *node2;
3767 char *display_name = NULL;
3768 char *uri;
3769 GSList *category_names = NULL;
3770 int aggreg_avail = 0;
3771 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3772 gboolean do_update_status = FALSE;
3773 gboolean has_note_cleaned = FALSE;
3775 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3777 xml = sipe_xml_parse(msg->body, msg->bodylen);
3778 if (!xml) return;
3780 contact = get_contact(sip);
3781 to = sip_uri_self(sip);
3784 /* categories */
3785 /* set list of categories participating in this XML */
3786 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3787 const gchar *name = sipe_xml_attribute(node, "name");
3788 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3790 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3791 category_names ? (int) g_slist_length(category_names) : -1);
3792 /* drop category information */
3793 if (category_names) {
3794 GSList *entry = category_names;
3795 while (entry) {
3796 GHashTable *cat_publications;
3797 const gchar *category = entry->data;
3798 entry = entry->next;
3799 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
3800 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3801 if (cat_publications) {
3802 g_hash_table_remove(sip->our_publications, category);
3803 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
3807 g_slist_free(category_names);
3808 /* filling our categories reflected in roaming data */
3809 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3810 const char *tmp;
3811 const gchar *name = sipe_xml_attribute(node, "name");
3812 guint container = sipe_xml_int_attribute(node, "container", -1);
3813 guint instance = sipe_xml_int_attribute(node, "instance", -1);
3814 guint version = sipe_xml_int_attribute(node, "version", 0);
3815 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
3816 sipe_utils_str_to_time(tmp) : 0;
3817 gchar *key;
3818 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3820 /* Ex. clear note: <category name="note"/> */
3821 if (container == (guint)-1) {
3822 g_free(sip->note);
3823 sip->note = NULL;
3824 do_update_status = TRUE;
3825 continue;
3828 /* Ex. clear note: <category name="note" container="200"/> */
3829 if (instance == (guint)-1) {
3830 if (container == 200) {
3831 g_free(sip->note);
3832 sip->note = NULL;
3833 do_update_status = TRUE;
3835 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
3836 sipe_remove_category_container_publications(
3837 sip->our_publications, name, container);
3838 continue;
3841 /* key is <category><instance><container> */
3842 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3843 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
3845 /* capture all userState publication for later clean up if required */
3846 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3847 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3849 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
3850 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3851 publication->category = g_strdup(name);
3852 publication->instance = instance;
3853 publication->container = container;
3854 publication->version = version;
3856 if (!sip->user_state_publications) {
3857 sip->user_state_publications = g_hash_table_new_full(
3858 g_str_hash, g_str_equal,
3859 g_free, (GDestroyNotify)free_publication);
3861 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3862 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3863 key, version);
3867 if (sipe_is_our_publication(sip, key)) {
3868 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3870 publication->category = g_strdup(name);
3871 publication->instance = instance;
3872 publication->container = container;
3873 publication->version = version;
3875 /* filling publication->availability */
3876 if (sipe_strequal(name, "state")) {
3877 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3878 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3880 if (xn_avail) {
3881 gchar *avail_str = sipe_xml_data(xn_avail);
3882 if (avail_str) {
3883 publication->availability = atoi(avail_str);
3885 g_free(avail_str);
3887 /* for calendarState */
3888 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
3889 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3890 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3892 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
3893 if (xn_activity) {
3894 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
3895 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3897 event->is_meeting = TRUE;
3900 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
3901 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
3903 publication->cal_event_hash = sipe_cal_event_hash(event);
3904 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3905 publication->cal_event_hash);
3906 sipe_cal_event_free(event);
3909 /* filling publication->note */
3910 if (sipe_strequal(name, "note")) {
3911 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
3913 if (!has_note_cleaned) {
3914 has_note_cleaned = TRUE;
3916 g_free(sip->note);
3917 sip->note = NULL;
3918 sip->note_since = publish_time;
3920 do_update_status = TRUE;
3923 g_free(publication->note);
3924 publication->note = NULL;
3925 if (xn_body) {
3926 char *tmp;
3928 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
3929 g_free(tmp);
3930 if (publish_time >= sip->note_since) {
3931 g_free(sip->note);
3932 sip->note = g_strdup(publication->note);
3933 sip->note_since = publish_time;
3934 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
3936 do_update_status = TRUE;
3941 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3942 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3943 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
3944 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
3945 if (xn_free_busy) {
3946 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3947 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
3949 if (xn_working_hours) {
3950 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
3954 if (!cat_publications) {
3955 cat_publications = g_hash_table_new_full(
3956 g_str_hash, g_str_equal,
3957 g_free, (GDestroyNotify)free_publication);
3958 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3959 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3961 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3962 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3964 g_free(key);
3966 /* aggregateState (not an our publication) from 2-nd container */
3967 if (sipe_strequal(name, "state") && container == 2) {
3968 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3970 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
3971 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3972 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3974 if (xn_avail) {
3975 gchar *avail_str = sipe_xml_data(xn_avail);
3976 if (avail_str) {
3977 aggreg_avail = atoi(avail_str);
3979 g_free(avail_str);
3982 if (xn_activity) {
3983 const char *activity_token = sipe_xml_attribute(xn_activity, "token");
3985 aggreg_activity = sipe_get_activity_by_token(activity_token);
3988 do_update_status = TRUE;
3992 /* userProperties published by server from AD */
3993 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3994 const sipe_xml *line;
3995 /* line, for Remote Call Control (RCC) */
3996 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
3997 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
3998 const gchar *line_type = sipe_xml_attribute(line, "lineType");
3999 gchar *line_uri;
4001 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
4003 line_uri = sipe_xml_data(line);
4004 if (line_uri) {
4005 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
4006 sip_csta_open(sip, line_uri, line_server);
4008 g_free(line_uri);
4010 break;
4014 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
4015 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
4017 /* containers */
4018 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
4019 guint id = sipe_xml_int_attribute(node, "id", 0);
4020 struct sipe_container *container = sipe_find_container(sip, id);
4022 if (container) {
4023 sip->containers = g_slist_remove(sip->containers, container);
4024 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
4025 free_container(container);
4027 container = g_new0(struct sipe_container, 1);
4028 container->id = id;
4029 container->version = sipe_xml_int_attribute(node, "version", 0);
4030 sip->containers = g_slist_append(sip->containers, container);
4031 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
4033 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
4034 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
4035 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
4036 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
4037 container->members = g_slist_append(container->members, member);
4038 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
4039 member->type, member->value ? member->value : "");
4043 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
4044 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
4045 char *container_xmls = NULL;
4046 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL, NULL);
4047 int federatedAL = sipe_find_access_level(sip, "federated", NULL, NULL);
4049 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
4050 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
4051 /* initial set-up to let counterparties see your status */
4052 if (sameEnterpriseAL < 0) {
4053 struct sipe_container *container = sipe_find_container(sip, 200);
4054 guint version = container ? container->version : 0;
4055 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
4057 if (federatedAL < 0) {
4058 struct sipe_container *container = sipe_find_container(sip, 100);
4059 guint version = container ? container->version : 0;
4060 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
4062 sip->access_level_set = TRUE;
4064 if (container_xmls) {
4065 sipe_send_set_container_members(sip, container_xmls);
4067 g_free(container_xmls);
4070 /* Refresh contacts' blocked status */
4071 sipe_refresh_blocked_status(sip);
4073 /* subscribers */
4074 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
4075 const char *user;
4076 const char *acknowledged;
4077 gchar *hdr;
4078 gchar *body;
4080 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
4081 if (!user) continue;
4082 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
4083 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
4084 uri = sip_uri_from_name(user);
4086 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
4088 acknowledged= sipe_xml_attribute(node, "acknowledged");
4089 if(sipe_strcase_equal(acknowledged,"false")){
4090 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
4091 if (!purple_find_buddy(sip->account, uri)) {
4092 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
4095 hdr = g_strdup_printf(
4096 "Contact: %s\r\n"
4097 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
4099 body = g_strdup_printf(
4100 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
4101 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
4102 "</setSubscribers>", user);
4104 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
4105 g_free(body);
4106 g_free(hdr);
4108 g_free(display_name);
4109 g_free(uri);
4112 g_free(contact);
4113 sipe_xml_free(xml);
4115 /* Publish initial state if not yet.
4116 * Assuming this happens on initial responce to subscription to roaming-self
4117 * so we've already updated our roaming data in full.
4118 * Only for 2007+
4120 if (!sip->initial_state_published) {
4121 send_publish_category_initial(sip);
4122 sip->initial_state_published = TRUE;
4123 /* dalayed run */
4124 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_core_update_calendar, NULL, sip, NULL);
4125 do_update_status = FALSE;
4126 } else if (aggreg_avail) {
4128 g_free(sip->status);
4129 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
4130 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
4131 } else {
4132 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
4136 if (do_update_status) {
4137 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
4138 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
4141 g_free(to);
4144 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
4146 gchar *to = sip_uri_self(sip);
4147 gchar *tmp = get_contact(sip);
4148 gchar *hdr = g_strdup_printf(
4149 "Event: vnd-microsoft-roaming-ACL\r\n"
4150 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
4151 "Supported: com.microsoft.autoextend\r\n"
4152 "Supported: ms-benotify\r\n"
4153 "Proxy-Require: ms-benotify\r\n"
4154 "Supported: ms-piggyback-first-notify\r\n"
4155 "Contact: %s\r\n", tmp);
4156 g_free(tmp);
4158 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
4159 g_free(to);
4160 g_free(hdr);
4164 * To request for presence information about the user, access level settings that have already been configured by the user
4165 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
4166 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
4169 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
4171 gchar *to = sip_uri_self(sip);
4172 gchar *tmp = get_contact(sip);
4173 gchar *hdr = g_strdup_printf(
4174 "Event: vnd-microsoft-roaming-self\r\n"
4175 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
4176 "Supported: ms-benotify\r\n"
4177 "Proxy-Require: ms-benotify\r\n"
4178 "Supported: ms-piggyback-first-notify\r\n"
4179 "Contact: %s\r\n"
4180 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
4182 gchar *body=g_strdup(
4183 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
4184 "<roaming type=\"categories\"/>"
4185 "<roaming type=\"containers\"/>"
4186 "<roaming type=\"subscribers\"/></roamingList>");
4188 g_free(tmp);
4189 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4190 g_free(body);
4191 g_free(to);
4192 g_free(hdr);
4196 * For 2005 version
4198 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
4200 gchar *to = sip_uri_self(sip);
4201 gchar *tmp = get_contact(sip);
4202 gchar *hdr = g_strdup_printf(
4203 "Event: vnd-microsoft-provisioning\r\n"
4204 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
4205 "Supported: com.microsoft.autoextend\r\n"
4206 "Supported: ms-benotify\r\n"
4207 "Proxy-Require: ms-benotify\r\n"
4208 "Supported: ms-piggyback-first-notify\r\n"
4209 "Expires: 0\r\n"
4210 "Contact: %s\r\n", tmp);
4212 g_free(tmp);
4213 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
4214 g_free(to);
4215 g_free(hdr);
4218 /** Subscription for provisioning information to help with initial
4219 * configuration. This subscription is a one-time query (denoted by the Expires header,
4220 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
4221 * configuration, meeting policies, and policy settings that Communicator must enforce.
4222 * TODO: for what we need this information.
4225 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
4227 gchar *to = sip_uri_self(sip);
4228 gchar *tmp = get_contact(sip);
4229 gchar *hdr = g_strdup_printf(
4230 "Event: vnd-microsoft-provisioning-v2\r\n"
4231 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
4232 "Supported: com.microsoft.autoextend\r\n"
4233 "Supported: ms-benotify\r\n"
4234 "Proxy-Require: ms-benotify\r\n"
4235 "Supported: ms-piggyback-first-notify\r\n"
4236 "Expires: 0\r\n"
4237 "Contact: %s\r\n"
4238 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
4239 gchar *body = g_strdup(
4240 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
4241 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
4242 "<provisioningGroup name=\"ucPolicy\"/>"
4243 "</provisioningGroupList>");
4245 g_free(tmp);
4246 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4247 g_free(body);
4248 g_free(to);
4249 g_free(hdr);
4252 static void
4253 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
4254 gpointer value, gpointer user_data)
4256 struct sip_subscription *subscription = value;
4257 struct sip_dialog *dialog = &subscription->dialog;
4258 struct sipe_account_data *sip = user_data;
4259 gchar *tmp = get_contact(sip);
4260 gchar *hdr = g_strdup_printf(
4261 "Event: %s\r\n"
4262 "Expires: 0\r\n"
4263 "Contact: %s\r\n", subscription->event, tmp);
4264 g_free(tmp);
4266 /* Rate limit to max. 25 requests per seconds */
4267 g_usleep(1000000 / 25);
4269 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
4270 g_free(hdr);
4273 /* IM Session (INVITE and MESSAGE methods) */
4275 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
4276 static gchar *
4277 get_end_points (struct sipe_account_data *sip,
4278 struct sip_session *session)
4280 gchar *res;
4282 if (session == NULL) {
4283 return NULL;
4286 res = g_strdup_printf("<sip:%s>", sip->username);
4288 SIPE_DIALOG_FOREACH {
4289 gchar *tmp = res;
4290 res = g_strdup_printf("%s, <%s>", res, dialog->with);
4291 g_free(tmp);
4293 if (dialog->theirepid) {
4294 tmp = res;
4295 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
4296 g_free(tmp);
4298 } SIPE_DIALOG_FOREACH_END;
4300 return res;
4303 static gboolean
4304 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
4305 struct sipmsg *msg,
4306 SIPE_UNUSED_PARAMETER struct transaction *trans)
4308 gboolean ret = TRUE;
4310 if (msg->response != 200) {
4311 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
4312 return FALSE;
4315 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
4317 return ret;
4321 * Asks UA/proxy about its capabilities.
4323 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
4325 gchar *to = sip_uri(who);
4326 gchar *contact = get_contact(sip);
4327 gchar *request = g_strdup_printf(
4328 "Accept: application/sdp\r\n"
4329 "Contact: %s\r\n", contact);
4330 g_free(contact);
4332 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
4334 g_free(to);
4335 g_free(request);
4338 static void
4339 sipe_notify_user(struct sipe_account_data *sip,
4340 struct sip_session *session,
4341 PurpleMessageFlags flags,
4342 const gchar *message)
4344 PurpleConversation *conv;
4346 if (!session->conv) {
4347 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
4348 } else {
4349 conv = session->conv;
4351 purple_conversation_write(conv, NULL, message, flags, time(NULL));
4354 void
4355 sipe_present_info(struct sipe_account_data *sip,
4356 struct sip_session *session,
4357 const gchar *message)
4359 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4362 static void
4363 sipe_present_err(struct sipe_account_data *sip,
4364 struct sip_session *session,
4365 const gchar *message)
4367 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4370 void
4371 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4372 struct sip_session *session,
4373 int sip_error,
4374 int sip_warning,
4375 const gchar *who,
4376 const gchar *message)
4378 char *msg, *msg_tmp, *msg_tmp2;
4379 const char *label;
4381 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
4382 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4383 g_free(msg_tmp);
4384 /* Service unavailable; Server Internal Error; Server Time-out */
4385 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4386 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4387 g_free(msg);
4388 msg = NULL;
4389 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4390 label = _("This message was not delivered to %s because the service is not available");
4391 } else if (sip_error == 486) { /* Busy Here */
4392 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4393 } else if (sip_error == 415) { /* Unsupported media type */
4394 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4395 } else {
4396 label = _("This message was not delivered to %s because one or more recipients are offline");
4399 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4400 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4401 msg ? ":" : "",
4402 msg ? msg : "");
4403 sipe_present_err(sip, session, msg_tmp);
4404 g_free(msg_tmp2);
4405 g_free(msg_tmp);
4406 g_free(msg);
4410 static gboolean
4411 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4412 SIPE_UNUSED_PARAMETER struct transaction *trans)
4414 gboolean ret = TRUE;
4415 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4416 struct sip_session *session = sipe_session_find_im(sip, with);
4417 struct sip_dialog *dialog;
4418 gchar *cseq;
4419 char *key;
4420 struct queued_message *message;
4422 if (!session) {
4423 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4424 g_free(with);
4425 return FALSE;
4428 dialog = sipe_dialog_find(session, with);
4429 if (!dialog) {
4430 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4431 g_free(with);
4432 return FALSE;
4435 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4436 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4437 g_free(cseq);
4438 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4440 if (msg->response >= 400) {
4441 PurpleBuddy *pbuddy;
4442 const char *alias = with;
4443 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4444 int warning = -1;
4446 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4448 if (warn_hdr) {
4449 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4450 if (parts[0]) {
4451 warning = atoi(parts[0]);
4453 g_strfreev(parts);
4456 /* cancel file transfer as rejected by server */
4457 if (msg->response == 606 && /* Not acceptable all. */
4458 warning == 309 && /* Message contents not allowed by policy */
4459 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4461 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4462 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4463 sipe_utils_nameval_free(parsed_body);
4466 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4467 alias = purple_buddy_get_alias(pbuddy);
4470 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4472 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4473 if (msg->response == 408 || /* Request timeout */
4474 msg->response == 480 || /* Temporarily Unavailable */
4475 msg->response == 481) { /* Call/Transaction Does Not Exist */
4476 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4477 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4480 ret = FALSE;
4481 } else {
4482 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4483 if (message_id) {
4484 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4485 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4486 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4489 g_hash_table_remove(session->unconfirmed_messages, key);
4490 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4491 key, g_hash_table_size(session->unconfirmed_messages));
4494 g_free(key);
4495 g_free(with);
4497 if (ret) sipe_im_process_queue(sip, session);
4498 return ret;
4501 static gboolean
4502 sipe_is_election_finished(struct sip_session *session);
4504 static void
4505 sipe_election_result(struct sipe_account_data *sip,
4506 void *sess);
4508 static gboolean
4509 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4510 SIPE_UNUSED_PARAMETER struct transaction *trans)
4512 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4513 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4514 struct sip_dialog *dialog;
4515 struct sip_session *session;
4517 session = sipe_session_find_chat_by_callid(sip, callid);
4518 if (!session) {
4519 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
4520 return FALSE;
4523 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4524 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
4525 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
4526 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
4528 if (xn_request_rm_response) {
4529 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
4530 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
4532 dialog = sipe_dialog_find(session, with);
4533 if (!dialog) {
4534 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
4535 sipe_xml_free(xn_action);
4536 return FALSE;
4539 if (allow && !g_strcasecmp(allow, "true")) {
4540 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
4541 dialog->election_vote = 1;
4542 } else if (allow && !g_strcasecmp(allow, "false")) {
4543 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
4544 dialog->election_vote = -1;
4547 if (sipe_is_election_finished(session)) {
4548 sipe_election_result(sip, session);
4551 } else if (xn_set_rm_response) {
4554 sipe_xml_free(xn_action);
4558 return TRUE;
4561 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4563 gchar *hdr;
4564 gchar *tmp;
4565 char *msgtext = NULL;
4566 const gchar *msgr = "";
4567 gchar *tmp2 = NULL;
4569 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4570 char *msgformat;
4571 gchar *msgr_value;
4573 sipe_parse_html(msg, &msgformat, &msgtext);
4574 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
4576 msgr_value = sipmsg_get_msgr_string(msgformat);
4577 g_free(msgformat);
4578 if (msgr_value) {
4579 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4580 g_free(msgr_value);
4582 } else {
4583 msgtext = g_strdup(msg);
4586 tmp = get_contact(sip);
4587 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4588 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4589 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4590 if (content_type == NULL)
4591 content_type = "text/plain";
4593 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4594 g_free(tmp);
4595 g_free(tmp2);
4597 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4598 g_free(msgtext);
4599 g_free(hdr);
4603 void
4604 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4606 GSList *entry2 = session->outgoing_message_queue;
4607 while (entry2) {
4608 struct queued_message *msg = entry2->data;
4610 /* for multiparty chat or conference */
4611 if (session->is_multiparty || session->focus_uri) {
4612 gchar *who = sip_uri_self(sip);
4613 serv_got_chat_in(sip->gc, session->chat_id, who,
4614 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4615 g_free(who);
4618 SIPE_DIALOG_FOREACH {
4619 char *key;
4620 struct queued_message *message;
4622 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4624 message = g_new0(struct queued_message,1);
4625 message->body = g_strdup(msg->body);
4626 if (msg->content_type != NULL)
4627 message->content_type = g_strdup(msg->content_type);
4629 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4630 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4631 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4632 key, g_hash_table_size(session->unconfirmed_messages));
4633 g_free(key);
4635 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4636 } SIPE_DIALOG_FOREACH_END;
4638 entry2 = sipe_session_dequeue_message(session);
4642 static void
4643 sipe_refer_notify(struct sipe_account_data *sip,
4644 struct sip_session *session,
4645 const gchar *who,
4646 int status,
4647 const gchar *desc)
4649 gchar *hdr;
4650 gchar *body;
4651 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4653 hdr = g_strdup_printf(
4654 "Event: refer\r\n"
4655 "Subscription-State: %s\r\n"
4656 "Content-Type: message/sipfrag\r\n",
4657 status >= 200 ? "terminated" : "active");
4659 body = g_strdup_printf(
4660 "SIP/2.0 %d %s\r\n",
4661 status, desc);
4663 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4665 g_free(hdr);
4666 g_free(body);
4669 static gboolean
4670 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4672 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4673 struct sip_session *session;
4674 struct sip_dialog *dialog;
4675 char *cseq;
4676 char *key;
4677 struct queued_message *message;
4678 struct sipmsg *request_msg = trans->msg;
4680 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4681 gchar *referred_by;
4683 session = sipe_session_find_chat_by_callid(sip, callid);
4684 if (!session) {
4685 session = sipe_session_find_im(sip, with);
4687 if (!session) {
4688 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4689 g_free(with);
4690 return FALSE;
4693 dialog = sipe_dialog_find(session, with);
4694 if (!dialog) {
4695 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4696 g_free(with);
4697 return FALSE;
4700 sipe_dialog_parse(dialog, msg, TRUE);
4702 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4703 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4704 g_free(cseq);
4705 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4707 if (msg->response != 200) {
4708 PurpleBuddy *pbuddy;
4709 const char *alias = with;
4710 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4711 int warning = -1;
4713 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4715 if (warn_hdr) {
4716 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4717 if (parts[0]) {
4718 warning = atoi(parts[0]);
4720 g_strfreev(parts);
4723 /* cancel file transfer as rejected by server */
4724 if (msg->response == 606 && /* Not acceptable all. */
4725 warning == 309 && /* Message contents not allowed by policy */
4726 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4728 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4729 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4730 sipe_utils_nameval_free(parsed_body);
4733 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4734 alias = purple_buddy_get_alias(pbuddy);
4737 if (message) {
4738 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4739 } else {
4740 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4741 sipe_present_err(sip, session, tmp_msg);
4742 g_free(tmp_msg);
4745 sipe_dialog_remove(session, with);
4747 g_free(key);
4748 g_free(with);
4749 return FALSE;
4752 dialog->cseq = 0;
4753 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4754 dialog->outgoing_invite = NULL;
4755 dialog->is_established = TRUE;
4757 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4758 if (referred_by) {
4759 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4760 g_free(referred_by);
4763 /* add user to chat if it is a multiparty session */
4764 if (session->is_multiparty) {
4765 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4766 with, NULL,
4767 PURPLE_CBFLAGS_NONE, TRUE);
4770 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4771 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4772 sipe_session_dequeue_message(session);
4775 sipe_im_process_queue(sip, session);
4777 g_hash_table_remove(session->unconfirmed_messages, key);
4778 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4779 key, g_hash_table_size(session->unconfirmed_messages));
4781 g_free(key);
4782 g_free(with);
4783 return TRUE;
4787 void
4788 sipe_invite(struct sipe_account_data *sip,
4789 struct sip_session *session,
4790 const gchar *who,
4791 const gchar *msg_body,
4792 const gchar *msg_content_type,
4793 const gchar *referred_by,
4794 const gboolean is_triggered)
4796 gchar *hdr;
4797 gchar *to;
4798 gchar *contact;
4799 gchar *body;
4800 gchar *self;
4801 char *ms_text_format = NULL;
4802 gchar *roster_manager;
4803 gchar *end_points;
4804 gchar *referred_by_str;
4805 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4807 if (dialog && dialog->is_established) {
4808 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
4809 return;
4812 if (!dialog) {
4813 dialog = sipe_dialog_add(session);
4814 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4815 dialog->with = g_strdup(who);
4818 if (!(dialog->ourtag)) {
4819 dialog->ourtag = gentag();
4822 to = sip_uri(who);
4824 if (msg_body) {
4825 char *msgtext = NULL;
4826 char *base64_msg;
4827 const gchar *msgr = "";
4828 char *key;
4829 struct queued_message *message;
4830 gchar *tmp = NULL;
4832 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4833 char *msgformat;
4834 gchar *msgr_value;
4836 sipe_parse_html(msg_body, &msgformat, &msgtext);
4837 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
4839 msgr_value = sipmsg_get_msgr_string(msgformat);
4840 g_free(msgformat);
4841 if (msgr_value) {
4842 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4843 g_free(msgr_value);
4845 } else {
4846 msgtext = g_strdup(msg_body);
4849 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4850 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4851 msg_content_type ? msg_content_type : "text/plain",
4852 msgr,
4853 base64_msg);
4854 g_free(msgtext);
4855 g_free(tmp);
4856 g_free(base64_msg);
4858 message = g_new0(struct queued_message,1);
4859 message->body = g_strdup(msg_body);
4860 if (msg_content_type != NULL)
4861 message->content_type = g_strdup(msg_content_type);
4863 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4864 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4865 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4866 key, g_hash_table_size(session->unconfirmed_messages));
4867 g_free(key);
4870 contact = get_contact(sip);
4871 end_points = get_end_points(sip, session);
4872 self = sip_uri_self(sip);
4873 roster_manager = g_strdup_printf(
4874 "Roster-Manager: %s\r\n"
4875 "EndPoints: %s\r\n",
4876 self,
4877 end_points);
4878 referred_by_str = referred_by ?
4879 g_strdup_printf(
4880 "Referred-By: %s\r\n",
4881 referred_by)
4882 : g_strdup("");
4883 hdr = g_strdup_printf(
4884 "Supported: ms-sender\r\n"
4885 "%s"
4886 "%s"
4887 "%s"
4888 "%s"
4889 "Contact: %s\r\n%s"
4890 "Content-Type: application/sdp\r\n",
4891 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4892 referred_by_str,
4893 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4894 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4895 contact,
4896 ms_text_format ? ms_text_format : "");
4897 g_free(ms_text_format);
4898 g_free(self);
4900 body = g_strdup_printf(
4901 "v=0\r\n"
4902 "o=- 0 0 IN IP4 %s\r\n"
4903 "s=session\r\n"
4904 "c=IN IP4 %s\r\n"
4905 "t=0 0\r\n"
4906 "m=%s %d sip null\r\n"
4907 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4908 sipe_backend_network_ip_address(),
4909 sipe_backend_network_ip_address(),
4910 sip->ocs2007 ? "message" : "x-ms-message",
4911 sip->realport);
4913 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4914 to, to, hdr, body, dialog, process_invite_response);
4916 g_free(to);
4917 g_free(roster_manager);
4918 g_free(end_points);
4919 g_free(referred_by_str);
4920 g_free(body);
4921 g_free(hdr);
4922 g_free(contact);
4925 static void
4926 sipe_refer(struct sipe_account_data *sip,
4927 struct sip_session *session,
4928 const gchar *who)
4930 gchar *hdr;
4931 gchar *contact;
4932 gchar *epid = get_epid(sip);
4933 struct sip_dialog *dialog = sipe_dialog_find(session,
4934 session->roster_manager);
4935 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4937 contact = get_contact(sip);
4938 hdr = g_strdup_printf(
4939 "Contact: %s\r\n"
4940 "Refer-to: <%s>\r\n"
4941 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4942 "Require: com.microsoft.rtc-multiparty\r\n",
4943 contact,
4944 who,
4945 sip->username,
4946 ourtag ? ";tag=" : "",
4947 ourtag ? ourtag : "",
4948 epid);
4949 g_free(epid);
4951 send_sip_request(sip->gc, "REFER",
4952 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4954 g_free(hdr);
4955 g_free(contact);
4958 static void
4959 sipe_send_election_request_rm(struct sipe_account_data *sip,
4960 struct sip_dialog *dialog,
4961 int bid)
4963 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4965 gchar *body = g_strdup_printf(
4966 "<?xml version=\"1.0\"?>\r\n"
4967 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4968 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4969 sip->username, bid);
4971 send_sip_request(sip->gc, "INFO",
4972 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4974 g_free(body);
4977 static void
4978 sipe_send_election_set_rm(struct sipe_account_data *sip,
4979 struct sip_dialog *dialog)
4981 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4983 gchar *body = g_strdup_printf(
4984 "<?xml version=\"1.0\"?>\r\n"
4985 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4986 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4987 sip->username);
4989 send_sip_request(sip->gc, "INFO",
4990 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4992 g_free(body);
4995 static void
4996 sipe_session_close(struct sipe_account_data *sip,
4997 struct sip_session * session)
4999 if (session && session->focus_uri) {
5000 sipe_conf_immcu_closed(sip, session);
5001 conf_session_close(sip, session);
5004 if (session) {
5005 SIPE_DIALOG_FOREACH {
5006 /* @TODO slow down BYE message sending rate */
5007 /* @see single subscription code */
5008 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
5009 } SIPE_DIALOG_FOREACH_END;
5011 sipe_session_remove(sip, session);
5015 static void
5016 sipe_session_close_all(struct sipe_account_data *sip)
5018 GSList *entry;
5019 while ((entry = sip->sessions) != NULL) {
5020 sipe_session_close(sip, entry->data);
5024 static void
5025 sipe_convo_closed(PurpleConnection * gc, const char *who)
5027 struct sipe_account_data *sip = gc->proto_data;
5029 SIPE_DEBUG_INFO("conversation with %s closed", who);
5030 sipe_session_close(sip, sipe_session_find_im(sip, who));
5033 static void
5034 sipe_chat_invite(PurpleConnection *gc, int id,
5035 SIPE_UNUSED_PARAMETER const char *message,
5036 const char *name)
5038 sipe_chat_create(gc->proto_data, id, name);
5041 static void
5042 sipe_chat_leave (PurpleConnection *gc, int id)
5044 struct sipe_account_data *sip = gc->proto_data;
5045 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
5047 sipe_session_close(sip, session);
5050 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
5051 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
5053 struct sipe_account_data *sip = gc->proto_data;
5054 struct sip_session *session;
5055 struct sip_dialog *dialog;
5056 gchar *uri = sip_uri(who);
5058 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
5060 session = sipe_session_find_or_add_im(sip, uri);
5061 dialog = sipe_dialog_find(session, uri);
5063 // Queue the message
5064 sipe_session_enqueue_message(session, what, NULL);
5066 if (dialog && !dialog->outgoing_invite) {
5067 sipe_im_process_queue(sip, session);
5068 } else if (!dialog || !dialog->outgoing_invite) {
5069 // Need to send the INVITE to get the outgoing dialog setup
5070 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
5073 g_free(uri);
5074 return 1;
5077 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
5078 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
5080 struct sipe_account_data *sip = gc->proto_data;
5081 struct sip_session *session;
5083 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
5085 session = sipe_session_find_chat_by_id(sip, id);
5087 // Queue the message
5088 if (session && session->dialogs) {
5089 sipe_session_enqueue_message(session,what,NULL);
5090 sipe_im_process_queue(sip, session);
5091 } else if (sip) {
5092 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
5093 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
5095 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
5096 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
5098 if (sip->ocs2007) {
5099 struct sip_session *session = sipe_session_add_chat(sip);
5101 session->is_multiparty = FALSE;
5102 session->focus_uri = g_strdup(proto_chat_id);
5103 sipe_session_enqueue_message(session, what, NULL);
5104 sipe_invite_conf_focus(sip, session);
5108 return 1;
5111 /* End IM Session (INVITE and MESSAGE methods) */
5113 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
5115 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
5116 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5117 gchar *from;
5118 struct sip_session *session;
5120 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
5122 /* Call Control protocol */
5123 if (g_str_has_prefix(contenttype, "application/csta+xml"))
5125 process_incoming_info_csta(sip, msg);
5126 return;
5129 from = parse_from(sipmsg_find_header(msg, "From"));
5130 session = sipe_session_find_chat_by_callid(sip, callid);
5131 if (!session) {
5132 session = sipe_session_find_im(sip, from);
5134 if (!session) {
5135 g_free(from);
5136 return;
5139 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
5141 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
5142 const sipe_xml *xn_request_rm = sipe_xml_child(xn_action, "RequestRM");
5143 const sipe_xml *xn_set_rm = sipe_xml_child(xn_action, "SetRM");
5145 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
5147 if (xn_request_rm) {
5148 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
5149 int bid = sipe_xml_int_attribute(xn_request_rm, "bid", 0);
5150 gchar *body = g_strdup_printf(
5151 "<?xml version=\"1.0\"?>\r\n"
5152 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5153 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
5154 sip->username,
5155 session->bid < bid ? "true" : "false");
5156 send_sip_response(sip->gc, msg, 200, "OK", body);
5157 g_free(body);
5158 } else if (xn_set_rm) {
5159 gchar *body;
5160 const char *rm = sipe_xml_attribute(xn_set_rm, "uri");
5161 g_free(session->roster_manager);
5162 session->roster_manager = g_strdup(rm);
5164 body = g_strdup_printf(
5165 "<?xml version=\"1.0\"?>\r\n"
5166 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5167 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
5168 sip->username);
5169 send_sip_response(sip->gc, msg, 200, "OK", body);
5170 g_free(body);
5172 sipe_xml_free(xn_action);
5175 else
5177 /* looks like purple lacks typing notification for chat */
5178 if (!session->is_multiparty && !session->focus_uri) {
5179 sipe_xml *xn_keyboard_activity = sipe_xml_parse(msg->body, msg->bodylen);
5180 const char *status = sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity, "status"),
5181 "status");
5182 if (sipe_strequal(status, "type")) {
5183 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
5184 } else if (sipe_strequal(status, "idle")) {
5185 serv_got_typing_stopped(sip->gc, from);
5187 sipe_xml_free(xn_keyboard_activity);
5190 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5192 g_free(from);
5195 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
5197 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5198 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5199 struct sip_session *session;
5200 struct sip_dialog *dialog;
5202 /* collect dialog identification
5203 * we need callid, ourtag and theirtag to unambiguously identify dialog
5205 /* take data before 'msg' will be modified by send_sip_response */
5206 dialog = g_new0(struct sip_dialog, 1);
5207 dialog->callid = g_strdup(callid);
5208 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
5209 dialog->with = g_strdup(from);
5210 sipe_dialog_parse(dialog, msg, FALSE);
5212 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5214 session = sipe_session_find_chat_by_callid(sip, callid);
5215 if (!session) {
5216 session = sipe_session_find_im(sip, from);
5218 if (!session) {
5219 sipe_dialog_free(dialog);
5220 g_free(from);
5221 return;
5224 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
5225 g_free(session->roster_manager);
5226 session->roster_manager = NULL;
5229 /* This what BYE is essentially for - terminating dialog */
5230 sipe_dialog_remove_3(session, dialog);
5231 sipe_dialog_free(dialog);
5232 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
5233 sipe_conf_immcu_closed(sip, session);
5234 } else if (session->is_multiparty) {
5235 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
5238 g_free(from);
5241 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
5243 gchar *self = sip_uri_self(sip);
5244 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5245 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5246 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
5247 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
5248 struct sip_session *session;
5249 struct sip_dialog *dialog;
5251 session = sipe_session_find_chat_by_callid(sip, callid);
5252 dialog = sipe_dialog_find(session, from);
5254 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
5255 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
5256 } else {
5257 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
5259 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
5262 g_free(self);
5263 g_free(from);
5264 g_free(refer_to);
5265 g_free(referred_by);
5268 static unsigned int
5269 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
5271 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
5272 struct sip_session *session;
5273 struct sip_dialog *dialog;
5275 if (state == PURPLE_NOT_TYPING)
5276 return 0;
5278 session = sipe_session_find_im(sip, who);
5279 dialog = sipe_dialog_find(session, who);
5281 if (session && dialog && dialog->is_established) {
5282 send_sip_request(gc, "INFO", who, who,
5283 "Content-Type: application/xml\r\n",
5284 SIPE_SEND_TYPING, dialog, NULL);
5286 return SIPE_TYPING_SEND_TIMEOUT;
5289 static gboolean resend_timeout(struct sipe_account_data *sip)
5291 GSList *tmp = sip->transactions;
5292 time_t currtime = time(NULL);
5293 while (tmp) {
5294 struct transaction *trans = tmp->data;
5295 tmp = tmp->next;
5296 SIPE_DEBUG_INFO("have open transaction age: %ld", (long int)currtime-trans->time);
5297 if ((currtime - trans->time > 5) && trans->retries >= 1) {
5298 /* TODO 408 */
5299 } else {
5300 if ((currtime - trans->time > 2) && trans->retries == 0) {
5301 trans->retries++;
5302 sendout_sipmsg(sip, trans->msg);
5306 return TRUE;
5309 static void do_reauthenticate_cb(struct sipe_account_data *sip,
5310 SIPE_UNUSED_PARAMETER void *unused)
5312 /* register again when security token expires */
5313 /* we have to start a new authentication as the security token
5314 * is almost expired by sending a not signed REGISTER message */
5315 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
5316 sipe_auth_free(&sip->registrar);
5317 sipe_auth_free(&sip->proxy);
5318 sip->registerstatus = 0;
5319 do_register(sip);
5320 sip->reauthenticate_set = FALSE;
5323 static gboolean
5324 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
5325 struct sipmsg *msg,
5326 GSList *parsed_body)
5328 gboolean found = FALSE;
5330 if (parsed_body) {
5331 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
5333 if (sipe_strequal(invitation_command, "INVITE")) {
5334 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
5335 found = TRUE;
5336 } else if (sipe_strequal(invitation_command, "CANCEL")) {
5337 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
5338 found = TRUE;
5339 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
5340 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
5341 found = TRUE;
5344 return found;
5347 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
5349 gchar *from;
5350 const gchar *contenttype;
5351 gboolean found = FALSE;
5353 from = parse_from(sipmsg_find_header(msg, "From"));
5355 if (!from) return;
5357 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
5359 contenttype = sipmsg_find_header(msg, "Content-Type");
5360 if (g_str_has_prefix(contenttype, "text/plain")
5361 || g_str_has_prefix(contenttype, "text/html")
5362 || g_str_has_prefix(contenttype, "multipart/related")
5363 || g_str_has_prefix(contenttype, "multipart/alternative"))
5365 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5366 gchar *html = get_html_message(contenttype, msg->body);
5368 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5369 if (!session) {
5370 session = sipe_session_find_im(sip, from);
5373 if (session && session->focus_uri) { /* a conference */
5374 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5375 gchar *sender = parse_from(tmp);
5376 g_free(tmp);
5377 serv_got_chat_in(sip->gc, session->chat_id, sender,
5378 PURPLE_MESSAGE_RECV, html, time(NULL));
5379 g_free(sender);
5380 } else if (session && session->is_multiparty) { /* a multiparty chat */
5381 serv_got_chat_in(sip->gc, session->chat_id, from,
5382 PURPLE_MESSAGE_RECV, html, time(NULL));
5383 } else {
5384 serv_got_im(sip->gc, from, html, 0, time(NULL));
5386 g_free(html);
5387 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5388 found = TRUE;
5390 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5391 sipe_xml *isc = sipe_xml_parse(msg->body, msg->bodylen);
5392 const sipe_xml *state;
5393 gchar *statedata;
5395 if (!isc) {
5396 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
5397 g_free(from);
5398 return;
5401 state = sipe_xml_child(isc, "state");
5403 if (!state) {
5404 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
5405 sipe_xml_free(isc);
5406 g_free(from);
5407 return;
5410 statedata = sipe_xml_data(state);
5411 if (statedata) {
5412 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5413 else serv_got_typing_stopped(sip->gc, from);
5415 g_free(statedata);
5417 sipe_xml_free(isc);
5418 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5419 found = TRUE;
5420 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5421 GSList *body = sipe_ft_parse_msg_body(msg->body);
5422 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5423 sipe_utils_nameval_free(body);
5424 if (found) {
5425 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5428 if (!found) {
5429 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5430 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5431 if (!session) {
5432 session = sipe_session_find_im(sip, from);
5434 if (session) {
5435 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5436 from);
5437 sipe_present_err(sip, session, errmsg);
5438 g_free(errmsg);
5441 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
5442 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5444 g_free(from);
5447 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5449 gchar *body;
5450 gchar *newTag;
5451 const gchar *oldHeader;
5452 gchar *newHeader;
5453 gboolean is_multiparty = FALSE;
5454 gboolean is_triggered = FALSE;
5455 gboolean was_multiparty = TRUE;
5456 gboolean just_joined = FALSE;
5457 gchar *from;
5458 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5459 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5460 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5461 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5462 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5463 GSList *end_points = NULL;
5464 char *tmp = NULL;
5465 struct sip_session *session;
5466 const gchar *ms_text_format;
5468 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
5469 g_free(tmp);
5471 /* Invitation to join conference */
5472 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5473 process_incoming_invite_conf(sip, msg);
5474 return;
5477 /* Only accept text invitations */
5478 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5479 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5480 return;
5483 // TODO There *must* be a better way to clean up the To header to add a tag...
5484 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5485 oldHeader = sipmsg_find_header(msg, "To");
5486 newTag = gentag();
5487 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5488 sipmsg_remove_header_now(msg, "To");
5489 sipmsg_add_header_now(msg, "To", newHeader);
5490 g_free(newHeader);
5492 if (end_points_hdr) {
5493 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5495 if (g_slist_length(end_points) > 2) {
5496 is_multiparty = TRUE;
5499 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5500 is_triggered = TRUE;
5501 is_multiparty = TRUE;
5504 session = sipe_session_find_chat_by_callid(sip, callid);
5505 /* Convert to multiparty */
5506 if (session && is_multiparty && !session->is_multiparty) {
5507 g_free(session->with);
5508 session->with = NULL;
5509 was_multiparty = FALSE;
5510 session->is_multiparty = TRUE;
5511 session->chat_id = rand();
5514 if (!session && is_multiparty) {
5515 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5517 /* IM session */
5518 from = parse_from(sipmsg_find_header(msg, "From"));
5519 if (!session) {
5520 session = sipe_session_find_or_add_im(sip, from);
5523 if (session) {
5524 g_free(session->callid);
5525 session->callid = g_strdup(callid);
5527 session->is_multiparty = is_multiparty;
5528 if (roster_manager) {
5529 session->roster_manager = g_strdup(roster_manager);
5533 if (is_multiparty && end_points) {
5534 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5535 GSList *entry = end_points;
5536 while (entry) {
5537 struct sip_dialog *dialog;
5538 struct sipendpoint *end_point = entry->data;
5539 entry = entry->next;
5541 if (!g_strcasecmp(from, end_point->contact) ||
5542 !g_strcasecmp(to, end_point->contact))
5543 continue;
5545 dialog = sipe_dialog_find(session, end_point->contact);
5546 if (dialog) {
5547 g_free(dialog->theirepid);
5548 dialog->theirepid = end_point->epid;
5549 end_point->epid = NULL;
5550 } else {
5551 dialog = sipe_dialog_add(session);
5553 dialog->callid = g_strdup(session->callid);
5554 dialog->with = end_point->contact;
5555 end_point->contact = NULL;
5556 dialog->theirepid = end_point->epid;
5557 end_point->epid = NULL;
5559 just_joined = TRUE;
5561 /* send triggered INVITE */
5562 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5565 g_free(to);
5568 if (end_points) {
5569 GSList *entry = end_points;
5570 while (entry) {
5571 struct sipendpoint *end_point = entry->data;
5572 entry = entry->next;
5573 g_free(end_point->contact);
5574 g_free(end_point->epid);
5575 g_free(end_point);
5577 g_slist_free(end_points);
5580 if (session) {
5581 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5582 if (dialog) {
5583 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5584 sipe_dialog_parse_routes(dialog, msg, FALSE);
5585 } else {
5586 dialog = sipe_dialog_add(session);
5588 dialog->callid = g_strdup(session->callid);
5589 dialog->with = g_strdup(from);
5590 sipe_dialog_parse(dialog, msg, FALSE);
5592 if (!dialog->ourtag) {
5593 dialog->ourtag = newTag;
5594 newTag = NULL;
5597 just_joined = TRUE;
5599 } else {
5600 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5602 g_free(newTag);
5604 if (is_multiparty && !session->conv) {
5605 gchar *chat_title = sipe_chat_get_name(callid);
5606 gchar *self = sip_uri_self(sip);
5607 /* create prpl chat */
5608 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5609 session->chat_title = g_strdup(chat_title);
5610 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5611 /* add self */
5612 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5613 self, NULL,
5614 PURPLE_CBFLAGS_NONE, FALSE);
5615 g_free(chat_title);
5616 g_free(self);
5619 if (is_multiparty && !was_multiparty) {
5620 /* add current IM counterparty to chat */
5621 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5622 sipe_dialog_first(session)->with, NULL,
5623 PURPLE_CBFLAGS_NONE, FALSE);
5626 /* add inviting party to chat */
5627 if (just_joined && session->conv) {
5628 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5629 from, NULL,
5630 PURPLE_CBFLAGS_NONE, TRUE);
5633 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5635 /* This used only in 2005 official client, not 2007 or Reuters.
5636 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5637 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5639 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5640 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5641 if (is_multiparty ||
5642 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5644 if (ms_text_format) {
5645 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5647 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5648 if (tmp) {
5649 gsize len;
5650 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5652 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5654 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5655 sipe_utils_nameval_free(parsed_body);
5656 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5658 g_free(tmp);
5660 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5662 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5663 gchar *html = get_html_message(ms_text_format, NULL);
5664 if (html) {
5665 if (is_multiparty) {
5666 serv_got_chat_in(sip->gc, session->chat_id, from,
5667 PURPLE_MESSAGE_RECV, html, time(NULL));
5668 } else {
5669 serv_got_im(sip->gc, from, html, 0, time(NULL));
5671 g_free(html);
5672 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5678 g_free(from);
5680 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5681 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5682 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5684 body = g_strdup_printf(
5685 "v=0\r\n"
5686 "o=- 0 0 IN IP4 %s\r\n"
5687 "s=session\r\n"
5688 "c=IN IP4 %s\r\n"
5689 "t=0 0\r\n"
5690 "m=%s %d sip sip:%s\r\n"
5691 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5692 sipe_backend_network_ip_address(),
5693 sipe_backend_network_ip_address(),
5694 sip->ocs2007 ? "message" : "x-ms-message",
5695 sip->realport,
5696 sip->username);
5697 send_sip_response(sip->gc, msg, 200, "OK", body);
5698 g_free(body);
5701 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5703 gchar *body;
5705 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5706 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5707 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5709 body = g_strdup_printf(
5710 "v=0\r\n"
5711 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5712 "s=session\r\n"
5713 "c=IN IP4 0.0.0.0\r\n"
5714 "t=0 0\r\n"
5715 "m=%s %d sip sip:%s\r\n"
5716 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5717 sip->ocs2007 ? "message" : "x-ms-message",
5718 sip->realport,
5719 sip->username);
5720 send_sip_response(sip->gc, msg, 200, "OK", body);
5721 g_free(body);
5724 static const char*
5725 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5727 const char *res = "NTLM";
5728 #ifdef HAVE_LIBKRB5
5729 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5730 res = "Kerberos";
5732 #else
5733 (void) sip; /* make compiler happy */
5734 #endif
5735 return res;
5738 static void sipe_connection_cleanup(struct sipe_account_data *);
5739 static void create_connection(struct sipe_account_data *, gchar *, int);
5741 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5742 SIPE_UNUSED_PARAMETER struct transaction *trans)
5744 gchar *tmp;
5745 const gchar *expires_header;
5746 int expires, i;
5747 GSList *hdr = msg->headers;
5748 struct sipnameval *elem;
5750 expires_header = sipmsg_find_header(msg, "Expires");
5751 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5752 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
5754 switch (msg->response) {
5755 case 200:
5756 if (expires == 0) {
5757 sip->registerstatus = 0;
5758 } else {
5759 const gchar *contact_hdr;
5760 gchar *gruu = NULL;
5761 gchar *epid;
5762 gchar *uuid;
5763 gchar *timeout;
5764 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5765 const char *auth_scheme;
5767 if (!sip->reregister_set) {
5768 gchar *action_name = g_strdup_printf("<%s>", "registration");
5769 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5770 g_free(action_name);
5771 sip->reregister_set = TRUE;
5774 sip->registerstatus = 3;
5776 if (server_hdr && !sip->server_version) {
5777 sip->server_version = g_strdup(server_hdr);
5778 g_free(default_ua);
5779 default_ua = NULL;
5782 auth_scheme = sipe_get_auth_scheme_name(sip);
5783 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5785 if (tmp) {
5786 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
5787 fill_auth(tmp, &sip->registrar);
5790 if (!sip->reauthenticate_set) {
5791 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5792 guint reauth_timeout;
5793 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5794 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5795 reauth_timeout = sip->registrar.expires - 300;
5796 } else {
5797 /* NTLM: we have to reauthenticate as our security token expires
5798 after eight hours (be five minutes early) */
5799 reauth_timeout = (8 * 3600) - 300;
5801 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5802 g_free(action_name);
5803 sip->reauthenticate_set = TRUE;
5806 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5808 epid = get_epid(sip);
5809 uuid = generateUUIDfromEPID(epid);
5810 g_free(epid);
5812 // There can be multiple Contact headers (one per location where the user is logged in) so
5813 // make sure to only get the one for this uuid
5814 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5815 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5816 if (valid_contact) {
5817 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5818 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5819 g_free(valid_contact);
5820 break;
5821 } else {
5822 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5825 g_free(uuid);
5827 g_free(sip->contact);
5828 if(gruu) {
5829 sip->contact = g_strdup_printf("<%s>", gruu);
5830 g_free(gruu);
5831 } else {
5832 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5833 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);
5835 sip->ocs2007 = FALSE;
5836 sip->batched_support = FALSE;
5838 while(hdr)
5840 elem = hdr->data;
5841 if (sipe_strcase_equal(elem->name, "Supported")) {
5842 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5843 /* We interpret this as OCS2007+ indicator */
5844 sip->ocs2007 = TRUE;
5845 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5847 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5848 sip->batched_support = TRUE;
5849 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5852 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5853 gchar **caps = g_strsplit(elem->value,",",0);
5854 i = 0;
5855 while (caps[i]) {
5856 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5857 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5858 i++;
5860 g_strfreev(caps);
5862 hdr = g_slist_next(hdr);
5865 /* rejoin open chats to be able to use them by continue to send messages */
5866 purple_conversation_foreach(sipe_rejoin_chat);
5868 /* subscriptions */
5869 if (!sip->subscribed) { //do it just once, not every re-register
5871 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5872 (GCompareFunc)g_ascii_strcasecmp)) {
5873 sipe_subscribe_roaming_contacts(sip);
5876 /* For 2007+ it does not make sence to subscribe to:
5877 * vnd-microsoft-roaming-ACL
5878 * vnd-microsoft-provisioning (not v2)
5879 * presence.wpending
5880 * These are for backward compatibility.
5882 if (sip->ocs2007)
5884 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5885 (GCompareFunc)g_ascii_strcasecmp)) {
5886 sipe_subscribe_roaming_self(sip);
5888 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5889 (GCompareFunc)g_ascii_strcasecmp)) {
5890 sipe_subscribe_roaming_provisioning_v2(sip);
5893 /* For 2005- servers */
5894 else
5896 //sipe_options_request(sip, sip->sipdomain);
5898 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5899 (GCompareFunc)g_ascii_strcasecmp)) {
5900 sipe_subscribe_roaming_acl(sip);
5902 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5903 (GCompareFunc)g_ascii_strcasecmp)) {
5904 sipe_subscribe_roaming_provisioning(sip);
5906 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5907 (GCompareFunc)g_ascii_strcasecmp)) {
5908 sipe_subscribe_presence_wpending(sip, msg);
5911 /* For 2007+ we publish our initial statuses and calendar data only after
5912 * received our existing publications in sipe_process_roaming_self()
5913 * Only in this case we know versions of current publications made
5914 * on our behalf.
5916 /* For 2005- we publish our initial statuses only after
5917 * received our existing UserInfo data in response to
5918 * self subscription.
5919 * Only in this case we won't override existing UserInfo data
5920 * set earlier or by other client on our behalf.
5924 sip->subscribed = TRUE;
5927 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5928 "timeout=", ";", NULL);
5929 if (timeout != NULL) {
5930 sscanf(timeout, "%u", &sip->keepalive_timeout);
5931 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5932 sip->keepalive_timeout);
5933 g_free(timeout);
5936 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5938 break;
5939 case 301:
5941 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5943 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5944 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5945 gchar **tmp;
5946 gchar *hostname;
5947 int port = 0;
5948 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5949 int i = 1;
5951 tmp = g_strsplit(parts[0], ":", 0);
5952 hostname = g_strdup(tmp[0]);
5953 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5954 g_strfreev(tmp);
5956 while (parts[i]) {
5957 tmp = g_strsplit(parts[i], "=", 0);
5958 if (tmp[1]) {
5959 if (g_strcasecmp("transport", tmp[0]) == 0) {
5960 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5961 transport = SIPE_TRANSPORT_TCP;
5962 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5963 transport = SIPE_TRANSPORT_UDP;
5967 g_strfreev(tmp);
5968 i++;
5970 g_strfreev(parts);
5972 /* Close old connection */
5973 sipe_connection_cleanup(sip);
5975 /* Create new connection */
5976 sip->transport = transport;
5977 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5978 hostname, port, TRANSPORT_DESCRIPTOR);
5979 create_connection(sip, hostname, port);
5981 g_free(redirect);
5983 break;
5984 case 401:
5985 if (sip->registerstatus != 2) {
5986 const char *auth_scheme;
5987 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5988 if (sip->registrar.retries > 3) {
5989 sip->gc->wants_to_die = TRUE;
5990 purple_connection_error(sip->gc, _("Authentication failed"));
5991 return TRUE;
5994 auth_scheme = sipe_get_auth_scheme_name(sip);
5995 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5997 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
5998 if (!tmp) {
5999 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
6000 sip->gc->wants_to_die = TRUE;
6001 purple_connection_error(sip->gc, tmp2);
6002 g_free(tmp2);
6003 return TRUE;
6005 fill_auth(tmp, &sip->registrar);
6006 sip->registerstatus = 2;
6007 if (sip->account->disconnecting) {
6008 do_register_exp(sip, 0);
6009 } else {
6010 do_register(sip);
6013 break;
6014 case 403:
6016 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
6017 gchar **reason = NULL;
6018 gchar *warning;
6019 if (diagnostics != NULL) {
6020 /* Example header:
6021 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
6023 reason = g_strsplit(diagnostics, "\"", 0);
6025 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
6026 (reason && reason[1]) ? reason[1] : _("no reason given"));
6027 g_strfreev(reason);
6029 sip->gc->wants_to_die = TRUE;
6030 purple_connection_error(sip->gc, warning);
6031 g_free(warning);
6032 return TRUE;
6034 break;
6035 case 404:
6037 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
6038 gchar *reason = NULL;
6039 gchar *warning;
6040 if (diagnostics != NULL) {
6041 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
6043 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
6044 diagnostics ? (reason ? reason : _("no reason given")) :
6045 _("SIP is either not enabled for the destination URI or it does not exist"));
6046 g_free(reason);
6048 sip->gc->wants_to_die = TRUE;
6049 purple_connection_error(sip->gc, warning);
6050 g_free(warning);
6051 return TRUE;
6053 break;
6054 case 503:
6055 case 504: /* Server time-out */
6057 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
6058 gchar *reason = NULL;
6059 gchar *warning;
6060 if (diagnostics != NULL) {
6061 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
6063 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
6064 g_free(reason);
6066 sip->gc->wants_to_die = TRUE;
6067 purple_connection_error(sip->gc, warning);
6068 g_free(warning);
6069 return TRUE;
6071 break;
6073 return TRUE;
6077 * Returns 2005-style activity and Availability.
6079 * @param status Sipe statis id.
6081 static void
6082 sipe_get_act_avail_by_status_2005(const char *status,
6083 int *activity,
6084 int *availability)
6086 int avail = 300; /* online */
6087 int act = 400; /* Available */
6089 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
6090 act = 100;
6091 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
6092 // act = 150;
6093 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
6094 act = 300;
6095 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
6096 act = 400;
6097 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
6098 // act = 500;
6099 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
6100 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
6101 act = 600;
6102 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
6103 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
6104 avail = 0; /* offline */
6105 act = 100;
6106 } else {
6107 act = 400; /* Available */
6110 if (activity) *activity = act;
6111 if (availability) *availability = avail;
6115 * [MS-SIP] 2.2.1
6117 * @param activity 2005 aggregated activity. Ex.: 600
6118 * @param availablity 2005 aggregated availablity. Ex.: 300
6120 static const char *
6121 sipe_get_status_by_act_avail_2005(const int activity,
6122 const int availablity,
6123 char **activity_desc)
6125 const char *status_id = NULL;
6126 const char *act = NULL;
6128 if (activity < 150) {
6129 status_id = SIPE_STATUS_ID_AWAY;
6130 } else if (activity < 200) {
6131 //status_id = SIPE_STATUS_ID_LUNCH;
6132 status_id = SIPE_STATUS_ID_AWAY;
6133 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
6134 } else if (activity < 300) {
6135 //status_id = SIPE_STATUS_ID_IDLE;
6136 status_id = SIPE_STATUS_ID_AWAY;
6137 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6138 } else if (activity < 400) {
6139 status_id = SIPE_STATUS_ID_BRB;
6140 } else if (activity < 500) {
6141 status_id = SIPE_STATUS_ID_AVAILABLE;
6142 } else if (activity < 600) {
6143 //status_id = SIPE_STATUS_ID_ON_PHONE;
6144 status_id = SIPE_STATUS_ID_BUSY;
6145 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
6146 } else if (activity < 700) {
6147 status_id = SIPE_STATUS_ID_BUSY;
6148 } else if (activity < 800) {
6149 status_id = SIPE_STATUS_ID_AWAY;
6150 } else {
6151 status_id = SIPE_STATUS_ID_AVAILABLE;
6154 if (availablity < 100)
6155 status_id = SIPE_STATUS_ID_OFFLINE;
6157 if (activity_desc && act) {
6158 g_free(*activity_desc);
6159 *activity_desc = g_strdup(act);
6162 return status_id;
6166 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
6168 static const char*
6169 sipe_get_status_by_availability(int avail,
6170 char** activity_desc)
6172 const char *status;
6173 const char *act = NULL;
6175 if (avail < 3000) {
6176 status = SIPE_STATUS_ID_OFFLINE;
6177 } else if (avail < 4500) {
6178 status = SIPE_STATUS_ID_AVAILABLE;
6179 } else if (avail < 6000) {
6180 //status = SIPE_STATUS_ID_IDLE;
6181 status = SIPE_STATUS_ID_AVAILABLE;
6182 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6183 } else if (avail < 7500) {
6184 status = SIPE_STATUS_ID_BUSY;
6185 } else if (avail < 9000) {
6186 //status = SIPE_STATUS_ID_BUSYIDLE;
6187 status = SIPE_STATUS_ID_BUSY;
6188 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
6189 } else if (avail < 12000) {
6190 status = SIPE_STATUS_ID_DND;
6191 } else if (avail < 15000) {
6192 status = SIPE_STATUS_ID_BRB;
6193 } else if (avail < 18000) {
6194 status = SIPE_STATUS_ID_AWAY;
6195 } else {
6196 status = SIPE_STATUS_ID_OFFLINE;
6199 if (activity_desc && act) {
6200 g_free(*activity_desc);
6201 *activity_desc = g_strdup(act);
6204 return status;
6208 * Returns 2007-style availability value
6210 * @param sipe_status_id (in)
6211 * @param activity_token (out) Must be g_free()'d after use if consumed.
6213 static int
6214 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
6216 int availability;
6217 sipe_activity activity = SIPE_ACTIVITY_UNSET;
6219 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
6220 availability = 15500;
6221 if (!activity_token || !(*activity_token)) {
6222 activity = SIPE_ACTIVITY_AWAY;
6224 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
6225 availability = 12500;
6226 activity = SIPE_ACTIVITY_BRB;
6227 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
6228 availability = 9500;
6229 activity = SIPE_ACTIVITY_DND;
6230 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
6231 availability = 6500;
6232 if (!activity_token || !(*activity_token)) {
6233 activity = SIPE_ACTIVITY_BUSY;
6235 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
6236 availability = 3500;
6237 activity = SIPE_ACTIVITY_ONLINE;
6238 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
6239 availability = 0;
6240 } else {
6241 // Offline or invisible
6242 availability = 18500;
6243 activity = SIPE_ACTIVITY_OFFLINE;
6246 if (activity_token) {
6247 *activity_token = g_strdup(sipe_activity_map[activity].token);
6249 return availability;
6252 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
6254 const char *uri;
6255 sipe_xml *xn_categories;
6256 const sipe_xml *xn_category;
6257 const char *status = NULL;
6258 gboolean do_update_status = FALSE;
6259 gboolean has_note_cleaned = FALSE;
6260 gboolean has_free_busy_cleaned = FALSE;
6262 xn_categories = sipe_xml_parse(data, len);
6263 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
6265 for (xn_category = sipe_xml_child(xn_categories, "category");
6266 xn_category ;
6267 xn_category = sipe_xml_twin(xn_category) )
6269 const sipe_xml *xn_node;
6270 const char *tmp;
6271 const char *attrVar = sipe_xml_attribute(xn_category, "name");
6272 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
6273 sipe_utils_str_to_time(tmp) : 0;
6275 /* contactCard */
6276 if (sipe_strequal(attrVar, "contactCard"))
6278 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
6280 if (card) {
6281 const sipe_xml *node;
6282 /* identity - Display Name and email */
6283 node = sipe_xml_child(card, "identity");
6284 if (node) {
6285 char* display_name = sipe_xml_data(
6286 sipe_xml_child(node, "name/displayName"));
6287 char* email = sipe_xml_data(
6288 sipe_xml_child(node, "email"));
6290 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6291 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6293 g_free(display_name);
6294 g_free(email);
6296 /* company */
6297 node = sipe_xml_child(card, "company");
6298 if (node) {
6299 char* company = sipe_xml_data(node);
6300 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
6301 g_free(company);
6303 /* department */
6304 node = sipe_xml_child(card, "department");
6305 if (node) {
6306 char* department = sipe_xml_data(node);
6307 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
6308 g_free(department);
6310 /* title */
6311 node = sipe_xml_child(card, "title");
6312 if (node) {
6313 char* title = sipe_xml_data(node);
6314 sipe_update_user_info(sip, uri, TITLE_PROP, title);
6315 g_free(title);
6317 /* office */
6318 node = sipe_xml_child(card, "office");
6319 if (node) {
6320 char* office = sipe_xml_data(node);
6321 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
6322 g_free(office);
6324 /* site (url) */
6325 node = sipe_xml_child(card, "url");
6326 if (node) {
6327 char* site = sipe_xml_data(node);
6328 sipe_update_user_info(sip, uri, SITE_PROP, site);
6329 g_free(site);
6331 /* phone */
6332 for (node = sipe_xml_child(card, "phone");
6333 node;
6334 node = sipe_xml_twin(node))
6336 const char *phone_type = sipe_xml_attribute(node, "type");
6337 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
6338 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
6340 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
6342 g_free(phone);
6343 g_free(phone_display_string);
6345 /* address */
6346 for (node = sipe_xml_child(card, "address");
6347 node;
6348 node = sipe_xml_twin(node))
6350 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
6351 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
6352 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
6353 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
6354 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
6355 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
6357 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
6358 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
6359 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
6360 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
6361 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
6363 g_free(street);
6364 g_free(city);
6365 g_free(state);
6366 g_free(zipcode);
6367 g_free(country_code);
6369 break;
6374 /* note */
6375 else if (sipe_strequal(attrVar, "note"))
6377 if (uri) {
6378 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6380 if (!has_note_cleaned) {
6381 has_note_cleaned = TRUE;
6383 g_free(sbuddy->note);
6384 sbuddy->note = NULL;
6385 sbuddy->is_oof_note = FALSE;
6386 sbuddy->note_since = publish_time;
6388 do_update_status = TRUE;
6390 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6391 /* clean up in case no 'note' element is supplied
6392 * which indicate note removal in client
6394 g_free(sbuddy->note);
6395 sbuddy->note = NULL;
6396 sbuddy->is_oof_note = FALSE;
6397 sbuddy->note_since = publish_time;
6399 xn_node = sipe_xml_child(xn_category, "note/body");
6400 if (xn_node) {
6401 char *tmp;
6402 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6403 g_free(tmp);
6404 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6405 sbuddy->note_since = publish_time;
6407 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6408 uri, sbuddy->note ? sbuddy->note : "");
6410 /* to trigger UI refresh in case no status info is supplied in this update */
6411 do_update_status = TRUE;
6415 /* state */
6416 else if(sipe_strequal(attrVar, "state"))
6418 char *tmp;
6419 int availability;
6420 const sipe_xml *xn_availability;
6421 const sipe_xml *xn_activity;
6422 const sipe_xml *xn_meeting_subject;
6423 const sipe_xml *xn_meeting_location;
6424 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6426 xn_node = sipe_xml_child(xn_category, "state");
6427 if (!xn_node) continue;
6428 xn_availability = sipe_xml_child(xn_node, "availability");
6429 if (!xn_availability) continue;
6430 xn_activity = sipe_xml_child(xn_node, "activity");
6431 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6432 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6434 tmp = sipe_xml_data(xn_availability);
6435 availability = atoi(tmp);
6436 g_free(tmp);
6438 /* activity, meeting_subject, meeting_location */
6439 if (sbuddy) {
6440 char *tmp = NULL;
6442 /* activity */
6443 g_free(sbuddy->activity);
6444 sbuddy->activity = NULL;
6445 if (xn_activity) {
6446 const char *token = sipe_xml_attribute(xn_activity, "token");
6447 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6449 /* from token */
6450 if (!is_empty(token)) {
6451 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6453 /* from custom element */
6454 if (xn_custom) {
6455 char *custom = sipe_xml_data(xn_custom);
6457 if (!is_empty(custom)) {
6458 sbuddy->activity = custom;
6459 custom = NULL;
6461 g_free(custom);
6464 /* meeting_subject */
6465 g_free(sbuddy->meeting_subject);
6466 sbuddy->meeting_subject = NULL;
6467 if (xn_meeting_subject) {
6468 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6470 if (!is_empty(meeting_subject)) {
6471 sbuddy->meeting_subject = meeting_subject;
6472 meeting_subject = NULL;
6474 g_free(meeting_subject);
6476 /* meeting_location */
6477 g_free(sbuddy->meeting_location);
6478 sbuddy->meeting_location = NULL;
6479 if (xn_meeting_location) {
6480 char *meeting_location = sipe_xml_data(xn_meeting_location);
6482 if (!is_empty(meeting_location)) {
6483 sbuddy->meeting_location = meeting_location;
6484 meeting_location = NULL;
6486 g_free(meeting_location);
6489 status = sipe_get_status_by_availability(availability, &tmp);
6490 if (sbuddy->activity && tmp) {
6491 char *tmp2 = sbuddy->activity;
6493 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6494 g_free(tmp);
6495 g_free(tmp2);
6496 } else if (tmp) {
6497 sbuddy->activity = tmp;
6501 do_update_status = TRUE;
6503 /* calendarData */
6504 else if(sipe_strequal(attrVar, "calendarData"))
6506 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6507 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6508 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6510 if (sbuddy && xn_free_busy) {
6511 if (!has_free_busy_cleaned) {
6512 has_free_busy_cleaned = TRUE;
6514 g_free(sbuddy->cal_start_time);
6515 sbuddy->cal_start_time = NULL;
6517 g_free(sbuddy->cal_free_busy_base64);
6518 sbuddy->cal_free_busy_base64 = NULL;
6520 g_free(sbuddy->cal_free_busy);
6521 sbuddy->cal_free_busy = NULL;
6523 sbuddy->cal_free_busy_published = publish_time;
6526 if (publish_time >= sbuddy->cal_free_busy_published) {
6527 g_free(sbuddy->cal_start_time);
6528 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6530 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6531 15 : 0;
6533 g_free(sbuddy->cal_free_busy_base64);
6534 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6536 g_free(sbuddy->cal_free_busy);
6537 sbuddy->cal_free_busy = NULL;
6539 sbuddy->cal_free_busy_published = publish_time;
6541 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);
6545 if (sbuddy && xn_working_hours) {
6546 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6551 if (do_update_status) {
6552 if (!status) { /* no status category in this update, using contact's current status */
6553 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6554 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6555 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6556 status = purple_status_get_id(pstatus);
6559 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
6560 sipe_got_user_status(sip, uri, status);
6563 sipe_xml_free(xn_categories);
6566 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6568 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6569 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
6570 payload->host = g_strdup(host);
6571 payload->buddies = server;
6572 sipe_subscribe_presence_batched_routed(sip, payload);
6573 sipe_subscribe_presence_batched_routed_free(payload);
6576 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6578 sipe_xml *xn_list;
6579 const sipe_xml *xn_resource;
6580 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6581 g_free, NULL);
6582 GSList *server;
6583 gchar *host;
6585 xn_list = sipe_xml_parse(data, len);
6587 for (xn_resource = sipe_xml_child(xn_list, "resource");
6588 xn_resource;
6589 xn_resource = sipe_xml_twin(xn_resource) )
6591 const char *uri, *state;
6592 const sipe_xml *xn_instance;
6594 xn_instance = sipe_xml_child(xn_resource, "instance");
6595 if (!xn_instance) continue;
6597 uri = sipe_xml_attribute(xn_resource, "uri");
6598 state = sipe_xml_attribute(xn_instance, "state");
6599 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
6601 if (strstr(state, "resubscribe")) {
6602 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
6604 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6605 gchar *user = g_strdup(uri);
6606 host = g_strdup(poolFqdn);
6607 server = g_hash_table_lookup(servers, host);
6608 server = g_slist_append(server, user);
6609 g_hash_table_insert(servers, host, server);
6610 } else {
6611 sipe_subscribe_presence_single(sip, (void *) uri);
6616 /* Send out any deferred poolFqdn subscriptions */
6617 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6618 g_hash_table_destroy(servers);
6620 sipe_xml_free(xn_list);
6623 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6625 gchar *uri;
6626 gchar *getbasic;
6627 gchar *activity = NULL;
6628 sipe_xml *pidf;
6629 const sipe_xml *basicstatus = NULL, *tuple, *status;
6630 gboolean isonline = FALSE;
6631 const sipe_xml *display_name_node;
6633 pidf = sipe_xml_parse(data, len);
6634 if (!pidf) {
6635 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
6636 return;
6639 if ((tuple = sipe_xml_child(pidf, "tuple")))
6641 if ((status = sipe_xml_child(tuple, "status"))) {
6642 basicstatus = sipe_xml_child(status, "basic");
6646 if (!basicstatus) {
6647 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6648 sipe_xml_free(pidf);
6649 return;
6652 getbasic = sipe_xml_data(basicstatus);
6653 if (!getbasic) {
6654 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6655 sipe_xml_free(pidf);
6656 return;
6659 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
6660 if (strstr(getbasic, "open")) {
6661 isonline = TRUE;
6663 g_free(getbasic);
6665 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6667 display_name_node = sipe_xml_child(pidf, "display-name");
6668 if (display_name_node) {
6669 char * display_name = sipe_xml_data(display_name_node);
6671 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6672 g_free(display_name);
6675 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
6676 if ((status = sipe_xml_child(tuple, "status"))) {
6677 if ((basicstatus = sipe_xml_child(status, "activities"))) {
6678 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
6679 activity = sipe_xml_data(basicstatus);
6680 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
6686 if (isonline) {
6687 const gchar * status_id = NULL;
6688 if (activity) {
6689 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6690 status_id = SIPE_STATUS_ID_BUSY;
6691 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6692 status_id = SIPE_STATUS_ID_AWAY;
6696 if (!status_id) {
6697 status_id = SIPE_STATUS_ID_AVAILABLE;
6700 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
6701 sipe_got_user_status(sip, uri, status_id);
6702 } else {
6703 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6706 g_free(activity);
6707 g_free(uri);
6708 sipe_xml_free(pidf);
6711 /** 2005 */
6712 static void
6713 sipe_user_info_has_updated(struct sipe_account_data *sip,
6714 const sipe_xml *xn_userinfo)
6716 const sipe_xml *xn_states;
6718 g_free(sip->user_states);
6719 sip->user_states = NULL;
6720 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
6721 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
6723 /* this is a hack-around to remove added newline after inner element,
6724 * state in this case, where it shouldn't be.
6725 * After several use of sipe_xml_stringify, amount of added newlines
6726 * grows significantly.
6728 if (orig) {
6729 gchar c, *stripped = orig;
6730 while ((c = *orig++)) {
6731 if ((c != '\n') /* && (c != '\r') */) {
6732 *stripped++ = c;
6735 *stripped = '\0';
6739 /* Publish initial state if not yet.
6740 * Assuming this happens on initial responce to self subscription
6741 * so we've already updated our UserInfo.
6743 if (!sip->initial_state_published) {
6744 send_presence_soap(sip, FALSE);
6745 /* dalayed run */
6746 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_core_update_calendar, NULL, sip, NULL);
6750 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6752 char *activity = NULL;
6753 const char *epid;
6754 const char *status_id = NULL;
6755 const char *name;
6756 char *uri;
6757 char *self_uri = sip_uri_self(sip);
6758 int avl;
6759 int act;
6760 const char *device_name = NULL;
6761 const char *cal_start_time = NULL;
6762 const char *cal_granularity = NULL;
6763 char *cal_free_busy_base64 = NULL;
6764 struct sipe_buddy *sbuddy;
6765 const sipe_xml *node;
6766 sipe_xml *xn_presentity;
6767 const sipe_xml *xn_availability;
6768 const sipe_xml *xn_activity;
6769 const sipe_xml *xn_display_name;
6770 const sipe_xml *xn_email;
6771 const sipe_xml *xn_phone_number;
6772 const sipe_xml *xn_userinfo;
6773 const sipe_xml *xn_note;
6774 const sipe_xml *xn_oof;
6775 const sipe_xml *xn_state;
6776 const sipe_xml *xn_contact;
6777 char *note;
6778 char *free_activity;
6779 int user_avail;
6780 const char *user_avail_nil;
6781 int res_avail;
6782 time_t user_avail_since = 0;
6783 time_t activity_since = 0;
6785 /* fix for Reuters environment on Linux */
6786 if (data && strstr(data, "encoding=\"utf-16\"")) {
6787 char *tmp_data;
6788 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6789 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
6790 g_free(tmp_data);
6791 } else {
6792 xn_presentity = sipe_xml_parse(data, len);
6795 xn_availability = sipe_xml_child(xn_presentity, "availability");
6796 xn_activity = sipe_xml_child(xn_presentity, "activity");
6797 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
6798 xn_email = sipe_xml_child(xn_presentity, "email");
6799 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
6800 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
6801 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
6802 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
6803 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
6804 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
6805 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
6806 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
6807 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
6808 note = xn_note ? sipe_xml_data(xn_note) : NULL;
6810 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6811 user_avail = 0;
6812 user_avail_since = 0;
6815 free_activity = NULL;
6817 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
6818 uri = sip_uri_from_name(name);
6819 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
6820 epid = sipe_xml_attribute(xn_availability, "epid");
6821 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
6823 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6824 res_avail = sipe_get_availability_by_status(status_id, NULL);
6825 if (user_avail > res_avail) {
6826 res_avail = user_avail;
6827 status_id = sipe_get_status_by_availability(user_avail, NULL);
6830 if (xn_display_name) {
6831 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
6832 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
6833 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
6834 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
6835 char *tel_uri = sip_to_tel_uri(phone_number);
6837 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6838 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6839 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6840 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6842 g_free(tel_uri);
6843 g_free(phone_label);
6844 g_free(phone_number);
6845 g_free(email);
6846 g_free(display_name);
6849 if (xn_contact) {
6850 /* tel */
6851 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
6853 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6854 const char *phone_type = sipe_xml_attribute(node, "type");
6855 char* phone = sipe_xml_data(node);
6857 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6859 g_free(phone);
6863 /* devicePresence */
6864 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
6865 const sipe_xml *xn_device_name;
6866 const sipe_xml *xn_calendar_info;
6867 const sipe_xml *xn_state;
6868 char *state;
6870 /* deviceName */
6871 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
6872 xn_device_name = sipe_xml_child(node, "deviceName");
6873 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
6876 /* calendarInfo */
6877 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
6878 if (xn_calendar_info) {
6879 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
6881 if (cal_start_time) {
6882 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6883 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6885 if (cal_start_time_t_tmp > cal_start_time_t) {
6886 cal_start_time = cal_start_time_tmp;
6887 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6888 g_free(cal_free_busy_base64);
6889 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6891 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);
6893 } else {
6894 cal_start_time = cal_start_time_tmp;
6895 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6896 g_free(cal_free_busy_base64);
6897 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6899 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);
6903 /* state */
6904 xn_state = sipe_xml_child(node, "states/state");
6905 if (xn_state) {
6906 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
6907 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
6909 state = sipe_xml_data(xn_state);
6910 if (dev_avail_since > user_avail_since &&
6911 dev_avail >= res_avail)
6913 res_avail = dev_avail;
6914 if (!is_empty(state))
6916 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6917 g_free(activity);
6918 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6919 } else if (sipe_strequal(state, "presenting")) {
6920 g_free(activity);
6921 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6922 } else {
6923 activity = state;
6924 state = NULL;
6926 activity_since = dev_avail_since;
6928 status_id = sipe_get_status_by_availability(res_avail, &activity);
6930 g_free(state);
6934 /* oof */
6935 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6936 g_free(activity);
6937 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6938 activity_since = 0;
6941 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6942 if (sbuddy)
6944 g_free(sbuddy->activity);
6945 sbuddy->activity = activity;
6946 activity = NULL;
6948 sbuddy->activity_since = activity_since;
6950 sbuddy->user_avail = user_avail;
6951 sbuddy->user_avail_since = user_avail_since;
6953 g_free(sbuddy->note);
6954 sbuddy->note = NULL;
6955 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6957 sbuddy->is_oof_note = (xn_oof != NULL);
6959 g_free(sbuddy->device_name);
6960 sbuddy->device_name = NULL;
6961 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6963 if (!is_empty(cal_free_busy_base64)) {
6964 g_free(sbuddy->cal_start_time);
6965 sbuddy->cal_start_time = g_strdup(cal_start_time);
6967 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6969 g_free(sbuddy->cal_free_busy_base64);
6970 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6971 cal_free_busy_base64 = NULL;
6973 g_free(sbuddy->cal_free_busy);
6974 sbuddy->cal_free_busy = NULL;
6977 sbuddy->last_non_cal_status_id = status_id;
6978 g_free(sbuddy->last_non_cal_activity);
6979 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6981 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6982 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6984 sip->is_oof_note = sbuddy->is_oof_note;
6986 g_free(sip->note);
6987 sip->note = g_strdup(sbuddy->note);
6989 sip->note_since = time(NULL);
6992 g_free(sip->status);
6993 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6996 g_free(cal_free_busy_base64);
6997 g_free(activity);
6999 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
7000 sipe_got_user_status(sip, uri, status_id);
7002 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
7003 sipe_user_info_has_updated(sip, xn_userinfo);
7006 g_free(note);
7007 sipe_xml_free(xn_presentity);
7008 g_free(uri);
7009 g_free(self_uri);
7012 static void sipe_presence_mime_cb(gpointer user_data,
7013 const gchar *type,
7014 const gchar *body,
7015 gsize length)
7017 if (strstr(type,"application/rlmi+xml")) {
7018 process_incoming_notify_rlmi_resub(user_data, body, length);
7019 } else if (strstr(type, "text/xml+msrtc.pidf")) {
7020 process_incoming_notify_msrtc(user_data, body, length);
7021 } else {
7022 process_incoming_notify_rlmi(user_data, body, length);
7026 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
7028 const char *ctype = sipmsg_find_header(msg, "Content-Type");
7030 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
7032 if (ctype &&
7033 (strstr(ctype, "application/rlmi+xml") ||
7034 strstr(ctype, "application/msrtc-event-categories+xml")))
7036 if (strstr(ctype, "multipart"))
7038 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sip);
7040 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
7042 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
7044 else if(strstr(ctype, "application/rlmi+xml"))
7046 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
7049 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
7051 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
7053 else
7055 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
7059 static void sipe_presence_timeout_mime_cb(gpointer user_data,
7060 SIPE_UNUSED_PARAMETER const gchar *type,
7061 const gchar *body,
7062 gsize length)
7064 GSList **buddies = user_data;
7065 sipe_xml *xml = sipe_xml_parse(body, length);
7067 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
7068 const gchar *uri = sipe_xml_attribute(xml, "uri");
7069 const sipe_xml *xn_category;
7072 * automaton: presence is never expected to change
7074 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
7076 for (xn_category = sipe_xml_child(xml, "category");
7077 xn_category;
7078 xn_category = sipe_xml_twin(xn_category)) {
7079 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
7080 "contactCard")) {
7081 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
7082 if (node) {
7083 char *boolean = sipe_xml_data(node);
7084 if (sipe_strequal(boolean, "true")) {
7085 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
7086 uri);
7087 uri = NULL;
7089 g_free(boolean);
7091 break;
7095 if (uri) {
7096 *buddies = g_slist_append(*buddies, sip_uri(uri));
7100 sipe_xml_free(xml);
7103 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
7105 const char *ctype = sipmsg_find_header(msg, "Content-Type");
7106 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7108 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
7110 if (ctype &&
7111 strstr(ctype, "multipart") &&
7112 (strstr(ctype, "application/rlmi+xml") ||
7113 strstr(ctype, "application/msrtc-event-categories+xml"))) {
7114 GSList *buddies = NULL;
7116 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
7118 if (buddies) {
7119 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
7120 payload->host = g_strdup(who);
7121 payload->buddies = buddies;
7122 sipe_schedule_action(action_name, timeout,
7123 sipe_subscribe_presence_batched_routed,
7124 sipe_subscribe_presence_batched_routed_free,
7125 sip, payload);
7126 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
7129 } else {
7130 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7131 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
7133 g_free(action_name);
7137 * Dispatcher for all incoming subscription information
7138 * whether it comes from NOTIFY, BENOTIFY requests or
7139 * piggy-backed to subscription's OK responce.
7141 * @param request whether initiated from BE/NOTIFY request or OK-response message.
7142 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
7144 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
7146 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
7147 const gchar *event = sipmsg_find_header(msg, "Event");
7148 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
7149 char *tmp;
7151 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
7152 event ? event : "",
7153 tmp = fix_newlines(msg->body));
7154 g_free(tmp);
7155 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
7157 /* implicit subscriptions */
7158 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
7159 sipe_process_imdn(sip, msg);
7162 if (event) {
7163 /* for one off subscriptions (send with Expire: 0) */
7164 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
7166 sipe_process_provisioning_v2(sip, msg);
7168 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
7170 sipe_process_provisioning(sip, msg);
7172 else if (sipe_strcase_equal(event, "presence"))
7174 sipe_process_presence(sip, msg);
7176 else if (sipe_strcase_equal(event, "registration-notify"))
7178 sipe_process_registration_notify(sip, msg);
7181 if (!subscription_state || strstr(subscription_state, "active"))
7183 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
7185 sipe_process_roaming_contacts(sip, msg);
7187 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
7189 sipe_process_roaming_self(sip, msg);
7191 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
7193 sipe_process_roaming_acl(sip, msg);
7195 else if (sipe_strcase_equal(event, "presence.wpending"))
7197 sipe_process_presence_wpending(sip, msg);
7199 else if (sipe_strcase_equal(event, "conference"))
7201 sipe_process_conference(sip, msg);
7206 /* The server sends status 'terminated' */
7207 if (subscription_state && strstr(subscription_state, "terminated") ) {
7208 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
7209 gchar *key = sipe_get_subscription_key(event, who);
7211 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
7212 g_free(who);
7214 if (g_hash_table_lookup(sip->subscriptions, key)) {
7215 g_hash_table_remove(sip->subscriptions, key);
7216 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
7219 g_free(key);
7222 if (!request && event) {
7223 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
7224 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
7225 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
7227 if (timeout) {
7228 /* 2 min ahead of expiration */
7229 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
7231 if (sipe_strcase_equal(event, "presence.wpending") &&
7232 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
7234 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
7235 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
7236 g_free(action_name);
7238 else if (sipe_strcase_equal(event, "presence") &&
7239 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
7241 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
7242 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7244 if (sip->batched_support) {
7245 sipe_process_presence_timeout(sip, msg, who, timeout);
7247 else {
7248 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7249 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
7251 g_free(action_name);
7252 g_free(who);
7257 /* The client responses on received a NOTIFY message */
7258 if (request && !benotify)
7260 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7265 * Whether user manually changed status or
7266 * it was changed automatically due to user
7267 * became inactive/active again
7269 static gboolean
7270 sipe_is_user_state(struct sipe_account_data *sip)
7272 gboolean res;
7273 time_t now = time(NULL);
7275 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
7276 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
7278 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
7280 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
7281 return res;
7284 static void
7285 send_presence_soap0(struct sipe_account_data *sip,
7286 gboolean do_publish_calendar,
7287 gboolean do_reset_status)
7289 struct sipe_ews* ews = sip->ews;
7290 int availability = 0;
7291 int activity = 0;
7292 gchar *body;
7293 gchar *tmp;
7294 gchar *tmp2 = NULL;
7295 gchar *res_note = NULL;
7296 gchar *res_oof = NULL;
7297 const gchar *note_pub = NULL;
7298 gchar *states = NULL;
7299 gchar *calendar_data = NULL;
7300 gchar *epid = get_epid(sip);
7301 time_t now = time(NULL);
7302 gchar *since_time_str = sipe_utils_time_to_str(now);
7303 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
7304 const char *user_input;
7305 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
7307 if (oof_note && sip->note) {
7308 SIPE_DEBUG_INFO("ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
7309 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
7312 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
7314 if (!sip->initial_state_published ||
7315 do_reset_status)
7317 g_free(sip->status);
7318 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
7321 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
7323 /* Note */
7324 if (pub_oof) {
7325 note_pub = oof_note;
7326 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
7327 ews->published = TRUE;
7328 } else if (sip->note) {
7329 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
7330 g_free(sip->note);
7331 sip->note = NULL;
7332 sip->is_oof_note = FALSE;
7333 sip->note_since = 0;
7334 } else {
7335 note_pub = sip->note;
7336 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
7340 if (note_pub)
7342 /* to protocol internal plain text format */
7343 tmp = sipe_backend_markup_strip_html(note_pub);
7344 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
7345 g_free(tmp);
7348 /* User State */
7349 if (!do_reset_status) {
7350 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
7352 gchar *activity_token = NULL;
7353 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
7355 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
7356 avail_2007,
7357 since_time_str,
7358 epid,
7359 activity_token);
7360 g_free(activity_token);
7362 else /* preserve existing publication */
7364 if (sip->user_states) {
7365 states = g_strdup(sip->user_states);
7368 } else {
7369 /* do nothing - then User state will be erased */
7371 sip->initial_state_published = TRUE;
7373 /* CalendarInfo */
7374 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
7376 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7377 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7378 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
7379 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
7380 fb_start_str,
7381 free_busy_base64);
7382 g_free(fb_start_str);
7383 g_free(free_busy_base64);
7386 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
7388 /* forming resulting XML */
7389 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7390 sip->username,
7391 availability,
7392 activity,
7393 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7394 res_note ? res_note : "",
7395 res_oof ? res_oof : "",
7396 states ? states : "",
7397 calendar_data ? calendar_data : "",
7398 epid,
7399 since_time_str,
7400 since_time_str,
7401 user_input);
7402 g_free(tmp);
7403 g_free(tmp2);
7404 g_free(res_note);
7405 g_free(states);
7406 g_free(calendar_data);
7408 send_soap_request(sip, body);
7410 g_free(body);
7411 g_free(since_time_str);
7412 g_free(epid);
7415 void
7416 send_presence_soap(struct sipe_account_data *sip,
7417 gboolean do_publish_calendar)
7419 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7423 static gboolean
7424 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7425 struct sipmsg *msg,
7426 struct transaction *trans)
7428 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7430 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7431 sipe_xml *xml;
7432 const sipe_xml *node;
7433 gchar *fault_code;
7434 GHashTable *faults;
7435 int index_our;
7436 gboolean has_device_publication = FALSE;
7438 xml = sipe_xml_parse(msg->body, msg->bodylen);
7440 /* test if version mismatch fault */
7441 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
7442 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7443 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
7444 g_free(fault_code);
7445 sipe_xml_free(xml);
7446 return TRUE;
7448 g_free(fault_code);
7450 /* accumulating information about faulty versions */
7451 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7452 for (node = sipe_xml_child(xml, "details/operation");
7453 node;
7454 node = sipe_xml_twin(node))
7456 const gchar *index = sipe_xml_attribute(node, "index");
7457 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
7459 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7460 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
7462 sipe_xml_free(xml);
7464 /* here we are parsing own request to figure out what publication
7465 * referensed here only by index went wrong
7467 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
7469 /* publication */
7470 for (node = sipe_xml_child(xml, "publications/publication"),
7471 index_our = 1; /* starts with 1 - our first publication */
7472 node;
7473 node = sipe_xml_twin(node), index_our++)
7475 gchar *idx = g_strdup_printf("%d", index_our);
7476 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7477 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
7478 g_free(idx);
7480 if (sipe_strequal("device", categoryName)) {
7481 has_device_publication = TRUE;
7484 if (curVersion) { /* fault exist on this index */
7485 const gchar *container = sipe_xml_attribute(node, "container");
7486 const gchar *instance = sipe_xml_attribute(node, "instance");
7487 /* key is <category><instance><container> */
7488 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7489 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
7491 if (category) {
7492 struct sipe_publication *publication =
7493 g_hash_table_lookup(category, key);
7495 SIPE_DEBUG_INFO("key is %s", key);
7497 if (publication) {
7498 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7499 key, curVersion, publication->version);
7500 /* updating publication's version to the correct one */
7501 publication->version = atoi(curVersion);
7503 } else {
7504 /* We somehow lost this category from our publications... */
7505 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
7506 publication->category = g_strdup(categoryName);
7507 publication->instance = atoi(instance);
7508 publication->container = atoi(container);
7509 publication->version = atoi(curVersion);
7510 category = g_hash_table_new_full(g_str_hash, g_str_equal,
7511 g_free, (GDestroyNotify)free_publication);
7512 g_hash_table_insert(category, g_strdup(key), publication);
7513 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
7514 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
7516 g_free(key);
7519 sipe_xml_free(xml);
7520 g_hash_table_destroy(faults);
7522 /* rebublishing with right versions */
7523 if (has_device_publication) {
7524 send_publish_category_initial(sip);
7525 } else {
7526 send_presence_status(sip);
7529 return TRUE;
7533 * Returns 'device' XML part for publication.
7534 * Must be g_free'd after use.
7536 static gchar *
7537 sipe_publish_get_category_device(struct sipe_account_data *sip)
7539 gchar *uri;
7540 gchar *doc;
7541 gchar *epid = get_epid(sip);
7542 gchar *uuid = generateUUIDfromEPID(epid);
7543 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7544 /* key is <category><instance><container> */
7545 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7546 struct sipe_publication *publication =
7547 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7549 g_free(key);
7550 g_free(epid);
7552 uri = sip_uri_self(sip);
7553 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7554 device_instance,
7555 publication ? publication->version : 0,
7556 uuid,
7557 uri,
7558 "00:00:00+01:00", /* @TODO make timezone real*/
7559 g_get_host_name()
7562 g_free(uri);
7563 g_free(uuid);
7565 return doc;
7569 * A service method - use
7570 * - send_publish_get_category_state_machine and
7571 * - send_publish_get_category_state_user instead.
7572 * Must be g_free'd after use.
7574 static gchar *
7575 sipe_publish_get_category_state(struct sipe_account_data *sip,
7576 gboolean is_user_state)
7578 int availability = sipe_get_availability_by_status(sip->status, NULL);
7579 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7580 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7581 /* key is <category><instance><container> */
7582 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7583 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7584 struct sipe_publication *publication_2 =
7585 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7586 struct sipe_publication *publication_3 =
7587 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7589 g_free(key_2);
7590 g_free(key_3);
7592 if (publication_2 && (publication_2->availability == availability))
7594 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7595 return NULL; /* nothing to update */
7598 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7599 instance,
7600 publication_2 ? publication_2->version : 0,
7601 availability,
7602 instance,
7603 publication_3 ? publication_3->version : 0,
7604 availability);
7608 * Only Busy and OOF calendar event are published.
7609 * Different instances are used for that.
7611 * Must be g_free'd after use.
7613 static gchar *
7614 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7615 struct sipe_cal_event *event,
7616 const char *uri,
7617 int cal_satus)
7619 gchar *start_time_str;
7620 int availability = 0;
7621 gchar *res;
7622 gchar *tmp = NULL;
7623 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7624 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7625 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7627 /* key is <category><instance><container> */
7628 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7629 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7630 struct sipe_publication *publication_2 =
7631 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7632 struct sipe_publication *publication_3 =
7633 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7635 g_free(key_2);
7636 g_free(key_3);
7638 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7639 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7640 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
7641 return NULL;
7644 if (event &&
7645 publication_3 &&
7646 (publication_3->availability == availability) &&
7647 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7649 g_free(tmp);
7650 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7651 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
7652 return NULL; /* nothing to update */
7654 g_free(tmp);
7656 if (event &&
7657 (event->cal_status == SIPE_CAL_BUSY ||
7658 event->cal_status == SIPE_CAL_OOF))
7660 gchar *availability_xml_str = NULL;
7661 gchar *activity_xml_str = NULL;
7663 if (event->cal_status == SIPE_CAL_BUSY) {
7664 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7667 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7668 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7669 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7670 "minAvailability=\"6500\"",
7671 "maxAvailability=\"8999\"");
7672 } else if (event->cal_status == SIPE_CAL_OOF) {
7673 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7674 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7675 "minAvailability=\"12000\"",
7676 "");
7678 start_time_str = sipe_utils_time_to_str(event->start_time);
7680 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7681 instance,
7682 publication_2 ? publication_2->version : 0,
7683 uri,
7684 start_time_str,
7685 availability_xml_str ? availability_xml_str : "",
7686 activity_xml_str ? activity_xml_str : "",
7687 event->subject ? event->subject : "",
7688 event->location ? event->location : "",
7690 instance,
7691 publication_3 ? publication_3->version : 0,
7692 uri,
7693 start_time_str,
7694 availability_xml_str ? availability_xml_str : "",
7695 activity_xml_str ? activity_xml_str : "",
7696 event->subject ? event->subject : "",
7697 event->location ? event->location : ""
7699 g_free(start_time_str);
7700 g_free(availability_xml_str);
7701 g_free(activity_xml_str);
7704 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7706 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7707 instance,
7708 publication_2 ? publication_2->version : 0,
7710 instance,
7711 publication_3 ? publication_3->version : 0
7715 return res;
7719 * Returns 'machineState' XML part for publication.
7720 * Must be g_free'd after use.
7722 static gchar *
7723 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7725 return sipe_publish_get_category_state(sip, FALSE);
7729 * Returns 'userState' XML part for publication.
7730 * Must be g_free'd after use.
7732 static gchar *
7733 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7735 return sipe_publish_get_category_state(sip, TRUE);
7739 * Returns 'note' XML part for publication.
7740 * Must be g_free'd after use.
7742 * Protocol format for Note is plain text.
7744 * @param note a note in Sipe internal HTML format
7745 * @param note_type either personal or OOF
7747 static gchar *
7748 sipe_publish_get_category_note(struct sipe_account_data *sip,
7749 const char *note, /* html */
7750 const char *note_type,
7751 time_t note_start,
7752 time_t note_end)
7754 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7755 /* key is <category><instance><container> */
7756 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7757 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7758 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7760 struct sipe_publication *publication_note_200 =
7761 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7762 struct sipe_publication *publication_note_300 =
7763 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7764 struct sipe_publication *publication_note_400 =
7765 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7767 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7768 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7769 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7770 char *res, *tmp1, *tmp2, *tmp3;
7771 char *start_time_attr;
7772 char *end_time_attr;
7774 g_free(tmp);
7775 tmp = NULL;
7776 g_free(key_note_200);
7777 g_free(key_note_300);
7778 g_free(key_note_400);
7780 /* we even need to republish empty note */
7781 if (sipe_strequal(n1, n2))
7783 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7784 g_free(n1);
7785 return NULL; /* nothing to update */
7788 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7789 g_free(tmp);
7790 tmp = NULL;
7791 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7792 g_free(tmp);
7794 if (n1) {
7795 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7796 instance,
7797 200,
7798 publication_note_200 ? publication_note_200->version : 0,
7799 note_type,
7800 start_time_attr ? start_time_attr : "",
7801 end_time_attr ? end_time_attr : "",
7802 n1);
7804 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7805 instance,
7806 300,
7807 publication_note_300 ? publication_note_300->version : 0,
7808 note_type,
7809 start_time_attr ? start_time_attr : "",
7810 end_time_attr ? end_time_attr : "",
7811 n1);
7813 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7814 instance,
7815 400,
7816 publication_note_400 ? publication_note_400->version : 0,
7817 note_type,
7818 start_time_attr ? start_time_attr : "",
7819 end_time_attr ? end_time_attr : "",
7820 n1);
7821 } else {
7822 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7823 "note",
7824 instance,
7825 200,
7826 publication_note_200 ? publication_note_200->version : 0,
7827 "static");
7828 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7829 "note",
7830 instance,
7831 300,
7832 publication_note_200 ? publication_note_200->version : 0,
7833 "static");
7834 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7835 "note",
7836 instance,
7837 400,
7838 publication_note_200 ? publication_note_200->version : 0,
7839 "static");
7841 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7843 g_free(start_time_attr);
7844 g_free(end_time_attr);
7845 g_free(tmp1);
7846 g_free(tmp2);
7847 g_free(tmp3);
7848 g_free(n1);
7850 return res;
7854 * Returns 'calendarData' XML part with WorkingHours for publication.
7855 * Must be g_free'd after use.
7857 static gchar *
7858 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7860 struct sipe_ews* ews = sip->ews;
7862 /* key is <category><instance><container> */
7863 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7864 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7865 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7866 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7867 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7868 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7870 struct sipe_publication *publication_cal_1 =
7871 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7872 struct sipe_publication *publication_cal_100 =
7873 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7874 struct sipe_publication *publication_cal_200 =
7875 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7876 struct sipe_publication *publication_cal_300 =
7877 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7878 struct sipe_publication *publication_cal_400 =
7879 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7880 struct sipe_publication *publication_cal_32000 =
7881 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7883 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7884 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7886 g_free(key_cal_1);
7887 g_free(key_cal_100);
7888 g_free(key_cal_200);
7889 g_free(key_cal_300);
7890 g_free(key_cal_400);
7891 g_free(key_cal_32000);
7893 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7894 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7895 return NULL;
7898 if (sipe_strequal(n1, n2))
7900 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7901 return NULL; /* nothing to update */
7904 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7905 /* 1 */
7906 publication_cal_1 ? publication_cal_1->version : 0,
7907 ews->email,
7908 ews->working_hours_xml_str,
7909 /* 100 - Public */
7910 publication_cal_100 ? publication_cal_100->version : 0,
7911 /* 200 - Company */
7912 publication_cal_200 ? publication_cal_200->version : 0,
7913 ews->email,
7914 ews->working_hours_xml_str,
7915 /* 300 - Team */
7916 publication_cal_300 ? publication_cal_300->version : 0,
7917 ews->email,
7918 ews->working_hours_xml_str,
7919 /* 400 - Personal */
7920 publication_cal_400 ? publication_cal_400->version : 0,
7921 ews->email,
7922 ews->working_hours_xml_str,
7923 /* 32000 - Blocked */
7924 publication_cal_32000 ? publication_cal_32000->version : 0
7929 * Returns 'calendarData' XML part with FreeBusy for publication.
7930 * Must be g_free'd after use.
7932 static gchar *
7933 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7935 struct sipe_ews* ews = sip->ews;
7936 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7937 char *fb_start_str;
7938 char *free_busy_base64;
7939 const char *st;
7940 const char *fb;
7941 char *res;
7943 /* key is <category><instance><container> */
7944 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7945 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7946 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7947 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7948 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7949 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7951 struct sipe_publication *publication_cal_1 =
7952 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7953 struct sipe_publication *publication_cal_100 =
7954 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7955 struct sipe_publication *publication_cal_200 =
7956 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7957 struct sipe_publication *publication_cal_300 =
7958 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7959 struct sipe_publication *publication_cal_400 =
7960 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7961 struct sipe_publication *publication_cal_32000 =
7962 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7964 g_free(key_cal_1);
7965 g_free(key_cal_100);
7966 g_free(key_cal_200);
7967 g_free(key_cal_300);
7968 g_free(key_cal_400);
7969 g_free(key_cal_32000);
7971 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7972 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7973 return NULL;
7976 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7977 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7979 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7980 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7982 /* we will rebuplish the same data to refresh publication time,
7983 * so if data from multiple sources, most recent will be choosen
7985 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7987 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7988 // g_free(fb_start_str);
7989 // g_free(free_busy_base64);
7990 // return NULL; /* nothing to update */
7993 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7994 /* 1 */
7995 cal_data_instance,
7996 publication_cal_1 ? publication_cal_1->version : 0,
7997 /* 100 - Public */
7998 cal_data_instance,
7999 publication_cal_100 ? publication_cal_100->version : 0,
8000 /* 200 - Company */
8001 cal_data_instance,
8002 publication_cal_200 ? publication_cal_200->version : 0,
8003 ews->email,
8004 fb_start_str,
8005 free_busy_base64,
8006 /* 300 - Team */
8007 cal_data_instance,
8008 publication_cal_300 ? publication_cal_300->version : 0,
8009 ews->email,
8010 fb_start_str,
8011 free_busy_base64,
8012 /* 400 - Personal */
8013 cal_data_instance,
8014 publication_cal_400 ? publication_cal_400->version : 0,
8015 ews->email,
8016 fb_start_str,
8017 free_busy_base64,
8018 /* 32000 - Blocked */
8019 cal_data_instance,
8020 publication_cal_32000 ? publication_cal_32000->version : 0
8023 g_free(fb_start_str);
8024 g_free(free_busy_base64);
8025 return res;
8028 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
8030 gchar *uri;
8031 gchar *doc;
8032 gchar *tmp;
8033 gchar *hdr;
8035 uri = sip_uri_self(sip);
8036 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
8037 uri,
8038 publications);
8040 tmp = get_contact(sip);
8041 hdr = g_strdup_printf("Contact: %s\r\n"
8042 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
8044 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
8046 g_free(tmp);
8047 g_free(hdr);
8048 g_free(uri);
8049 g_free(doc);
8052 static void
8053 send_publish_category_initial(struct sipe_account_data *sip)
8055 gchar *pub_device = sipe_publish_get_category_device(sip);
8056 gchar *pub_machine;
8057 gchar *publications;
8059 g_free(sip->status);
8060 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
8062 pub_machine = sipe_publish_get_category_state_machine(sip);
8063 publications = g_strdup_printf("%s%s",
8064 pub_device,
8065 pub_machine ? pub_machine : "");
8066 g_free(pub_device);
8067 g_free(pub_machine);
8069 send_presence_publish(sip, publications);
8070 g_free(publications);
8073 static void
8074 send_presence_category_publish(struct sipe_account_data *sip)
8076 gchar *pub_state = sipe_is_user_state(sip) ?
8077 sipe_publish_get_category_state_user(sip) :
8078 sipe_publish_get_category_state_machine(sip);
8079 gchar *pub_note = sipe_publish_get_category_note(sip,
8080 sip->note,
8081 sip->is_oof_note ? "OOF" : "personal",
8084 gchar *publications;
8086 if (!pub_state && !pub_note) {
8087 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
8088 return;
8091 publications = g_strdup_printf("%s%s",
8092 pub_state ? pub_state : "",
8093 pub_note ? pub_note : "");
8095 g_free(pub_state);
8096 g_free(pub_note);
8098 send_presence_publish(sip, publications);
8099 g_free(publications);
8103 * Publishes self status
8104 * based on own calendar information.
8106 * For 2007+
8108 void
8109 publish_calendar_status_self(struct sipe_account_data *sip)
8111 struct sipe_cal_event* event = NULL;
8112 gchar *pub_cal_working_hours = NULL;
8113 gchar *pub_cal_free_busy = NULL;
8114 gchar *pub_calendar = NULL;
8115 gchar *pub_calendar2 = NULL;
8116 gchar *pub_oof_note = NULL;
8117 const gchar *oof_note;
8118 time_t oof_start = 0;
8119 time_t oof_end = 0;
8121 if (!sip->ews) {
8122 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
8123 return;
8126 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
8127 if (sip->ews->cal_events) {
8128 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
8131 if (!event) {
8132 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
8133 } else {
8134 char *desc = sipe_cal_event_describe(event);
8135 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
8136 g_free(desc);
8139 /* Logic
8140 if OOF
8141 OOF publish, Busy clean
8142 ilse if Busy
8143 OOF clean, Busy publish
8144 else
8145 OOF clean, Busy clean
8147 if (event && event->cal_status == SIPE_CAL_OOF) {
8148 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
8149 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8150 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
8151 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8152 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
8153 } else {
8154 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8155 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8158 oof_note = sipe_ews_get_oof_note(sip->ews);
8159 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
8160 oof_start = sip->ews->oof_start;
8161 oof_end = sip->ews->oof_end;
8163 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
8165 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
8166 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
8168 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
8169 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
8170 } else {
8171 gchar *publications = g_strdup_printf("%s%s%s%s%s",
8172 pub_cal_working_hours ? pub_cal_working_hours : "",
8173 pub_cal_free_busy ? pub_cal_free_busy : "",
8174 pub_calendar ? pub_calendar : "",
8175 pub_calendar2 ? pub_calendar2 : "",
8176 pub_oof_note ? pub_oof_note : "");
8178 send_presence_publish(sip, publications);
8179 g_free(publications);
8182 g_free(pub_cal_working_hours);
8183 g_free(pub_cal_free_busy);
8184 g_free(pub_calendar);
8185 g_free(pub_calendar2);
8186 g_free(pub_oof_note);
8188 /* repeat scheduling */
8189 sipe_sched_calendar_status_self_publish(sip, time(NULL));
8192 static void send_presence_status(struct sipe_account_data *sip)
8194 PurpleStatus * status = purple_account_get_active_status(sip->account);
8196 if (!status) return;
8198 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
8199 purple_status_get_id(status) ? purple_status_get_id(status) : "",
8200 sipe_is_user_state(sip) ? "USER" : "MACHINE");
8202 if (sip->ocs2007) {
8203 send_presence_category_publish(sip);
8204 } else {
8205 send_presence_soap(sip, FALSE);
8209 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
8211 gboolean found = FALSE;
8212 const char *method = msg->method ? msg->method : "NOT FOUND";
8213 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
8214 if (msg->response == 0) { /* request */
8215 if (sipe_strequal(method, "MESSAGE")) {
8216 process_incoming_message(sip, msg);
8217 found = TRUE;
8218 } else if (sipe_strequal(method, "NOTIFY")) {
8219 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
8220 process_incoming_notify(sip, msg, TRUE, FALSE);
8221 found = TRUE;
8222 } else if (sipe_strequal(method, "BENOTIFY")) {
8223 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
8224 process_incoming_notify(sip, msg, TRUE, TRUE);
8225 found = TRUE;
8226 } else if (sipe_strequal(method, "INVITE")) {
8227 process_incoming_invite(sip, msg);
8228 found = TRUE;
8229 } else if (sipe_strequal(method, "REFER")) {
8230 process_incoming_refer(sip, msg);
8231 found = TRUE;
8232 } else if (sipe_strequal(method, "OPTIONS")) {
8233 process_incoming_options(sip, msg);
8234 found = TRUE;
8235 } else if (sipe_strequal(method, "INFO")) {
8236 process_incoming_info(sip, msg);
8237 found = TRUE;
8238 } else if (sipe_strequal(method, "ACK")) {
8239 // ACK's don't need any response
8240 found = TRUE;
8241 } else if (sipe_strequal(method, "SUBSCRIBE")) {
8242 // LCS 2005 sends us these - just respond 200 OK
8243 found = TRUE;
8244 send_sip_response(sip->gc, msg, 200, "OK", NULL);
8245 } else if (sipe_strequal(method, "BYE")) {
8246 process_incoming_bye(sip, msg);
8247 found = TRUE;
8248 } else {
8249 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
8251 } else { /* response */
8252 struct transaction *trans = transactions_find(sip, msg);
8253 if (trans) {
8254 if (msg->response == 407) {
8255 gchar *resend, *auth;
8256 const gchar *ptmp;
8258 if (sip->proxy.retries > 30) return;
8259 sip->proxy.retries++;
8260 /* do proxy authentication */
8262 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
8264 fill_auth(ptmp, &sip->proxy);
8265 auth = auth_header(sip, &sip->proxy, trans->msg);
8266 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
8267 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
8268 g_free(auth);
8269 resend = sipmsg_to_string(trans->msg);
8270 /* resend request */
8271 sendout_pkt(sip->gc, resend);
8272 g_free(resend);
8273 } else {
8274 if (msg->response < 200) {
8275 /* ignore provisional response */
8276 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
8277 } else {
8278 sip->proxy.retries = 0;
8279 if (sipe_strequal(trans->msg->method, "REGISTER")) {
8280 if (msg->response == 401)
8282 sip->registrar.retries++;
8284 else
8286 sip->registrar.retries = 0;
8288 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
8289 } else {
8290 if (msg->response == 401) {
8291 gchar *resend, *auth, *ptmp;
8292 const char* auth_scheme;
8294 if (sip->registrar.retries > 4) return;
8295 sip->registrar.retries++;
8297 auth_scheme = sipe_get_auth_scheme_name(sip);
8298 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
8300 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
8301 if (!ptmp) {
8302 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
8303 sip->gc->wants_to_die = TRUE;
8304 purple_connection_error(sip->gc, tmp2);
8305 g_free(tmp2);
8306 return;
8309 fill_auth(ptmp, &sip->registrar);
8310 auth = auth_header(sip, &sip->registrar, trans->msg);
8311 sipmsg_remove_header_now(trans->msg, "Authorization");
8312 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
8313 g_free(auth);
8314 resend = sipmsg_to_string(trans->msg);
8315 /* resend request */
8316 sendout_pkt(sip->gc, resend);
8317 g_free(resend);
8321 if (trans->callback) {
8322 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
8323 /* call the callback to process response*/
8324 (trans->callback)(sip, msg, trans);
8327 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
8328 transactions_remove(sip, trans);
8332 found = TRUE;
8333 } else {
8334 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
8337 if (!found) {
8338 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
8342 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
8344 char *cur;
8345 char *dummy;
8346 char *tmp;
8347 struct sipmsg *msg;
8348 int restlen;
8349 cur = conn->inbuf;
8351 /* according to the RFC remove CRLF at the beginning */
8352 while (*cur == '\r' || *cur == '\n') {
8353 cur++;
8355 if (cur != conn->inbuf) {
8356 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
8357 conn->inbufused = strlen(conn->inbuf);
8360 /* Received a full Header? */
8361 sip->processing_input = TRUE;
8362 while (sip->processing_input &&
8363 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
8364 time_t currtime = time(NULL);
8365 cur += 2;
8366 cur[0] = '\0';
8367 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
8368 g_free(tmp);
8369 msg = sipmsg_parse_header(conn->inbuf);
8370 cur[0] = '\r';
8371 cur += 2;
8372 restlen = conn->inbufused - (cur - conn->inbuf);
8373 if (msg && restlen >= msg->bodylen) {
8374 dummy = g_malloc(msg->bodylen + 1);
8375 memcpy(dummy, cur, msg->bodylen);
8376 dummy[msg->bodylen] = '\0';
8377 msg->body = dummy;
8378 cur += msg->bodylen;
8379 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
8380 conn->inbufused = strlen(conn->inbuf);
8381 } else {
8382 if (msg){
8383 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen, msg->bodylen, (int)strlen(conn->inbuf));
8384 sipmsg_free(msg);
8386 return;
8389 /*if (msg->body) {
8390 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8393 // Verify the signature before processing it
8394 if (sip->registrar.gssapi_context) {
8395 struct sipmsg_breakdown msgbd;
8396 gchar *signature_input_str;
8397 gchar *rspauth;
8398 msgbd.msg = msg;
8399 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8400 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8402 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8404 if (rspauth != NULL) {
8405 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8406 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8407 process_input_message(sip, msg);
8408 } else {
8409 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8410 purple_connection_error(sip->gc, _("Invalid message signature received"));
8411 sip->gc->wants_to_die = TRUE;
8413 } else if (msg->response == 401) {
8414 purple_connection_error(sip->gc, _("Authentication failed"));
8415 sip->gc->wants_to_die = TRUE;
8417 g_free(signature_input_str);
8419 g_free(rspauth);
8420 sipmsg_breakdown_free(&msgbd);
8421 } else {
8422 process_input_message(sip, msg);
8425 sipmsg_free(msg);
8429 static void sipe_udp_process(gpointer data, gint source,
8430 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8432 PurpleConnection *gc = data;
8433 struct sipe_account_data *sip = gc->proto_data;
8434 int len;
8436 static char buffer[65536];
8437 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8438 time_t currtime = time(NULL);
8439 struct sipmsg *msg;
8440 buffer[len] = '\0';
8441 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), buffer);
8442 msg = sipmsg_parse_msg(buffer);
8443 if (msg) process_input_message(sip, msg);
8447 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8449 struct sipe_account_data *sip = gc->proto_data;
8450 PurpleSslConnection *gsc = sip->gsc;
8452 SIPE_DEBUG_ERROR("%s", debug);
8453 purple_connection_error(gc, msg);
8455 /* Invalidate this connection. Next send will open a new one */
8456 if (gsc) {
8457 connection_remove(sip, gsc->fd);
8458 purple_ssl_close(gsc);
8460 sip->gsc = NULL;
8461 sip->fd = -1;
8464 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8465 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8467 PurpleConnection *gc = data;
8468 struct sipe_account_data *sip;
8469 struct sip_connection *conn;
8470 int readlen, len;
8471 gboolean firstread = TRUE;
8473 /* NOTE: This check *IS* necessary */
8474 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8475 purple_ssl_close(gsc);
8476 return;
8479 sip = gc->proto_data;
8480 conn = connection_find(sip, gsc->fd);
8481 if (conn == NULL) {
8482 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
8483 gc->wants_to_die = TRUE;
8484 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8485 return;
8488 /* Read all available data from the SSL connection */
8489 do {
8490 /* Increase input buffer size as needed */
8491 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8492 conn->inbuflen += SIMPLE_BUF_INC;
8493 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8494 SIPE_DEBUG_INFO("sipe_input_cb_ssl: new input buffer length %d", conn->inbuflen);
8497 /* Try to read as much as there is space left in the buffer */
8498 readlen = conn->inbuflen - conn->inbufused - 1;
8499 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8501 if (len < 0 && errno == EAGAIN) {
8502 /* Try again later */
8503 return;
8504 } else if (len < 0) {
8505 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8506 return;
8507 } else if (firstread && (len == 0)) {
8508 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8509 return;
8512 conn->inbufused += len;
8513 firstread = FALSE;
8515 /* Equivalence indicates that there is possibly more data to read */
8516 } while (len == readlen);
8518 conn->inbuf[conn->inbufused] = '\0';
8519 process_input(sip, conn);
8523 static void sipe_input_cb(gpointer data, gint source,
8524 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8526 PurpleConnection *gc = data;
8527 struct sipe_account_data *sip = gc->proto_data;
8528 int len;
8529 struct sip_connection *conn = connection_find(sip, source);
8530 if (!conn) {
8531 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found!");
8532 return;
8535 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8536 conn->inbuflen += SIMPLE_BUF_INC;
8537 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8540 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8542 if (len < 0 && errno == EAGAIN)
8543 return;
8544 else if (len <= 0) {
8545 SIPE_DEBUG_INFO_NOFORMAT("sipe_input_cb: read error");
8546 connection_remove(sip, source);
8547 if (sip->fd == source) sip->fd = -1;
8548 return;
8551 conn->inbufused += len;
8552 conn->inbuf[conn->inbufused] = '\0';
8554 process_input(sip, conn);
8557 /* Callback for new connections on incoming TCP port */
8558 static void sipe_newconn_cb(gpointer data, gint source,
8559 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8561 PurpleConnection *gc = data;
8562 struct sipe_account_data *sip = gc->proto_data;
8563 struct sip_connection *conn;
8565 int newfd = accept(source, NULL, NULL);
8567 conn = connection_create(sip, newfd);
8569 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8572 static void login_cb(gpointer data, gint source,
8573 SIPE_UNUSED_PARAMETER const gchar *error_message)
8575 PurpleConnection *gc = data;
8576 struct sipe_account_data *sip;
8577 struct sip_connection *conn;
8579 if (!PURPLE_CONNECTION_IS_VALID(gc))
8581 if (source >= 0)
8582 close(source);
8583 return;
8586 if (source < 0) {
8587 purple_connection_error(gc, _("Could not connect"));
8588 return;
8591 sip = gc->proto_data;
8592 sip->fd = source;
8593 sip->last_keepalive = time(NULL);
8595 conn = connection_create(sip, source);
8597 do_register(sip);
8599 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8602 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8603 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8605 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8606 if (sip == NULL) return;
8608 do_register(sip);
8611 static guint sipe_ht_hash_nick(const char *nick)
8613 char *lc = g_utf8_strdown(nick, -1);
8614 guint bucket = g_str_hash(lc);
8615 g_free(lc);
8617 return bucket;
8620 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8622 char *nick1_norm = NULL;
8623 char *nick2_norm = NULL;
8624 gboolean equal;
8626 if (nick1 == NULL && nick2 == NULL) return TRUE;
8627 if (nick1 == NULL || nick2 == NULL ||
8628 !g_utf8_validate(nick1, -1, NULL) ||
8629 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
8631 nick1_norm = g_utf8_casefold(nick1, -1);
8632 nick2_norm = g_utf8_casefold(nick2, -1);
8633 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
8634 g_free(nick2_norm);
8635 g_free(nick1_norm);
8637 return equal;
8640 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8642 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8644 sip->listen_data = NULL;
8646 if (listenfd == -1) {
8647 purple_connection_error(sip->gc, _("Could not create listen socket"));
8648 return;
8651 sip->fd = listenfd;
8653 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8654 sip->listenfd = sip->fd;
8656 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8658 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8659 do_register(sip);
8662 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8663 SIPE_UNUSED_PARAMETER const char *error_message)
8665 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8667 sip->query_data = NULL;
8669 if (!hosts || !hosts->data) {
8670 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8671 return;
8674 hosts = g_slist_remove(hosts, hosts->data);
8675 g_free(sip->serveraddr);
8676 sip->serveraddr = hosts->data;
8677 hosts = g_slist_remove(hosts, hosts->data);
8678 while (hosts) {
8679 void *tmp = hosts->data;
8680 hosts = g_slist_remove(hosts, tmp);
8681 hosts = g_slist_remove(hosts, tmp);
8682 g_free(tmp);
8685 /* create socket for incoming connections */
8686 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8687 sipe_udp_host_resolved_listen_cb, sip);
8688 if (sip->listen_data == NULL) {
8689 purple_connection_error(sip->gc, _("Could not create listen socket"));
8690 return;
8694 static const struct sipe_service_data *current_service = NULL;
8696 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8697 PurpleSslErrorType error,
8698 gpointer data)
8700 PurpleConnection *gc = data;
8701 struct sipe_account_data *sip;
8703 /* If the connection is already disconnected, we don't need to do anything else */
8704 if (!PURPLE_CONNECTION_IS_VALID(gc))
8705 return;
8707 sip = gc->proto_data;
8708 current_service = sip->service_data;
8709 if (current_service) {
8710 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8711 current_service->transport ? current_service->transport : "NULL",
8712 current_service->service ? current_service->service : "NULL");
8715 sip->fd = -1;
8716 sip->gsc = NULL;
8718 switch(error) {
8719 case PURPLE_SSL_CONNECT_FAILED:
8720 purple_connection_error(gc, _("Connection failed"));
8721 break;
8722 case PURPLE_SSL_HANDSHAKE_FAILED:
8723 purple_connection_error(gc, _("SSL handshake failed"));
8724 break;
8725 case PURPLE_SSL_CERTIFICATE_INVALID:
8726 purple_connection_error(gc, _("SSL certificate invalid"));
8727 break;
8731 static void
8732 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8734 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8735 PurpleProxyConnectData *connect_data;
8737 sip->listen_data = NULL;
8739 sip->listenfd = listenfd;
8740 if (sip->listenfd == -1) {
8741 purple_connection_error(sip->gc, _("Could not create listen socket"));
8742 return;
8745 SIPE_DEBUG_INFO("listenfd: %d", sip->listenfd);
8746 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8747 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8748 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8749 sipe_newconn_cb, sip->gc);
8750 SIPE_DEBUG_INFO("connecting to %s port %d",
8751 sip->realhostname, sip->realport);
8752 /* open tcp connection to the server */
8753 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8754 sip->realport, login_cb, sip->gc);
8756 if (connect_data == NULL) {
8757 purple_connection_error(sip->gc, _("Could not create socket"));
8761 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8763 PurpleAccount *account = sip->account;
8764 PurpleConnection *gc = sip->gc;
8766 if (port == 0) {
8767 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8770 sip->realhostname = hostname;
8771 sip->realport = port;
8773 SIPE_DEBUG_INFO("create_connection - hostname: %s port: %d",
8774 hostname, port);
8776 /* TODO: is there a good default grow size? */
8777 if (sip->transport != SIPE_TRANSPORT_UDP)
8778 sip->txbuf = purple_circ_buffer_new(0);
8780 if (sip->transport == SIPE_TRANSPORT_TLS) {
8781 /* SSL case */
8782 if (!purple_ssl_is_supported()) {
8783 gc->wants_to_die = TRUE;
8784 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8785 return;
8788 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
8790 sip->gsc = purple_ssl_connect(account, hostname, port,
8791 login_cb_ssl, sipe_ssl_connect_failure, gc);
8792 if (sip->gsc == NULL) {
8793 purple_connection_error(gc, _("Could not create SSL context"));
8794 return;
8796 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8797 /* UDP case */
8798 SIPE_DEBUG_INFO_NOFORMAT("using UDP");
8800 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8801 if (sip->query_data == NULL) {
8802 purple_connection_error(gc, _("Could not resolve hostname"));
8804 } else {
8805 /* TCP case */
8806 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
8807 /* create socket for incoming connections */
8808 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8809 sipe_tcp_connect_listen_cb, sip);
8810 if (sip->listen_data == NULL) {
8811 purple_connection_error(gc, _("Could not create listen socket"));
8812 return;
8817 /* Service list for autodection */
8818 static const struct sipe_service_data service_autodetect[] = {
8819 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8820 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8821 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8822 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8823 { NULL, NULL, 0 }
8826 /* Service list for SSL/TLS */
8827 static const struct sipe_service_data service_tls[] = {
8828 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8829 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8830 { NULL, NULL, 0 }
8833 /* Service list for TCP */
8834 static const struct sipe_service_data service_tcp[] = {
8835 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8836 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8837 { NULL, NULL, 0 }
8840 /* Service list for UDP */
8841 static const struct sipe_service_data service_udp[] = {
8842 { "sip", "udp", SIPE_TRANSPORT_UDP },
8843 { NULL, NULL, 0 }
8846 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8847 static void resolve_next_service(struct sipe_account_data *sip,
8848 const struct sipe_service_data *start)
8850 if (start) {
8851 sip->service_data = start;
8852 } else {
8853 sip->service_data++;
8854 if (sip->service_data->service == NULL) {
8855 gchar *hostname;
8856 /* Try connecting to the SIP hostname directly */
8857 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8858 if (sip->auto_transport) {
8859 // If SSL is supported, default to using it; OCS servers aren't configured
8860 // by default to accept TCP
8861 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8862 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8863 SIPE_DEBUG_INFO_NOFORMAT("set transport type..");
8866 hostname = g_strdup(sip->sipdomain);
8867 create_connection(sip, hostname, 0);
8868 return;
8872 /* Try to resolve next service */
8873 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8874 sip->service_data->transport,
8875 sip->sipdomain,
8876 srvresolved, sip);
8879 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8881 struct sipe_account_data *sip = data;
8883 sip->srv_query_data = NULL;
8885 /* find the host to connect to */
8886 if (results) {
8887 gchar *hostname = g_strdup(resp->hostname);
8888 int port = resp->port;
8889 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8890 hostname, port);
8891 g_free(resp);
8893 sip->transport = sip->service_data->type;
8895 create_connection(sip, hostname, port);
8896 } else {
8897 resolve_next_service(sip, NULL);
8901 static void sipe_login(PurpleAccount *account)
8903 PurpleConnection *gc;
8904 struct sipe_account_data *sip;
8905 gchar **signinname_login, **userserver;
8906 const char *transport;
8907 const char *email;
8909 const char *username = purple_account_get_username(account);
8910 gc = purple_account_get_connection(account);
8912 SIPE_DEBUG_INFO("sipe_login: username '%s'", username);
8914 if (strpbrk(username, "\t\v\r\n") != NULL) {
8915 gc->wants_to_die = TRUE;
8916 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8917 return;
8920 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8921 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8922 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8923 sip->gc = gc;
8924 sip->account = account;
8925 sip->reregister_set = FALSE;
8926 sip->reauthenticate_set = FALSE;
8927 sip->subscribed = FALSE;
8928 sip->subscribed_buddies = FALSE;
8929 sip->initial_state_published = FALSE;
8931 /* username format: <username>,[<optional login>] */
8932 signinname_login = g_strsplit(username, ",", 2);
8933 SIPE_DEBUG_INFO("sipe_login: signinname[0] '%s'", signinname_login[0]);
8935 /* ensure that username format is name@domain */
8936 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8937 g_strfreev(signinname_login);
8938 gc->wants_to_die = TRUE;
8939 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8940 return;
8942 sip->username = g_strdup(signinname_login[0]);
8944 /* ensure that email format is name@domain if provided */
8945 email = purple_account_get_string(sip->account, "email", NULL);
8946 if (!is_empty(email) &&
8947 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8949 gc->wants_to_die = TRUE;
8950 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8951 return;
8953 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8955 /* login name specified? */
8956 if (signinname_login[1] && strlen(signinname_login[1])) {
8957 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8958 gboolean has_domain = domain_user[1] != NULL;
8959 SIPE_DEBUG_INFO("sipe_login: signinname[1] '%s'", signinname_login[1]);
8960 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8961 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8962 SIPE_DEBUG_INFO("sipe_login: auth domain '%s' user '%s'",
8963 sip->authdomain ? sip->authdomain : "", sip->authuser);
8964 g_strfreev(domain_user);
8967 userserver = g_strsplit(signinname_login[0], "@", 2);
8968 SIPE_DEBUG_INFO("sipe_login: user '%s' server '%s'", userserver[0], userserver[1]);
8969 purple_connection_set_display_name(gc, userserver[0]);
8970 sip->sipdomain = g_strdup(userserver[1]);
8971 g_strfreev(userserver);
8972 g_strfreev(signinname_login);
8974 if (strchr(sip->username, ' ') != NULL) {
8975 gc->wants_to_die = TRUE;
8976 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8977 return;
8980 sip->password = g_strdup(purple_connection_get_password(gc));
8982 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8983 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8984 g_free, (GDestroyNotify)g_hash_table_destroy);
8985 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8986 g_free, (GDestroyNotify)sipe_subscription_free);
8988 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8990 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8992 g_free(sip->status);
8993 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8995 sip->auto_transport = FALSE;
8996 transport = purple_account_get_string(account, "transport", "auto");
8997 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8998 if (userserver[0]) {
8999 /* Use user specified server[:port] */
9000 int port = 0;
9002 if (userserver[1])
9003 port = atoi(userserver[1]);
9005 SIPE_DEBUG_INFO("sipe_login: user specified SIP server %s:%d",
9006 userserver[0], port);
9008 if (sipe_strequal(transport, "auto")) {
9009 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
9010 } else if (sipe_strequal(transport, "tls")) {
9011 sip->transport = SIPE_TRANSPORT_TLS;
9012 } else if (sipe_strequal(transport, "tcp")) {
9013 sip->transport = SIPE_TRANSPORT_TCP;
9014 } else {
9015 sip->transport = SIPE_TRANSPORT_UDP;
9018 create_connection(sip, g_strdup(userserver[0]), port);
9019 } else {
9020 /* Server auto-discovery */
9021 if (sipe_strequal(transport, "auto")) {
9022 sip->auto_transport = TRUE;
9023 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
9024 current_service++;
9025 resolve_next_service(sip, current_service);
9026 } else {
9027 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
9029 } else if (sipe_strequal(transport, "tls")) {
9030 resolve_next_service(sip, service_tls);
9031 } else if (sipe_strequal(transport, "tcp")) {
9032 resolve_next_service(sip, service_tcp);
9033 } else {
9034 resolve_next_service(sip, service_udp);
9037 g_strfreev(userserver);
9040 static void sipe_connection_cleanup(struct sipe_account_data *sip)
9042 connection_free_all(sip);
9044 g_free(sip->epid);
9045 sip->epid = NULL;
9047 if (sip->query_data != NULL)
9048 purple_dnsquery_destroy(sip->query_data);
9049 sip->query_data = NULL;
9051 if (sip->srv_query_data != NULL)
9052 purple_srv_cancel(sip->srv_query_data);
9053 sip->srv_query_data = NULL;
9055 if (sip->listen_data != NULL)
9056 purple_network_listen_cancel(sip->listen_data);
9057 sip->listen_data = NULL;
9059 if (sip->gsc != NULL)
9060 purple_ssl_close(sip->gsc);
9061 sip->gsc = NULL;
9063 sipe_auth_free(&sip->registrar);
9064 sipe_auth_free(&sip->proxy);
9066 if (sip->txbuf)
9067 purple_circ_buffer_destroy(sip->txbuf);
9068 sip->txbuf = NULL;
9070 g_free(sip->realhostname);
9071 sip->realhostname = NULL;
9073 g_free(sip->server_version);
9074 sip->server_version = NULL;
9076 if (sip->listenpa)
9077 purple_input_remove(sip->listenpa);
9078 sip->listenpa = 0;
9079 if (sip->tx_handler)
9080 purple_input_remove(sip->tx_handler);
9081 sip->tx_handler = 0;
9082 if (sip->resendtimeout)
9083 purple_timeout_remove(sip->resendtimeout);
9084 sip->resendtimeout = 0;
9085 if (sip->timeouts) {
9086 GSList *entry = sip->timeouts;
9087 while (entry) {
9088 struct scheduled_action *sched_action = entry->data;
9089 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
9090 purple_timeout_remove(sched_action->timeout_handler);
9091 if (sched_action->destroy) {
9092 (*sched_action->destroy)(sched_action->payload);
9094 g_free(sched_action->name);
9095 g_free(sched_action);
9096 entry = entry->next;
9099 g_slist_free(sip->timeouts);
9101 if (sip->allow_events) {
9102 GSList *entry = sip->allow_events;
9103 while (entry) {
9104 g_free(entry->data);
9105 entry = entry->next;
9108 g_slist_free(sip->allow_events);
9110 if (sip->containers) {
9111 GSList *entry = sip->containers;
9112 while (entry) {
9113 free_container((struct sipe_container *)entry->data);
9114 entry = entry->next;
9117 g_slist_free(sip->containers);
9119 if (sip->contact)
9120 g_free(sip->contact);
9121 sip->contact = NULL;
9122 if (sip->regcallid)
9123 g_free(sip->regcallid);
9124 sip->regcallid = NULL;
9126 if (sip->serveraddr)
9127 g_free(sip->serveraddr);
9128 sip->serveraddr = NULL;
9130 if (sip->focus_factory_uri)
9131 g_free(sip->focus_factory_uri);
9132 sip->focus_factory_uri = NULL;
9134 sip->fd = -1;
9135 sip->processing_input = FALSE;
9137 if (sip->ews) {
9138 sipe_ews_free(sip->ews);
9140 sip->ews = NULL;
9144 * A callback for g_hash_table_foreach_remove
9146 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
9147 SIPE_UNUSED_PARAMETER gpointer user_data)
9149 sipe_free_buddy((struct sipe_buddy *) buddy);
9151 /* We must return TRUE as the key/value have already been deleted */
9152 return(TRUE);
9155 static void sipe_close(PurpleConnection *gc)
9157 struct sipe_account_data *sip = gc->proto_data;
9159 if (sip) {
9160 /* leave all conversations */
9161 sipe_session_close_all(sip);
9162 sipe_session_remove_all(sip);
9164 if (sip->csta) {
9165 sip_csta_close(sip);
9168 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
9169 /* unsubscribe all */
9170 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
9172 /* unregister */
9173 do_register_exp(sip, 0);
9176 sipe_connection_cleanup(sip);
9177 g_free(sip->sipdomain);
9178 g_free(sip->username);
9179 g_free(sip->email);
9180 g_free(sip->password);
9181 g_free(sip->authdomain);
9182 g_free(sip->authuser);
9183 g_free(sip->status);
9184 g_free(sip->note);
9185 g_free(sip->user_states);
9187 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
9188 g_hash_table_destroy(sip->buddies);
9189 g_hash_table_destroy(sip->our_publications);
9190 g_hash_table_destroy(sip->user_state_publications);
9191 g_hash_table_destroy(sip->subscriptions);
9192 g_hash_table_destroy(sip->filetransfers);
9194 if (sip->groups) {
9195 GSList *entry = sip->groups;
9196 while (entry) {
9197 struct sipe_group *group = entry->data;
9198 g_free(group->name);
9199 g_free(group);
9200 entry = entry->next;
9203 g_slist_free(sip->groups);
9205 if (sip->our_publication_keys) {
9206 GSList *entry = sip->our_publication_keys;
9207 while (entry) {
9208 g_free(entry->data);
9209 entry = entry->next;
9212 g_slist_free(sip->our_publication_keys);
9214 while (sip->transactions)
9215 transactions_remove(sip, sip->transactions->data);
9217 g_free(gc->proto_data);
9218 gc->proto_data = NULL;
9221 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
9222 SIPE_UNUSED_PARAMETER void *user_data)
9224 PurpleAccount *acct = purple_connection_get_account(gc);
9225 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
9226 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
9227 if (conv == NULL)
9228 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
9229 purple_conversation_present(conv);
9230 g_free(id);
9233 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
9234 SIPE_UNUSED_PARAMETER void *user_data)
9237 purple_blist_request_add_buddy(purple_connection_get_account(gc),
9238 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
9241 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
9242 SIPE_UNUSED_PARAMETER struct transaction *trans)
9244 PurpleNotifySearchResults *results;
9245 PurpleNotifySearchColumn *column;
9246 sipe_xml *searchResults;
9247 const sipe_xml *mrow;
9248 int match_count = 0;
9249 gboolean more = FALSE;
9250 gchar *secondary;
9252 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
9254 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
9255 if (!searchResults) {
9256 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
9257 return FALSE;
9260 results = purple_notify_searchresults_new();
9262 if (results == NULL) {
9263 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
9264 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
9266 sipe_xml_free(searchResults);
9267 return FALSE;
9270 column = purple_notify_searchresults_column_new(_("User name"));
9271 purple_notify_searchresults_column_add(results, column);
9273 column = purple_notify_searchresults_column_new(_("Name"));
9274 purple_notify_searchresults_column_add(results, column);
9276 column = purple_notify_searchresults_column_new(_("Company"));
9277 purple_notify_searchresults_column_add(results, column);
9279 column = purple_notify_searchresults_column_new(_("Country"));
9280 purple_notify_searchresults_column_add(results, column);
9282 column = purple_notify_searchresults_column_new(_("Email"));
9283 purple_notify_searchresults_column_add(results, column);
9285 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
9286 GList *row = NULL;
9288 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
9289 row = g_list_append(row, g_strdup(uri_parts[1]));
9290 g_strfreev(uri_parts);
9292 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
9293 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
9294 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
9295 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
9297 purple_notify_searchresults_row_add(results, row);
9298 match_count++;
9301 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
9302 char *data = sipe_xml_data(mrow);
9303 more = (g_strcasecmp(data, "true") == 0);
9304 g_free(data);
9307 secondary = g_strdup_printf(
9308 dngettext(PACKAGE_NAME,
9309 "Found %d contact%s:",
9310 "Found %d contacts%s:", match_count),
9311 match_count, more ? _(" (more matched your query)") : "");
9313 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
9314 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
9315 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
9317 g_free(secondary);
9318 sipe_xml_free(searchResults);
9319 return TRUE;
9322 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
9324 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
9325 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
9326 unsigned i = 0;
9328 if (!attrs) return;
9330 do {
9331 PurpleRequestField *field = entries->data;
9332 const char *id = purple_request_field_get_id(field);
9333 const char *value = purple_request_field_string_get_value(field);
9335 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
9337 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
9338 } while ((entries = g_list_next(entries)) != NULL);
9339 attrs[i] = NULL;
9341 if (i > 0) {
9342 struct sipe_account_data *sip = gc->proto_data;
9343 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9344 gchar *query = g_strjoinv(NULL, attrs);
9345 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
9346 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
9347 send_soap_request_with_cb(sip, domain_uri, body,
9348 (TransCallback) process_search_contact_response, NULL);
9349 g_free(domain_uri);
9350 g_free(body);
9351 g_free(query);
9354 g_strfreev(attrs);
9357 gchar *sipe_core_about(void)
9359 return g_strdup_printf(
9361 * Non-translatable parts, like markup, are hard-coded
9362 * into the format string. This requires more translatable
9363 * texts but it makes the translations less error prone.
9365 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
9366 "<br/>"
9367 /* 1 */ "%s:<br/>"
9368 "<li> - MS Office Communications Server 2007 R2</li><br/>"
9369 "<li> - MS Office Communications Server 2007</li><br/>"
9370 "<li> - MS Live Communications Server 2005</li><br/>"
9371 "<li> - MS Live Communications Server 2003</li><br/>"
9372 "<li> - Reuters Messaging</li><br/>"
9373 "<br/>"
9374 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
9375 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
9376 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
9377 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
9378 /* 8 */ "%s: GPLv2+<br/>"
9379 "<br/>"
9380 /* 9 */ "%s:<br/>"
9381 " - CERN<br/>"
9382 " - Reuters Messaging network<br/>"
9383 " - Deutsche Bank<br/>"
9384 " - Merrill Lynch<br/>"
9385 " - Wachovia<br/>"
9386 " - Intel<br/>"
9387 " - Nokia<br/>"
9388 " - HP<br/>"
9389 " - Symantec<br/>"
9390 " - Accenture<br/>"
9391 " - Capgemini<br/>"
9392 " - Siemens<br/>"
9393 " - Alcatel-Lucent<br/>"
9394 " - BT<br/>"
9395 "<br/>"
9396 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9397 "<br/>"
9398 /* 12 */ "<b>%s:</b><br/>"
9399 " - Anibal Avelar<br/>"
9400 " - Gabriel Burt<br/>"
9401 " - Stefan Becker<br/>"
9402 " - pier11<br/>"
9403 " - Jakub Adam<br/>"
9404 " - Tomáš Hrabčík<br/>"
9405 "<br/>"
9406 /* 13 */ "%s<br/>"
9408 /* The next 13 texts make up the SIPE about note text */
9409 /* About note, part 1/13: introduction */
9410 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9411 /* About note, part 2/13: home page URL (label) */
9412 _("Home"),
9413 /* About note, part 3/13: support forum URL (label) */
9414 _("Support"),
9415 /* About note, part 4/13: support forum name (hyperlink text) */
9416 _("Help Forum"),
9417 /* About note, part 5/13: bug tracker URL (label) */
9418 _("Report Problems"),
9419 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9420 _("Bug Tracker"),
9421 /* About note, part 7/13: translation service URL (label) */
9422 _("Translations"),
9423 /* About note, part 8/13: license type (label) */
9424 _("License"),
9425 /* About note, part 9/13: known users */
9426 _("We support users in such organizations as"),
9427 /* About note, part 10/13: translation request, text before Transifex.net URL */
9428 /* append a space if text is not empty */
9429 _("Please help us to translate SIPE to your native language here at "),
9430 /* About note, part 11/13: translation request, text after Transifex.net URL */
9431 /* start with a space if text is not empty */
9432 _(" using convenient web interface"),
9433 /* About note, part 12/13: author list (header) */
9434 _("Authors"),
9435 /* About note, part 13/13: Localization credit */
9436 /* PLEASE NOTE: do *NOT* simply translate the english original */
9437 /* but write something similar to the following sentence: */
9438 /* "Localization for <language name> (<language code>): <name>" */
9439 _("Original texts in English (en): SIPE developers")
9443 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9444 gpointer value,
9445 GString* str)
9447 struct sipe_publication *publication = value;
9449 g_string_append_printf( str,
9450 SIPE_PUB_XML_PUBLICATION_CLEAR,
9451 publication->category,
9452 publication->instance,
9453 publication->container,
9454 publication->version,
9455 "static");
9458 void sipe_core_reset_status(struct sipe_account_data *sip)
9460 if (sip->ocs2007) /* 2007+ */
9462 GString* str = g_string_new(NULL);
9463 gchar *publications;
9465 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9466 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
9467 return;
9470 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9471 publications = g_string_free(str, FALSE);
9473 send_presence_publish(sip, publications);
9474 g_free(publications);
9476 else /* 2005 */
9478 send_presence_soap0(sip, FALSE, TRUE);
9482 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9486 static char *sipe_status_text(PurpleBuddy *buddy)
9488 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9489 const PurpleStatus *status = purple_presence_get_active_status(presence);
9490 const char *status_id = purple_status_get_id(status);
9491 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9492 struct sipe_buddy *sbuddy;
9493 char *text = NULL;
9495 if (!sip) return NULL; /* happens on pidgin exit */
9497 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9498 if (sbuddy) {
9499 const char *activity_str = sbuddy->activity ?
9500 sbuddy->activity :
9501 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9502 purple_status_get_name(status) : NULL;
9504 if (activity_str && sbuddy->note)
9506 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9508 else if (activity_str)
9510 text = g_strdup(activity_str);
9512 else if (sbuddy->note)
9514 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9518 return text;
9521 /** for Access levels menu */
9522 #define INDENT_FMT " %s"
9524 /** Member is directly placed to access level container.
9525 * For example SIP URI of user is in the container.
9527 #define INDENT_MARKED_FMT "* %s"
9529 /** Member is indirectly belong to access level container.
9530 * For example 'sameEnterprise' is in the container and user
9531 * belongs to that same enterprise.
9533 #define INDENT_MARKED_INHERITED_FMT "= %s"
9535 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9537 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9538 const PurpleStatus *status = purple_presence_get_active_status(presence);
9539 struct sipe_account_data *sip;
9540 struct sipe_buddy *sbuddy;
9541 char *note = NULL;
9542 gboolean is_oof_note = FALSE;
9543 char *activity = NULL;
9544 char *calendar = NULL;
9545 char *meeting_subject = NULL;
9546 char *meeting_location = NULL;
9548 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9549 if (sip) //happens on pidgin exit
9551 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9552 if (sbuddy)
9554 note = sbuddy->note;
9555 is_oof_note = sbuddy->is_oof_note;
9556 activity = sbuddy->activity;
9557 calendar = sipe_cal_get_description(sbuddy);
9558 meeting_subject = sbuddy->meeting_subject;
9559 meeting_location = sbuddy->meeting_location;
9563 //Layout
9564 if (purple_presence_is_online(presence))
9566 const char *status_str = activity ? activity : purple_status_get_name(status);
9568 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9570 if (purple_presence_is_online(presence) &&
9571 !is_empty(calendar))
9573 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9575 g_free(calendar);
9576 if (!is_empty(meeting_location))
9578 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9580 if (!is_empty(meeting_subject))
9582 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9585 if (note)
9587 char *tmp = g_strdup_printf("<i>%s</i>", note);
9588 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", buddy->name, note);
9590 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9591 g_free(tmp);
9594 if (sip && sip->ocs2007) {
9595 gboolean is_group_access = FALSE;
9596 const int container_id = sipe_find_access_level(sip, "user", sipe_get_no_sip_uri(buddy->name), &is_group_access);
9597 const char *access_level = sipe_get_access_level_name(container_id);
9598 char *text = is_group_access ?
9599 g_strdup(access_level) :
9600 g_strdup_printf(INDENT_MARKED_FMT, access_level);
9602 purple_notify_user_info_add_pair(user_info, _("Access level"), text);
9603 g_free(text);
9607 #if PURPLE_VERSION_CHECK(2,5,0)
9608 static GHashTable *
9609 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9611 GHashTable *table;
9612 table = g_hash_table_new(g_str_hash, g_str_equal);
9613 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9614 return table;
9616 #endif
9618 static PurpleBuddy *
9619 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9621 PurpleBuddy *clone;
9622 const gchar *server_alias, *email;
9623 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9625 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9627 purple_blist_add_buddy(clone, NULL, group, NULL);
9629 server_alias = purple_buddy_get_server_alias(buddy);
9630 if (server_alias) {
9631 purple_blist_server_alias_buddy(clone, server_alias);
9634 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9635 if (email) {
9636 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9639 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9640 //for UI to update;
9641 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9642 return clone;
9645 static void
9646 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9648 PurpleBuddy *buddy, *b;
9649 PurpleConnection *gc;
9650 PurpleGroup * group = purple_find_group(group_name);
9652 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9654 buddy = (PurpleBuddy *)node;
9656 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
9657 gc = purple_account_get_connection(buddy->account);
9659 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9660 if (!b){
9661 purple_blist_add_buddy_clone(group, buddy);
9664 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9667 static void
9668 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9670 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9672 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
9674 /* 2007+ conference */
9675 if (sip->ocs2007)
9677 sipe_conf_add(sip, buddy->name);
9679 else /* 2005- multiparty chat */
9681 gchar *self = sip_uri_self(sip);
9682 struct sip_session *session;
9684 session = sipe_session_add_chat(sip);
9685 session->chat_title = sipe_chat_get_name(session->callid);
9686 session->roster_manager = g_strdup(self);
9688 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9689 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9690 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9691 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9693 g_free(self);
9697 static gboolean
9698 sipe_is_election_finished(struct sip_session *session)
9700 gboolean res = TRUE;
9702 SIPE_DIALOG_FOREACH {
9703 if (dialog->election_vote == 0) {
9704 res = FALSE;
9705 break;
9707 } SIPE_DIALOG_FOREACH_END;
9709 if (res) {
9710 session->is_voting_in_progress = FALSE;
9712 return res;
9715 static void
9716 sipe_election_start(struct sipe_account_data *sip,
9717 struct sip_session *session)
9719 int election_timeout;
9721 if (session->is_voting_in_progress) {
9722 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
9723 return;
9724 } else {
9725 session->is_voting_in_progress = TRUE;
9727 session->bid = rand();
9729 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
9731 SIPE_DIALOG_FOREACH {
9732 /* reset election_vote for each chat participant */
9733 dialog->election_vote = 0;
9735 /* send RequestRM to each chat participant*/
9736 sipe_send_election_request_rm(sip, dialog, session->bid);
9737 } SIPE_DIALOG_FOREACH_END;
9739 election_timeout = 15; /* sec */
9740 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9744 * @param who a URI to whom to invite to chat
9746 void
9747 sipe_invite_to_chat(struct sipe_account_data *sip,
9748 struct sip_session *session,
9749 const gchar *who)
9751 /* a conference */
9752 if (session->focus_uri)
9754 sipe_invite_conf(sip, session, who);
9756 else /* a multi-party chat */
9758 gchar *self = sip_uri_self(sip);
9759 if (session->roster_manager) {
9760 if (sipe_strcase_equal(session->roster_manager, self)) {
9761 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9762 } else {
9763 sipe_refer(sip, session, who);
9765 } else {
9766 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
9768 session->pending_invite_queue = slist_insert_unique_sorted(
9769 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9771 sipe_election_start(sip, session);
9773 g_free(self);
9777 void
9778 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9779 struct sip_session *session)
9781 gchar *invitee;
9782 GSList *entry = session->pending_invite_queue;
9784 while (entry) {
9785 invitee = entry->data;
9786 sipe_invite_to_chat(sip, session, invitee);
9787 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9788 g_free(invitee);
9792 static void
9793 sipe_election_result(struct sipe_account_data *sip,
9794 void *sess)
9796 struct sip_session *session = (struct sip_session *)sess;
9797 gchar *rival;
9798 gboolean has_won = TRUE;
9800 if (session->roster_manager) {
9801 SIPE_DEBUG_INFO(
9802 "sipe_election_result: RM has already been elected in the meantime. It is %s",
9803 session->roster_manager);
9804 return;
9807 session->is_voting_in_progress = FALSE;
9809 SIPE_DIALOG_FOREACH {
9810 if (dialog->election_vote < 0) {
9811 has_won = FALSE;
9812 rival = dialog->with;
9813 break;
9815 } SIPE_DIALOG_FOREACH_END;
9817 if (has_won) {
9818 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
9820 session->roster_manager = sip_uri_self(sip);
9822 SIPE_DIALOG_FOREACH {
9823 /* send SetRM to each chat participant*/
9824 sipe_send_election_set_rm(sip, dialog);
9825 } SIPE_DIALOG_FOREACH_END;
9826 } else {
9827 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
9829 session->bid = 0;
9831 sipe_process_pending_invite_queue(sip, session);
9835 * For 2007+ conference only.
9837 static void
9838 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9840 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9841 struct sip_session *session;
9843 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
9844 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
9846 session = sipe_session_find_chat_by_title(sip, chat_title);
9848 sipe_conf_modify_user_role(sip, session, buddy->name);
9852 * For 2007+ conference only.
9854 static void
9855 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9857 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9858 struct sip_session *session;
9860 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
9861 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
9863 session = sipe_session_find_chat_by_title(sip, chat_title);
9865 sipe_conf_delete_user(sip, session, buddy->name);
9868 static void
9869 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9871 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9872 struct sip_session *session;
9874 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
9875 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
9877 session = sipe_session_find_chat_by_title(sip, chat_title);
9879 sipe_invite_to_chat(sip, session, buddy->name);
9882 static void
9883 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9885 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9887 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
9888 if (phone) {
9889 char *tel_uri = sip_to_tel_uri(phone);
9891 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
9892 sip_csta_make_call(sip, tel_uri);
9894 g_free(tel_uri);
9898 static void
9899 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
9901 /** Translators: replace with URL to localized page
9902 * If it doesn't exist copy the original URL */
9903 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
9906 static void
9907 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9909 const gchar *email;
9910 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
9912 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9913 if (email)
9915 char *command_line = g_strdup_printf(
9916 #ifdef _WIN32
9917 "cmd /c start"
9918 #else
9919 "xdg-email"
9920 #endif
9921 " mailto:%s", email);
9922 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
9924 g_spawn_command_line_async(command_line, NULL);
9925 g_free(command_line);
9927 else
9929 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
9933 static void
9934 sipe_buddy_menu_access_level_cb(SIPE_UNUSED_PARAMETER PurpleBuddy *buddy,
9935 struct sipe_container *container)
9937 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9938 struct sipe_container_member *member;
9940 if (!container || !container->members) return;
9942 member = ((struct sipe_container_member *)container->members->data);
9944 if (!member->type) return;
9946 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
9947 container->id, member->type, member->value ? member->value : "");
9949 sipe_change_access_level(sip, container->id, member->type, member->value);
9952 static GList *
9953 sipe_get_access_control_menu(struct sipe_account_data *sip,
9954 const char* uri);
9957 * A menu which appear when right-clicking on buddy in contact list.
9959 static GList *
9960 sipe_buddy_menu(PurpleBuddy *buddy)
9962 PurpleBlistNode *g_node;
9963 PurpleGroup *group, *gr_parent;
9964 PurpleMenuAction *act;
9965 GList *menu = NULL;
9966 GList *menu_groups = NULL;
9967 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9968 const char *email;
9969 const char *phone;
9970 const char *phone_disp_str;
9971 gchar *self = sip_uri_self(sip);
9973 SIPE_SESSION_FOREACH {
9974 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9976 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9978 PurpleConvChatBuddyFlags flags;
9979 PurpleConvChatBuddyFlags flags_us;
9981 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9982 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9983 if (session->focus_uri
9984 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9985 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9987 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9988 act = purple_menu_action_new(label,
9989 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9990 session->chat_title, NULL);
9991 g_free(label);
9992 menu = g_list_prepend(menu, act);
9995 if (session->focus_uri
9996 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9998 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9999 act = purple_menu_action_new(label,
10000 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
10001 session->chat_title, NULL);
10002 g_free(label);
10003 menu = g_list_prepend(menu, act);
10006 else
10008 if (!session->focus_uri
10009 || (session->focus_uri && !session->locked))
10011 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
10012 act = purple_menu_action_new(label,
10013 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
10014 session->chat_title, NULL);
10015 g_free(label);
10016 menu = g_list_prepend(menu, act);
10020 } SIPE_SESSION_FOREACH_END;
10022 act = purple_menu_action_new(_("New chat"),
10023 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
10024 NULL, NULL);
10025 menu = g_list_prepend(menu, act);
10027 if (sip->csta && !sip->csta->line_status) {
10028 gchar *tmp = NULL;
10029 /* work phone */
10030 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
10031 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
10032 if (phone) {
10033 gchar *label = g_strdup_printf(_("Work %s"),
10034 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10035 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10036 g_free(tmp);
10037 tmp = NULL;
10038 g_free(label);
10039 menu = g_list_prepend(menu, act);
10042 /* mobile phone */
10043 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
10044 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
10045 if (phone) {
10046 gchar *label = g_strdup_printf(_("Mobile %s"),
10047 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10048 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10049 g_free(tmp);
10050 tmp = NULL;
10051 g_free(label);
10052 menu = g_list_prepend(menu, act);
10055 /* home phone */
10056 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
10057 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
10058 if (phone) {
10059 gchar *label = g_strdup_printf(_("Home %s"),
10060 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10061 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10062 g_free(tmp);
10063 tmp = NULL;
10064 g_free(label);
10065 menu = g_list_prepend(menu, act);
10068 /* other phone */
10069 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
10070 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
10071 if (phone) {
10072 gchar *label = g_strdup_printf(_("Other %s"),
10073 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10074 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10075 g_free(tmp);
10076 tmp = NULL;
10077 g_free(label);
10078 menu = g_list_prepend(menu, act);
10081 /* custom1 phone */
10082 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
10083 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
10084 if (phone) {
10085 gchar *label = g_strdup_printf(_("Custom1 %s"),
10086 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10087 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10088 g_free(tmp);
10089 tmp = NULL;
10090 g_free(label);
10091 menu = g_list_prepend(menu, act);
10095 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
10096 if (email) {
10097 act = purple_menu_action_new(_("Send email..."),
10098 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
10099 NULL, NULL);
10100 menu = g_list_prepend(menu, act);
10103 /* Access Level */
10104 if (sip->ocs2007) {
10105 GList *menu_access_levels = sipe_get_access_control_menu(sip, buddy->name);
10107 act = purple_menu_action_new(_("Access level"),
10108 NULL,
10109 NULL, menu_access_levels);
10110 menu = g_list_prepend(menu, act);
10113 /* Copy to */
10114 gr_parent = purple_buddy_get_group(buddy);
10115 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
10116 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
10117 continue;
10119 group = (PurpleGroup *)g_node;
10120 if (group == gr_parent)
10121 continue;
10123 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
10124 continue;
10126 act = purple_menu_action_new(purple_group_get_name(group),
10127 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
10128 group->name, NULL);
10129 menu_groups = g_list_prepend(menu_groups, act);
10131 menu_groups = g_list_reverse(menu_groups);
10133 act = purple_menu_action_new(_("Copy to"),
10134 NULL,
10135 NULL, menu_groups);
10136 menu = g_list_prepend(menu, act);
10138 menu = g_list_reverse(menu);
10140 g_free(self);
10141 return menu;
10144 static void
10145 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
10147 struct sipe_account_data *sip = gc->proto_data;
10148 const char *domain = purple_request_fields_get_string(fields, "access_domain");
10149 int index = purple_request_fields_get_choice(fields, "container_id");
10150 /* move Blocked first */
10151 int i = (index == 4) ? 0 : index + 1;
10152 int container_id = containers[i];
10154 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
10156 sipe_change_access_level(sip, container_id, "domain", domain);
10159 static void
10160 sipe_ask_access_domain(struct sipe_account_data *sip)
10162 PurpleAccount *account = sip->account;
10163 PurpleConnection *gc = sip->gc;
10164 PurpleRequestFields *fields;
10165 PurpleRequestFieldGroup *g;
10166 PurpleRequestField *f;
10168 fields = purple_request_fields_new();
10170 g = purple_request_field_group_new(NULL);
10171 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
10172 purple_request_field_set_required(f, TRUE);
10173 purple_request_field_group_add_field(g, f);
10175 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
10176 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
10177 purple_request_field_choice_add(f, _("Team"));
10178 purple_request_field_choice_add(f, _("Company"));
10179 purple_request_field_choice_add(f, _("Public"));
10180 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
10181 purple_request_field_choice_set_default_value(f, 3); /* index */
10182 purple_request_field_set_required(f, TRUE);
10183 purple_request_field_group_add_field(g, f);
10185 purple_request_fields_add_group(fields, g);
10187 purple_request_fields(gc, _("Add new domain"),
10188 _("Add new domain"), NULL, fields,
10189 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
10190 _("Cancel"), NULL,
10191 account, NULL, NULL, gc);
10194 static void
10195 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
10197 sipe_ask_access_domain((struct sipe_account_data *)buddy->account->gc->proto_data);
10200 static GList *
10201 sipe_get_access_levels_menu(struct sipe_account_data *sip,
10202 const char* member_type,
10203 const char* member_value,
10204 const gboolean extra_menu)
10206 GList *menu_access_levels = NULL;
10207 unsigned int i;
10208 char *menu_name;
10209 PurpleMenuAction *act;
10210 struct sipe_container *container;
10211 struct sipe_container_member *member;
10212 gboolean is_group_access = FALSE;
10213 int container_id = sipe_find_access_level(sip, member_type, member_value, &is_group_access);
10215 for (i = 1; i <= CONTAINERS_LEN; i++) {
10216 /* to put Blocked level last in menu list.
10217 * Blocked should remaim in the first place in the containers[] array.
10219 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
10220 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
10222 container = g_new0(struct sipe_container, 1);
10223 member = g_new0(struct sipe_container_member, 1);
10224 container->id = containers[j];
10225 container->members = g_slist_append(container->members, member);
10226 member->type = g_strdup(member_type);
10227 member->value = g_strdup(member_value);
10229 /* current container/access level */
10230 if (((int)containers[j]) == container_id) {
10231 menu_name = is_group_access ?
10232 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
10233 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
10234 } else {
10235 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
10238 act = purple_menu_action_new(menu_name,
10239 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
10240 container, NULL);
10241 g_free(menu_name);
10242 menu_access_levels = g_list_prepend(menu_access_levels, act);
10245 if (extra_menu && (container_id >= 0)) {
10246 /* separator */
10247 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
10248 menu_access_levels = g_list_prepend(menu_access_levels, act);
10250 if (!is_group_access) {
10251 container = g_new0(struct sipe_container, 1);
10252 member = g_new0(struct sipe_container_member, 1);
10253 container->id = -1;
10254 container->members = g_slist_append(container->members, member);
10255 member->type = g_strdup(member_type);
10256 member->value = g_strdup(member_value);
10258 /* Translators: remove (clear) previously assigned access level */
10259 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
10260 act = purple_menu_action_new(menu_name,
10261 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
10262 container, NULL);
10263 g_free(menu_name);
10264 menu_access_levels = g_list_prepend(menu_access_levels, act);
10268 menu_access_levels = g_list_reverse(menu_access_levels);
10269 return menu_access_levels;
10272 static GList *
10273 sipe_get_access_groups_menu(struct sipe_account_data *sip)
10275 GList *menu_access_groups = NULL;
10276 PurpleMenuAction *act;
10277 GSList *access_domains = NULL;
10278 GSList *entry;
10279 char *menu_name;
10280 char *domain;
10282 act = purple_menu_action_new(_("People in my company"),
10283 NULL,
10284 NULL, sipe_get_access_levels_menu(sip, "sameEnterprise", NULL, FALSE));
10285 menu_access_groups = g_list_prepend(menu_access_groups, act);
10287 /* this is original name, don't edit */
10288 act = purple_menu_action_new(_("People in domains connected with my company"),
10289 NULL,
10290 NULL, sipe_get_access_levels_menu(sip, "federated", NULL, FALSE));
10291 menu_access_groups = g_list_prepend(menu_access_groups, act);
10293 act = purple_menu_action_new(_("People in public domains"),
10294 NULL,
10295 NULL, sipe_get_access_levels_menu(sip, "publicCloud", NULL, TRUE));
10296 menu_access_groups = g_list_prepend(menu_access_groups, act);
10298 access_domains = sipe_get_access_domains(sip);
10299 entry = access_domains;
10300 while (entry) {
10301 domain = entry->data;
10303 menu_name = g_strdup_printf(_("People at %s"), domain);
10304 act = purple_menu_action_new(menu_name,
10305 NULL,
10306 NULL, sipe_get_access_levels_menu(sip, "domain", g_strdup(domain), TRUE));
10307 menu_access_groups = g_list_prepend(menu_access_groups, act);
10308 g_free(menu_name);
10310 entry = entry->next;
10313 /* separator */
10314 /* People in domains connected with my company */
10315 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
10316 menu_access_groups = g_list_prepend(menu_access_groups, act);
10318 act = purple_menu_action_new(_("Add new domain..."),
10319 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
10320 NULL, NULL);
10321 menu_access_groups = g_list_prepend(menu_access_groups, act);
10323 menu_access_groups = g_list_reverse(menu_access_groups);
10325 return menu_access_groups;
10328 static GList *
10329 sipe_get_access_control_menu(struct sipe_account_data *sip,
10330 const char* uri)
10332 GList *menu_access_levels = NULL;
10333 GList *menu_access_groups = NULL;
10334 char *menu_name;
10335 PurpleMenuAction *act;
10337 menu_access_levels = sipe_get_access_levels_menu(sip, "user", sipe_get_no_sip_uri(uri), TRUE);
10339 menu_access_groups = sipe_get_access_groups_menu(sip);
10341 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
10342 act = purple_menu_action_new(menu_name,
10343 NULL,
10344 NULL, menu_access_groups);
10345 g_free(menu_name);
10346 menu_access_levels = g_list_append(menu_access_levels, act);
10348 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
10349 act = purple_menu_action_new(menu_name,
10350 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
10351 NULL, NULL);
10352 g_free(menu_name);
10353 menu_access_levels = g_list_append(menu_access_levels, act);
10355 return menu_access_levels;
10358 static void
10359 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
10361 struct sipe_account_data *sip = chat->account->gc->proto_data;
10362 struct sip_session *session;
10364 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10365 sipe_conf_modify_conference_lock(sip, session, locked);
10368 static void
10369 sipe_chat_menu_unlock_cb(PurpleChat *chat)
10371 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
10372 sipe_conf_modify_lock(chat, FALSE);
10375 static void
10376 sipe_chat_menu_lock_cb(PurpleChat *chat)
10378 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
10379 sipe_conf_modify_lock(chat, TRUE);
10382 static GList *
10383 sipe_chat_menu(PurpleChat *chat)
10385 PurpleMenuAction *act;
10386 PurpleConvChatBuddyFlags flags_us;
10387 GList *menu = NULL;
10388 struct sipe_account_data *sip = chat->account->gc->proto_data;
10389 struct sip_session *session;
10390 gchar *self;
10392 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10393 if (!session) return NULL;
10395 self = sip_uri_self(sip);
10396 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
10398 if (session->focus_uri
10399 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
10401 if (session->locked) {
10402 act = purple_menu_action_new(_("Unlock"),
10403 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
10404 NULL, NULL);
10405 menu = g_list_prepend(menu, act);
10406 } else {
10407 act = purple_menu_action_new(_("Lock"),
10408 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
10409 NULL, NULL);
10410 menu = g_list_prepend(menu, act);
10414 menu = g_list_reverse(menu);
10416 g_free(self);
10417 return menu;
10420 static GList *
10421 sipe_blist_node_menu(PurpleBlistNode *node)
10423 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
10424 return sipe_buddy_menu((PurpleBuddy *) node);
10425 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
10426 return sipe_chat_menu((PurpleChat *)node);
10427 } else {
10428 return NULL;
10432 static gboolean
10433 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
10435 char *uri = trans->payload->data;
10437 PurpleNotifyUserInfo *info;
10438 PurpleBuddy *pbuddy = NULL;
10439 struct sipe_buddy *sbuddy;
10440 const char *alias = NULL;
10441 char *device_name = NULL;
10442 char *server_alias = NULL;
10443 char *phone_number = NULL;
10444 char *email = NULL;
10445 const char *site;
10446 char *first_name = NULL;
10447 char *last_name = NULL;
10449 if (!sip) return FALSE;
10451 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sip->username);
10453 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
10454 alias = purple_buddy_get_local_alias(pbuddy);
10456 //will query buddy UA's capabilities and send answer to log
10457 sipe_options_request(sip, uri);
10459 sbuddy = g_hash_table_lookup(sip->buddies, uri);
10460 if (sbuddy) {
10461 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
10464 info = purple_notify_user_info_new();
10466 if (msg->response != 200) {
10467 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg->response);
10468 } else {
10469 sipe_xml *searchResults;
10470 const sipe_xml *mrow;
10472 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
10473 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
10474 if (!searchResults) {
10475 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
10476 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
10477 const char *value;
10478 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
10479 email = g_strdup(sipe_xml_attribute(mrow, "email"));
10480 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
10482 /* For 2007 system we will take this from ContactCard -
10483 * it has cleaner tel: URIs at least
10485 if (!sip->ocs2007) {
10486 char *tel_uri = sip_to_tel_uri(phone_number);
10487 /* trims its parameters, so call first */
10488 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
10489 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
10490 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
10491 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
10492 g_free(tel_uri);
10495 if (server_alias && strlen(server_alias) > 0) {
10496 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10498 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
10499 purple_notify_user_info_add_pair(info, _("Job title"), value);
10501 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
10502 purple_notify_user_info_add_pair(info, _("Office"), value);
10504 if (phone_number && strlen(phone_number) > 0) {
10505 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
10507 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
10508 purple_notify_user_info_add_pair(info, _("Company"), value);
10510 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
10511 purple_notify_user_info_add_pair(info, _("City"), value);
10513 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
10514 purple_notify_user_info_add_pair(info, _("State"), value);
10516 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
10517 purple_notify_user_info_add_pair(info, _("Country"), value);
10519 if (email && strlen(email) > 0) {
10520 purple_notify_user_info_add_pair(info, _("Email address"), email);
10524 sipe_xml_free(searchResults);
10527 purple_notify_user_info_add_section_break(info);
10529 if (is_empty(server_alias)) {
10530 g_free(server_alias);
10531 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
10532 if (server_alias) {
10533 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10537 /* present alias if it differs from server alias */
10538 if (alias && !sipe_strequal(alias, server_alias))
10540 purple_notify_user_info_add_pair(info, _("Alias"), alias);
10543 if (is_empty(email)) {
10544 g_free(email);
10545 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
10546 if (email) {
10547 purple_notify_user_info_add_pair(info, _("Email address"), email);
10551 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
10552 if (site) {
10553 purple_notify_user_info_add_pair(info, _("Site"), site);
10556 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
10557 if (first_name && last_name) {
10558 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
10560 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
10561 g_free(link);
10563 g_free(first_name);
10564 g_free(last_name);
10566 if (device_name) {
10567 purple_notify_user_info_add_pair(info, _("Device"), device_name);
10570 /* show a buddy's user info in a nice dialog box */
10571 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
10572 uri, /* buddy's URI */
10573 info, /* body */
10574 NULL, /* callback called when dialog closed */
10575 NULL); /* userdata for callback */
10577 g_free(phone_number);
10578 g_free(server_alias);
10579 g_free(email);
10580 g_free(device_name);
10582 return TRUE;
10586 * AD search first, LDAP based
10588 static void sipe_get_info(PurpleConnection *gc, const char *username)
10590 struct sipe_account_data *sip = gc->proto_data;
10591 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
10592 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
10593 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
10594 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
10596 payload->destroy = g_free;
10597 payload->data = g_strdup(username);
10599 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
10600 send_soap_request_with_cb(sip, domain_uri, body,
10601 (TransCallback) process_get_info_response, payload);
10602 g_free(domain_uri);
10603 g_free(body);
10604 g_free(row);
10607 PurplePluginProtocolInfo prpl_info =
10609 OPT_PROTO_CHAT_TOPIC,
10610 NULL, /* user_splits */
10611 NULL, /* protocol_options */
10612 NO_BUDDY_ICONS, /* icon_spec */
10613 sipe_list_icon, /* list_icon */
10614 NULL, /* list_emblems */
10615 sipe_status_text, /* status_text */
10616 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10617 sipe_status_types, /* away_states */
10618 sipe_blist_node_menu, /* blist_node_menu */
10619 NULL, /* chat_info */
10620 NULL, /* chat_info_defaults */
10621 sipe_login, /* login */
10622 sipe_close, /* close */
10623 sipe_im_send, /* send_im */
10624 NULL, /* set_info */ // TODO maybe
10625 sipe_send_typing, /* send_typing */
10626 sipe_get_info, /* get_info */
10627 sipe_set_status, /* set_status */
10628 sipe_set_idle, /* set_idle */
10629 NULL, /* change_passwd */
10630 sipe_add_buddy, /* add_buddy */
10631 NULL, /* add_buddies */
10632 sipe_remove_buddy, /* remove_buddy */
10633 NULL, /* remove_buddies */
10634 sipe_add_permit, /* add_permit */
10635 sipe_add_deny, /* add_deny */
10636 sipe_add_deny, /* rem_permit */
10637 sipe_add_permit, /* rem_deny */
10638 dummy_permit_deny, /* set_permit_deny */
10639 NULL, /* join_chat */
10640 NULL, /* reject_chat */
10641 NULL, /* get_chat_name */
10642 sipe_chat_invite, /* chat_invite */
10643 sipe_chat_leave, /* chat_leave */
10644 NULL, /* chat_whisper */
10645 sipe_chat_send, /* chat_send */
10646 sipe_keep_alive, /* keepalive */
10647 NULL, /* register_user */
10648 NULL, /* get_cb_info */ // deprecated
10649 NULL, /* get_cb_away */ // deprecated
10650 sipe_alias_buddy, /* alias_buddy */
10651 sipe_group_buddy, /* group_buddy */
10652 sipe_rename_group, /* rename_group */
10653 NULL, /* buddy_free */
10654 sipe_convo_closed, /* convo_closed */
10655 purple_normalize_nocase, /* normalize */
10656 NULL, /* set_buddy_icon */
10657 sipe_remove_group, /* remove_group */
10658 NULL, /* get_cb_real_name */ // TODO?
10659 NULL, /* set_chat_topic */
10660 NULL, /* find_blist_chat */
10661 NULL, /* roomlist_get_list */
10662 NULL, /* roomlist_cancel */
10663 NULL, /* roomlist_expand_category */
10664 NULL, /* can_receive_file */
10665 sipe_ft_send_file, /* send_file */
10666 sipe_ft_new_xfer, /* new_xfer */
10667 NULL, /* offline_message */
10668 NULL, /* whiteboard_prpl_ops */
10669 sipe_send_raw, /* send_raw */
10670 NULL, /* roomlist_room_serialize */
10671 NULL, /* unregister_user */
10672 NULL, /* send_attention */
10673 NULL, /* get_attention_types */
10674 #if !PURPLE_VERSION_CHECK(2,5,0)
10675 /* Backward compatibility when compiling against 2.4.x API */
10676 (void (*)(void)) /* _purple_reserved4 */
10677 #endif
10678 sizeof(PurplePluginProtocolInfo), /* struct_size */
10679 #if PURPLE_VERSION_CHECK(2,5,0)
10680 sipe_get_account_text_table, /* get_account_text_table */
10681 #if PURPLE_VERSION_CHECK(2,6,0)
10682 NULL, /* initiate_media */
10683 NULL, /* get_media_caps */
10684 #if PURPLE_VERSION_CHECK(2,7,0)
10685 NULL, /* get_moods */
10686 #endif
10687 #endif
10688 #endif
10691 void sipe_core_init(void)
10693 srand(time(NULL));
10694 sip_sec_init();
10696 #ifdef ENABLE_NLS
10697 SIPE_DEBUG_INFO("bindtextdomain = %s",
10698 bindtextdomain(PACKAGE_NAME, LOCALEDIR));
10699 SIPE_DEBUG_INFO("bind_textdomain_codeset = %s",
10700 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"));
10701 textdomain(PACKAGE_NAME);
10702 #endif
10703 #ifdef HAVE_GMIME
10704 g_mime_init(0);
10705 #endif
10708 void sipe_core_destroy(void)
10710 #ifdef HAVE_GMIME
10711 g_mime_shutdown();
10712 #endif
10713 sip_sec_destroy();
10717 Local Variables:
10718 mode: c
10719 c-file-style: "bsd"
10720 indent-tabs-mode: t
10721 tab-width: 8
10722 End: