access: actually disabled "Unspecify" option for
[siplcs.git] / src / core / sipe.c
blobb6df0328cb824eed199f6e08d5c99b44c141755e
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 gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2546 int len = msg->bodylen;
2548 const gchar *tmp = sipmsg_find_header(msg, "Event");
2549 const sipe_xml *item;
2550 sipe_xml *isc;
2551 const gchar *contacts_delta;
2552 const sipe_xml *group_node;
2553 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2554 return FALSE;
2557 /* Convert the contact from XML to Purple Buddies */
2558 isc = sipe_xml_parse(msg->body, len);
2559 if (!isc) {
2560 return FALSE;
2563 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
2564 if (contacts_delta) {
2565 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2568 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
2570 /* Parse groups */
2571 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
2572 struct sipe_group * group = g_new0(struct sipe_group, 1);
2573 const char *name = sipe_xml_attribute(group_node, "name");
2575 if (g_str_has_prefix(name, "~")) {
2576 name = _("Other Contacts");
2578 group->name = g_strdup(name);
2579 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
2581 sipe_group_add(sip, group);
2584 // Make sure we have at least one group
2585 if (g_slist_length(sip->groups) == 0) {
2586 struct sipe_group * group = g_new0(struct sipe_group, 1);
2587 PurpleGroup *purple_group;
2588 group->name = g_strdup(_("Other Contacts"));
2589 group->id = 1;
2590 purple_group = purple_group_new(group->name);
2591 purple_blist_add_group(purple_group, NULL);
2592 sip->groups = g_slist_append(sip->groups, group);
2595 /* Parse contacts */
2596 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
2597 const gchar *uri = sipe_xml_attribute(item, "uri");
2598 const gchar *name = sipe_xml_attribute(item, "name");
2599 gchar *buddy_name;
2600 struct sipe_buddy *buddy = NULL;
2601 gchar *tmp;
2602 gchar **item_groups;
2603 int i = 0;
2605 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2606 tmp = sip_uri_from_name(uri);
2607 buddy_name = g_ascii_strdown(tmp, -1);
2608 g_free(tmp);
2610 /* assign to group Other Contacts if nothing else received */
2611 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
2612 if(is_empty(tmp)) {
2613 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2614 g_free(tmp);
2615 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2617 item_groups = g_strsplit(tmp, " ", 0);
2618 g_free(tmp);
2620 while (item_groups[i]) {
2621 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2623 // If couldn't find the right group for this contact, just put them in the first group we have
2624 if (group == NULL && g_slist_length(sip->groups) > 0) {
2625 group = sip->groups->data;
2628 if (group != NULL) {
2629 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2630 if (!b){
2631 b = purple_buddy_new(sip->account, buddy_name, uri);
2632 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2634 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
2637 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2638 if (name != NULL && strlen(name) != 0) {
2639 purple_blist_alias_buddy(b, name);
2641 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
2645 if (!buddy) {
2646 buddy = g_new0(struct sipe_buddy, 1);
2647 buddy->name = g_strdup(b->name);
2648 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2651 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2653 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
2654 } else {
2655 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2656 name);
2659 i++;
2660 } // while, contact groups
2661 g_strfreev(item_groups);
2662 g_free(buddy_name);
2664 } // for, contacts
2666 sipe_cleanup_local_blist(sip);
2668 /* Add self-contact if not there yet. 2005 systems. */
2669 /* This will resemble subscription to roaming_self in 2007 systems */
2670 if (!sip->ocs2007) {
2671 gchar *self_uri = sip_uri_self(sip);
2672 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2674 if (!buddy) {
2675 buddy = g_new0(struct sipe_buddy, 1);
2676 buddy->name = g_strdup(self_uri);
2677 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2679 g_free(self_uri);
2682 sipe_xml_free(isc);
2684 /* subscribe to buddies */
2685 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2686 if (sip->batched_support) {
2687 sipe_subscribe_presence_batched(sip, NULL);
2688 } else {
2689 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2691 sip->subscribed_buddies = TRUE;
2693 /* for 2005 systems schedule contacts' status update
2694 * based on their calendar information
2696 if (!sip->ocs2007) {
2697 sipe_sched_calendar_status_update(sip, time(NULL));
2700 return 0;
2704 * Subscribe roaming contacts
2706 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2708 gchar *to = sip_uri_self(sip);
2709 gchar *tmp = get_contact(sip);
2710 gchar *hdr = g_strdup_printf(
2711 "Event: vnd-microsoft-roaming-contacts\r\n"
2712 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2713 "Supported: com.microsoft.autoextend\r\n"
2714 "Supported: ms-benotify\r\n"
2715 "Proxy-Require: ms-benotify\r\n"
2716 "Supported: ms-piggyback-first-notify\r\n"
2717 "Contact: %s\r\n", tmp);
2718 g_free(tmp);
2720 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2721 g_free(to);
2722 g_free(hdr);
2725 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2726 SIPE_UNUSED_PARAMETER void *unused)
2728 gchar *key;
2729 struct sip_dialog *dialog;
2730 gchar *to = sip_uri_self(sip);
2731 gchar *tmp = get_contact(sip);
2732 gchar *hdr = g_strdup_printf(
2733 "Event: presence.wpending\r\n"
2734 "Accept: text/xml+msrtc.wpending\r\n"
2735 "Supported: com.microsoft.autoextend\r\n"
2736 "Supported: ms-benotify\r\n"
2737 "Proxy-Require: ms-benotify\r\n"
2738 "Supported: ms-piggyback-first-notify\r\n"
2739 "Contact: %s\r\n", tmp);
2740 g_free(tmp);
2742 /* Subscription is identified by <event> key */
2743 key = g_strdup_printf("<%s>", "presence.wpending");
2744 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2745 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2747 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2749 g_free(to);
2750 g_free(hdr);
2751 g_free(key);
2755 * Fires on deregistration event initiated by server.
2756 * [MS-SIPREGE] SIP extension.
2759 // 2007 Example
2761 // Content-Type: text/registration-event
2762 // subscription-state: terminated;expires=0
2763 // ms-diagnostics-public: 4141;reason="User disabled"
2765 // deregistered;event=rejected
2767 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2769 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2770 gchar *event = NULL;
2771 gchar *reason = NULL;
2772 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2773 gchar *warning;
2775 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2776 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2778 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2779 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2780 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2781 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2782 } else {
2783 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2784 return;
2787 if (diagnostics != NULL) {
2788 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2789 } else { // for LCS2005
2790 int error_id = 0;
2791 if (event && sipe_strcase_equal(event, "unregistered")) {
2792 error_id = 4140; // [MS-SIPREGE]
2793 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2794 reason = g_strdup(_("you are already signed in at another location"));
2795 } else if (event && sipe_strcase_equal(event, "rejected")) {
2796 error_id = 4141;
2797 reason = g_strdup(_("user disabled")); // [MS-OCER]
2798 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2799 error_id = 4142;
2800 reason = g_strdup(_("user moved")); // [MS-OCER]
2803 g_free(event);
2804 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2805 g_free(reason);
2807 sip->gc->wants_to_die = TRUE;
2808 purple_connection_error(sip->gc, warning);
2809 g_free(warning);
2813 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2815 sipe_xml *xn_provision_group_list;
2816 const sipe_xml *node;
2818 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
2820 /* provisionGroup */
2821 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
2822 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
2823 g_free(sip->focus_factory_uri);
2824 sip->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
2825 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2826 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2827 break;
2830 sipe_xml_free(xn_provision_group_list);
2833 /** for 2005 system */
2834 static void
2835 sipe_process_provisioning(struct sipe_account_data *sip,
2836 struct sipmsg *msg)
2838 sipe_xml *xn_provision;
2839 const sipe_xml *node;
2841 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
2842 if ((node = sipe_xml_child(xn_provision, "user"))) {
2843 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
2844 if ((node = sipe_xml_child(node, "line"))) {
2845 const gchar *line_uri = sipe_xml_attribute(node, "uri");
2846 const gchar *server = sipe_xml_attribute(node, "server");
2847 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2848 sip_csta_open(sip, line_uri, server);
2851 sipe_xml_free(xn_provision);
2854 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2856 const gchar *contacts_delta;
2857 sipe_xml *xml;
2859 xml = sipe_xml_parse(msg->body, msg->bodylen);
2860 if (!xml)
2862 return;
2865 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
2866 if (contacts_delta)
2868 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2871 sipe_xml_free(xml);
2874 static void
2875 free_container_member(struct sipe_container_member *member)
2877 if (!member) return;
2879 g_free(member->type);
2880 g_free(member->value);
2881 g_free(member);
2884 static void
2885 free_container(struct sipe_container *container)
2887 GSList *entry;
2889 if (!container) return;
2891 entry = container->members;
2892 while (entry) {
2893 void *data = entry->data;
2894 entry = g_slist_remove(entry, data);
2895 free_container_member((struct sipe_container_member *)data);
2897 g_free(container);
2900 static void
2901 sipe_send_container_members_prepare(const guint container_id,
2902 const guint container_version,
2903 const gchar *action,
2904 const gchar *type,
2905 const gchar *value,
2906 char **container_xmls)
2908 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2909 gchar *body;
2911 if (!container_xmls) return;
2913 body = g_strdup_printf(
2914 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
2915 container_id,
2916 container_version,
2917 action,
2918 type,
2919 value_str);
2920 g_free(value_str);
2922 if ((*container_xmls) == NULL) {
2923 *container_xmls = body;
2924 } else {
2925 char *tmp = *container_xmls;
2927 *container_xmls = g_strconcat(*container_xmls, body, NULL);
2928 g_free(tmp);
2929 g_free(body);
2933 static void
2934 sipe_send_set_container_members(struct sipe_account_data *sip,
2935 char *container_xmls)
2937 gchar *self;
2938 gchar *contact;
2939 gchar *hdr;
2940 gchar *body;
2942 if (!container_xmls) return;
2944 self = sip_uri_self(sip);
2945 body = g_strdup_printf(
2946 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2947 "%s"
2948 "</setContainerMembers>",
2949 container_xmls);
2951 contact = get_contact(sip);
2952 hdr = g_strdup_printf("Contact: %s\r\n"
2953 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2954 g_free(contact);
2956 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2958 g_free(hdr);
2959 g_free(body);
2960 g_free(self);
2964 * Finds locally stored MS-PRES container member
2966 static struct sipe_container_member *
2967 sipe_find_container_member(struct sipe_container *container,
2968 const gchar *type,
2969 const gchar *value)
2971 struct sipe_container_member *member;
2972 GSList *entry;
2974 if (container == NULL || type == NULL) {
2975 return NULL;
2978 entry = container->members;
2979 while (entry) {
2980 member = entry->data;
2981 if (sipe_strcase_equal(member->type, type) &&
2982 sipe_strcase_equal(member->value, value))
2984 return member;
2986 entry = entry->next;
2988 return NULL;
2992 * Finds locally stored MS-PRES container by id
2994 static struct sipe_container *
2995 sipe_find_container(struct sipe_account_data *sip,
2996 guint id)
2998 struct sipe_container *container;
2999 GSList *entry;
3001 if (sip == NULL) {
3002 return NULL;
3005 entry = sip->containers;
3006 while (entry) {
3007 container = entry->data;
3008 if (id == container->id) {
3009 return container;
3011 entry = entry->next;
3013 return NULL;
3017 * Returns pointer to domain part in provided Email URL
3019 * @param email an email URL. Example: first.last@hq.company.com
3020 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
3022 * Doesn't allocate memory
3024 static const char *
3025 sipe_get_domain(const char *email)
3027 char *tmp;
3029 if (!email) return NULL;
3031 tmp = strstr(email, "@");
3033 if (tmp && ((tmp+1) < (email + strlen(email)))) {
3034 return tmp+1;
3035 } else {
3036 return NULL;
3041 /* @TODO: replace with binary search for faster access? */
3042 /** source: http://support.microsoft.com/kb/897567 */
3043 static const char * const public_domains [] = {
3044 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
3045 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
3046 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
3047 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
3048 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
3049 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
3050 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
3051 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
3052 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
3053 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
3054 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
3055 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
3056 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
3057 "yahoo.com",
3058 NULL};
3060 static gboolean
3061 sipe_is_public_domain(const char *domain)
3063 int i = 0;
3064 while (public_domains[i]) {
3065 if (sipe_strcase_equal(public_domains[i], domain)) {
3066 return TRUE;
3068 i++;
3070 return FALSE;
3074 * Access Levels
3075 * 32000 - Blocked
3076 * 400 - Personal
3077 * 300 - Team
3078 * 200 - Company
3079 * 100 - Public
3081 static const char *
3082 sipe_get_access_level_name(int container_id)
3084 switch(container_id) {
3085 case 32000: return _("Blocked");
3086 case 400: return _("Personal");
3087 case 300: return _("Team");
3088 case 200: return _("Company");
3089 case 100: return _("Public");
3091 return _("Unknown");
3094 static const guint containers[] = {32000, 400, 300, 200, 100};
3095 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
3098 static int
3099 sipe_find_member_access_level(struct sipe_account_data *sip,
3100 const gchar *type,
3101 const gchar *value)
3103 unsigned int i = 0;
3104 const gchar *value_mod = value;
3106 if (!type) return -1;
3108 if (sipe_strequal("user", type)) {
3109 value_mod = sipe_get_no_sip_uri(value);
3112 for (i = 0; i < CONTAINERS_LEN; i++) {
3113 struct sipe_container_member *member;
3114 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3115 if (!container) continue;
3117 member = sipe_find_container_member(container, type, value_mod);
3118 if (member) return containers[i];
3121 return -1;
3124 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
3125 static int
3126 sipe_find_access_level(struct sipe_account_data *sip,
3127 const gchar *type,
3128 const gchar *value)
3130 int container_id = -1;
3132 if (sipe_strequal("user", type)) {
3133 const char *domain;
3134 const char *no_sip_uri = sipe_get_no_sip_uri(value);
3136 container_id = sipe_find_member_access_level(sip, "user", no_sip_uri);
3137 if (container_id >= 0) return container_id;
3139 domain = sipe_get_domain(no_sip_uri);
3140 container_id = sipe_find_member_access_level(sip, "domain", domain);
3141 if (container_id >= 0) return container_id;
3143 container_id = sipe_find_member_access_level(sip, "sameEnterprise", NULL);
3144 if ((container_id >= 0) && sipe_strcase_equal(sip->sipdomain, domain)) {
3145 return container_id;
3148 container_id = sipe_find_member_access_level(sip, "publicCloud", NULL);
3149 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
3150 return container_id;
3153 container_id = sipe_find_member_access_level(sip, "everyone", NULL);
3154 if ((container_id >= 0)) {
3155 return container_id;
3157 } else {
3158 container_id = sipe_find_member_access_level(sip, type, value);
3161 return container_id;
3165 * @param container_id a new access level. If -1 then current access level
3166 * is just removed (I.e. the member is removed from all containers).
3167 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
3168 * @param value a value for member. E.g. SIP URI for "user" member type.
3170 static void
3171 sipe_change_access_level(struct sipe_account_data *sip,
3172 const int container_id,
3173 const gchar *type,
3174 const gchar *value)
3176 unsigned int i;
3177 int current_container_id = -1;
3178 char *container_xmls = NULL;
3180 /* for each container: find/delete */
3181 for (i = 0; i < CONTAINERS_LEN; i++) {
3182 struct sipe_container_member *member;
3183 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3185 if (!container) continue;
3187 member = sipe_find_container_member(container, type, value);
3188 if (member) {
3189 current_container_id = containers[i];
3190 /* delete/publish current access level */
3191 if (container_id < 0 || container_id != current_container_id) {
3192 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
3193 /* remove member from our cache, to be able to recalculate AL below */
3194 container->members = g_slist_remove(container->members, member);
3195 current_container_id = -1;
3200 /* recalculate AL below */
3201 current_container_id = sipe_find_access_level(sip, type, value);
3203 /* assign/publish new access level */
3204 if (container_id != current_container_id && container_id >= 0) {
3205 struct sipe_container *container = sipe_find_container(sip, container_id);
3206 guint version = container ? container->version : 0;
3208 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
3211 if (container_xmls) {
3212 sipe_send_set_container_members(sip, container_xmls);
3214 g_free(container_xmls);
3217 static void
3218 free_publication(struct sipe_publication *publication)
3220 g_free(publication->category);
3221 g_free(publication->cal_event_hash);
3222 g_free(publication->note);
3224 g_free(publication->working_hours_xml_str);
3225 g_free(publication->fb_start_str);
3226 g_free(publication->free_busy_base64);
3228 g_free(publication);
3231 /* key is <category><instance><container> */
3232 static gboolean
3233 sipe_is_our_publication(struct sipe_account_data *sip,
3234 const gchar *key)
3236 GSList *entry;
3238 /* filling keys for our publications if not yet cached */
3239 if (!sip->our_publication_keys) {
3240 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
3241 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
3242 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
3243 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
3244 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
3245 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
3246 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
3248 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
3249 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
3250 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
3251 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
3252 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
3253 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
3254 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
3255 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
3256 SIPE_DEBUG_INFO("\tNote : %u", 0);
3257 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
3259 /* device */
3260 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3261 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
3263 /* state:machineState */
3264 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3265 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
3266 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3267 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3269 /* state:userState */
3270 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3271 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3272 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3273 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3275 /* state:calendarState */
3276 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3277 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3278 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3279 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3281 /* state:calendarState OOF */
3282 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3283 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3284 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3285 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3287 /* note */
3288 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3289 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3290 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3291 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3292 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3293 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3295 /* note OOF */
3296 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3297 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3298 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3299 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3300 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3301 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3303 /* calendarData:WorkingHours */
3304 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3305 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3306 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3307 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3308 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3309 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3310 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3311 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3312 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3313 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3314 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3315 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3317 /* calendarData:FreeBusy */
3318 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3319 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3320 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3321 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3322 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3323 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3324 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3325 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3326 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3327 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3328 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3329 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3331 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3332 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3335 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3337 entry = sip->our_publication_keys;
3338 while (entry) {
3339 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3340 if (sipe_strequal(entry->data, key)) {
3341 return TRUE;
3343 entry = entry->next;
3345 return FALSE;
3348 /** Property names to store in blist.xml */
3349 #define ALIAS_PROP "alias"
3350 #define EMAIL_PROP "email"
3351 #define PHONE_PROP "phone"
3352 #define PHONE_DISPLAY_PROP "phone-display"
3353 #define PHONE_MOBILE_PROP "phone-mobile"
3354 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3355 #define PHONE_HOME_PROP "phone-home"
3356 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3357 #define PHONE_OTHER_PROP "phone-other"
3358 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3359 #define PHONE_CUSTOM1_PROP "phone-custom1"
3360 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3361 #define SITE_PROP "site"
3362 #define COMPANY_PROP "company"
3363 #define DEPARTMENT_PROP "department"
3364 #define TITLE_PROP "title"
3365 #define OFFICE_PROP "office"
3366 /** implies work address */
3367 #define ADDRESS_STREET_PROP "address-street"
3368 #define ADDRESS_CITY_PROP "address-city"
3369 #define ADDRESS_STATE_PROP "address-state"
3370 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3371 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3374 * Tries to figure out user first and last name
3375 * based on Display Name and email properties.
3377 * Allocates memory - must be g_free()'d
3379 * Examples to parse:
3380 * First Last
3381 * First Last - Company Name
3382 * Last, First
3383 * Last, First M.
3384 * Last, First (C)(STP) (Company)
3385 * first.last@company.com (preprocessed as "first last")
3386 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3388 * Unusable examples:
3389 * user@company.com (preprocessed as "user")
3390 * first.m.last@company.com (preprocessed as "first m last")
3391 * user.company.com@reuters.net (preprocessed as "user company com")
3393 static void
3394 sipe_get_first_last_names(struct sipe_account_data *sip,
3395 const char *uri,
3396 char **first_name,
3397 char **last_name)
3399 PurpleBuddy *p_buddy;
3400 char *display_name;
3401 const char *email;
3402 const char *first, *last;
3403 char *tmp;
3404 char **parts;
3405 gboolean has_comma = FALSE;
3407 if (!sip || !uri) return;
3409 p_buddy = purple_find_buddy(sip->account, uri);
3411 if (!p_buddy) return;
3413 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3414 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3416 if (!display_name && !email) return;
3418 /* if no display name, make "first last anything_else" out of email */
3419 if (email && !display_name) {
3420 display_name = g_strndup(email, strstr(email, "@") - email);
3421 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
3422 g_free(tmp);
3425 if (display_name) {
3426 has_comma = (strstr(display_name, ",") != NULL);
3427 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
3428 g_free(tmp);
3429 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
3430 g_free(tmp);
3433 parts = g_strsplit(display_name, " ", 0);
3435 if (!parts[0] || !parts[1]) {
3436 g_free(display_name);
3437 g_strfreev(parts);
3438 return;
3441 if (has_comma) {
3442 last = parts[0];
3443 first = parts[1];
3444 } else {
3445 first = parts[0];
3446 last = parts[1];
3449 if (first_name) {
3450 *first_name = g_strstrip(g_strdup(first));
3453 if (last_name) {
3454 *last_name = g_strstrip(g_strdup(last));
3457 g_free(display_name);
3458 g_strfreev(parts);
3462 * Update user information
3464 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3465 * @param property_name
3466 * @param property_value may be modified to strip white space
3468 static void
3469 sipe_update_user_info(struct sipe_account_data *sip,
3470 const char *uri,
3471 const char *property_name,
3472 char *property_value)
3474 GSList *buddies, *entry;
3476 if (!property_name || strlen(property_name) == 0) return;
3478 if (property_value)
3479 property_value = g_strstrip(property_value);
3481 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3482 while (entry) {
3483 const char *prop_str;
3484 const char *server_alias;
3485 PurpleBuddy *p_buddy = entry->data;
3487 /* for Display Name */
3488 if (sipe_strequal(property_name, ALIAS_PROP)) {
3489 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3490 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
3491 purple_blist_alias_buddy(p_buddy, property_value);
3494 server_alias = purple_buddy_get_server_alias(p_buddy);
3495 if (!is_empty(property_value) &&
3496 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3498 purple_blist_server_alias_buddy(p_buddy, property_value);
3501 /* for other properties */
3502 else {
3503 if (!is_empty(property_value)) {
3504 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3505 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3506 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3511 entry = entry->next;
3513 g_slist_free(buddies);
3517 * Update user phone
3518 * Suitable for both 2005 and 2007 systems.
3520 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3521 * @param phone_type
3522 * @param phone may be modified to strip white space
3523 * @param phone_display_string may be modified to strip white space
3525 static void
3526 sipe_update_user_phone(struct sipe_account_data *sip,
3527 const char *uri,
3528 const gchar *phone_type,
3529 gchar *phone,
3530 gchar *phone_display_string)
3532 const char *phone_node = PHONE_PROP; /* work phone by default */
3533 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3535 if(!phone || strlen(phone) == 0) return;
3537 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3538 phone_node = PHONE_MOBILE_PROP;
3539 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3540 } else if (sipe_strequal(phone_type, "home")) {
3541 phone_node = PHONE_HOME_PROP;
3542 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3543 } else if (sipe_strequal(phone_type, "other")) {
3544 phone_node = PHONE_OTHER_PROP;
3545 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3546 } else if (sipe_strequal(phone_type, "custom1")) {
3547 phone_node = PHONE_CUSTOM1_PROP;
3548 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3551 sipe_update_user_info(sip, uri, phone_node, phone);
3552 if (phone_display_string) {
3553 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3557 static void
3558 sipe_update_calendar(struct sipe_account_data *sip)
3560 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3562 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: started.");
3564 if (sipe_strequal(calendar, "EXCH")) {
3565 sipe_ews_update_calendar(sip);
3568 /* schedule repeat */
3569 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3571 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: finished.");
3575 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3576 * by using standard Purple's means of signals and saved statuses.
3578 * Thus all UI elements get updated: Status Button with Note, docklet.
3579 * This is ablolutely important as both our status and note can come
3580 * inbound (roaming) or be updated programmatically (e.g. based on our
3581 * calendar data).
3583 static void
3584 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3585 const char *status_id,
3586 const char *message,
3587 time_t do_not_publish[])
3589 PurpleStatus *status = purple_account_get_active_status(account);
3590 gboolean changed = TRUE;
3592 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3593 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3595 changed = FALSE;
3598 if (purple_savedstatus_is_idleaway()) {
3599 changed = FALSE;
3602 if (changed) {
3603 PurpleSavedStatus *saved_status;
3604 const PurpleStatusType *acct_status_type =
3605 purple_status_type_find_with_id(account->status_types, status_id);
3606 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3607 sipe_activity activity = sipe_get_activity_by_token(status_id);
3609 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3610 if (saved_status) {
3611 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3614 /* If this type+message is unique then create a new transient saved status
3615 * Ref: gtkstatusbox.c
3617 if (!saved_status) {
3618 GList *tmp;
3619 GList *active_accts = purple_accounts_get_all_active();
3621 saved_status = purple_savedstatus_new(NULL, primitive);
3622 purple_savedstatus_set_message(saved_status, message);
3624 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3625 purple_savedstatus_set_substatus(saved_status,
3626 (PurpleAccount *)tmp->data, acct_status_type, message);
3628 g_list_free(active_accts);
3631 do_not_publish[activity] = time(NULL);
3632 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3633 status_id, (int)do_not_publish[activity]);
3635 /* Set the status for each account */
3636 purple_savedstatus_activate(saved_status);
3640 struct hash_table_delete_payload {
3641 GHashTable *hash_table;
3642 guint container;
3645 static void
3646 sipe_remove_category_container_publications_cb(const char *name,
3647 struct sipe_publication *publication,
3648 struct hash_table_delete_payload *payload)
3650 if (publication->container == payload->container) {
3651 g_hash_table_remove(payload->hash_table, name);
3654 static void
3655 sipe_remove_category_container_publications(GHashTable *our_publications,
3656 const char *category,
3657 guint container)
3659 struct hash_table_delete_payload payload;
3660 payload.hash_table = g_hash_table_lookup(our_publications, category);
3662 if (!payload.hash_table) return;
3664 payload.container = container;
3665 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3668 static void
3669 send_publish_category_initial(struct sipe_account_data *sip);
3672 * When we receive some self (BE) NOTIFY with a new subscriber
3673 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3676 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3678 gchar *contact;
3679 gchar *to;
3680 sipe_xml *xml;
3681 const sipe_xml *node;
3682 const sipe_xml *node2;
3683 char *display_name = NULL;
3684 char *uri;
3685 GSList *category_names = NULL;
3686 int aggreg_avail = 0;
3687 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3688 gboolean do_update_status = FALSE;
3689 gboolean has_note_cleaned = FALSE;
3691 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3693 xml = sipe_xml_parse(msg->body, msg->bodylen);
3694 if (!xml) return;
3696 contact = get_contact(sip);
3697 to = sip_uri_self(sip);
3700 /* categories */
3701 /* set list of categories participating in this XML */
3702 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3703 const gchar *name = sipe_xml_attribute(node, "name");
3704 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3706 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3707 category_names ? (int) g_slist_length(category_names) : -1);
3708 /* drop category information */
3709 if (category_names) {
3710 GSList *entry = category_names;
3711 while (entry) {
3712 GHashTable *cat_publications;
3713 const gchar *category = entry->data;
3714 entry = entry->next;
3715 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
3716 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3717 if (cat_publications) {
3718 g_hash_table_remove(sip->our_publications, category);
3719 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
3723 g_slist_free(category_names);
3724 /* filling our categories reflected in roaming data */
3725 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3726 const char *tmp;
3727 const gchar *name = sipe_xml_attribute(node, "name");
3728 guint container = sipe_xml_int_attribute(node, "container", -1);
3729 guint instance = sipe_xml_int_attribute(node, "instance", -1);
3730 guint version = sipe_xml_int_attribute(node, "version", 0);
3731 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
3732 sipe_utils_str_to_time(tmp) : 0;
3733 gchar *key;
3734 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3736 /* Ex. clear note: <category name="note"/> */
3737 if (container == (guint)-1) {
3738 g_free(sip->note);
3739 sip->note = NULL;
3740 do_update_status = TRUE;
3741 continue;
3744 /* Ex. clear note: <category name="note" container="200"/> */
3745 if (instance == (guint)-1) {
3746 if (container == 200) {
3747 g_free(sip->note);
3748 sip->note = NULL;
3749 do_update_status = TRUE;
3751 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
3752 sipe_remove_category_container_publications(
3753 sip->our_publications, name, container);
3754 continue;
3757 /* key is <category><instance><container> */
3758 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3759 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
3761 /* capture all userState publication for later clean up if required */
3762 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3763 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3765 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
3766 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3767 publication->category = g_strdup(name);
3768 publication->instance = instance;
3769 publication->container = container;
3770 publication->version = version;
3772 if (!sip->user_state_publications) {
3773 sip->user_state_publications = g_hash_table_new_full(
3774 g_str_hash, g_str_equal,
3775 g_free, (GDestroyNotify)free_publication);
3777 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3778 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3779 key, version);
3783 if (sipe_is_our_publication(sip, key)) {
3784 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3786 publication->category = g_strdup(name);
3787 publication->instance = instance;
3788 publication->container = container;
3789 publication->version = version;
3791 /* filling publication->availability */
3792 if (sipe_strequal(name, "state")) {
3793 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3794 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3796 if (xn_avail) {
3797 gchar *avail_str = sipe_xml_data(xn_avail);
3798 if (avail_str) {
3799 publication->availability = atoi(avail_str);
3801 g_free(avail_str);
3803 /* for calendarState */
3804 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
3805 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3806 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3808 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
3809 if (xn_activity) {
3810 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
3811 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3813 event->is_meeting = TRUE;
3816 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
3817 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
3819 publication->cal_event_hash = sipe_cal_event_hash(event);
3820 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3821 publication->cal_event_hash);
3822 sipe_cal_event_free(event);
3825 /* filling publication->note */
3826 if (sipe_strequal(name, "note")) {
3827 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
3829 if (!has_note_cleaned) {
3830 has_note_cleaned = TRUE;
3832 g_free(sip->note);
3833 sip->note = NULL;
3834 sip->note_since = publish_time;
3836 do_update_status = TRUE;
3839 g_free(publication->note);
3840 publication->note = NULL;
3841 if (xn_body) {
3842 char *tmp;
3844 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
3845 g_free(tmp);
3846 if (publish_time >= sip->note_since) {
3847 g_free(sip->note);
3848 sip->note = g_strdup(publication->note);
3849 sip->note_since = publish_time;
3850 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
3852 do_update_status = TRUE;
3857 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3858 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3859 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
3860 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
3861 if (xn_free_busy) {
3862 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3863 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
3865 if (xn_working_hours) {
3866 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
3870 if (!cat_publications) {
3871 cat_publications = g_hash_table_new_full(
3872 g_str_hash, g_str_equal,
3873 g_free, (GDestroyNotify)free_publication);
3874 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3875 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3877 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3878 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3880 g_free(key);
3882 /* aggregateState (not an our publication) from 2-nd container */
3883 if (sipe_strequal(name, "state") && container == 2) {
3884 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3886 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
3887 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3888 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3890 if (xn_avail) {
3891 gchar *avail_str = sipe_xml_data(xn_avail);
3892 if (avail_str) {
3893 aggreg_avail = atoi(avail_str);
3895 g_free(avail_str);
3898 if (xn_activity) {
3899 const char *activity_token = sipe_xml_attribute(xn_activity, "token");
3901 aggreg_activity = sipe_get_activity_by_token(activity_token);
3904 do_update_status = TRUE;
3908 /* userProperties published by server from AD */
3909 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3910 const sipe_xml *line;
3911 /* line, for Remote Call Control (RCC) */
3912 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
3913 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
3914 const gchar *line_type = sipe_xml_attribute(line, "lineType");
3915 gchar *line_uri;
3917 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3919 line_uri = sipe_xml_data(line);
3920 if (line_uri) {
3921 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
3922 sip_csta_open(sip, line_uri, line_server);
3924 g_free(line_uri);
3926 break;
3930 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
3931 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3933 /* containers */
3934 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
3935 guint id = sipe_xml_int_attribute(node, "id", 0);
3936 struct sipe_container *container = sipe_find_container(sip, id);
3938 if (container) {
3939 sip->containers = g_slist_remove(sip->containers, container);
3940 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
3941 free_container(container);
3943 container = g_new0(struct sipe_container, 1);
3944 container->id = id;
3945 container->version = sipe_xml_int_attribute(node, "version", 0);
3946 sip->containers = g_slist_append(sip->containers, container);
3947 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
3949 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
3950 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3951 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
3952 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
3953 container->members = g_slist_append(container->members, member);
3954 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
3955 member->type, member->value ? member->value : "");
3959 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
3960 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
3961 char *container_xmls = NULL;
3962 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3963 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3965 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
3966 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
3967 /* initial set-up to let counterparties see your status */
3968 if (sameEnterpriseAL < 0) {
3969 struct sipe_container *container = sipe_find_container(sip, 200);
3970 guint version = container ? container->version : 0;
3971 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
3973 if (federatedAL < 0) {
3974 struct sipe_container *container = sipe_find_container(sip, 100);
3975 guint version = container ? container->version : 0;
3976 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
3978 sip->access_level_set = TRUE;
3980 if (container_xmls) {
3981 sipe_send_set_container_members(sip, container_xmls);
3983 g_free(container_xmls);
3986 /* subscribers */
3987 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
3988 const char *user;
3989 const char *acknowledged;
3990 gchar *hdr;
3991 gchar *body;
3993 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
3994 if (!user) continue;
3995 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
3996 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
3997 uri = sip_uri_from_name(user);
3999 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
4001 acknowledged= sipe_xml_attribute(node, "acknowledged");
4002 if(sipe_strcase_equal(acknowledged,"false")){
4003 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
4004 if (!purple_find_buddy(sip->account, uri)) {
4005 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
4008 hdr = g_strdup_printf(
4009 "Contact: %s\r\n"
4010 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
4012 body = g_strdup_printf(
4013 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
4014 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
4015 "</setSubscribers>", user);
4017 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
4018 g_free(body);
4019 g_free(hdr);
4021 g_free(display_name);
4022 g_free(uri);
4025 g_free(contact);
4026 sipe_xml_free(xml);
4028 /* Publish initial state if not yet.
4029 * Assuming this happens on initial responce to subscription to roaming-self
4030 * so we've already updated our roaming data in full.
4031 * Only for 2007+
4033 if (!sip->initial_state_published) {
4034 send_publish_category_initial(sip);
4035 sip->initial_state_published = TRUE;
4036 /* dalayed run */
4037 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
4038 do_update_status = FALSE;
4039 } else if (aggreg_avail) {
4041 g_free(sip->status);
4042 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
4043 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
4044 } else {
4045 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
4049 if (do_update_status) {
4050 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
4051 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
4054 g_free(to);
4057 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
4059 gchar *to = sip_uri_self(sip);
4060 gchar *tmp = get_contact(sip);
4061 gchar *hdr = g_strdup_printf(
4062 "Event: vnd-microsoft-roaming-ACL\r\n"
4063 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
4064 "Supported: com.microsoft.autoextend\r\n"
4065 "Supported: ms-benotify\r\n"
4066 "Proxy-Require: ms-benotify\r\n"
4067 "Supported: ms-piggyback-first-notify\r\n"
4068 "Contact: %s\r\n", tmp);
4069 g_free(tmp);
4071 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
4072 g_free(to);
4073 g_free(hdr);
4077 * To request for presence information about the user, access level settings that have already been configured by the user
4078 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
4079 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
4082 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
4084 gchar *to = sip_uri_self(sip);
4085 gchar *tmp = get_contact(sip);
4086 gchar *hdr = g_strdup_printf(
4087 "Event: vnd-microsoft-roaming-self\r\n"
4088 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
4089 "Supported: ms-benotify\r\n"
4090 "Proxy-Require: ms-benotify\r\n"
4091 "Supported: ms-piggyback-first-notify\r\n"
4092 "Contact: %s\r\n"
4093 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
4095 gchar *body=g_strdup(
4096 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
4097 "<roaming type=\"categories\"/>"
4098 "<roaming type=\"containers\"/>"
4099 "<roaming type=\"subscribers\"/></roamingList>");
4101 g_free(tmp);
4102 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4103 g_free(body);
4104 g_free(to);
4105 g_free(hdr);
4109 * For 2005 version
4111 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
4113 gchar *to = sip_uri_self(sip);
4114 gchar *tmp = get_contact(sip);
4115 gchar *hdr = g_strdup_printf(
4116 "Event: vnd-microsoft-provisioning\r\n"
4117 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
4118 "Supported: com.microsoft.autoextend\r\n"
4119 "Supported: ms-benotify\r\n"
4120 "Proxy-Require: ms-benotify\r\n"
4121 "Supported: ms-piggyback-first-notify\r\n"
4122 "Expires: 0\r\n"
4123 "Contact: %s\r\n", tmp);
4125 g_free(tmp);
4126 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
4127 g_free(to);
4128 g_free(hdr);
4131 /** Subscription for provisioning information to help with initial
4132 * configuration. This subscription is a one-time query (denoted by the Expires header,
4133 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
4134 * configuration, meeting policies, and policy settings that Communicator must enforce.
4135 * TODO: for what we need this information.
4138 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
4140 gchar *to = sip_uri_self(sip);
4141 gchar *tmp = get_contact(sip);
4142 gchar *hdr = g_strdup_printf(
4143 "Event: vnd-microsoft-provisioning-v2\r\n"
4144 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
4145 "Supported: com.microsoft.autoextend\r\n"
4146 "Supported: ms-benotify\r\n"
4147 "Proxy-Require: ms-benotify\r\n"
4148 "Supported: ms-piggyback-first-notify\r\n"
4149 "Expires: 0\r\n"
4150 "Contact: %s\r\n"
4151 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
4152 gchar *body = g_strdup(
4153 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
4154 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
4155 "<provisioningGroup name=\"ucPolicy\"/>"
4156 "</provisioningGroupList>");
4158 g_free(tmp);
4159 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4160 g_free(body);
4161 g_free(to);
4162 g_free(hdr);
4165 static void
4166 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
4167 gpointer value, gpointer user_data)
4169 struct sip_subscription *subscription = value;
4170 struct sip_dialog *dialog = &subscription->dialog;
4171 struct sipe_account_data *sip = user_data;
4172 gchar *tmp = get_contact(sip);
4173 gchar *hdr = g_strdup_printf(
4174 "Event: %s\r\n"
4175 "Expires: 0\r\n"
4176 "Contact: %s\r\n", subscription->event, tmp);
4177 g_free(tmp);
4179 /* Rate limit to max. 25 requests per seconds */
4180 g_usleep(1000000 / 25);
4182 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
4183 g_free(hdr);
4186 /* IM Session (INVITE and MESSAGE methods) */
4188 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
4189 static gchar *
4190 get_end_points (struct sipe_account_data *sip,
4191 struct sip_session *session)
4193 gchar *res;
4195 if (session == NULL) {
4196 return NULL;
4199 res = g_strdup_printf("<sip:%s>", sip->username);
4201 SIPE_DIALOG_FOREACH {
4202 gchar *tmp = res;
4203 res = g_strdup_printf("%s, <%s>", res, dialog->with);
4204 g_free(tmp);
4206 if (dialog->theirepid) {
4207 tmp = res;
4208 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
4209 g_free(tmp);
4211 } SIPE_DIALOG_FOREACH_END;
4213 return res;
4216 static gboolean
4217 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
4218 struct sipmsg *msg,
4219 SIPE_UNUSED_PARAMETER struct transaction *trans)
4221 gboolean ret = TRUE;
4223 if (msg->response != 200) {
4224 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
4225 return FALSE;
4228 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
4230 return ret;
4234 * Asks UA/proxy about its capabilities.
4236 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
4238 gchar *to = sip_uri(who);
4239 gchar *contact = get_contact(sip);
4240 gchar *request = g_strdup_printf(
4241 "Accept: application/sdp\r\n"
4242 "Contact: %s\r\n", contact);
4243 g_free(contact);
4245 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
4247 g_free(to);
4248 g_free(request);
4251 static void
4252 sipe_notify_user(struct sipe_account_data *sip,
4253 struct sip_session *session,
4254 PurpleMessageFlags flags,
4255 const gchar *message)
4257 PurpleConversation *conv;
4259 if (!session->conv) {
4260 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
4261 } else {
4262 conv = session->conv;
4264 purple_conversation_write(conv, NULL, message, flags, time(NULL));
4267 void
4268 sipe_present_info(struct sipe_account_data *sip,
4269 struct sip_session *session,
4270 const gchar *message)
4272 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4275 static void
4276 sipe_present_err(struct sipe_account_data *sip,
4277 struct sip_session *session,
4278 const gchar *message)
4280 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4283 void
4284 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4285 struct sip_session *session,
4286 int sip_error,
4287 int sip_warning,
4288 const gchar *who,
4289 const gchar *message)
4291 char *msg, *msg_tmp, *msg_tmp2;
4292 const char *label;
4294 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
4295 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4296 g_free(msg_tmp);
4297 /* Service unavailable; Server Internal Error; Server Time-out */
4298 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4299 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4300 g_free(msg);
4301 msg = NULL;
4302 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4303 label = _("This message was not delivered to %s because the service is not available");
4304 } else if (sip_error == 486) { /* Busy Here */
4305 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4306 } else if (sip_error == 415) { /* Unsupported media type */
4307 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4308 } else {
4309 label = _("This message was not delivered to %s because one or more recipients are offline");
4312 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4313 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4314 msg ? ":" : "",
4315 msg ? msg : "");
4316 sipe_present_err(sip, session, msg_tmp);
4317 g_free(msg_tmp2);
4318 g_free(msg_tmp);
4319 g_free(msg);
4323 static gboolean
4324 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4325 SIPE_UNUSED_PARAMETER struct transaction *trans)
4327 gboolean ret = TRUE;
4328 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4329 struct sip_session *session = sipe_session_find_im(sip, with);
4330 struct sip_dialog *dialog;
4331 gchar *cseq;
4332 char *key;
4333 struct queued_message *message;
4335 if (!session) {
4336 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4337 g_free(with);
4338 return FALSE;
4341 dialog = sipe_dialog_find(session, with);
4342 if (!dialog) {
4343 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4344 g_free(with);
4345 return FALSE;
4348 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4349 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4350 g_free(cseq);
4351 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4353 if (msg->response >= 400) {
4354 PurpleBuddy *pbuddy;
4355 const char *alias = with;
4356 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4357 int warning = -1;
4359 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4361 if (warn_hdr) {
4362 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4363 if (parts[0]) {
4364 warning = atoi(parts[0]);
4366 g_strfreev(parts);
4369 /* cancel file transfer as rejected by server */
4370 if (msg->response == 606 && /* Not acceptable all. */
4371 warning == 309 && /* Message contents not allowed by policy */
4372 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4374 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4375 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4376 sipe_utils_nameval_free(parsed_body);
4379 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4380 alias = purple_buddy_get_alias(pbuddy);
4383 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4385 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4386 if (msg->response == 408 || /* Request timeout */
4387 msg->response == 480 || /* Temporarily Unavailable */
4388 msg->response == 481) { /* Call/Transaction Does Not Exist */
4389 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4390 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4393 ret = FALSE;
4394 } else {
4395 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4396 if (message_id) {
4397 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4398 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4399 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4402 g_hash_table_remove(session->unconfirmed_messages, key);
4403 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4404 key, g_hash_table_size(session->unconfirmed_messages));
4407 g_free(key);
4408 g_free(with);
4410 if (ret) sipe_im_process_queue(sip, session);
4411 return ret;
4414 static gboolean
4415 sipe_is_election_finished(struct sip_session *session);
4417 static void
4418 sipe_election_result(struct sipe_account_data *sip,
4419 void *sess);
4421 static gboolean
4422 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4423 SIPE_UNUSED_PARAMETER struct transaction *trans)
4425 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4426 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4427 struct sip_dialog *dialog;
4428 struct sip_session *session;
4430 session = sipe_session_find_chat_by_callid(sip, callid);
4431 if (!session) {
4432 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
4433 return FALSE;
4436 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4437 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
4438 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
4439 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
4441 if (xn_request_rm_response) {
4442 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
4443 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
4445 dialog = sipe_dialog_find(session, with);
4446 if (!dialog) {
4447 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
4448 sipe_xml_free(xn_action);
4449 return FALSE;
4452 if (allow && !g_strcasecmp(allow, "true")) {
4453 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
4454 dialog->election_vote = 1;
4455 } else if (allow && !g_strcasecmp(allow, "false")) {
4456 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
4457 dialog->election_vote = -1;
4460 if (sipe_is_election_finished(session)) {
4461 sipe_election_result(sip, session);
4464 } else if (xn_set_rm_response) {
4467 sipe_xml_free(xn_action);
4471 return TRUE;
4474 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4476 gchar *hdr;
4477 gchar *tmp;
4478 char *msgtext = NULL;
4479 const gchar *msgr = "";
4480 gchar *tmp2 = NULL;
4482 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4483 char *msgformat;
4484 gchar *msgr_value;
4486 sipe_parse_html(msg, &msgformat, &msgtext);
4487 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
4489 msgr_value = sipmsg_get_msgr_string(msgformat);
4490 g_free(msgformat);
4491 if (msgr_value) {
4492 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4493 g_free(msgr_value);
4495 } else {
4496 msgtext = g_strdup(msg);
4499 tmp = get_contact(sip);
4500 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4501 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4502 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4503 if (content_type == NULL)
4504 content_type = "text/plain";
4506 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4507 g_free(tmp);
4508 g_free(tmp2);
4510 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4511 g_free(msgtext);
4512 g_free(hdr);
4516 void
4517 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4519 GSList *entry2 = session->outgoing_message_queue;
4520 while (entry2) {
4521 struct queued_message *msg = entry2->data;
4523 /* for multiparty chat or conference */
4524 if (session->is_multiparty || session->focus_uri) {
4525 gchar *who = sip_uri_self(sip);
4526 serv_got_chat_in(sip->gc, session->chat_id, who,
4527 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4528 g_free(who);
4531 SIPE_DIALOG_FOREACH {
4532 char *key;
4533 struct queued_message *message;
4535 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4537 message = g_new0(struct queued_message,1);
4538 message->body = g_strdup(msg->body);
4539 if (msg->content_type != NULL)
4540 message->content_type = g_strdup(msg->content_type);
4542 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4543 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4544 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4545 key, g_hash_table_size(session->unconfirmed_messages));
4546 g_free(key);
4548 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4549 } SIPE_DIALOG_FOREACH_END;
4551 entry2 = sipe_session_dequeue_message(session);
4555 static void
4556 sipe_refer_notify(struct sipe_account_data *sip,
4557 struct sip_session *session,
4558 const gchar *who,
4559 int status,
4560 const gchar *desc)
4562 gchar *hdr;
4563 gchar *body;
4564 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4566 hdr = g_strdup_printf(
4567 "Event: refer\r\n"
4568 "Subscription-State: %s\r\n"
4569 "Content-Type: message/sipfrag\r\n",
4570 status >= 200 ? "terminated" : "active");
4572 body = g_strdup_printf(
4573 "SIP/2.0 %d %s\r\n",
4574 status, desc);
4576 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4578 g_free(hdr);
4579 g_free(body);
4582 static gboolean
4583 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4585 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4586 struct sip_session *session;
4587 struct sip_dialog *dialog;
4588 char *cseq;
4589 char *key;
4590 struct queued_message *message;
4591 struct sipmsg *request_msg = trans->msg;
4593 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4594 gchar *referred_by;
4596 session = sipe_session_find_chat_by_callid(sip, callid);
4597 if (!session) {
4598 session = sipe_session_find_im(sip, with);
4600 if (!session) {
4601 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4602 g_free(with);
4603 return FALSE;
4606 dialog = sipe_dialog_find(session, with);
4607 if (!dialog) {
4608 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4609 g_free(with);
4610 return FALSE;
4613 sipe_dialog_parse(dialog, msg, TRUE);
4615 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4616 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4617 g_free(cseq);
4618 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4620 if (msg->response != 200) {
4621 PurpleBuddy *pbuddy;
4622 const char *alias = with;
4623 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4624 int warning = -1;
4626 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4628 if (warn_hdr) {
4629 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4630 if (parts[0]) {
4631 warning = atoi(parts[0]);
4633 g_strfreev(parts);
4636 /* cancel file transfer as rejected by server */
4637 if (msg->response == 606 && /* Not acceptable all. */
4638 warning == 309 && /* Message contents not allowed by policy */
4639 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4641 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4642 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4643 sipe_utils_nameval_free(parsed_body);
4646 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4647 alias = purple_buddy_get_alias(pbuddy);
4650 if (message) {
4651 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4652 } else {
4653 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4654 sipe_present_err(sip, session, tmp_msg);
4655 g_free(tmp_msg);
4658 sipe_dialog_remove(session, with);
4660 g_free(key);
4661 g_free(with);
4662 return FALSE;
4665 dialog->cseq = 0;
4666 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4667 dialog->outgoing_invite = NULL;
4668 dialog->is_established = TRUE;
4670 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4671 if (referred_by) {
4672 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4673 g_free(referred_by);
4676 /* add user to chat if it is a multiparty session */
4677 if (session->is_multiparty) {
4678 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4679 with, NULL,
4680 PURPLE_CBFLAGS_NONE, TRUE);
4683 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4684 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4685 sipe_session_dequeue_message(session);
4688 sipe_im_process_queue(sip, session);
4690 g_hash_table_remove(session->unconfirmed_messages, key);
4691 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4692 key, g_hash_table_size(session->unconfirmed_messages));
4694 g_free(key);
4695 g_free(with);
4696 return TRUE;
4700 void
4701 sipe_invite(struct sipe_account_data *sip,
4702 struct sip_session *session,
4703 const gchar *who,
4704 const gchar *msg_body,
4705 const gchar *msg_content_type,
4706 const gchar *referred_by,
4707 const gboolean is_triggered)
4709 gchar *hdr;
4710 gchar *to;
4711 gchar *contact;
4712 gchar *body;
4713 gchar *self;
4714 char *ms_text_format = NULL;
4715 gchar *roster_manager;
4716 gchar *end_points;
4717 gchar *referred_by_str;
4718 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4720 if (dialog && dialog->is_established) {
4721 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
4722 return;
4725 if (!dialog) {
4726 dialog = sipe_dialog_add(session);
4727 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4728 dialog->with = g_strdup(who);
4731 if (!(dialog->ourtag)) {
4732 dialog->ourtag = gentag();
4735 to = sip_uri(who);
4737 if (msg_body) {
4738 char *msgtext = NULL;
4739 char *base64_msg;
4740 const gchar *msgr = "";
4741 char *key;
4742 struct queued_message *message;
4743 gchar *tmp = NULL;
4745 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4746 char *msgformat;
4747 gchar *msgr_value;
4749 sipe_parse_html(msg_body, &msgformat, &msgtext);
4750 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
4752 msgr_value = sipmsg_get_msgr_string(msgformat);
4753 g_free(msgformat);
4754 if (msgr_value) {
4755 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4756 g_free(msgr_value);
4758 } else {
4759 msgtext = g_strdup(msg_body);
4762 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4763 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4764 msg_content_type ? msg_content_type : "text/plain",
4765 msgr,
4766 base64_msg);
4767 g_free(msgtext);
4768 g_free(tmp);
4769 g_free(base64_msg);
4771 message = g_new0(struct queued_message,1);
4772 message->body = g_strdup(msg_body);
4773 if (msg_content_type != NULL)
4774 message->content_type = g_strdup(msg_content_type);
4776 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4777 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4778 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4779 key, g_hash_table_size(session->unconfirmed_messages));
4780 g_free(key);
4783 contact = get_contact(sip);
4784 end_points = get_end_points(sip, session);
4785 self = sip_uri_self(sip);
4786 roster_manager = g_strdup_printf(
4787 "Roster-Manager: %s\r\n"
4788 "EndPoints: %s\r\n",
4789 self,
4790 end_points);
4791 referred_by_str = referred_by ?
4792 g_strdup_printf(
4793 "Referred-By: %s\r\n",
4794 referred_by)
4795 : g_strdup("");
4796 hdr = g_strdup_printf(
4797 "Supported: ms-sender\r\n"
4798 "%s"
4799 "%s"
4800 "%s"
4801 "%s"
4802 "Contact: %s\r\n%s"
4803 "Content-Type: application/sdp\r\n",
4804 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4805 referred_by_str,
4806 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4807 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4808 contact,
4809 ms_text_format ? ms_text_format : "");
4810 g_free(ms_text_format);
4811 g_free(self);
4813 body = g_strdup_printf(
4814 "v=0\r\n"
4815 "o=- 0 0 IN IP4 %s\r\n"
4816 "s=session\r\n"
4817 "c=IN IP4 %s\r\n"
4818 "t=0 0\r\n"
4819 "m=%s %d sip null\r\n"
4820 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4821 sipe_backend_network_ip_address(),
4822 sipe_backend_network_ip_address(),
4823 sip->ocs2007 ? "message" : "x-ms-message",
4824 sip->realport);
4826 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4827 to, to, hdr, body, dialog, process_invite_response);
4829 g_free(to);
4830 g_free(roster_manager);
4831 g_free(end_points);
4832 g_free(referred_by_str);
4833 g_free(body);
4834 g_free(hdr);
4835 g_free(contact);
4838 static void
4839 sipe_refer(struct sipe_account_data *sip,
4840 struct sip_session *session,
4841 const gchar *who)
4843 gchar *hdr;
4844 gchar *contact;
4845 gchar *epid = get_epid(sip);
4846 struct sip_dialog *dialog = sipe_dialog_find(session,
4847 session->roster_manager);
4848 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4850 contact = get_contact(sip);
4851 hdr = g_strdup_printf(
4852 "Contact: %s\r\n"
4853 "Refer-to: <%s>\r\n"
4854 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4855 "Require: com.microsoft.rtc-multiparty\r\n",
4856 contact,
4857 who,
4858 sip->username,
4859 ourtag ? ";tag=" : "",
4860 ourtag ? ourtag : "",
4861 epid);
4862 g_free(epid);
4864 send_sip_request(sip->gc, "REFER",
4865 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4867 g_free(hdr);
4868 g_free(contact);
4871 static void
4872 sipe_send_election_request_rm(struct sipe_account_data *sip,
4873 struct sip_dialog *dialog,
4874 int bid)
4876 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4878 gchar *body = g_strdup_printf(
4879 "<?xml version=\"1.0\"?>\r\n"
4880 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4881 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4882 sip->username, bid);
4884 send_sip_request(sip->gc, "INFO",
4885 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4887 g_free(body);
4890 static void
4891 sipe_send_election_set_rm(struct sipe_account_data *sip,
4892 struct sip_dialog *dialog)
4894 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4896 gchar *body = g_strdup_printf(
4897 "<?xml version=\"1.0\"?>\r\n"
4898 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4899 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4900 sip->username);
4902 send_sip_request(sip->gc, "INFO",
4903 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4905 g_free(body);
4908 static void
4909 sipe_session_close(struct sipe_account_data *sip,
4910 struct sip_session * session)
4912 if (session && session->focus_uri) {
4913 sipe_conf_immcu_closed(sip, session);
4914 conf_session_close(sip, session);
4917 if (session) {
4918 SIPE_DIALOG_FOREACH {
4919 /* @TODO slow down BYE message sending rate */
4920 /* @see single subscription code */
4921 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4922 } SIPE_DIALOG_FOREACH_END;
4924 sipe_session_remove(sip, session);
4928 static void
4929 sipe_session_close_all(struct sipe_account_data *sip)
4931 GSList *entry;
4932 while ((entry = sip->sessions) != NULL) {
4933 sipe_session_close(sip, entry->data);
4937 static void
4938 sipe_convo_closed(PurpleConnection * gc, const char *who)
4940 struct sipe_account_data *sip = gc->proto_data;
4942 SIPE_DEBUG_INFO("conversation with %s closed", who);
4943 sipe_session_close(sip, sipe_session_find_im(sip, who));
4946 static void
4947 sipe_chat_invite(PurpleConnection *gc, int id,
4948 SIPE_UNUSED_PARAMETER const char *message,
4949 const char *name)
4951 sipe_chat_create(gc->proto_data, id, name);
4954 static void
4955 sipe_chat_leave (PurpleConnection *gc, int id)
4957 struct sipe_account_data *sip = gc->proto_data;
4958 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4960 sipe_session_close(sip, session);
4963 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4964 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4966 struct sipe_account_data *sip = gc->proto_data;
4967 struct sip_session *session;
4968 struct sip_dialog *dialog;
4969 gchar *uri = sip_uri(who);
4971 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
4973 session = sipe_session_find_or_add_im(sip, uri);
4974 dialog = sipe_dialog_find(session, uri);
4976 // Queue the message
4977 sipe_session_enqueue_message(session, what, NULL);
4979 if (dialog && !dialog->outgoing_invite) {
4980 sipe_im_process_queue(sip, session);
4981 } else if (!dialog || !dialog->outgoing_invite) {
4982 // Need to send the INVITE to get the outgoing dialog setup
4983 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4986 g_free(uri);
4987 return 1;
4990 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4991 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4993 struct sipe_account_data *sip = gc->proto_data;
4994 struct sip_session *session;
4996 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
4998 session = sipe_session_find_chat_by_id(sip, id);
5000 // Queue the message
5001 if (session && session->dialogs) {
5002 sipe_session_enqueue_message(session,what,NULL);
5003 sipe_im_process_queue(sip, session);
5004 } else if (sip) {
5005 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
5006 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
5008 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
5009 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
5011 if (sip->ocs2007) {
5012 struct sip_session *session = sipe_session_add_chat(sip);
5014 session->is_multiparty = FALSE;
5015 session->focus_uri = g_strdup(proto_chat_id);
5016 sipe_session_enqueue_message(session, what, NULL);
5017 sipe_invite_conf_focus(sip, session);
5021 return 1;
5024 /* End IM Session (INVITE and MESSAGE methods) */
5026 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
5028 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
5029 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5030 gchar *from;
5031 struct sip_session *session;
5033 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
5035 /* Call Control protocol */
5036 if (g_str_has_prefix(contenttype, "application/csta+xml"))
5038 process_incoming_info_csta(sip, msg);
5039 return;
5042 from = parse_from(sipmsg_find_header(msg, "From"));
5043 session = sipe_session_find_chat_by_callid(sip, callid);
5044 if (!session) {
5045 session = sipe_session_find_im(sip, from);
5047 if (!session) {
5048 g_free(from);
5049 return;
5052 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
5054 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
5055 const sipe_xml *xn_request_rm = sipe_xml_child(xn_action, "RequestRM");
5056 const sipe_xml *xn_set_rm = sipe_xml_child(xn_action, "SetRM");
5058 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
5060 if (xn_request_rm) {
5061 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
5062 int bid = sipe_xml_int_attribute(xn_request_rm, "bid", 0);
5063 gchar *body = g_strdup_printf(
5064 "<?xml version=\"1.0\"?>\r\n"
5065 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5066 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
5067 sip->username,
5068 session->bid < bid ? "true" : "false");
5069 send_sip_response(sip->gc, msg, 200, "OK", body);
5070 g_free(body);
5071 } else if (xn_set_rm) {
5072 gchar *body;
5073 const char *rm = sipe_xml_attribute(xn_set_rm, "uri");
5074 g_free(session->roster_manager);
5075 session->roster_manager = g_strdup(rm);
5077 body = g_strdup_printf(
5078 "<?xml version=\"1.0\"?>\r\n"
5079 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5080 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
5081 sip->username);
5082 send_sip_response(sip->gc, msg, 200, "OK", body);
5083 g_free(body);
5085 sipe_xml_free(xn_action);
5088 else
5090 /* looks like purple lacks typing notification for chat */
5091 if (!session->is_multiparty && !session->focus_uri) {
5092 sipe_xml *xn_keyboard_activity = sipe_xml_parse(msg->body, msg->bodylen);
5093 const char *status = sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity, "status"),
5094 "status");
5095 if (sipe_strequal(status, "type")) {
5096 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
5097 } else if (sipe_strequal(status, "idle")) {
5098 serv_got_typing_stopped(sip->gc, from);
5100 sipe_xml_free(xn_keyboard_activity);
5103 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5105 g_free(from);
5108 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
5110 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5111 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5112 struct sip_session *session;
5113 struct sip_dialog *dialog;
5115 /* collect dialog identification
5116 * we need callid, ourtag and theirtag to unambiguously identify dialog
5118 /* take data before 'msg' will be modified by send_sip_response */
5119 dialog = g_new0(struct sip_dialog, 1);
5120 dialog->callid = g_strdup(callid);
5121 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
5122 dialog->with = g_strdup(from);
5123 sipe_dialog_parse(dialog, msg, FALSE);
5125 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5127 session = sipe_session_find_chat_by_callid(sip, callid);
5128 if (!session) {
5129 session = sipe_session_find_im(sip, from);
5131 if (!session) {
5132 sipe_dialog_free(dialog);
5133 g_free(from);
5134 return;
5137 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
5138 g_free(session->roster_manager);
5139 session->roster_manager = NULL;
5142 /* This what BYE is essentially for - terminating dialog */
5143 sipe_dialog_remove_3(session, dialog);
5144 sipe_dialog_free(dialog);
5145 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
5146 sipe_conf_immcu_closed(sip, session);
5147 } else if (session->is_multiparty) {
5148 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
5151 g_free(from);
5154 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
5156 gchar *self = sip_uri_self(sip);
5157 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5158 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5159 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
5160 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
5161 struct sip_session *session;
5162 struct sip_dialog *dialog;
5164 session = sipe_session_find_chat_by_callid(sip, callid);
5165 dialog = sipe_dialog_find(session, from);
5167 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
5168 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
5169 } else {
5170 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
5172 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
5175 g_free(self);
5176 g_free(from);
5177 g_free(refer_to);
5178 g_free(referred_by);
5181 static unsigned int
5182 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
5184 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
5185 struct sip_session *session;
5186 struct sip_dialog *dialog;
5188 if (state == PURPLE_NOT_TYPING)
5189 return 0;
5191 session = sipe_session_find_im(sip, who);
5192 dialog = sipe_dialog_find(session, who);
5194 if (session && dialog && dialog->is_established) {
5195 send_sip_request(gc, "INFO", who, who,
5196 "Content-Type: application/xml\r\n",
5197 SIPE_SEND_TYPING, dialog, NULL);
5199 return SIPE_TYPING_SEND_TIMEOUT;
5202 static gboolean resend_timeout(struct sipe_account_data *sip)
5204 GSList *tmp = sip->transactions;
5205 time_t currtime = time(NULL);
5206 while (tmp) {
5207 struct transaction *trans = tmp->data;
5208 tmp = tmp->next;
5209 SIPE_DEBUG_INFO("have open transaction age: %ld", (long int)currtime-trans->time);
5210 if ((currtime - trans->time > 5) && trans->retries >= 1) {
5211 /* TODO 408 */
5212 } else {
5213 if ((currtime - trans->time > 2) && trans->retries == 0) {
5214 trans->retries++;
5215 sendout_sipmsg(sip, trans->msg);
5219 return TRUE;
5222 static void do_reauthenticate_cb(struct sipe_account_data *sip,
5223 SIPE_UNUSED_PARAMETER void *unused)
5225 /* register again when security token expires */
5226 /* we have to start a new authentication as the security token
5227 * is almost expired by sending a not signed REGISTER message */
5228 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
5229 sipe_auth_free(&sip->registrar);
5230 sipe_auth_free(&sip->proxy);
5231 sip->registerstatus = 0;
5232 do_register(sip);
5233 sip->reauthenticate_set = FALSE;
5236 static gboolean
5237 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
5238 struct sipmsg *msg,
5239 GSList *parsed_body)
5241 gboolean found = FALSE;
5243 if (parsed_body) {
5244 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
5246 if (sipe_strequal(invitation_command, "INVITE")) {
5247 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
5248 found = TRUE;
5249 } else if (sipe_strequal(invitation_command, "CANCEL")) {
5250 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
5251 found = TRUE;
5252 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
5253 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
5254 found = TRUE;
5257 return found;
5260 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
5262 gchar *from;
5263 const gchar *contenttype;
5264 gboolean found = FALSE;
5266 from = parse_from(sipmsg_find_header(msg, "From"));
5268 if (!from) return;
5270 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
5272 contenttype = sipmsg_find_header(msg, "Content-Type");
5273 if (g_str_has_prefix(contenttype, "text/plain")
5274 || g_str_has_prefix(contenttype, "text/html")
5275 || g_str_has_prefix(contenttype, "multipart/related")
5276 || g_str_has_prefix(contenttype, "multipart/alternative"))
5278 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5279 gchar *html = get_html_message(contenttype, msg->body);
5281 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5282 if (!session) {
5283 session = sipe_session_find_im(sip, from);
5286 if (session && session->focus_uri) { /* a conference */
5287 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5288 gchar *sender = parse_from(tmp);
5289 g_free(tmp);
5290 serv_got_chat_in(sip->gc, session->chat_id, sender,
5291 PURPLE_MESSAGE_RECV, html, time(NULL));
5292 g_free(sender);
5293 } else if (session && session->is_multiparty) { /* a multiparty chat */
5294 serv_got_chat_in(sip->gc, session->chat_id, from,
5295 PURPLE_MESSAGE_RECV, html, time(NULL));
5296 } else {
5297 serv_got_im(sip->gc, from, html, 0, time(NULL));
5299 g_free(html);
5300 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5301 found = TRUE;
5303 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5304 sipe_xml *isc = sipe_xml_parse(msg->body, msg->bodylen);
5305 const sipe_xml *state;
5306 gchar *statedata;
5308 if (!isc) {
5309 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
5310 g_free(from);
5311 return;
5314 state = sipe_xml_child(isc, "state");
5316 if (!state) {
5317 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
5318 sipe_xml_free(isc);
5319 g_free(from);
5320 return;
5323 statedata = sipe_xml_data(state);
5324 if (statedata) {
5325 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5326 else serv_got_typing_stopped(sip->gc, from);
5328 g_free(statedata);
5330 sipe_xml_free(isc);
5331 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5332 found = TRUE;
5333 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5334 GSList *body = sipe_ft_parse_msg_body(msg->body);
5335 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5336 sipe_utils_nameval_free(body);
5337 if (found) {
5338 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5341 if (!found) {
5342 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5343 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5344 if (!session) {
5345 session = sipe_session_find_im(sip, from);
5347 if (session) {
5348 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5349 from);
5350 sipe_present_err(sip, session, errmsg);
5351 g_free(errmsg);
5354 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
5355 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5357 g_free(from);
5360 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5362 gchar *body;
5363 gchar *newTag;
5364 const gchar *oldHeader;
5365 gchar *newHeader;
5366 gboolean is_multiparty = FALSE;
5367 gboolean is_triggered = FALSE;
5368 gboolean was_multiparty = TRUE;
5369 gboolean just_joined = FALSE;
5370 gchar *from;
5371 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5372 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5373 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5374 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5375 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5376 GSList *end_points = NULL;
5377 char *tmp = NULL;
5378 struct sip_session *session;
5379 const gchar *ms_text_format;
5381 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
5382 g_free(tmp);
5384 /* Invitation to join conference */
5385 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5386 process_incoming_invite_conf(sip, msg);
5387 return;
5390 /* Only accept text invitations */
5391 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5392 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5393 return;
5396 // TODO There *must* be a better way to clean up the To header to add a tag...
5397 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5398 oldHeader = sipmsg_find_header(msg, "To");
5399 newTag = gentag();
5400 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5401 sipmsg_remove_header_now(msg, "To");
5402 sipmsg_add_header_now(msg, "To", newHeader);
5403 g_free(newHeader);
5405 if (end_points_hdr) {
5406 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5408 if (g_slist_length(end_points) > 2) {
5409 is_multiparty = TRUE;
5412 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5413 is_triggered = TRUE;
5414 is_multiparty = TRUE;
5417 session = sipe_session_find_chat_by_callid(sip, callid);
5418 /* Convert to multiparty */
5419 if (session && is_multiparty && !session->is_multiparty) {
5420 g_free(session->with);
5421 session->with = NULL;
5422 was_multiparty = FALSE;
5423 session->is_multiparty = TRUE;
5424 session->chat_id = rand();
5427 if (!session && is_multiparty) {
5428 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5430 /* IM session */
5431 from = parse_from(sipmsg_find_header(msg, "From"));
5432 if (!session) {
5433 session = sipe_session_find_or_add_im(sip, from);
5436 if (session) {
5437 g_free(session->callid);
5438 session->callid = g_strdup(callid);
5440 session->is_multiparty = is_multiparty;
5441 if (roster_manager) {
5442 session->roster_manager = g_strdup(roster_manager);
5446 if (is_multiparty && end_points) {
5447 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5448 GSList *entry = end_points;
5449 while (entry) {
5450 struct sip_dialog *dialog;
5451 struct sipendpoint *end_point = entry->data;
5452 entry = entry->next;
5454 if (!g_strcasecmp(from, end_point->contact) ||
5455 !g_strcasecmp(to, end_point->contact))
5456 continue;
5458 dialog = sipe_dialog_find(session, end_point->contact);
5459 if (dialog) {
5460 g_free(dialog->theirepid);
5461 dialog->theirepid = end_point->epid;
5462 end_point->epid = NULL;
5463 } else {
5464 dialog = sipe_dialog_add(session);
5466 dialog->callid = g_strdup(session->callid);
5467 dialog->with = end_point->contact;
5468 end_point->contact = NULL;
5469 dialog->theirepid = end_point->epid;
5470 end_point->epid = NULL;
5472 just_joined = TRUE;
5474 /* send triggered INVITE */
5475 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5478 g_free(to);
5481 if (end_points) {
5482 GSList *entry = end_points;
5483 while (entry) {
5484 struct sipendpoint *end_point = entry->data;
5485 entry = entry->next;
5486 g_free(end_point->contact);
5487 g_free(end_point->epid);
5488 g_free(end_point);
5490 g_slist_free(end_points);
5493 if (session) {
5494 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5495 if (dialog) {
5496 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5497 sipe_dialog_parse_routes(dialog, msg, FALSE);
5498 } else {
5499 dialog = sipe_dialog_add(session);
5501 dialog->callid = g_strdup(session->callid);
5502 dialog->with = g_strdup(from);
5503 sipe_dialog_parse(dialog, msg, FALSE);
5505 if (!dialog->ourtag) {
5506 dialog->ourtag = newTag;
5507 newTag = NULL;
5510 just_joined = TRUE;
5512 } else {
5513 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5515 g_free(newTag);
5517 if (is_multiparty && !session->conv) {
5518 gchar *chat_title = sipe_chat_get_name(callid);
5519 gchar *self = sip_uri_self(sip);
5520 /* create prpl chat */
5521 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5522 session->chat_title = g_strdup(chat_title);
5523 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5524 /* add self */
5525 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5526 self, NULL,
5527 PURPLE_CBFLAGS_NONE, FALSE);
5528 g_free(chat_title);
5529 g_free(self);
5532 if (is_multiparty && !was_multiparty) {
5533 /* add current IM counterparty to chat */
5534 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5535 sipe_dialog_first(session)->with, NULL,
5536 PURPLE_CBFLAGS_NONE, FALSE);
5539 /* add inviting party to chat */
5540 if (just_joined && session->conv) {
5541 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5542 from, NULL,
5543 PURPLE_CBFLAGS_NONE, TRUE);
5546 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5548 /* This used only in 2005 official client, not 2007 or Reuters.
5549 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5550 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5552 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5553 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5554 if (is_multiparty ||
5555 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5557 if (ms_text_format) {
5558 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5560 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5561 if (tmp) {
5562 gsize len;
5563 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5565 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5567 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5568 sipe_utils_nameval_free(parsed_body);
5569 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5571 g_free(tmp);
5573 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5575 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5576 gchar *html = get_html_message(ms_text_format, NULL);
5577 if (html) {
5578 if (is_multiparty) {
5579 serv_got_chat_in(sip->gc, session->chat_id, from,
5580 PURPLE_MESSAGE_RECV, html, time(NULL));
5581 } else {
5582 serv_got_im(sip->gc, from, html, 0, time(NULL));
5584 g_free(html);
5585 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5591 g_free(from);
5593 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5594 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5595 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5597 body = g_strdup_printf(
5598 "v=0\r\n"
5599 "o=- 0 0 IN IP4 %s\r\n"
5600 "s=session\r\n"
5601 "c=IN IP4 %s\r\n"
5602 "t=0 0\r\n"
5603 "m=%s %d sip sip:%s\r\n"
5604 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5605 sipe_backend_network_ip_address(),
5606 sipe_backend_network_ip_address(),
5607 sip->ocs2007 ? "message" : "x-ms-message",
5608 sip->realport,
5609 sip->username);
5610 send_sip_response(sip->gc, msg, 200, "OK", body);
5611 g_free(body);
5614 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5616 gchar *body;
5618 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5619 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5620 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5622 body = g_strdup_printf(
5623 "v=0\r\n"
5624 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5625 "s=session\r\n"
5626 "c=IN IP4 0.0.0.0\r\n"
5627 "t=0 0\r\n"
5628 "m=%s %d sip sip:%s\r\n"
5629 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5630 sip->ocs2007 ? "message" : "x-ms-message",
5631 sip->realport,
5632 sip->username);
5633 send_sip_response(sip->gc, msg, 200, "OK", body);
5634 g_free(body);
5637 static const char*
5638 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5640 const char *res = "NTLM";
5641 #ifdef HAVE_LIBKRB5
5642 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5643 res = "Kerberos";
5645 #else
5646 (void) sip; /* make compiler happy */
5647 #endif
5648 return res;
5651 static void sipe_connection_cleanup(struct sipe_account_data *);
5652 static void create_connection(struct sipe_account_data *, gchar *, int);
5654 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5655 SIPE_UNUSED_PARAMETER struct transaction *trans)
5657 gchar *tmp;
5658 const gchar *expires_header;
5659 int expires, i;
5660 GSList *hdr = msg->headers;
5661 struct sipnameval *elem;
5663 expires_header = sipmsg_find_header(msg, "Expires");
5664 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5665 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
5667 switch (msg->response) {
5668 case 200:
5669 if (expires == 0) {
5670 sip->registerstatus = 0;
5671 } else {
5672 const gchar *contact_hdr;
5673 gchar *gruu = NULL;
5674 gchar *epid;
5675 gchar *uuid;
5676 gchar *timeout;
5677 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5678 const char *auth_scheme;
5680 if (!sip->reregister_set) {
5681 gchar *action_name = g_strdup_printf("<%s>", "registration");
5682 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5683 g_free(action_name);
5684 sip->reregister_set = TRUE;
5687 sip->registerstatus = 3;
5689 if (server_hdr && !sip->server_version) {
5690 sip->server_version = g_strdup(server_hdr);
5691 g_free(default_ua);
5692 default_ua = NULL;
5695 auth_scheme = sipe_get_auth_scheme_name(sip);
5696 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5698 if (tmp) {
5699 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
5700 fill_auth(tmp, &sip->registrar);
5703 if (!sip->reauthenticate_set) {
5704 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5705 guint reauth_timeout;
5706 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5707 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5708 reauth_timeout = sip->registrar.expires - 300;
5709 } else {
5710 /* NTLM: we have to reauthenticate as our security token expires
5711 after eight hours (be five minutes early) */
5712 reauth_timeout = (8 * 3600) - 300;
5714 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5715 g_free(action_name);
5716 sip->reauthenticate_set = TRUE;
5719 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5721 epid = get_epid(sip);
5722 uuid = generateUUIDfromEPID(epid);
5723 g_free(epid);
5725 // There can be multiple Contact headers (one per location where the user is logged in) so
5726 // make sure to only get the one for this uuid
5727 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5728 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5729 if (valid_contact) {
5730 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5731 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5732 g_free(valid_contact);
5733 break;
5734 } else {
5735 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5738 g_free(uuid);
5740 g_free(sip->contact);
5741 if(gruu) {
5742 sip->contact = g_strdup_printf("<%s>", gruu);
5743 g_free(gruu);
5744 } else {
5745 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5746 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);
5748 sip->ocs2007 = FALSE;
5749 sip->batched_support = FALSE;
5751 while(hdr)
5753 elem = hdr->data;
5754 if (sipe_strcase_equal(elem->name, "Supported")) {
5755 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5756 /* We interpret this as OCS2007+ indicator */
5757 sip->ocs2007 = TRUE;
5758 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5760 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5761 sip->batched_support = TRUE;
5762 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5765 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5766 gchar **caps = g_strsplit(elem->value,",",0);
5767 i = 0;
5768 while (caps[i]) {
5769 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5770 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5771 i++;
5773 g_strfreev(caps);
5775 hdr = g_slist_next(hdr);
5778 /* rejoin open chats to be able to use them by continue to send messages */
5779 purple_conversation_foreach(sipe_rejoin_chat);
5781 /* subscriptions */
5782 if (!sip->subscribed) { //do it just once, not every re-register
5784 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5785 (GCompareFunc)g_ascii_strcasecmp)) {
5786 sipe_subscribe_roaming_contacts(sip);
5789 /* For 2007+ it does not make sence to subscribe to:
5790 * vnd-microsoft-roaming-ACL
5791 * vnd-microsoft-provisioning (not v2)
5792 * presence.wpending
5793 * These are for backward compatibility.
5795 if (sip->ocs2007)
5797 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5798 (GCompareFunc)g_ascii_strcasecmp)) {
5799 sipe_subscribe_roaming_self(sip);
5801 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5802 (GCompareFunc)g_ascii_strcasecmp)) {
5803 sipe_subscribe_roaming_provisioning_v2(sip);
5806 /* For 2005- servers */
5807 else
5809 //sipe_options_request(sip, sip->sipdomain);
5811 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5812 (GCompareFunc)g_ascii_strcasecmp)) {
5813 sipe_subscribe_roaming_acl(sip);
5815 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5816 (GCompareFunc)g_ascii_strcasecmp)) {
5817 sipe_subscribe_roaming_provisioning(sip);
5819 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5820 (GCompareFunc)g_ascii_strcasecmp)) {
5821 sipe_subscribe_presence_wpending(sip, msg);
5824 /* For 2007+ we publish our initial statuses and calendar data only after
5825 * received our existing publications in sipe_process_roaming_self()
5826 * Only in this case we know versions of current publications made
5827 * on our behalf.
5829 /* For 2005- we publish our initial statuses only after
5830 * received our existing UserInfo data in response to
5831 * self subscription.
5832 * Only in this case we won't override existing UserInfo data
5833 * set earlier or by other client on our behalf.
5837 sip->subscribed = TRUE;
5840 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5841 "timeout=", ";", NULL);
5842 if (timeout != NULL) {
5843 sscanf(timeout, "%u", &sip->keepalive_timeout);
5844 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5845 sip->keepalive_timeout);
5846 g_free(timeout);
5849 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5851 break;
5852 case 301:
5854 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5856 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5857 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5858 gchar **tmp;
5859 gchar *hostname;
5860 int port = 0;
5861 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5862 int i = 1;
5864 tmp = g_strsplit(parts[0], ":", 0);
5865 hostname = g_strdup(tmp[0]);
5866 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5867 g_strfreev(tmp);
5869 while (parts[i]) {
5870 tmp = g_strsplit(parts[i], "=", 0);
5871 if (tmp[1]) {
5872 if (g_strcasecmp("transport", tmp[0]) == 0) {
5873 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5874 transport = SIPE_TRANSPORT_TCP;
5875 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5876 transport = SIPE_TRANSPORT_UDP;
5880 g_strfreev(tmp);
5881 i++;
5883 g_strfreev(parts);
5885 /* Close old connection */
5886 sipe_connection_cleanup(sip);
5888 /* Create new connection */
5889 sip->transport = transport;
5890 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5891 hostname, port, TRANSPORT_DESCRIPTOR);
5892 create_connection(sip, hostname, port);
5894 g_free(redirect);
5896 break;
5897 case 401:
5898 if (sip->registerstatus != 2) {
5899 const char *auth_scheme;
5900 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5901 if (sip->registrar.retries > 3) {
5902 sip->gc->wants_to_die = TRUE;
5903 purple_connection_error(sip->gc, _("Authentication failed"));
5904 return TRUE;
5907 auth_scheme = sipe_get_auth_scheme_name(sip);
5908 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5910 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
5911 if (!tmp) {
5912 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5913 sip->gc->wants_to_die = TRUE;
5914 purple_connection_error(sip->gc, tmp2);
5915 g_free(tmp2);
5916 return TRUE;
5918 fill_auth(tmp, &sip->registrar);
5919 sip->registerstatus = 2;
5920 if (sip->account->disconnecting) {
5921 do_register_exp(sip, 0);
5922 } else {
5923 do_register(sip);
5926 break;
5927 case 403:
5929 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5930 gchar **reason = NULL;
5931 gchar *warning;
5932 if (diagnostics != NULL) {
5933 /* Example header:
5934 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5936 reason = g_strsplit(diagnostics, "\"", 0);
5938 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5939 (reason && reason[1]) ? reason[1] : _("no reason given"));
5940 g_strfreev(reason);
5942 sip->gc->wants_to_die = TRUE;
5943 purple_connection_error(sip->gc, warning);
5944 g_free(warning);
5945 return TRUE;
5947 break;
5948 case 404:
5950 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5951 gchar *reason = NULL;
5952 gchar *warning;
5953 if (diagnostics != NULL) {
5954 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5956 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5957 diagnostics ? (reason ? reason : _("no reason given")) :
5958 _("SIP is either not enabled for the destination URI or it does not exist"));
5959 g_free(reason);
5961 sip->gc->wants_to_die = TRUE;
5962 purple_connection_error(sip->gc, warning);
5963 g_free(warning);
5964 return TRUE;
5966 break;
5967 case 503:
5968 case 504: /* Server time-out */
5970 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5971 gchar *reason = NULL;
5972 gchar *warning;
5973 if (diagnostics != NULL) {
5974 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5976 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5977 g_free(reason);
5979 sip->gc->wants_to_die = TRUE;
5980 purple_connection_error(sip->gc, warning);
5981 g_free(warning);
5982 return TRUE;
5984 break;
5986 return TRUE;
5990 * Returns 2005-style activity and Availability.
5992 * @param status Sipe statis id.
5994 static void
5995 sipe_get_act_avail_by_status_2005(const char *status,
5996 int *activity,
5997 int *availability)
5999 int avail = 300; /* online */
6000 int act = 400; /* Available */
6002 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
6003 act = 100;
6004 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
6005 // act = 150;
6006 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
6007 act = 300;
6008 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
6009 act = 400;
6010 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
6011 // act = 500;
6012 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
6013 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
6014 act = 600;
6015 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
6016 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
6017 avail = 0; /* offline */
6018 act = 100;
6019 } else {
6020 act = 400; /* Available */
6023 if (activity) *activity = act;
6024 if (availability) *availability = avail;
6028 * [MS-SIP] 2.2.1
6030 * @param activity 2005 aggregated activity. Ex.: 600
6031 * @param availablity 2005 aggregated availablity. Ex.: 300
6033 static const char *
6034 sipe_get_status_by_act_avail_2005(const int activity,
6035 const int availablity,
6036 char **activity_desc)
6038 const char *status_id = NULL;
6039 const char *act = NULL;
6041 if (activity < 150) {
6042 status_id = SIPE_STATUS_ID_AWAY;
6043 } else if (activity < 200) {
6044 //status_id = SIPE_STATUS_ID_LUNCH;
6045 status_id = SIPE_STATUS_ID_AWAY;
6046 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
6047 } else if (activity < 300) {
6048 //status_id = SIPE_STATUS_ID_IDLE;
6049 status_id = SIPE_STATUS_ID_AWAY;
6050 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6051 } else if (activity < 400) {
6052 status_id = SIPE_STATUS_ID_BRB;
6053 } else if (activity < 500) {
6054 status_id = SIPE_STATUS_ID_AVAILABLE;
6055 } else if (activity < 600) {
6056 //status_id = SIPE_STATUS_ID_ON_PHONE;
6057 status_id = SIPE_STATUS_ID_BUSY;
6058 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
6059 } else if (activity < 700) {
6060 status_id = SIPE_STATUS_ID_BUSY;
6061 } else if (activity < 800) {
6062 status_id = SIPE_STATUS_ID_AWAY;
6063 } else {
6064 status_id = SIPE_STATUS_ID_AVAILABLE;
6067 if (availablity < 100)
6068 status_id = SIPE_STATUS_ID_OFFLINE;
6070 if (activity_desc && act) {
6071 g_free(*activity_desc);
6072 *activity_desc = g_strdup(act);
6075 return status_id;
6079 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
6081 static const char*
6082 sipe_get_status_by_availability(int avail,
6083 char** activity_desc)
6085 const char *status;
6086 const char *act = NULL;
6088 if (avail < 3000) {
6089 status = SIPE_STATUS_ID_OFFLINE;
6090 } else if (avail < 4500) {
6091 status = SIPE_STATUS_ID_AVAILABLE;
6092 } else if (avail < 6000) {
6093 //status = SIPE_STATUS_ID_IDLE;
6094 status = SIPE_STATUS_ID_AVAILABLE;
6095 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6096 } else if (avail < 7500) {
6097 status = SIPE_STATUS_ID_BUSY;
6098 } else if (avail < 9000) {
6099 //status = SIPE_STATUS_ID_BUSYIDLE;
6100 status = SIPE_STATUS_ID_BUSY;
6101 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
6102 } else if (avail < 12000) {
6103 status = SIPE_STATUS_ID_DND;
6104 } else if (avail < 15000) {
6105 status = SIPE_STATUS_ID_BRB;
6106 } else if (avail < 18000) {
6107 status = SIPE_STATUS_ID_AWAY;
6108 } else {
6109 status = SIPE_STATUS_ID_OFFLINE;
6112 if (activity_desc && act) {
6113 g_free(*activity_desc);
6114 *activity_desc = g_strdup(act);
6117 return status;
6121 * Returns 2007-style availability value
6123 * @param sipe_status_id (in)
6124 * @param activity_token (out) Must be g_free()'d after use if consumed.
6126 static int
6127 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
6129 int availability;
6130 sipe_activity activity = SIPE_ACTIVITY_UNSET;
6132 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
6133 availability = 15500;
6134 if (!activity_token || !(*activity_token)) {
6135 activity = SIPE_ACTIVITY_AWAY;
6137 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
6138 availability = 12500;
6139 activity = SIPE_ACTIVITY_BRB;
6140 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
6141 availability = 9500;
6142 activity = SIPE_ACTIVITY_DND;
6143 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
6144 availability = 6500;
6145 if (!activity_token || !(*activity_token)) {
6146 activity = SIPE_ACTIVITY_BUSY;
6148 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
6149 availability = 3500;
6150 activity = SIPE_ACTIVITY_ONLINE;
6151 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
6152 availability = 0;
6153 } else {
6154 // Offline or invisible
6155 availability = 18500;
6156 activity = SIPE_ACTIVITY_OFFLINE;
6159 if (activity_token) {
6160 *activity_token = g_strdup(sipe_activity_map[activity].token);
6162 return availability;
6165 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
6167 const char *uri;
6168 sipe_xml *xn_categories;
6169 const sipe_xml *xn_category;
6170 const char *status = NULL;
6171 gboolean do_update_status = FALSE;
6172 gboolean has_note_cleaned = FALSE;
6173 gboolean has_free_busy_cleaned = FALSE;
6175 xn_categories = sipe_xml_parse(data, len);
6176 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
6178 for (xn_category = sipe_xml_child(xn_categories, "category");
6179 xn_category ;
6180 xn_category = sipe_xml_twin(xn_category) )
6182 const sipe_xml *xn_node;
6183 const char *tmp;
6184 const char *attrVar = sipe_xml_attribute(xn_category, "name");
6185 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
6186 sipe_utils_str_to_time(tmp) : 0;
6188 /* contactCard */
6189 if (sipe_strequal(attrVar, "contactCard"))
6191 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
6193 if (card) {
6194 const sipe_xml *node;
6195 /* identity - Display Name and email */
6196 node = sipe_xml_child(card, "identity");
6197 if (node) {
6198 char* display_name = sipe_xml_data(
6199 sipe_xml_child(node, "name/displayName"));
6200 char* email = sipe_xml_data(
6201 sipe_xml_child(node, "email"));
6203 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6204 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6206 g_free(display_name);
6207 g_free(email);
6209 /* company */
6210 node = sipe_xml_child(card, "company");
6211 if (node) {
6212 char* company = sipe_xml_data(node);
6213 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
6214 g_free(company);
6216 /* department */
6217 node = sipe_xml_child(card, "department");
6218 if (node) {
6219 char* department = sipe_xml_data(node);
6220 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
6221 g_free(department);
6223 /* title */
6224 node = sipe_xml_child(card, "title");
6225 if (node) {
6226 char* title = sipe_xml_data(node);
6227 sipe_update_user_info(sip, uri, TITLE_PROP, title);
6228 g_free(title);
6230 /* office */
6231 node = sipe_xml_child(card, "office");
6232 if (node) {
6233 char* office = sipe_xml_data(node);
6234 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
6235 g_free(office);
6237 /* site (url) */
6238 node = sipe_xml_child(card, "url");
6239 if (node) {
6240 char* site = sipe_xml_data(node);
6241 sipe_update_user_info(sip, uri, SITE_PROP, site);
6242 g_free(site);
6244 /* phone */
6245 for (node = sipe_xml_child(card, "phone");
6246 node;
6247 node = sipe_xml_twin(node))
6249 const char *phone_type = sipe_xml_attribute(node, "type");
6250 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
6251 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
6253 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
6255 g_free(phone);
6256 g_free(phone_display_string);
6258 /* address */
6259 for (node = sipe_xml_child(card, "address");
6260 node;
6261 node = sipe_xml_twin(node))
6263 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
6264 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
6265 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
6266 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
6267 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
6268 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
6270 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
6271 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
6272 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
6273 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
6274 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
6276 g_free(street);
6277 g_free(city);
6278 g_free(state);
6279 g_free(zipcode);
6280 g_free(country_code);
6282 break;
6287 /* note */
6288 else if (sipe_strequal(attrVar, "note"))
6290 if (uri) {
6291 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6293 if (!has_note_cleaned) {
6294 has_note_cleaned = TRUE;
6296 g_free(sbuddy->note);
6297 sbuddy->note = NULL;
6298 sbuddy->is_oof_note = FALSE;
6299 sbuddy->note_since = publish_time;
6301 do_update_status = TRUE;
6303 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6304 /* clean up in case no 'note' element is supplied
6305 * which indicate note removal in client
6307 g_free(sbuddy->note);
6308 sbuddy->note = NULL;
6309 sbuddy->is_oof_note = FALSE;
6310 sbuddy->note_since = publish_time;
6312 xn_node = sipe_xml_child(xn_category, "note/body");
6313 if (xn_node) {
6314 char *tmp;
6315 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6316 g_free(tmp);
6317 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6318 sbuddy->note_since = publish_time;
6320 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6321 uri, sbuddy->note ? sbuddy->note : "");
6323 /* to trigger UI refresh in case no status info is supplied in this update */
6324 do_update_status = TRUE;
6328 /* state */
6329 else if(sipe_strequal(attrVar, "state"))
6331 char *tmp;
6332 int availability;
6333 const sipe_xml *xn_availability;
6334 const sipe_xml *xn_activity;
6335 const sipe_xml *xn_meeting_subject;
6336 const sipe_xml *xn_meeting_location;
6337 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6339 xn_node = sipe_xml_child(xn_category, "state");
6340 if (!xn_node) continue;
6341 xn_availability = sipe_xml_child(xn_node, "availability");
6342 if (!xn_availability) continue;
6343 xn_activity = sipe_xml_child(xn_node, "activity");
6344 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6345 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6347 tmp = sipe_xml_data(xn_availability);
6348 availability = atoi(tmp);
6349 g_free(tmp);
6351 /* activity, meeting_subject, meeting_location */
6352 if (sbuddy) {
6353 char *tmp = NULL;
6355 /* activity */
6356 g_free(sbuddy->activity);
6357 sbuddy->activity = NULL;
6358 if (xn_activity) {
6359 const char *token = sipe_xml_attribute(xn_activity, "token");
6360 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6362 /* from token */
6363 if (!is_empty(token)) {
6364 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6366 /* from custom element */
6367 if (xn_custom) {
6368 char *custom = sipe_xml_data(xn_custom);
6370 if (!is_empty(custom)) {
6371 sbuddy->activity = custom;
6372 custom = NULL;
6374 g_free(custom);
6377 /* meeting_subject */
6378 g_free(sbuddy->meeting_subject);
6379 sbuddy->meeting_subject = NULL;
6380 if (xn_meeting_subject) {
6381 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6383 if (!is_empty(meeting_subject)) {
6384 sbuddy->meeting_subject = meeting_subject;
6385 meeting_subject = NULL;
6387 g_free(meeting_subject);
6389 /* meeting_location */
6390 g_free(sbuddy->meeting_location);
6391 sbuddy->meeting_location = NULL;
6392 if (xn_meeting_location) {
6393 char *meeting_location = sipe_xml_data(xn_meeting_location);
6395 if (!is_empty(meeting_location)) {
6396 sbuddy->meeting_location = meeting_location;
6397 meeting_location = NULL;
6399 g_free(meeting_location);
6402 status = sipe_get_status_by_availability(availability, &tmp);
6403 if (sbuddy->activity && tmp) {
6404 char *tmp2 = sbuddy->activity;
6406 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6407 g_free(tmp);
6408 g_free(tmp2);
6409 } else if (tmp) {
6410 sbuddy->activity = tmp;
6414 do_update_status = TRUE;
6416 /* calendarData */
6417 else if(sipe_strequal(attrVar, "calendarData"))
6419 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6420 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6421 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6423 if (sbuddy && xn_free_busy) {
6424 if (!has_free_busy_cleaned) {
6425 has_free_busy_cleaned = TRUE;
6427 g_free(sbuddy->cal_start_time);
6428 sbuddy->cal_start_time = NULL;
6430 g_free(sbuddy->cal_free_busy_base64);
6431 sbuddy->cal_free_busy_base64 = NULL;
6433 g_free(sbuddy->cal_free_busy);
6434 sbuddy->cal_free_busy = NULL;
6436 sbuddy->cal_free_busy_published = publish_time;
6439 if (publish_time >= sbuddy->cal_free_busy_published) {
6440 g_free(sbuddy->cal_start_time);
6441 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6443 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6444 15 : 0;
6446 g_free(sbuddy->cal_free_busy_base64);
6447 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6449 g_free(sbuddy->cal_free_busy);
6450 sbuddy->cal_free_busy = NULL;
6452 sbuddy->cal_free_busy_published = publish_time;
6454 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);
6458 if (sbuddy && xn_working_hours) {
6459 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6464 if (do_update_status) {
6465 if (!status) { /* no status category in this update, using contact's current status */
6466 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6467 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6468 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6469 status = purple_status_get_id(pstatus);
6472 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
6473 sipe_got_user_status(sip, uri, status);
6476 sipe_xml_free(xn_categories);
6479 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6481 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6482 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
6483 payload->host = g_strdup(host);
6484 payload->buddies = server;
6485 sipe_subscribe_presence_batched_routed(sip, payload);
6486 sipe_subscribe_presence_batched_routed_free(payload);
6489 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6491 sipe_xml *xn_list;
6492 const sipe_xml *xn_resource;
6493 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6494 g_free, NULL);
6495 GSList *server;
6496 gchar *host;
6498 xn_list = sipe_xml_parse(data, len);
6500 for (xn_resource = sipe_xml_child(xn_list, "resource");
6501 xn_resource;
6502 xn_resource = sipe_xml_twin(xn_resource) )
6504 const char *uri, *state;
6505 const sipe_xml *xn_instance;
6507 xn_instance = sipe_xml_child(xn_resource, "instance");
6508 if (!xn_instance) continue;
6510 uri = sipe_xml_attribute(xn_resource, "uri");
6511 state = sipe_xml_attribute(xn_instance, "state");
6512 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
6514 if (strstr(state, "resubscribe")) {
6515 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
6517 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6518 gchar *user = g_strdup(uri);
6519 host = g_strdup(poolFqdn);
6520 server = g_hash_table_lookup(servers, host);
6521 server = g_slist_append(server, user);
6522 g_hash_table_insert(servers, host, server);
6523 } else {
6524 sipe_subscribe_presence_single(sip, (void *) uri);
6529 /* Send out any deferred poolFqdn subscriptions */
6530 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6531 g_hash_table_destroy(servers);
6533 sipe_xml_free(xn_list);
6536 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6538 gchar *uri;
6539 gchar *getbasic;
6540 gchar *activity = NULL;
6541 sipe_xml *pidf;
6542 const sipe_xml *basicstatus = NULL, *tuple, *status;
6543 gboolean isonline = FALSE;
6544 const sipe_xml *display_name_node;
6546 pidf = sipe_xml_parse(data, len);
6547 if (!pidf) {
6548 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
6549 return;
6552 if ((tuple = sipe_xml_child(pidf, "tuple")))
6554 if ((status = sipe_xml_child(tuple, "status"))) {
6555 basicstatus = sipe_xml_child(status, "basic");
6559 if (!basicstatus) {
6560 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6561 sipe_xml_free(pidf);
6562 return;
6565 getbasic = sipe_xml_data(basicstatus);
6566 if (!getbasic) {
6567 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6568 sipe_xml_free(pidf);
6569 return;
6572 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
6573 if (strstr(getbasic, "open")) {
6574 isonline = TRUE;
6576 g_free(getbasic);
6578 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6580 display_name_node = sipe_xml_child(pidf, "display-name");
6581 if (display_name_node) {
6582 char * display_name = sipe_xml_data(display_name_node);
6584 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6585 g_free(display_name);
6588 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
6589 if ((status = sipe_xml_child(tuple, "status"))) {
6590 if ((basicstatus = sipe_xml_child(status, "activities"))) {
6591 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
6592 activity = sipe_xml_data(basicstatus);
6593 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
6599 if (isonline) {
6600 const gchar * status_id = NULL;
6601 if (activity) {
6602 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6603 status_id = SIPE_STATUS_ID_BUSY;
6604 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6605 status_id = SIPE_STATUS_ID_AWAY;
6609 if (!status_id) {
6610 status_id = SIPE_STATUS_ID_AVAILABLE;
6613 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
6614 sipe_got_user_status(sip, uri, status_id);
6615 } else {
6616 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6619 g_free(activity);
6620 g_free(uri);
6621 sipe_xml_free(pidf);
6624 /** 2005 */
6625 static void
6626 sipe_user_info_has_updated(struct sipe_account_data *sip,
6627 const sipe_xml *xn_userinfo)
6629 const sipe_xml *xn_states;
6631 g_free(sip->user_states);
6632 sip->user_states = NULL;
6633 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
6634 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
6636 /* this is a hack-around to remove added newline after inner element,
6637 * state in this case, where it shouldn't be.
6638 * After several use of sipe_xml_stringify, amount of added newlines
6639 * grows significantly.
6641 if (orig) {
6642 gchar c, *stripped = orig;
6643 while ((c = *orig++)) {
6644 if ((c != '\n') /* && (c != '\r') */) {
6645 *stripped++ = c;
6648 *stripped = '\0';
6652 /* Publish initial state if not yet.
6653 * Assuming this happens on initial responce to self subscription
6654 * so we've already updated our UserInfo.
6656 if (!sip->initial_state_published) {
6657 send_presence_soap(sip, FALSE);
6658 /* dalayed run */
6659 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6663 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6665 char *activity = NULL;
6666 const char *epid;
6667 const char *status_id = NULL;
6668 const char *name;
6669 char *uri;
6670 char *self_uri = sip_uri_self(sip);
6671 int avl;
6672 int act;
6673 const char *device_name = NULL;
6674 const char *cal_start_time = NULL;
6675 const char *cal_granularity = NULL;
6676 char *cal_free_busy_base64 = NULL;
6677 struct sipe_buddy *sbuddy;
6678 const sipe_xml *node;
6679 sipe_xml *xn_presentity;
6680 const sipe_xml *xn_availability;
6681 const sipe_xml *xn_activity;
6682 const sipe_xml *xn_display_name;
6683 const sipe_xml *xn_email;
6684 const sipe_xml *xn_phone_number;
6685 const sipe_xml *xn_userinfo;
6686 const sipe_xml *xn_note;
6687 const sipe_xml *xn_oof;
6688 const sipe_xml *xn_state;
6689 const sipe_xml *xn_contact;
6690 char *note;
6691 char *free_activity;
6692 int user_avail;
6693 const char *user_avail_nil;
6694 int res_avail;
6695 time_t user_avail_since = 0;
6696 time_t activity_since = 0;
6698 /* fix for Reuters environment on Linux */
6699 if (data && strstr(data, "encoding=\"utf-16\"")) {
6700 char *tmp_data;
6701 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6702 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
6703 g_free(tmp_data);
6704 } else {
6705 xn_presentity = sipe_xml_parse(data, len);
6708 xn_availability = sipe_xml_child(xn_presentity, "availability");
6709 xn_activity = sipe_xml_child(xn_presentity, "activity");
6710 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
6711 xn_email = sipe_xml_child(xn_presentity, "email");
6712 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
6713 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
6714 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
6715 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
6716 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
6717 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
6718 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
6719 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
6720 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
6721 note = xn_note ? sipe_xml_data(xn_note) : NULL;
6723 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6724 user_avail = 0;
6725 user_avail_since = 0;
6728 free_activity = NULL;
6730 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
6731 uri = sip_uri_from_name(name);
6732 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
6733 epid = sipe_xml_attribute(xn_availability, "epid");
6734 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
6736 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6737 res_avail = sipe_get_availability_by_status(status_id, NULL);
6738 if (user_avail > res_avail) {
6739 res_avail = user_avail;
6740 status_id = sipe_get_status_by_availability(user_avail, NULL);
6743 if (xn_display_name) {
6744 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
6745 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
6746 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
6747 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
6748 char *tel_uri = sip_to_tel_uri(phone_number);
6750 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6751 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6752 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6753 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6755 g_free(tel_uri);
6756 g_free(phone_label);
6757 g_free(phone_number);
6758 g_free(email);
6759 g_free(display_name);
6762 if (xn_contact) {
6763 /* tel */
6764 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
6766 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6767 const char *phone_type = sipe_xml_attribute(node, "type");
6768 char* phone = sipe_xml_data(node);
6770 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6772 g_free(phone);
6776 /* devicePresence */
6777 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
6778 const sipe_xml *xn_device_name;
6779 const sipe_xml *xn_calendar_info;
6780 const sipe_xml *xn_state;
6781 char *state;
6783 /* deviceName */
6784 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
6785 xn_device_name = sipe_xml_child(node, "deviceName");
6786 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
6789 /* calendarInfo */
6790 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
6791 if (xn_calendar_info) {
6792 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
6794 if (cal_start_time) {
6795 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6796 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6798 if (cal_start_time_t_tmp > cal_start_time_t) {
6799 cal_start_time = cal_start_time_tmp;
6800 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6801 g_free(cal_free_busy_base64);
6802 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6804 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);
6806 } else {
6807 cal_start_time = cal_start_time_tmp;
6808 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6809 g_free(cal_free_busy_base64);
6810 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6812 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);
6816 /* state */
6817 xn_state = sipe_xml_child(node, "states/state");
6818 if (xn_state) {
6819 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
6820 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
6822 state = sipe_xml_data(xn_state);
6823 if (dev_avail_since > user_avail_since &&
6824 dev_avail >= res_avail)
6826 res_avail = dev_avail;
6827 if (!is_empty(state))
6829 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6830 g_free(activity);
6831 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6832 } else if (sipe_strequal(state, "presenting")) {
6833 g_free(activity);
6834 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6835 } else {
6836 activity = state;
6837 state = NULL;
6839 activity_since = dev_avail_since;
6841 status_id = sipe_get_status_by_availability(res_avail, &activity);
6843 g_free(state);
6847 /* oof */
6848 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6849 g_free(activity);
6850 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6851 activity_since = 0;
6854 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6855 if (sbuddy)
6857 g_free(sbuddy->activity);
6858 sbuddy->activity = activity;
6859 activity = NULL;
6861 sbuddy->activity_since = activity_since;
6863 sbuddy->user_avail = user_avail;
6864 sbuddy->user_avail_since = user_avail_since;
6866 g_free(sbuddy->note);
6867 sbuddy->note = NULL;
6868 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6870 sbuddy->is_oof_note = (xn_oof != NULL);
6872 g_free(sbuddy->device_name);
6873 sbuddy->device_name = NULL;
6874 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6876 if (!is_empty(cal_free_busy_base64)) {
6877 g_free(sbuddy->cal_start_time);
6878 sbuddy->cal_start_time = g_strdup(cal_start_time);
6880 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6882 g_free(sbuddy->cal_free_busy_base64);
6883 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6884 cal_free_busy_base64 = NULL;
6886 g_free(sbuddy->cal_free_busy);
6887 sbuddy->cal_free_busy = NULL;
6890 sbuddy->last_non_cal_status_id = status_id;
6891 g_free(sbuddy->last_non_cal_activity);
6892 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6894 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6895 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6897 sip->is_oof_note = sbuddy->is_oof_note;
6899 g_free(sip->note);
6900 sip->note = g_strdup(sbuddy->note);
6902 sip->note_since = time(NULL);
6905 g_free(sip->status);
6906 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6909 g_free(cal_free_busy_base64);
6910 g_free(activity);
6912 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
6913 sipe_got_user_status(sip, uri, status_id);
6915 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6916 sipe_user_info_has_updated(sip, xn_userinfo);
6919 g_free(note);
6920 sipe_xml_free(xn_presentity);
6921 g_free(uri);
6922 g_free(self_uri);
6925 static void sipe_presence_mime_cb(gpointer user_data,
6926 const gchar *type,
6927 const gchar *body,
6928 gsize length)
6930 if (strstr(type,"application/rlmi+xml")) {
6931 process_incoming_notify_rlmi_resub(user_data, body, length);
6932 } else if (strstr(type, "text/xml+msrtc.pidf")) {
6933 process_incoming_notify_msrtc(user_data, body, length);
6934 } else {
6935 process_incoming_notify_rlmi(user_data, body, length);
6939 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6941 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6943 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
6945 if (ctype &&
6946 (strstr(ctype, "application/rlmi+xml") ||
6947 strstr(ctype, "application/msrtc-event-categories+xml")))
6949 if (strstr(ctype, "multipart"))
6951 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sip);
6953 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6955 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6957 else if(strstr(ctype, "application/rlmi+xml"))
6959 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6962 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6964 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6966 else
6968 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6972 static void sipe_presence_timeout_mime_cb(gpointer user_data,
6973 SIPE_UNUSED_PARAMETER const gchar *type,
6974 const gchar *body,
6975 gsize length)
6977 GSList **buddies = user_data;
6978 sipe_xml *xml = sipe_xml_parse(body, length);
6980 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
6981 const gchar *uri = sipe_xml_attribute(xml, "uri");
6982 const sipe_xml *xn_category;
6985 * automaton: presence is never expected to change
6987 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
6989 for (xn_category = sipe_xml_child(xml, "category");
6990 xn_category;
6991 xn_category = sipe_xml_twin(xn_category)) {
6992 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
6993 "contactCard")) {
6994 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
6995 if (node) {
6996 char *boolean = sipe_xml_data(node);
6997 if (sipe_strequal(boolean, "true")) {
6998 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
6999 uri);
7000 uri = NULL;
7002 g_free(boolean);
7004 break;
7008 if (uri) {
7009 *buddies = g_slist_append(*buddies, sip_uri(uri));
7013 sipe_xml_free(xml);
7016 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
7018 const char *ctype = sipmsg_find_header(msg, "Content-Type");
7019 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7021 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
7023 if (ctype &&
7024 strstr(ctype, "multipart") &&
7025 (strstr(ctype, "application/rlmi+xml") ||
7026 strstr(ctype, "application/msrtc-event-categories+xml"))) {
7027 GSList *buddies = NULL;
7029 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
7031 if (buddies) {
7032 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
7033 payload->host = g_strdup(who);
7034 payload->buddies = buddies;
7035 sipe_schedule_action(action_name, timeout,
7036 sipe_subscribe_presence_batched_routed,
7037 sipe_subscribe_presence_batched_routed_free,
7038 sip, payload);
7039 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
7042 } else {
7043 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7044 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
7046 g_free(action_name);
7050 * Dispatcher for all incoming subscription information
7051 * whether it comes from NOTIFY, BENOTIFY requests or
7052 * piggy-backed to subscription's OK responce.
7054 * @param request whether initiated from BE/NOTIFY request or OK-response message.
7055 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
7057 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
7059 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
7060 const gchar *event = sipmsg_find_header(msg, "Event");
7061 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
7062 char *tmp;
7064 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
7065 event ? event : "",
7066 tmp = fix_newlines(msg->body));
7067 g_free(tmp);
7068 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
7070 /* implicit subscriptions */
7071 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
7072 sipe_process_imdn(sip, msg);
7075 if (event) {
7076 /* for one off subscriptions (send with Expire: 0) */
7077 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
7079 sipe_process_provisioning_v2(sip, msg);
7081 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
7083 sipe_process_provisioning(sip, msg);
7085 else if (sipe_strcase_equal(event, "presence"))
7087 sipe_process_presence(sip, msg);
7089 else if (sipe_strcase_equal(event, "registration-notify"))
7091 sipe_process_registration_notify(sip, msg);
7094 if (!subscription_state || strstr(subscription_state, "active"))
7096 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
7098 sipe_process_roaming_contacts(sip, msg);
7100 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
7102 sipe_process_roaming_self(sip, msg);
7104 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
7106 sipe_process_roaming_acl(sip, msg);
7108 else if (sipe_strcase_equal(event, "presence.wpending"))
7110 sipe_process_presence_wpending(sip, msg);
7112 else if (sipe_strcase_equal(event, "conference"))
7114 sipe_process_conference(sip, msg);
7119 /* The server sends status 'terminated' */
7120 if (subscription_state && strstr(subscription_state, "terminated") ) {
7121 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
7122 gchar *key = sipe_get_subscription_key(event, who);
7124 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
7125 g_free(who);
7127 if (g_hash_table_lookup(sip->subscriptions, key)) {
7128 g_hash_table_remove(sip->subscriptions, key);
7129 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
7132 g_free(key);
7135 if (!request && event) {
7136 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
7137 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
7138 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
7140 if (timeout) {
7141 /* 2 min ahead of expiration */
7142 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
7144 if (sipe_strcase_equal(event, "presence.wpending") &&
7145 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
7147 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
7148 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
7149 g_free(action_name);
7151 else if (sipe_strcase_equal(event, "presence") &&
7152 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
7154 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
7155 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7157 if (sip->batched_support) {
7158 sipe_process_presence_timeout(sip, msg, who, timeout);
7160 else {
7161 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7162 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
7164 g_free(action_name);
7165 g_free(who);
7170 /* The client responses on received a NOTIFY message */
7171 if (request && !benotify)
7173 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7178 * Whether user manually changed status or
7179 * it was changed automatically due to user
7180 * became inactive/active again
7182 static gboolean
7183 sipe_is_user_state(struct sipe_account_data *sip)
7185 gboolean res;
7186 time_t now = time(NULL);
7188 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
7189 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
7191 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
7193 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
7194 return res;
7197 static void
7198 send_presence_soap0(struct sipe_account_data *sip,
7199 gboolean do_publish_calendar,
7200 gboolean do_reset_status)
7202 struct sipe_ews* ews = sip->ews;
7203 int availability = 0;
7204 int activity = 0;
7205 gchar *body;
7206 gchar *tmp;
7207 gchar *tmp2 = NULL;
7208 gchar *res_note = NULL;
7209 gchar *res_oof = NULL;
7210 const gchar *note_pub = NULL;
7211 gchar *states = NULL;
7212 gchar *calendar_data = NULL;
7213 gchar *epid = get_epid(sip);
7214 time_t now = time(NULL);
7215 gchar *since_time_str = sipe_utils_time_to_str(now);
7216 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
7217 const char *user_input;
7218 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
7220 if (oof_note && sip->note) {
7221 SIPE_DEBUG_INFO("ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
7222 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
7225 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
7227 if (!sip->initial_state_published ||
7228 do_reset_status)
7230 g_free(sip->status);
7231 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
7234 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
7236 /* Note */
7237 if (pub_oof) {
7238 note_pub = oof_note;
7239 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
7240 ews->published = TRUE;
7241 } else if (sip->note) {
7242 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
7243 g_free(sip->note);
7244 sip->note = NULL;
7245 sip->is_oof_note = FALSE;
7246 sip->note_since = 0;
7247 } else {
7248 note_pub = sip->note;
7249 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
7253 if (note_pub)
7255 /* to protocol internal plain text format */
7256 tmp = sipe_backend_markup_strip_html(note_pub);
7257 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
7258 g_free(tmp);
7261 /* User State */
7262 if (!do_reset_status) {
7263 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
7265 gchar *activity_token = NULL;
7266 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
7268 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
7269 avail_2007,
7270 since_time_str,
7271 epid,
7272 activity_token);
7273 g_free(activity_token);
7275 else /* preserve existing publication */
7277 if (sip->user_states) {
7278 states = g_strdup(sip->user_states);
7281 } else {
7282 /* do nothing - then User state will be erased */
7284 sip->initial_state_published = TRUE;
7286 /* CalendarInfo */
7287 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
7289 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7290 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7291 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
7292 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
7293 fb_start_str,
7294 free_busy_base64);
7295 g_free(fb_start_str);
7296 g_free(free_busy_base64);
7299 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
7301 /* forming resulting XML */
7302 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7303 sip->username,
7304 availability,
7305 activity,
7306 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7307 res_note ? res_note : "",
7308 res_oof ? res_oof : "",
7309 states ? states : "",
7310 calendar_data ? calendar_data : "",
7311 epid,
7312 since_time_str,
7313 since_time_str,
7314 user_input);
7315 g_free(tmp);
7316 g_free(tmp2);
7317 g_free(res_note);
7318 g_free(states);
7319 g_free(calendar_data);
7321 send_soap_request(sip, body);
7323 g_free(body);
7324 g_free(since_time_str);
7325 g_free(epid);
7328 void
7329 send_presence_soap(struct sipe_account_data *sip,
7330 gboolean do_publish_calendar)
7332 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7336 static gboolean
7337 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7338 struct sipmsg *msg,
7339 struct transaction *trans)
7341 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7343 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7344 sipe_xml *xml;
7345 const sipe_xml *node;
7346 gchar *fault_code;
7347 GHashTable *faults;
7348 int index_our;
7349 gboolean has_device_publication = FALSE;
7351 xml = sipe_xml_parse(msg->body, msg->bodylen);
7353 /* test if version mismatch fault */
7354 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
7355 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7356 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
7357 g_free(fault_code);
7358 sipe_xml_free(xml);
7359 return TRUE;
7361 g_free(fault_code);
7363 /* accumulating information about faulty versions */
7364 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7365 for (node = sipe_xml_child(xml, "details/operation");
7366 node;
7367 node = sipe_xml_twin(node))
7369 const gchar *index = sipe_xml_attribute(node, "index");
7370 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
7372 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7373 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
7375 sipe_xml_free(xml);
7377 /* here we are parsing own request to figure out what publication
7378 * referensed here only by index went wrong
7380 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
7382 /* publication */
7383 for (node = sipe_xml_child(xml, "publications/publication"),
7384 index_our = 1; /* starts with 1 - our first publication */
7385 node;
7386 node = sipe_xml_twin(node), index_our++)
7388 gchar *idx = g_strdup_printf("%d", index_our);
7389 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7390 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
7391 g_free(idx);
7393 if (sipe_strequal("device", categoryName)) {
7394 has_device_publication = TRUE;
7397 if (curVersion) { /* fault exist on this index */
7398 const gchar *container = sipe_xml_attribute(node, "container");
7399 const gchar *instance = sipe_xml_attribute(node, "instance");
7400 /* key is <category><instance><container> */
7401 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7402 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
7404 if (category) {
7405 struct sipe_publication *publication =
7406 g_hash_table_lookup(category, key);
7408 SIPE_DEBUG_INFO("key is %s", key);
7410 if (publication) {
7411 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7412 key, curVersion, publication->version);
7413 /* updating publication's version to the correct one */
7414 publication->version = atoi(curVersion);
7416 } else {
7417 /* We somehow lost this category from our publications... */
7418 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
7419 publication->category = g_strdup(categoryName);
7420 publication->instance = atoi(instance);
7421 publication->container = atoi(container);
7422 publication->version = atoi(curVersion);
7423 category = g_hash_table_new_full(g_str_hash, g_str_equal,
7424 g_free, (GDestroyNotify)free_publication);
7425 g_hash_table_insert(category, g_strdup(key), publication);
7426 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
7427 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
7429 g_free(key);
7432 sipe_xml_free(xml);
7433 g_hash_table_destroy(faults);
7435 /* rebublishing with right versions */
7436 if (has_device_publication) {
7437 send_publish_category_initial(sip);
7438 } else {
7439 send_presence_status(sip);
7442 return TRUE;
7446 * Returns 'device' XML part for publication.
7447 * Must be g_free'd after use.
7449 static gchar *
7450 sipe_publish_get_category_device(struct sipe_account_data *sip)
7452 gchar *uri;
7453 gchar *doc;
7454 gchar *epid = get_epid(sip);
7455 gchar *uuid = generateUUIDfromEPID(epid);
7456 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7457 /* key is <category><instance><container> */
7458 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7459 struct sipe_publication *publication =
7460 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7462 g_free(key);
7463 g_free(epid);
7465 uri = sip_uri_self(sip);
7466 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7467 device_instance,
7468 publication ? publication->version : 0,
7469 uuid,
7470 uri,
7471 "00:00:00+01:00", /* @TODO make timezone real*/
7472 g_get_host_name()
7475 g_free(uri);
7476 g_free(uuid);
7478 return doc;
7482 * A service method - use
7483 * - send_publish_get_category_state_machine and
7484 * - send_publish_get_category_state_user instead.
7485 * Must be g_free'd after use.
7487 static gchar *
7488 sipe_publish_get_category_state(struct sipe_account_data *sip,
7489 gboolean is_user_state)
7491 int availability = sipe_get_availability_by_status(sip->status, NULL);
7492 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7493 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7494 /* key is <category><instance><container> */
7495 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7496 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7497 struct sipe_publication *publication_2 =
7498 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7499 struct sipe_publication *publication_3 =
7500 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7502 g_free(key_2);
7503 g_free(key_3);
7505 if (publication_2 && (publication_2->availability == availability))
7507 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7508 return NULL; /* nothing to update */
7511 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7512 instance,
7513 publication_2 ? publication_2->version : 0,
7514 availability,
7515 instance,
7516 publication_3 ? publication_3->version : 0,
7517 availability);
7521 * Only Busy and OOF calendar event are published.
7522 * Different instances are used for that.
7524 * Must be g_free'd after use.
7526 static gchar *
7527 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7528 struct sipe_cal_event *event,
7529 const char *uri,
7530 int cal_satus)
7532 gchar *start_time_str;
7533 int availability = 0;
7534 gchar *res;
7535 gchar *tmp = NULL;
7536 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7537 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7538 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7540 /* key is <category><instance><container> */
7541 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7542 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7543 struct sipe_publication *publication_2 =
7544 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7545 struct sipe_publication *publication_3 =
7546 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7548 g_free(key_2);
7549 g_free(key_3);
7551 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7552 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7553 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
7554 return NULL;
7557 if (event &&
7558 publication_3 &&
7559 (publication_3->availability == availability) &&
7560 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7562 g_free(tmp);
7563 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7564 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
7565 return NULL; /* nothing to update */
7567 g_free(tmp);
7569 if (event &&
7570 (event->cal_status == SIPE_CAL_BUSY ||
7571 event->cal_status == SIPE_CAL_OOF))
7573 gchar *availability_xml_str = NULL;
7574 gchar *activity_xml_str = NULL;
7576 if (event->cal_status == SIPE_CAL_BUSY) {
7577 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7580 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7581 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7582 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7583 "minAvailability=\"6500\"",
7584 "maxAvailability=\"8999\"");
7585 } else if (event->cal_status == SIPE_CAL_OOF) {
7586 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7587 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7588 "minAvailability=\"12000\"",
7589 "");
7591 start_time_str = sipe_utils_time_to_str(event->start_time);
7593 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7594 instance,
7595 publication_2 ? publication_2->version : 0,
7596 uri,
7597 start_time_str,
7598 availability_xml_str ? availability_xml_str : "",
7599 activity_xml_str ? activity_xml_str : "",
7600 event->subject ? event->subject : "",
7601 event->location ? event->location : "",
7603 instance,
7604 publication_3 ? publication_3->version : 0,
7605 uri,
7606 start_time_str,
7607 availability_xml_str ? availability_xml_str : "",
7608 activity_xml_str ? activity_xml_str : "",
7609 event->subject ? event->subject : "",
7610 event->location ? event->location : ""
7612 g_free(start_time_str);
7613 g_free(availability_xml_str);
7614 g_free(activity_xml_str);
7617 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7619 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7620 instance,
7621 publication_2 ? publication_2->version : 0,
7623 instance,
7624 publication_3 ? publication_3->version : 0
7628 return res;
7632 * Returns 'machineState' XML part for publication.
7633 * Must be g_free'd after use.
7635 static gchar *
7636 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7638 return sipe_publish_get_category_state(sip, FALSE);
7642 * Returns 'userState' XML part for publication.
7643 * Must be g_free'd after use.
7645 static gchar *
7646 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7648 return sipe_publish_get_category_state(sip, TRUE);
7652 * Returns 'note' XML part for publication.
7653 * Must be g_free'd after use.
7655 * Protocol format for Note is plain text.
7657 * @param note a note in Sipe internal HTML format
7658 * @param note_type either personal or OOF
7660 static gchar *
7661 sipe_publish_get_category_note(struct sipe_account_data *sip,
7662 const char *note, /* html */
7663 const char *note_type,
7664 time_t note_start,
7665 time_t note_end)
7667 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7668 /* key is <category><instance><container> */
7669 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7670 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7671 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7673 struct sipe_publication *publication_note_200 =
7674 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7675 struct sipe_publication *publication_note_300 =
7676 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7677 struct sipe_publication *publication_note_400 =
7678 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7680 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7681 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7682 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7683 char *res, *tmp1, *tmp2, *tmp3;
7684 char *start_time_attr;
7685 char *end_time_attr;
7687 g_free(tmp);
7688 tmp = NULL;
7689 g_free(key_note_200);
7690 g_free(key_note_300);
7691 g_free(key_note_400);
7693 /* we even need to republish empty note */
7694 if (sipe_strequal(n1, n2))
7696 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7697 g_free(n1);
7698 return NULL; /* nothing to update */
7701 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7702 g_free(tmp);
7703 tmp = NULL;
7704 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7705 g_free(tmp);
7707 if (n1) {
7708 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7709 instance,
7710 200,
7711 publication_note_200 ? publication_note_200->version : 0,
7712 note_type,
7713 start_time_attr ? start_time_attr : "",
7714 end_time_attr ? end_time_attr : "",
7715 n1);
7717 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7718 instance,
7719 300,
7720 publication_note_300 ? publication_note_300->version : 0,
7721 note_type,
7722 start_time_attr ? start_time_attr : "",
7723 end_time_attr ? end_time_attr : "",
7724 n1);
7726 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7727 instance,
7728 400,
7729 publication_note_400 ? publication_note_400->version : 0,
7730 note_type,
7731 start_time_attr ? start_time_attr : "",
7732 end_time_attr ? end_time_attr : "",
7733 n1);
7734 } else {
7735 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7736 "note",
7737 instance,
7738 200,
7739 publication_note_200 ? publication_note_200->version : 0,
7740 "static");
7741 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7742 "note",
7743 instance,
7744 300,
7745 publication_note_200 ? publication_note_200->version : 0,
7746 "static");
7747 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7748 "note",
7749 instance,
7750 400,
7751 publication_note_200 ? publication_note_200->version : 0,
7752 "static");
7754 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7756 g_free(start_time_attr);
7757 g_free(end_time_attr);
7758 g_free(tmp1);
7759 g_free(tmp2);
7760 g_free(tmp3);
7761 g_free(n1);
7763 return res;
7767 * Returns 'calendarData' XML part with WorkingHours for publication.
7768 * Must be g_free'd after use.
7770 static gchar *
7771 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7773 struct sipe_ews* ews = sip->ews;
7775 /* key is <category><instance><container> */
7776 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7777 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7778 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7779 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7780 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7781 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7783 struct sipe_publication *publication_cal_1 =
7784 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7785 struct sipe_publication *publication_cal_100 =
7786 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7787 struct sipe_publication *publication_cal_200 =
7788 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7789 struct sipe_publication *publication_cal_300 =
7790 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7791 struct sipe_publication *publication_cal_400 =
7792 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7793 struct sipe_publication *publication_cal_32000 =
7794 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7796 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7797 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7799 g_free(key_cal_1);
7800 g_free(key_cal_100);
7801 g_free(key_cal_200);
7802 g_free(key_cal_300);
7803 g_free(key_cal_400);
7804 g_free(key_cal_32000);
7806 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7807 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7808 return NULL;
7811 if (sipe_strequal(n1, n2))
7813 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7814 return NULL; /* nothing to update */
7817 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7818 /* 1 */
7819 publication_cal_1 ? publication_cal_1->version : 0,
7820 ews->email,
7821 ews->working_hours_xml_str,
7822 /* 100 - Public */
7823 publication_cal_100 ? publication_cal_100->version : 0,
7824 /* 200 - Company */
7825 publication_cal_200 ? publication_cal_200->version : 0,
7826 ews->email,
7827 ews->working_hours_xml_str,
7828 /* 300 - Team */
7829 publication_cal_300 ? publication_cal_300->version : 0,
7830 ews->email,
7831 ews->working_hours_xml_str,
7832 /* 400 - Personal */
7833 publication_cal_400 ? publication_cal_400->version : 0,
7834 ews->email,
7835 ews->working_hours_xml_str,
7836 /* 32000 - Blocked */
7837 publication_cal_32000 ? publication_cal_32000->version : 0
7842 * Returns 'calendarData' XML part with FreeBusy for publication.
7843 * Must be g_free'd after use.
7845 static gchar *
7846 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7848 struct sipe_ews* ews = sip->ews;
7849 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7850 char *fb_start_str;
7851 char *free_busy_base64;
7852 const char *st;
7853 const char *fb;
7854 char *res;
7856 /* key is <category><instance><container> */
7857 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7858 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7859 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7860 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7861 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7862 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7864 struct sipe_publication *publication_cal_1 =
7865 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7866 struct sipe_publication *publication_cal_100 =
7867 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7868 struct sipe_publication *publication_cal_200 =
7869 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7870 struct sipe_publication *publication_cal_300 =
7871 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7872 struct sipe_publication *publication_cal_400 =
7873 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7874 struct sipe_publication *publication_cal_32000 =
7875 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7877 g_free(key_cal_1);
7878 g_free(key_cal_100);
7879 g_free(key_cal_200);
7880 g_free(key_cal_300);
7881 g_free(key_cal_400);
7882 g_free(key_cal_32000);
7884 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7885 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7886 return NULL;
7889 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7890 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7892 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7893 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7895 /* we will rebuplish the same data to refresh publication time,
7896 * so if data from multiple sources, most recent will be choosen
7898 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7900 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7901 // g_free(fb_start_str);
7902 // g_free(free_busy_base64);
7903 // return NULL; /* nothing to update */
7906 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7907 /* 1 */
7908 cal_data_instance,
7909 publication_cal_1 ? publication_cal_1->version : 0,
7910 /* 100 - Public */
7911 cal_data_instance,
7912 publication_cal_100 ? publication_cal_100->version : 0,
7913 /* 200 - Company */
7914 cal_data_instance,
7915 publication_cal_200 ? publication_cal_200->version : 0,
7916 ews->email,
7917 fb_start_str,
7918 free_busy_base64,
7919 /* 300 - Team */
7920 cal_data_instance,
7921 publication_cal_300 ? publication_cal_300->version : 0,
7922 ews->email,
7923 fb_start_str,
7924 free_busy_base64,
7925 /* 400 - Personal */
7926 cal_data_instance,
7927 publication_cal_400 ? publication_cal_400->version : 0,
7928 ews->email,
7929 fb_start_str,
7930 free_busy_base64,
7931 /* 32000 - Blocked */
7932 cal_data_instance,
7933 publication_cal_32000 ? publication_cal_32000->version : 0
7936 g_free(fb_start_str);
7937 g_free(free_busy_base64);
7938 return res;
7941 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7943 gchar *uri;
7944 gchar *doc;
7945 gchar *tmp;
7946 gchar *hdr;
7948 uri = sip_uri_self(sip);
7949 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7950 uri,
7951 publications);
7953 tmp = get_contact(sip);
7954 hdr = g_strdup_printf("Contact: %s\r\n"
7955 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7957 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7959 g_free(tmp);
7960 g_free(hdr);
7961 g_free(uri);
7962 g_free(doc);
7965 static void
7966 send_publish_category_initial(struct sipe_account_data *sip)
7968 gchar *pub_device = sipe_publish_get_category_device(sip);
7969 gchar *pub_machine;
7970 gchar *publications;
7972 g_free(sip->status);
7973 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7975 pub_machine = sipe_publish_get_category_state_machine(sip);
7976 publications = g_strdup_printf("%s%s",
7977 pub_device,
7978 pub_machine ? pub_machine : "");
7979 g_free(pub_device);
7980 g_free(pub_machine);
7982 send_presence_publish(sip, publications);
7983 g_free(publications);
7986 static void
7987 send_presence_category_publish(struct sipe_account_data *sip)
7989 gchar *pub_state = sipe_is_user_state(sip) ?
7990 sipe_publish_get_category_state_user(sip) :
7991 sipe_publish_get_category_state_machine(sip);
7992 gchar *pub_note = sipe_publish_get_category_note(sip,
7993 sip->note,
7994 sip->is_oof_note ? "OOF" : "personal",
7997 gchar *publications;
7999 if (!pub_state && !pub_note) {
8000 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
8001 return;
8004 publications = g_strdup_printf("%s%s",
8005 pub_state ? pub_state : "",
8006 pub_note ? pub_note : "");
8008 g_free(pub_state);
8009 g_free(pub_note);
8011 send_presence_publish(sip, publications);
8012 g_free(publications);
8016 * Publishes self status
8017 * based on own calendar information.
8019 * For 2007+
8021 void
8022 publish_calendar_status_self(struct sipe_account_data *sip)
8024 struct sipe_cal_event* event = NULL;
8025 gchar *pub_cal_working_hours = NULL;
8026 gchar *pub_cal_free_busy = NULL;
8027 gchar *pub_calendar = NULL;
8028 gchar *pub_calendar2 = NULL;
8029 gchar *pub_oof_note = NULL;
8030 const gchar *oof_note;
8031 time_t oof_start = 0;
8032 time_t oof_end = 0;
8034 if (!sip->ews) {
8035 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
8036 return;
8039 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
8040 if (sip->ews->cal_events) {
8041 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
8044 if (!event) {
8045 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
8046 } else {
8047 char *desc = sipe_cal_event_describe(event);
8048 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
8049 g_free(desc);
8052 /* Logic
8053 if OOF
8054 OOF publish, Busy clean
8055 ilse if Busy
8056 OOF clean, Busy publish
8057 else
8058 OOF clean, Busy clean
8060 if (event && event->cal_status == SIPE_CAL_OOF) {
8061 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
8062 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8063 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
8064 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8065 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
8066 } else {
8067 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8068 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8071 oof_note = sipe_ews_get_oof_note(sip->ews);
8072 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
8073 oof_start = sip->ews->oof_start;
8074 oof_end = sip->ews->oof_end;
8076 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
8078 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
8079 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
8081 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
8082 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
8083 } else {
8084 gchar *publications = g_strdup_printf("%s%s%s%s%s",
8085 pub_cal_working_hours ? pub_cal_working_hours : "",
8086 pub_cal_free_busy ? pub_cal_free_busy : "",
8087 pub_calendar ? pub_calendar : "",
8088 pub_calendar2 ? pub_calendar2 : "",
8089 pub_oof_note ? pub_oof_note : "");
8091 send_presence_publish(sip, publications);
8092 g_free(publications);
8095 g_free(pub_cal_working_hours);
8096 g_free(pub_cal_free_busy);
8097 g_free(pub_calendar);
8098 g_free(pub_calendar2);
8099 g_free(pub_oof_note);
8101 /* repeat scheduling */
8102 sipe_sched_calendar_status_self_publish(sip, time(NULL));
8105 static void send_presence_status(struct sipe_account_data *sip)
8107 PurpleStatus * status = purple_account_get_active_status(sip->account);
8109 if (!status) return;
8111 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
8112 purple_status_get_id(status) ? purple_status_get_id(status) : "",
8113 sipe_is_user_state(sip) ? "USER" : "MACHINE");
8115 if (sip->ocs2007) {
8116 send_presence_category_publish(sip);
8117 } else {
8118 send_presence_soap(sip, FALSE);
8122 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
8124 gboolean found = FALSE;
8125 const char *method = msg->method ? msg->method : "NOT FOUND";
8126 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
8127 if (msg->response == 0) { /* request */
8128 if (sipe_strequal(method, "MESSAGE")) {
8129 process_incoming_message(sip, msg);
8130 found = TRUE;
8131 } else if (sipe_strequal(method, "NOTIFY")) {
8132 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
8133 process_incoming_notify(sip, msg, TRUE, FALSE);
8134 found = TRUE;
8135 } else if (sipe_strequal(method, "BENOTIFY")) {
8136 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
8137 process_incoming_notify(sip, msg, TRUE, TRUE);
8138 found = TRUE;
8139 } else if (sipe_strequal(method, "INVITE")) {
8140 process_incoming_invite(sip, msg);
8141 found = TRUE;
8142 } else if (sipe_strequal(method, "REFER")) {
8143 process_incoming_refer(sip, msg);
8144 found = TRUE;
8145 } else if (sipe_strequal(method, "OPTIONS")) {
8146 process_incoming_options(sip, msg);
8147 found = TRUE;
8148 } else if (sipe_strequal(method, "INFO")) {
8149 process_incoming_info(sip, msg);
8150 found = TRUE;
8151 } else if (sipe_strequal(method, "ACK")) {
8152 // ACK's don't need any response
8153 found = TRUE;
8154 } else if (sipe_strequal(method, "SUBSCRIBE")) {
8155 // LCS 2005 sends us these - just respond 200 OK
8156 found = TRUE;
8157 send_sip_response(sip->gc, msg, 200, "OK", NULL);
8158 } else if (sipe_strequal(method, "BYE")) {
8159 process_incoming_bye(sip, msg);
8160 found = TRUE;
8161 } else {
8162 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
8164 } else { /* response */
8165 struct transaction *trans = transactions_find(sip, msg);
8166 if (trans) {
8167 if (msg->response == 407) {
8168 gchar *resend, *auth;
8169 const gchar *ptmp;
8171 if (sip->proxy.retries > 30) return;
8172 sip->proxy.retries++;
8173 /* do proxy authentication */
8175 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
8177 fill_auth(ptmp, &sip->proxy);
8178 auth = auth_header(sip, &sip->proxy, trans->msg);
8179 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
8180 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
8181 g_free(auth);
8182 resend = sipmsg_to_string(trans->msg);
8183 /* resend request */
8184 sendout_pkt(sip->gc, resend);
8185 g_free(resend);
8186 } else {
8187 if (msg->response < 200) {
8188 /* ignore provisional response */
8189 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
8190 } else {
8191 sip->proxy.retries = 0;
8192 if (sipe_strequal(trans->msg->method, "REGISTER")) {
8193 if (msg->response == 401)
8195 sip->registrar.retries++;
8197 else
8199 sip->registrar.retries = 0;
8201 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
8202 } else {
8203 if (msg->response == 401) {
8204 gchar *resend, *auth, *ptmp;
8205 const char* auth_scheme;
8207 if (sip->registrar.retries > 4) return;
8208 sip->registrar.retries++;
8210 auth_scheme = sipe_get_auth_scheme_name(sip);
8211 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
8213 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
8214 if (!ptmp) {
8215 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
8216 sip->gc->wants_to_die = TRUE;
8217 purple_connection_error(sip->gc, tmp2);
8218 g_free(tmp2);
8219 return;
8222 fill_auth(ptmp, &sip->registrar);
8223 auth = auth_header(sip, &sip->registrar, trans->msg);
8224 sipmsg_remove_header_now(trans->msg, "Authorization");
8225 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
8226 g_free(auth);
8227 resend = sipmsg_to_string(trans->msg);
8228 /* resend request */
8229 sendout_pkt(sip->gc, resend);
8230 g_free(resend);
8234 if (trans->callback) {
8235 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
8236 /* call the callback to process response*/
8237 (trans->callback)(sip, msg, trans);
8240 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
8241 transactions_remove(sip, trans);
8245 found = TRUE;
8246 } else {
8247 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
8250 if (!found) {
8251 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
8255 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
8257 char *cur;
8258 char *dummy;
8259 char *tmp;
8260 struct sipmsg *msg;
8261 int restlen;
8262 cur = conn->inbuf;
8264 /* according to the RFC remove CRLF at the beginning */
8265 while (*cur == '\r' || *cur == '\n') {
8266 cur++;
8268 if (cur != conn->inbuf) {
8269 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
8270 conn->inbufused = strlen(conn->inbuf);
8273 /* Received a full Header? */
8274 sip->processing_input = TRUE;
8275 while (sip->processing_input &&
8276 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
8277 time_t currtime = time(NULL);
8278 cur += 2;
8279 cur[0] = '\0';
8280 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
8281 g_free(tmp);
8282 msg = sipmsg_parse_header(conn->inbuf);
8283 cur[0] = '\r';
8284 cur += 2;
8285 restlen = conn->inbufused - (cur - conn->inbuf);
8286 if (msg && restlen >= msg->bodylen) {
8287 dummy = g_malloc(msg->bodylen + 1);
8288 memcpy(dummy, cur, msg->bodylen);
8289 dummy[msg->bodylen] = '\0';
8290 msg->body = dummy;
8291 cur += msg->bodylen;
8292 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
8293 conn->inbufused = strlen(conn->inbuf);
8294 } else {
8295 if (msg){
8296 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen, msg->bodylen, (int)strlen(conn->inbuf));
8297 sipmsg_free(msg);
8299 return;
8302 /*if (msg->body) {
8303 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8306 // Verify the signature before processing it
8307 if (sip->registrar.gssapi_context) {
8308 struct sipmsg_breakdown msgbd;
8309 gchar *signature_input_str;
8310 gchar *rspauth;
8311 msgbd.msg = msg;
8312 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8313 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8315 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8317 if (rspauth != NULL) {
8318 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8319 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8320 process_input_message(sip, msg);
8321 } else {
8322 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8323 purple_connection_error(sip->gc, _("Invalid message signature received"));
8324 sip->gc->wants_to_die = TRUE;
8326 } else if (msg->response == 401) {
8327 purple_connection_error(sip->gc, _("Authentication failed"));
8328 sip->gc->wants_to_die = TRUE;
8330 g_free(signature_input_str);
8332 g_free(rspauth);
8333 sipmsg_breakdown_free(&msgbd);
8334 } else {
8335 process_input_message(sip, msg);
8338 sipmsg_free(msg);
8342 static void sipe_udp_process(gpointer data, gint source,
8343 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8345 PurpleConnection *gc = data;
8346 struct sipe_account_data *sip = gc->proto_data;
8347 int len;
8349 static char buffer[65536];
8350 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8351 time_t currtime = time(NULL);
8352 struct sipmsg *msg;
8353 buffer[len] = '\0';
8354 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), buffer);
8355 msg = sipmsg_parse_msg(buffer);
8356 if (msg) process_input_message(sip, msg);
8360 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8362 struct sipe_account_data *sip = gc->proto_data;
8363 PurpleSslConnection *gsc = sip->gsc;
8365 SIPE_DEBUG_ERROR("%s", debug);
8366 purple_connection_error(gc, msg);
8368 /* Invalidate this connection. Next send will open a new one */
8369 if (gsc) {
8370 connection_remove(sip, gsc->fd);
8371 purple_ssl_close(gsc);
8373 sip->gsc = NULL;
8374 sip->fd = -1;
8377 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8378 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8380 PurpleConnection *gc = data;
8381 struct sipe_account_data *sip;
8382 struct sip_connection *conn;
8383 int readlen, len;
8384 gboolean firstread = TRUE;
8386 /* NOTE: This check *IS* necessary */
8387 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8388 purple_ssl_close(gsc);
8389 return;
8392 sip = gc->proto_data;
8393 conn = connection_find(sip, gsc->fd);
8394 if (conn == NULL) {
8395 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
8396 gc->wants_to_die = TRUE;
8397 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8398 return;
8401 /* Read all available data from the SSL connection */
8402 do {
8403 /* Increase input buffer size as needed */
8404 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8405 conn->inbuflen += SIMPLE_BUF_INC;
8406 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8407 SIPE_DEBUG_INFO("sipe_input_cb_ssl: new input buffer length %d", conn->inbuflen);
8410 /* Try to read as much as there is space left in the buffer */
8411 readlen = conn->inbuflen - conn->inbufused - 1;
8412 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8414 if (len < 0 && errno == EAGAIN) {
8415 /* Try again later */
8416 return;
8417 } else if (len < 0) {
8418 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8419 return;
8420 } else if (firstread && (len == 0)) {
8421 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8422 return;
8425 conn->inbufused += len;
8426 firstread = FALSE;
8428 /* Equivalence indicates that there is possibly more data to read */
8429 } while (len == readlen);
8431 conn->inbuf[conn->inbufused] = '\0';
8432 process_input(sip, conn);
8436 static void sipe_input_cb(gpointer data, gint source,
8437 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8439 PurpleConnection *gc = data;
8440 struct sipe_account_data *sip = gc->proto_data;
8441 int len;
8442 struct sip_connection *conn = connection_find(sip, source);
8443 if (!conn) {
8444 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found!");
8445 return;
8448 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8449 conn->inbuflen += SIMPLE_BUF_INC;
8450 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8453 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8455 if (len < 0 && errno == EAGAIN)
8456 return;
8457 else if (len <= 0) {
8458 SIPE_DEBUG_INFO_NOFORMAT("sipe_input_cb: read error");
8459 connection_remove(sip, source);
8460 if (sip->fd == source) sip->fd = -1;
8461 return;
8464 conn->inbufused += len;
8465 conn->inbuf[conn->inbufused] = '\0';
8467 process_input(sip, conn);
8470 /* Callback for new connections on incoming TCP port */
8471 static void sipe_newconn_cb(gpointer data, gint source,
8472 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8474 PurpleConnection *gc = data;
8475 struct sipe_account_data *sip = gc->proto_data;
8476 struct sip_connection *conn;
8478 int newfd = accept(source, NULL, NULL);
8480 conn = connection_create(sip, newfd);
8482 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8485 static void login_cb(gpointer data, gint source,
8486 SIPE_UNUSED_PARAMETER const gchar *error_message)
8488 PurpleConnection *gc = data;
8489 struct sipe_account_data *sip;
8490 struct sip_connection *conn;
8492 if (!PURPLE_CONNECTION_IS_VALID(gc))
8494 if (source >= 0)
8495 close(source);
8496 return;
8499 if (source < 0) {
8500 purple_connection_error(gc, _("Could not connect"));
8501 return;
8504 sip = gc->proto_data;
8505 sip->fd = source;
8506 sip->last_keepalive = time(NULL);
8508 conn = connection_create(sip, source);
8510 do_register(sip);
8512 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8515 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8516 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8518 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8519 if (sip == NULL) return;
8521 do_register(sip);
8524 static guint sipe_ht_hash_nick(const char *nick)
8526 char *lc = g_utf8_strdown(nick, -1);
8527 guint bucket = g_str_hash(lc);
8528 g_free(lc);
8530 return bucket;
8533 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8535 char *nick1_norm = NULL;
8536 char *nick2_norm = NULL;
8537 gboolean equal;
8539 if (nick1 == NULL && nick2 == NULL) return TRUE;
8540 if (nick1 == NULL || nick2 == NULL ||
8541 !g_utf8_validate(nick1, -1, NULL) ||
8542 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
8544 nick1_norm = g_utf8_casefold(nick1, -1);
8545 nick2_norm = g_utf8_casefold(nick2, -1);
8546 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
8547 g_free(nick2_norm);
8548 g_free(nick1_norm);
8550 return equal;
8553 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8555 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8557 sip->listen_data = NULL;
8559 if (listenfd == -1) {
8560 purple_connection_error(sip->gc, _("Could not create listen socket"));
8561 return;
8564 sip->fd = listenfd;
8566 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8567 sip->listenfd = sip->fd;
8569 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8571 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8572 do_register(sip);
8575 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8576 SIPE_UNUSED_PARAMETER const char *error_message)
8578 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8580 sip->query_data = NULL;
8582 if (!hosts || !hosts->data) {
8583 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8584 return;
8587 hosts = g_slist_remove(hosts, hosts->data);
8588 g_free(sip->serveraddr);
8589 sip->serveraddr = hosts->data;
8590 hosts = g_slist_remove(hosts, hosts->data);
8591 while (hosts) {
8592 void *tmp = hosts->data;
8593 hosts = g_slist_remove(hosts, tmp);
8594 hosts = g_slist_remove(hosts, tmp);
8595 g_free(tmp);
8598 /* create socket for incoming connections */
8599 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8600 sipe_udp_host_resolved_listen_cb, sip);
8601 if (sip->listen_data == NULL) {
8602 purple_connection_error(sip->gc, _("Could not create listen socket"));
8603 return;
8607 static const struct sipe_service_data *current_service = NULL;
8609 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8610 PurpleSslErrorType error,
8611 gpointer data)
8613 PurpleConnection *gc = data;
8614 struct sipe_account_data *sip;
8616 /* If the connection is already disconnected, we don't need to do anything else */
8617 if (!PURPLE_CONNECTION_IS_VALID(gc))
8618 return;
8620 sip = gc->proto_data;
8621 current_service = sip->service_data;
8622 if (current_service) {
8623 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8624 current_service->transport ? current_service->transport : "NULL",
8625 current_service->service ? current_service->service : "NULL");
8628 sip->fd = -1;
8629 sip->gsc = NULL;
8631 switch(error) {
8632 case PURPLE_SSL_CONNECT_FAILED:
8633 purple_connection_error(gc, _("Connection failed"));
8634 break;
8635 case PURPLE_SSL_HANDSHAKE_FAILED:
8636 purple_connection_error(gc, _("SSL handshake failed"));
8637 break;
8638 case PURPLE_SSL_CERTIFICATE_INVALID:
8639 purple_connection_error(gc, _("SSL certificate invalid"));
8640 break;
8644 static void
8645 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8647 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8648 PurpleProxyConnectData *connect_data;
8650 sip->listen_data = NULL;
8652 sip->listenfd = listenfd;
8653 if (sip->listenfd == -1) {
8654 purple_connection_error(sip->gc, _("Could not create listen socket"));
8655 return;
8658 SIPE_DEBUG_INFO("listenfd: %d", sip->listenfd);
8659 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8660 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8661 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8662 sipe_newconn_cb, sip->gc);
8663 SIPE_DEBUG_INFO("connecting to %s port %d",
8664 sip->realhostname, sip->realport);
8665 /* open tcp connection to the server */
8666 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8667 sip->realport, login_cb, sip->gc);
8669 if (connect_data == NULL) {
8670 purple_connection_error(sip->gc, _("Could not create socket"));
8674 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8676 PurpleAccount *account = sip->account;
8677 PurpleConnection *gc = sip->gc;
8679 if (port == 0) {
8680 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8683 sip->realhostname = hostname;
8684 sip->realport = port;
8686 SIPE_DEBUG_INFO("create_connection - hostname: %s port: %d",
8687 hostname, port);
8689 /* TODO: is there a good default grow size? */
8690 if (sip->transport != SIPE_TRANSPORT_UDP)
8691 sip->txbuf = purple_circ_buffer_new(0);
8693 if (sip->transport == SIPE_TRANSPORT_TLS) {
8694 /* SSL case */
8695 if (!purple_ssl_is_supported()) {
8696 gc->wants_to_die = TRUE;
8697 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8698 return;
8701 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
8703 sip->gsc = purple_ssl_connect(account, hostname, port,
8704 login_cb_ssl, sipe_ssl_connect_failure, gc);
8705 if (sip->gsc == NULL) {
8706 purple_connection_error(gc, _("Could not create SSL context"));
8707 return;
8709 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8710 /* UDP case */
8711 SIPE_DEBUG_INFO_NOFORMAT("using UDP");
8713 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8714 if (sip->query_data == NULL) {
8715 purple_connection_error(gc, _("Could not resolve hostname"));
8717 } else {
8718 /* TCP case */
8719 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
8720 /* create socket for incoming connections */
8721 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8722 sipe_tcp_connect_listen_cb, sip);
8723 if (sip->listen_data == NULL) {
8724 purple_connection_error(gc, _("Could not create listen socket"));
8725 return;
8730 /* Service list for autodection */
8731 static const struct sipe_service_data service_autodetect[] = {
8732 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8733 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8734 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8735 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8736 { NULL, NULL, 0 }
8739 /* Service list for SSL/TLS */
8740 static const struct sipe_service_data service_tls[] = {
8741 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8742 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8743 { NULL, NULL, 0 }
8746 /* Service list for TCP */
8747 static const struct sipe_service_data service_tcp[] = {
8748 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8749 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8750 { NULL, NULL, 0 }
8753 /* Service list for UDP */
8754 static const struct sipe_service_data service_udp[] = {
8755 { "sip", "udp", SIPE_TRANSPORT_UDP },
8756 { NULL, NULL, 0 }
8759 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8760 static void resolve_next_service(struct sipe_account_data *sip,
8761 const struct sipe_service_data *start)
8763 if (start) {
8764 sip->service_data = start;
8765 } else {
8766 sip->service_data++;
8767 if (sip->service_data->service == NULL) {
8768 gchar *hostname;
8769 /* Try connecting to the SIP hostname directly */
8770 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8771 if (sip->auto_transport) {
8772 // If SSL is supported, default to using it; OCS servers aren't configured
8773 // by default to accept TCP
8774 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8775 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8776 SIPE_DEBUG_INFO_NOFORMAT("set transport type..");
8779 hostname = g_strdup(sip->sipdomain);
8780 create_connection(sip, hostname, 0);
8781 return;
8785 /* Try to resolve next service */
8786 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8787 sip->service_data->transport,
8788 sip->sipdomain,
8789 srvresolved, sip);
8792 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8794 struct sipe_account_data *sip = data;
8796 sip->srv_query_data = NULL;
8798 /* find the host to connect to */
8799 if (results) {
8800 gchar *hostname = g_strdup(resp->hostname);
8801 int port = resp->port;
8802 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8803 hostname, port);
8804 g_free(resp);
8806 sip->transport = sip->service_data->type;
8808 create_connection(sip, hostname, port);
8809 } else {
8810 resolve_next_service(sip, NULL);
8814 static void sipe_login(PurpleAccount *account)
8816 PurpleConnection *gc;
8817 struct sipe_account_data *sip;
8818 gchar **signinname_login, **userserver;
8819 const char *transport;
8820 const char *email;
8822 const char *username = purple_account_get_username(account);
8823 gc = purple_account_get_connection(account);
8825 SIPE_DEBUG_INFO("sipe_login: username '%s'", username);
8827 if (strpbrk(username, "\t\v\r\n") != NULL) {
8828 gc->wants_to_die = TRUE;
8829 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8830 return;
8833 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8834 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8835 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8836 sip->gc = gc;
8837 sip->account = account;
8838 sip->reregister_set = FALSE;
8839 sip->reauthenticate_set = FALSE;
8840 sip->subscribed = FALSE;
8841 sip->subscribed_buddies = FALSE;
8842 sip->initial_state_published = FALSE;
8844 /* username format: <username>,[<optional login>] */
8845 signinname_login = g_strsplit(username, ",", 2);
8846 SIPE_DEBUG_INFO("sipe_login: signinname[0] '%s'", signinname_login[0]);
8848 /* ensure that username format is name@domain */
8849 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8850 g_strfreev(signinname_login);
8851 gc->wants_to_die = TRUE;
8852 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8853 return;
8855 sip->username = g_strdup(signinname_login[0]);
8857 /* ensure that email format is name@domain if provided */
8858 email = purple_account_get_string(sip->account, "email", NULL);
8859 if (!is_empty(email) &&
8860 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8862 gc->wants_to_die = TRUE;
8863 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8864 return;
8866 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8868 /* login name specified? */
8869 if (signinname_login[1] && strlen(signinname_login[1])) {
8870 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8871 gboolean has_domain = domain_user[1] != NULL;
8872 SIPE_DEBUG_INFO("sipe_login: signinname[1] '%s'", signinname_login[1]);
8873 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8874 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8875 SIPE_DEBUG_INFO("sipe_login: auth domain '%s' user '%s'",
8876 sip->authdomain ? sip->authdomain : "", sip->authuser);
8877 g_strfreev(domain_user);
8880 userserver = g_strsplit(signinname_login[0], "@", 2);
8881 SIPE_DEBUG_INFO("sipe_login: user '%s' server '%s'", userserver[0], userserver[1]);
8882 purple_connection_set_display_name(gc, userserver[0]);
8883 sip->sipdomain = g_strdup(userserver[1]);
8884 g_strfreev(userserver);
8885 g_strfreev(signinname_login);
8887 if (strchr(sip->username, ' ') != NULL) {
8888 gc->wants_to_die = TRUE;
8889 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8890 return;
8893 sip->password = g_strdup(purple_connection_get_password(gc));
8895 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8896 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8897 g_free, (GDestroyNotify)g_hash_table_destroy);
8898 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8899 g_free, (GDestroyNotify)sipe_subscription_free);
8901 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8903 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8905 g_free(sip->status);
8906 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8908 sip->auto_transport = FALSE;
8909 transport = purple_account_get_string(account, "transport", "auto");
8910 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8911 if (userserver[0]) {
8912 /* Use user specified server[:port] */
8913 int port = 0;
8915 if (userserver[1])
8916 port = atoi(userserver[1]);
8918 SIPE_DEBUG_INFO("sipe_login: user specified SIP server %s:%d",
8919 userserver[0], port);
8921 if (sipe_strequal(transport, "auto")) {
8922 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8923 } else if (sipe_strequal(transport, "tls")) {
8924 sip->transport = SIPE_TRANSPORT_TLS;
8925 } else if (sipe_strequal(transport, "tcp")) {
8926 sip->transport = SIPE_TRANSPORT_TCP;
8927 } else {
8928 sip->transport = SIPE_TRANSPORT_UDP;
8931 create_connection(sip, g_strdup(userserver[0]), port);
8932 } else {
8933 /* Server auto-discovery */
8934 if (sipe_strequal(transport, "auto")) {
8935 sip->auto_transport = TRUE;
8936 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8937 current_service++;
8938 resolve_next_service(sip, current_service);
8939 } else {
8940 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8942 } else if (sipe_strequal(transport, "tls")) {
8943 resolve_next_service(sip, service_tls);
8944 } else if (sipe_strequal(transport, "tcp")) {
8945 resolve_next_service(sip, service_tcp);
8946 } else {
8947 resolve_next_service(sip, service_udp);
8950 g_strfreev(userserver);
8953 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8955 connection_free_all(sip);
8957 g_free(sip->epid);
8958 sip->epid = NULL;
8960 if (sip->query_data != NULL)
8961 purple_dnsquery_destroy(sip->query_data);
8962 sip->query_data = NULL;
8964 if (sip->srv_query_data != NULL)
8965 purple_srv_cancel(sip->srv_query_data);
8966 sip->srv_query_data = NULL;
8968 if (sip->listen_data != NULL)
8969 purple_network_listen_cancel(sip->listen_data);
8970 sip->listen_data = NULL;
8972 if (sip->gsc != NULL)
8973 purple_ssl_close(sip->gsc);
8974 sip->gsc = NULL;
8976 sipe_auth_free(&sip->registrar);
8977 sipe_auth_free(&sip->proxy);
8979 if (sip->txbuf)
8980 purple_circ_buffer_destroy(sip->txbuf);
8981 sip->txbuf = NULL;
8983 g_free(sip->realhostname);
8984 sip->realhostname = NULL;
8986 g_free(sip->server_version);
8987 sip->server_version = NULL;
8989 if (sip->listenpa)
8990 purple_input_remove(sip->listenpa);
8991 sip->listenpa = 0;
8992 if (sip->tx_handler)
8993 purple_input_remove(sip->tx_handler);
8994 sip->tx_handler = 0;
8995 if (sip->resendtimeout)
8996 purple_timeout_remove(sip->resendtimeout);
8997 sip->resendtimeout = 0;
8998 if (sip->timeouts) {
8999 GSList *entry = sip->timeouts;
9000 while (entry) {
9001 struct scheduled_action *sched_action = entry->data;
9002 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
9003 purple_timeout_remove(sched_action->timeout_handler);
9004 if (sched_action->destroy) {
9005 (*sched_action->destroy)(sched_action->payload);
9007 g_free(sched_action->name);
9008 g_free(sched_action);
9009 entry = entry->next;
9012 g_slist_free(sip->timeouts);
9014 if (sip->allow_events) {
9015 GSList *entry = sip->allow_events;
9016 while (entry) {
9017 g_free(entry->data);
9018 entry = entry->next;
9021 g_slist_free(sip->allow_events);
9023 if (sip->containers) {
9024 GSList *entry = sip->containers;
9025 while (entry) {
9026 free_container((struct sipe_container *)entry->data);
9027 entry = entry->next;
9030 g_slist_free(sip->containers);
9032 if (sip->contact)
9033 g_free(sip->contact);
9034 sip->contact = NULL;
9035 if (sip->regcallid)
9036 g_free(sip->regcallid);
9037 sip->regcallid = NULL;
9039 if (sip->serveraddr)
9040 g_free(sip->serveraddr);
9041 sip->serveraddr = NULL;
9043 if (sip->focus_factory_uri)
9044 g_free(sip->focus_factory_uri);
9045 sip->focus_factory_uri = NULL;
9047 sip->fd = -1;
9048 sip->processing_input = FALSE;
9050 if (sip->ews) {
9051 sipe_ews_free(sip->ews);
9053 sip->ews = NULL;
9057 * A callback for g_hash_table_foreach_remove
9059 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
9060 SIPE_UNUSED_PARAMETER gpointer user_data)
9062 sipe_free_buddy((struct sipe_buddy *) buddy);
9064 /* We must return TRUE as the key/value have already been deleted */
9065 return(TRUE);
9068 static void sipe_close(PurpleConnection *gc)
9070 struct sipe_account_data *sip = gc->proto_data;
9072 if (sip) {
9073 /* leave all conversations */
9074 sipe_session_close_all(sip);
9075 sipe_session_remove_all(sip);
9077 if (sip->csta) {
9078 sip_csta_close(sip);
9081 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
9082 /* unsubscribe all */
9083 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
9085 /* unregister */
9086 do_register_exp(sip, 0);
9089 sipe_connection_cleanup(sip);
9090 g_free(sip->sipdomain);
9091 g_free(sip->username);
9092 g_free(sip->email);
9093 g_free(sip->password);
9094 g_free(sip->authdomain);
9095 g_free(sip->authuser);
9096 g_free(sip->status);
9097 g_free(sip->note);
9098 g_free(sip->user_states);
9100 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
9101 g_hash_table_destroy(sip->buddies);
9102 g_hash_table_destroy(sip->our_publications);
9103 g_hash_table_destroy(sip->user_state_publications);
9104 g_hash_table_destroy(sip->subscriptions);
9105 g_hash_table_destroy(sip->filetransfers);
9107 if (sip->groups) {
9108 GSList *entry = sip->groups;
9109 while (entry) {
9110 struct sipe_group *group = entry->data;
9111 g_free(group->name);
9112 g_free(group);
9113 entry = entry->next;
9116 g_slist_free(sip->groups);
9118 if (sip->our_publication_keys) {
9119 GSList *entry = sip->our_publication_keys;
9120 while (entry) {
9121 g_free(entry->data);
9122 entry = entry->next;
9125 g_slist_free(sip->our_publication_keys);
9127 while (sip->transactions)
9128 transactions_remove(sip, sip->transactions->data);
9130 g_free(gc->proto_data);
9131 gc->proto_data = NULL;
9134 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
9135 SIPE_UNUSED_PARAMETER void *user_data)
9137 PurpleAccount *acct = purple_connection_get_account(gc);
9138 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
9139 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
9140 if (conv == NULL)
9141 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
9142 purple_conversation_present(conv);
9143 g_free(id);
9146 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
9147 SIPE_UNUSED_PARAMETER void *user_data)
9150 purple_blist_request_add_buddy(purple_connection_get_account(gc),
9151 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
9154 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
9155 SIPE_UNUSED_PARAMETER struct transaction *trans)
9157 PurpleNotifySearchResults *results;
9158 PurpleNotifySearchColumn *column;
9159 sipe_xml *searchResults;
9160 const sipe_xml *mrow;
9161 int match_count = 0;
9162 gboolean more = FALSE;
9163 gchar *secondary;
9165 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
9167 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
9168 if (!searchResults) {
9169 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
9170 return FALSE;
9173 results = purple_notify_searchresults_new();
9175 if (results == NULL) {
9176 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
9177 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
9179 sipe_xml_free(searchResults);
9180 return FALSE;
9183 column = purple_notify_searchresults_column_new(_("User name"));
9184 purple_notify_searchresults_column_add(results, column);
9186 column = purple_notify_searchresults_column_new(_("Name"));
9187 purple_notify_searchresults_column_add(results, column);
9189 column = purple_notify_searchresults_column_new(_("Company"));
9190 purple_notify_searchresults_column_add(results, column);
9192 column = purple_notify_searchresults_column_new(_("Country"));
9193 purple_notify_searchresults_column_add(results, column);
9195 column = purple_notify_searchresults_column_new(_("Email"));
9196 purple_notify_searchresults_column_add(results, column);
9198 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
9199 GList *row = NULL;
9201 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
9202 row = g_list_append(row, g_strdup(uri_parts[1]));
9203 g_strfreev(uri_parts);
9205 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
9206 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
9207 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
9208 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
9210 purple_notify_searchresults_row_add(results, row);
9211 match_count++;
9214 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
9215 char *data = sipe_xml_data(mrow);
9216 more = (g_strcasecmp(data, "true") == 0);
9217 g_free(data);
9220 secondary = g_strdup_printf(
9221 dngettext(PACKAGE_NAME,
9222 "Found %d contact%s:",
9223 "Found %d contacts%s:", match_count),
9224 match_count, more ? _(" (more matched your query)") : "");
9226 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
9227 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
9228 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
9230 g_free(secondary);
9231 sipe_xml_free(searchResults);
9232 return TRUE;
9235 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
9237 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
9238 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
9239 unsigned i = 0;
9241 if (!attrs) return;
9243 do {
9244 PurpleRequestField *field = entries->data;
9245 const char *id = purple_request_field_get_id(field);
9246 const char *value = purple_request_field_string_get_value(field);
9248 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
9250 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
9251 } while ((entries = g_list_next(entries)) != NULL);
9252 attrs[i] = NULL;
9254 if (i > 0) {
9255 struct sipe_account_data *sip = gc->proto_data;
9256 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9257 gchar *query = g_strjoinv(NULL, attrs);
9258 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
9259 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
9260 send_soap_request_with_cb(sip, domain_uri, body,
9261 (TransCallback) process_search_contact_response, NULL);
9262 g_free(domain_uri);
9263 g_free(body);
9264 g_free(query);
9267 g_strfreev(attrs);
9270 static void sipe_show_find_contact(PurplePluginAction *action)
9272 PurpleConnection *gc = (PurpleConnection *) action->context;
9273 PurpleRequestFields *fields;
9274 PurpleRequestFieldGroup *group;
9275 PurpleRequestField *field;
9277 fields = purple_request_fields_new();
9278 group = purple_request_field_group_new(NULL);
9279 purple_request_fields_add_group(fields, group);
9281 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
9282 purple_request_field_group_add_field(group, field);
9283 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
9284 purple_request_field_group_add_field(group, field);
9285 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
9286 purple_request_field_group_add_field(group, field);
9287 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
9288 purple_request_field_group_add_field(group, field);
9290 purple_request_fields(gc,
9291 _("Search"),
9292 _("Search for a contact"),
9293 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
9294 fields,
9295 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
9296 _("_Cancel"), NULL,
9297 purple_connection_get_account(gc), NULL, NULL, gc);
9300 static void sipe_show_about_plugin(PurplePluginAction *action)
9302 PurpleConnection *gc = (PurpleConnection *) action->context;
9303 char *tmp = g_strdup_printf(
9305 * Non-translatable parts, like markup, are hard-coded
9306 * into the format string. This requires more translatable
9307 * texts but it makes the translations less error prone.
9309 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
9310 "<br/>"
9311 /* 1 */ "%s:<br/>"
9312 "<li> - MS Office Communications Server 2007 R2</li><br/>"
9313 "<li> - MS Office Communications Server 2007</li><br/>"
9314 "<li> - MS Live Communications Server 2005</li><br/>"
9315 "<li> - MS Live Communications Server 2003</li><br/>"
9316 "<li> - Reuters Messaging</li><br/>"
9317 "<br/>"
9318 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
9319 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
9320 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
9321 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
9322 /* 8 */ "%s: GPLv2+<br/>"
9323 "<br/>"
9324 /* 9 */ "%s:<br/>"
9325 " - CERN<br/>"
9326 " - Reuters Messaging network<br/>"
9327 " - Deutsche Bank<br/>"
9328 " - Merrill Lynch<br/>"
9329 " - Wachovia<br/>"
9330 " - Intel<br/>"
9331 " - Nokia<br/>"
9332 " - HP<br/>"
9333 " - Symantec<br/>"
9334 " - Accenture<br/>"
9335 " - Capgemini<br/>"
9336 " - Siemens<br/>"
9337 " - Alcatel-Lucent<br/>"
9338 " - BT<br/>"
9339 "<br/>"
9340 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9341 "<br/>"
9342 /* 12 */ "<b>%s:</b><br/>"
9343 " - Anibal Avelar<br/>"
9344 " - Gabriel Burt<br/>"
9345 " - Stefan Becker<br/>"
9346 " - pier11<br/>"
9347 " - Jakub Adam<br/>"
9348 " - Tomáš Hrabčík<br/>"
9349 "<br/>"
9350 /* 13 */ "%s<br/>"
9352 /* The next 13 texts make up the SIPE about note text */
9353 /* About note, part 1/13: introduction */
9354 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9355 /* About note, part 2/13: home page URL (label) */
9356 _("Home"),
9357 /* About note, part 3/13: support forum URL (label) */
9358 _("Support"),
9359 /* About note, part 4/13: support forum name (hyperlink text) */
9360 _("Help Forum"),
9361 /* About note, part 5/13: bug tracker URL (label) */
9362 _("Report Problems"),
9363 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9364 _("Bug Tracker"),
9365 /* About note, part 7/13: translation service URL (label) */
9366 _("Translations"),
9367 /* About note, part 8/13: license type (label) */
9368 _("License"),
9369 /* About note, part 9/13: known users */
9370 _("We support users in such organizations as"),
9371 /* About note, part 10/13: translation request, text before Transifex.net URL */
9372 /* append a space if text is not empty */
9373 _("Please help us to translate SIPE to your native language here at "),
9374 /* About note, part 11/13: translation request, text after Transifex.net URL */
9375 /* start with a space if text is not empty */
9376 _(" using convenient web interface"),
9377 /* About note, part 12/13: author list (header) */
9378 _("Authors"),
9379 /* About note, part 13/13: Localization credit */
9380 /* PLEASE NOTE: do *NOT* simply translate the english original */
9381 /* but write something similar to the following sentence: */
9382 /* "Localization for <language name> (<language code>): <name>" */
9383 _("Original texts in English (en): SIPE developers")
9385 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9386 g_free(tmp);
9389 static void sipe_republish_calendar(PurplePluginAction *action)
9391 PurpleConnection *gc = (PurpleConnection *) action->context;
9392 struct sipe_account_data *sip = gc->proto_data;
9394 sipe_update_calendar(sip);
9397 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9398 gpointer value,
9399 GString* str)
9401 struct sipe_publication *publication = value;
9403 g_string_append_printf( str,
9404 SIPE_PUB_XML_PUBLICATION_CLEAR,
9405 publication->category,
9406 publication->instance,
9407 publication->container,
9408 publication->version,
9409 "static");
9412 static void sipe_reset_status(PurplePluginAction *action)
9414 PurpleConnection *gc = (PurpleConnection *) action->context;
9415 struct sipe_account_data *sip = gc->proto_data;
9417 if (sip->ocs2007) /* 2007+ */
9419 GString* str = g_string_new(NULL);
9420 gchar *publications;
9422 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9423 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
9424 return;
9427 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9428 publications = g_string_free(str, FALSE);
9430 send_presence_publish(sip, publications);
9431 g_free(publications);
9433 else /* 2005 */
9435 send_presence_soap0(sip, FALSE, TRUE);
9439 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9440 gpointer context)
9442 PurpleConnection *gc = (PurpleConnection *)context;
9443 struct sipe_account_data *sip = gc->proto_data;
9444 GList *menu = NULL;
9445 PurplePluginAction *act;
9446 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9448 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9449 menu = g_list_prepend(menu, act);
9451 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9452 menu = g_list_prepend(menu, act);
9454 if (sipe_strequal(calendar, "EXCH")) {
9455 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9456 menu = g_list_prepend(menu, act);
9459 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9460 menu = g_list_prepend(menu, act);
9462 menu = g_list_reverse(menu);
9464 return menu;
9467 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9471 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9473 return TRUE;
9477 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9479 return TRUE;
9483 static char *sipe_status_text(PurpleBuddy *buddy)
9485 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9486 const PurpleStatus *status = purple_presence_get_active_status(presence);
9487 const char *status_id = purple_status_get_id(status);
9488 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9489 struct sipe_buddy *sbuddy;
9490 char *text = NULL;
9492 if (!sip) return NULL; /* happens on pidgin exit */
9494 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9495 if (sbuddy) {
9496 const char *activity_str = sbuddy->activity ?
9497 sbuddy->activity :
9498 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9499 purple_status_get_name(status) : NULL;
9501 if (activity_str && sbuddy->note)
9503 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9505 else if (activity_str)
9507 text = g_strdup(activity_str);
9509 else if (sbuddy->note)
9511 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9515 return text;
9518 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9520 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9521 const PurpleStatus *status = purple_presence_get_active_status(presence);
9522 struct sipe_account_data *sip;
9523 struct sipe_buddy *sbuddy;
9524 char *note = NULL;
9525 gboolean is_oof_note = FALSE;
9526 char *activity = NULL;
9527 char *calendar = NULL;
9528 char *meeting_subject = NULL;
9529 char *meeting_location = NULL;
9531 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9532 if (sip) //happens on pidgin exit
9534 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9535 if (sbuddy)
9537 note = sbuddy->note;
9538 is_oof_note = sbuddy->is_oof_note;
9539 activity = sbuddy->activity;
9540 calendar = sipe_cal_get_description(sbuddy);
9541 meeting_subject = sbuddy->meeting_subject;
9542 meeting_location = sbuddy->meeting_location;
9546 //Layout
9547 if (purple_presence_is_online(presence))
9549 const char *status_str = activity ? activity : purple_status_get_name(status);
9551 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9553 if (purple_presence_is_online(presence) &&
9554 !is_empty(calendar))
9556 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9558 g_free(calendar);
9559 if (!is_empty(meeting_location))
9561 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9563 if (!is_empty(meeting_subject))
9565 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9568 if (note)
9570 char *tmp = g_strdup_printf("<i>%s</i>", note);
9571 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", buddy->name, note);
9573 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9574 g_free(tmp);
9577 if (sip && sip->ocs2007) {
9578 const int container_id = sipe_find_access_level(sip, "user", sipe_get_no_sip_uri(buddy->name));
9579 const char *access_level = sipe_get_access_level_name(container_id);
9581 purple_notify_user_info_add_pair(user_info, _("Access level"), access_level);
9585 #if PURPLE_VERSION_CHECK(2,5,0)
9586 static GHashTable *
9587 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9589 GHashTable *table;
9590 table = g_hash_table_new(g_str_hash, g_str_equal);
9591 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9592 return table;
9594 #endif
9596 static PurpleBuddy *
9597 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9599 PurpleBuddy *clone;
9600 const gchar *server_alias, *email;
9601 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9603 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9605 purple_blist_add_buddy(clone, NULL, group, NULL);
9607 server_alias = purple_buddy_get_server_alias(buddy);
9608 if (server_alias) {
9609 purple_blist_server_alias_buddy(clone, server_alias);
9612 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9613 if (email) {
9614 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9617 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9618 //for UI to update;
9619 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9620 return clone;
9623 static void
9624 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9626 PurpleBuddy *buddy, *b;
9627 PurpleConnection *gc;
9628 PurpleGroup * group = purple_find_group(group_name);
9630 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9632 buddy = (PurpleBuddy *)node;
9634 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
9635 gc = purple_account_get_connection(buddy->account);
9637 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9638 if (!b){
9639 purple_blist_add_buddy_clone(group, buddy);
9642 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9645 static void
9646 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9648 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9650 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
9652 /* 2007+ conference */
9653 if (sip->ocs2007)
9655 sipe_conf_add(sip, buddy->name);
9657 else /* 2005- multiparty chat */
9659 gchar *self = sip_uri_self(sip);
9660 struct sip_session *session;
9662 session = sipe_session_add_chat(sip);
9663 session->chat_title = sipe_chat_get_name(session->callid);
9664 session->roster_manager = g_strdup(self);
9666 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9667 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9668 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9669 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9671 g_free(self);
9675 static gboolean
9676 sipe_is_election_finished(struct sip_session *session)
9678 gboolean res = TRUE;
9680 SIPE_DIALOG_FOREACH {
9681 if (dialog->election_vote == 0) {
9682 res = FALSE;
9683 break;
9685 } SIPE_DIALOG_FOREACH_END;
9687 if (res) {
9688 session->is_voting_in_progress = FALSE;
9690 return res;
9693 static void
9694 sipe_election_start(struct sipe_account_data *sip,
9695 struct sip_session *session)
9697 int election_timeout;
9699 if (session->is_voting_in_progress) {
9700 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
9701 return;
9702 } else {
9703 session->is_voting_in_progress = TRUE;
9705 session->bid = rand();
9707 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
9709 SIPE_DIALOG_FOREACH {
9710 /* reset election_vote for each chat participant */
9711 dialog->election_vote = 0;
9713 /* send RequestRM to each chat participant*/
9714 sipe_send_election_request_rm(sip, dialog, session->bid);
9715 } SIPE_DIALOG_FOREACH_END;
9717 election_timeout = 15; /* sec */
9718 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9722 * @param who a URI to whom to invite to chat
9724 void
9725 sipe_invite_to_chat(struct sipe_account_data *sip,
9726 struct sip_session *session,
9727 const gchar *who)
9729 /* a conference */
9730 if (session->focus_uri)
9732 sipe_invite_conf(sip, session, who);
9734 else /* a multi-party chat */
9736 gchar *self = sip_uri_self(sip);
9737 if (session->roster_manager) {
9738 if (sipe_strcase_equal(session->roster_manager, self)) {
9739 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9740 } else {
9741 sipe_refer(sip, session, who);
9743 } else {
9744 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
9746 session->pending_invite_queue = slist_insert_unique_sorted(
9747 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9749 sipe_election_start(sip, session);
9751 g_free(self);
9755 void
9756 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9757 struct sip_session *session)
9759 gchar *invitee;
9760 GSList *entry = session->pending_invite_queue;
9762 while (entry) {
9763 invitee = entry->data;
9764 sipe_invite_to_chat(sip, session, invitee);
9765 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9766 g_free(invitee);
9770 static void
9771 sipe_election_result(struct sipe_account_data *sip,
9772 void *sess)
9774 struct sip_session *session = (struct sip_session *)sess;
9775 gchar *rival;
9776 gboolean has_won = TRUE;
9778 if (session->roster_manager) {
9779 SIPE_DEBUG_INFO(
9780 "sipe_election_result: RM has already been elected in the meantime. It is %s",
9781 session->roster_manager);
9782 return;
9785 session->is_voting_in_progress = FALSE;
9787 SIPE_DIALOG_FOREACH {
9788 if (dialog->election_vote < 0) {
9789 has_won = FALSE;
9790 rival = dialog->with;
9791 break;
9793 } SIPE_DIALOG_FOREACH_END;
9795 if (has_won) {
9796 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
9798 session->roster_manager = sip_uri_self(sip);
9800 SIPE_DIALOG_FOREACH {
9801 /* send SetRM to each chat participant*/
9802 sipe_send_election_set_rm(sip, dialog);
9803 } SIPE_DIALOG_FOREACH_END;
9804 } else {
9805 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
9807 session->bid = 0;
9809 sipe_process_pending_invite_queue(sip, session);
9813 * For 2007+ conference only.
9815 static void
9816 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9818 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9819 struct sip_session *session;
9821 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
9822 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
9824 session = sipe_session_find_chat_by_title(sip, chat_title);
9826 sipe_conf_modify_user_role(sip, session, buddy->name);
9830 * For 2007+ conference only.
9832 static void
9833 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9835 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9836 struct sip_session *session;
9838 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
9839 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
9841 session = sipe_session_find_chat_by_title(sip, chat_title);
9843 sipe_conf_delete_user(sip, session, buddy->name);
9846 static void
9847 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9849 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9850 struct sip_session *session;
9852 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
9853 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
9855 session = sipe_session_find_chat_by_title(sip, chat_title);
9857 sipe_invite_to_chat(sip, session, buddy->name);
9860 static void
9861 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9863 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9865 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
9866 if (phone) {
9867 char *tel_uri = sip_to_tel_uri(phone);
9869 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
9870 sip_csta_make_call(sip, tel_uri);
9872 g_free(tel_uri);
9876 static void
9877 sipe_open_url(const char *url)
9879 const char *util;
9880 char *command_line;
9882 if (!url) return;
9884 #ifdef _WIN32
9885 util = "cmd /c start";
9886 #else
9887 util = g_str_has_prefix(url, "mailto:") ? "xdg-email" : "xdg-open";
9888 #endif
9890 command_line = g_strdup_printf("%s %s", util, url);
9891 g_spawn_command_line_async(command_line, NULL);
9892 g_free(command_line);
9895 static void
9896 sipe_buddy_menu_access_level_help_cb(SIPE_UNUSED_PARAMETER PurpleBuddy *buddy)
9898 /** Translators: replace with URL to localized page
9899 * If it doesn't exist copy the original URL */
9900 sipe_open_url(_("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
9903 static void
9904 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9906 const gchar *email;
9907 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
9909 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9910 if (email)
9912 char *mailto = g_strdup_printf("mailto:%s", email);
9913 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s", email);
9914 sipe_open_url(mailto);
9915 g_free(mailto);
9917 else
9919 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
9923 static void
9924 sipe_buddy_menu_access_level_cb(SIPE_UNUSED_PARAMETER PurpleBuddy *buddy,
9925 struct sipe_container *container)
9927 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9928 struct sipe_container_member *member;
9930 if (!container || !container->members) return;
9932 member = ((struct sipe_container_member *)container->members->data);
9934 if (!member->type) return;
9936 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
9937 container->id, member->type, member->value ? member->value : "");
9939 sipe_change_access_level(sip, container->id, member->type, member->value);
9942 static GList *
9943 sipe_get_access_control_menu(struct sipe_account_data *sip,
9944 const char* uri);
9947 * A menu which appear when right-clicking on buddy in contact list.
9949 static GList *
9950 sipe_buddy_menu(PurpleBuddy *buddy)
9952 PurpleBlistNode *g_node;
9953 PurpleGroup *group, *gr_parent;
9954 PurpleMenuAction *act;
9955 GList *menu = NULL;
9956 GList *menu_groups = NULL;
9957 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9958 const char *email;
9959 const char *phone;
9960 const char *phone_disp_str;
9961 gchar *self = sip_uri_self(sip);
9963 SIPE_SESSION_FOREACH {
9964 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9966 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9968 PurpleConvChatBuddyFlags flags;
9969 PurpleConvChatBuddyFlags flags_us;
9971 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9972 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9973 if (session->focus_uri
9974 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9975 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9977 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9978 act = purple_menu_action_new(label,
9979 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9980 session->chat_title, NULL);
9981 g_free(label);
9982 menu = g_list_prepend(menu, act);
9985 if (session->focus_uri
9986 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9988 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9989 act = purple_menu_action_new(label,
9990 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9991 session->chat_title, NULL);
9992 g_free(label);
9993 menu = g_list_prepend(menu, act);
9996 else
9998 if (!session->focus_uri
9999 || (session->focus_uri && !session->locked))
10001 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
10002 act = purple_menu_action_new(label,
10003 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
10004 session->chat_title, NULL);
10005 g_free(label);
10006 menu = g_list_prepend(menu, act);
10010 } SIPE_SESSION_FOREACH_END;
10012 act = purple_menu_action_new(_("New chat"),
10013 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
10014 NULL, NULL);
10015 menu = g_list_prepend(menu, act);
10017 if (sip->csta && !sip->csta->line_status) {
10018 gchar *tmp = NULL;
10019 /* work phone */
10020 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
10021 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
10022 if (phone) {
10023 gchar *label = g_strdup_printf(_("Work %s"),
10024 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10025 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10026 g_free(tmp);
10027 tmp = NULL;
10028 g_free(label);
10029 menu = g_list_prepend(menu, act);
10032 /* mobile phone */
10033 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
10034 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
10035 if (phone) {
10036 gchar *label = g_strdup_printf(_("Mobile %s"),
10037 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10038 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10039 g_free(tmp);
10040 tmp = NULL;
10041 g_free(label);
10042 menu = g_list_prepend(menu, act);
10045 /* home phone */
10046 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
10047 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
10048 if (phone) {
10049 gchar *label = g_strdup_printf(_("Home %s"),
10050 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10051 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10052 g_free(tmp);
10053 tmp = NULL;
10054 g_free(label);
10055 menu = g_list_prepend(menu, act);
10058 /* other phone */
10059 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
10060 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
10061 if (phone) {
10062 gchar *label = g_strdup_printf(_("Other %s"),
10063 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10064 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10065 g_free(tmp);
10066 tmp = NULL;
10067 g_free(label);
10068 menu = g_list_prepend(menu, act);
10071 /* custom1 phone */
10072 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
10073 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
10074 if (phone) {
10075 gchar *label = g_strdup_printf(_("Custom1 %s"),
10076 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10077 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10078 g_free(tmp);
10079 tmp = NULL;
10080 g_free(label);
10081 menu = g_list_prepend(menu, act);
10085 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
10086 if (email) {
10087 act = purple_menu_action_new(_("Send email..."),
10088 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
10089 NULL, NULL);
10090 menu = g_list_prepend(menu, act);
10093 /* Access Level */
10094 if (sip->ocs2007) {
10095 GList *menu_access_levels = sipe_get_access_control_menu(sip, buddy->name);
10097 act = purple_menu_action_new(_("Access level"),
10098 NULL,
10099 NULL, menu_access_levels);
10100 menu = g_list_prepend(menu, act);
10103 /* Copy to */
10104 gr_parent = purple_buddy_get_group(buddy);
10105 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
10106 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
10107 continue;
10109 group = (PurpleGroup *)g_node;
10110 if (group == gr_parent)
10111 continue;
10113 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
10114 continue;
10116 act = purple_menu_action_new(purple_group_get_name(group),
10117 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
10118 group->name, NULL);
10119 menu_groups = g_list_prepend(menu_groups, act);
10121 menu_groups = g_list_reverse(menu_groups);
10123 act = purple_menu_action_new(_("Copy to"),
10124 NULL,
10125 NULL, menu_groups);
10126 menu = g_list_prepend(menu, act);
10128 menu = g_list_reverse(menu);
10130 g_free(self);
10131 return menu;
10134 #define INDENT_FMT " %s"
10135 #define INDENT_MARKED_FMT "* %s"
10136 static GList *
10137 sipe_get_access_levels_menu(struct sipe_account_data *sip,
10138 const char* member_type,
10139 const char* member_value,
10140 const gboolean extra_menu)
10142 GList *menu_access_levels = NULL;
10143 unsigned int i;
10144 char *menu_name;
10145 PurpleMenuAction *act;
10146 struct sipe_container *container;
10147 struct sipe_container_member *member;
10148 int container_id = sipe_find_access_level(sip, member_type, member_value);
10150 for (i = 1; i <= CONTAINERS_LEN; i++) {
10151 /* to put Blocked level last in menu list.
10152 * Blocked should remaim in the first place in the containers[] array.
10154 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
10155 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
10157 container = g_new0(struct sipe_container, 1);
10158 member = g_new0(struct sipe_container_member, 1);
10159 container->id = containers[j];
10160 container->members = g_slist_append(container->members, member);
10161 member->type = g_strdup(member_type);
10162 member->value = g_strdup(member_value);
10164 /* current container/access level */
10165 if (((int)containers[j]) == container_id) {
10166 menu_name = g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
10167 } else {
10168 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
10171 act = purple_menu_action_new(menu_name,
10172 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
10173 container, NULL);
10174 g_free(menu_name);
10175 menu_access_levels = g_list_prepend(menu_access_levels, act);
10178 if (extra_menu && (container_id >= 0)) {
10179 /* separator */
10180 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
10181 menu_access_levels = g_list_prepend(menu_access_levels, act);
10183 container = g_new0(struct sipe_container, 1);
10184 member = g_new0(struct sipe_container_member, 1);
10185 container->id = -1;
10186 container->members = g_slist_append(container->members, member);
10187 member->type = g_strdup(member_type);
10188 member->value = g_strdup(member_value);
10190 /* Translators: remove (clear) previously assigned access level */
10191 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
10192 act = purple_menu_action_new(menu_name,
10193 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
10194 container, NULL);
10195 g_free(menu_name);
10196 menu_access_levels = g_list_prepend(menu_access_levels, act);
10199 menu_access_levels = g_list_reverse(menu_access_levels);
10200 return menu_access_levels;
10203 static GList *
10204 sipe_get_access_control_menu(struct sipe_account_data *sip,
10205 const char* uri)
10207 GList *menu_access_levels = NULL;
10208 GList *menu_access_groups = NULL;
10209 char *menu_name;
10210 PurpleMenuAction *act;
10212 menu_access_levels = sipe_get_access_levels_menu(sip, "user", sipe_get_no_sip_uri(uri), FALSE);
10214 /* Access Groups submenu */
10215 act = purple_menu_action_new(_("People in my company"),
10216 NULL,
10217 NULL, sipe_get_access_levels_menu(sip, "sameEnterprise", NULL, FALSE));
10218 menu_access_groups = g_list_prepend(menu_access_groups, act);
10220 /* this is original name, don't edit */
10221 act = purple_menu_action_new(_("People in domains connected with my company"),
10222 NULL,
10223 NULL, sipe_get_access_levels_menu(sip, "federated", NULL, FALSE));
10224 menu_access_groups = g_list_prepend(menu_access_groups, act);
10226 act = purple_menu_action_new(_("People in public domains"),
10227 NULL,
10228 NULL, sipe_get_access_levels_menu(sip, "publicCloud", NULL, TRUE));
10229 menu_access_groups = g_list_prepend(menu_access_groups, act);
10231 /* @TODO Add Domain */
10233 menu_access_groups = g_list_reverse(menu_access_groups);
10234 /* End of Access Groups submenu */
10236 /* separator */
10237 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
10238 menu_access_levels = g_list_append(menu_access_levels, act);
10240 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
10241 act = purple_menu_action_new(menu_name,
10242 NULL,
10243 NULL, menu_access_groups);
10244 g_free(menu_name);
10245 menu_access_levels = g_list_append(menu_access_levels, act);
10247 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
10248 act = purple_menu_action_new(menu_name,
10249 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
10250 NULL, NULL);
10251 g_free(menu_name);
10252 menu_access_levels = g_list_append(menu_access_levels, act);
10254 return menu_access_levels;
10257 static void
10258 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
10260 struct sipe_account_data *sip = chat->account->gc->proto_data;
10261 struct sip_session *session;
10263 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10264 sipe_conf_modify_conference_lock(sip, session, locked);
10267 static void
10268 sipe_chat_menu_unlock_cb(PurpleChat *chat)
10270 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
10271 sipe_conf_modify_lock(chat, FALSE);
10274 static void
10275 sipe_chat_menu_lock_cb(PurpleChat *chat)
10277 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
10278 sipe_conf_modify_lock(chat, TRUE);
10281 static GList *
10282 sipe_chat_menu(PurpleChat *chat)
10284 PurpleMenuAction *act;
10285 PurpleConvChatBuddyFlags flags_us;
10286 GList *menu = NULL;
10287 struct sipe_account_data *sip = chat->account->gc->proto_data;
10288 struct sip_session *session;
10289 gchar *self;
10291 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10292 if (!session) return NULL;
10294 self = sip_uri_self(sip);
10295 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
10297 if (session->focus_uri
10298 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
10300 if (session->locked) {
10301 act = purple_menu_action_new(_("Unlock"),
10302 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
10303 NULL, NULL);
10304 menu = g_list_prepend(menu, act);
10305 } else {
10306 act = purple_menu_action_new(_("Lock"),
10307 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
10308 NULL, NULL);
10309 menu = g_list_prepend(menu, act);
10313 menu = g_list_reverse(menu);
10315 g_free(self);
10316 return menu;
10319 static GList *
10320 sipe_blist_node_menu(PurpleBlistNode *node)
10322 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
10323 return sipe_buddy_menu((PurpleBuddy *) node);
10324 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
10325 return sipe_chat_menu((PurpleChat *)node);
10326 } else {
10327 return NULL;
10331 static gboolean
10332 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
10334 char *uri = trans->payload->data;
10336 PurpleNotifyUserInfo *info;
10337 PurpleBuddy *pbuddy = NULL;
10338 struct sipe_buddy *sbuddy;
10339 const char *alias = NULL;
10340 char *device_name = NULL;
10341 char *server_alias = NULL;
10342 char *phone_number = NULL;
10343 char *email = NULL;
10344 const char *site;
10345 char *first_name = NULL;
10346 char *last_name = NULL;
10348 if (!sip) return FALSE;
10350 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sip->username);
10352 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
10353 alias = purple_buddy_get_local_alias(pbuddy);
10355 //will query buddy UA's capabilities and send answer to log
10356 sipe_options_request(sip, uri);
10358 sbuddy = g_hash_table_lookup(sip->buddies, uri);
10359 if (sbuddy) {
10360 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
10363 info = purple_notify_user_info_new();
10365 if (msg->response != 200) {
10366 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg->response);
10367 } else {
10368 sipe_xml *searchResults;
10369 const sipe_xml *mrow;
10371 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
10372 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
10373 if (!searchResults) {
10374 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
10375 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
10376 const char *value;
10377 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
10378 email = g_strdup(sipe_xml_attribute(mrow, "email"));
10379 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
10381 /* For 2007 system we will take this from ContactCard -
10382 * it has cleaner tel: URIs at least
10384 if (!sip->ocs2007) {
10385 char *tel_uri = sip_to_tel_uri(phone_number);
10386 /* trims its parameters, so call first */
10387 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
10388 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
10389 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
10390 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
10391 g_free(tel_uri);
10394 if (server_alias && strlen(server_alias) > 0) {
10395 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10397 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
10398 purple_notify_user_info_add_pair(info, _("Job title"), value);
10400 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
10401 purple_notify_user_info_add_pair(info, _("Office"), value);
10403 if (phone_number && strlen(phone_number) > 0) {
10404 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
10406 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
10407 purple_notify_user_info_add_pair(info, _("Company"), value);
10409 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
10410 purple_notify_user_info_add_pair(info, _("City"), value);
10412 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
10413 purple_notify_user_info_add_pair(info, _("State"), value);
10415 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
10416 purple_notify_user_info_add_pair(info, _("Country"), value);
10418 if (email && strlen(email) > 0) {
10419 purple_notify_user_info_add_pair(info, _("Email address"), email);
10423 sipe_xml_free(searchResults);
10426 purple_notify_user_info_add_section_break(info);
10428 if (is_empty(server_alias)) {
10429 g_free(server_alias);
10430 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
10431 if (server_alias) {
10432 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10436 /* present alias if it differs from server alias */
10437 if (alias && !sipe_strequal(alias, server_alias))
10439 purple_notify_user_info_add_pair(info, _("Alias"), alias);
10442 if (is_empty(email)) {
10443 g_free(email);
10444 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
10445 if (email) {
10446 purple_notify_user_info_add_pair(info, _("Email address"), email);
10450 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
10451 if (site) {
10452 purple_notify_user_info_add_pair(info, _("Site"), site);
10455 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
10456 if (first_name && last_name) {
10457 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
10459 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
10460 g_free(link);
10462 g_free(first_name);
10463 g_free(last_name);
10465 if (device_name) {
10466 purple_notify_user_info_add_pair(info, _("Device"), device_name);
10469 /* show a buddy's user info in a nice dialog box */
10470 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
10471 uri, /* buddy's URI */
10472 info, /* body */
10473 NULL, /* callback called when dialog closed */
10474 NULL); /* userdata for callback */
10476 g_free(phone_number);
10477 g_free(server_alias);
10478 g_free(email);
10479 g_free(device_name);
10481 return TRUE;
10485 * AD search first, LDAP based
10487 static void sipe_get_info(PurpleConnection *gc, const char *username)
10489 struct sipe_account_data *sip = gc->proto_data;
10490 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
10491 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
10492 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
10493 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
10495 payload->destroy = g_free;
10496 payload->data = g_strdup(username);
10498 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
10499 send_soap_request_with_cb(sip, domain_uri, body,
10500 (TransCallback) process_get_info_response, payload);
10501 g_free(domain_uri);
10502 g_free(body);
10503 g_free(row);
10506 PurplePluginProtocolInfo prpl_info =
10508 OPT_PROTO_CHAT_TOPIC,
10509 NULL, /* user_splits */
10510 NULL, /* protocol_options */
10511 NO_BUDDY_ICONS, /* icon_spec */
10512 sipe_list_icon, /* list_icon */
10513 NULL, /* list_emblems */
10514 sipe_status_text, /* status_text */
10515 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10516 sipe_status_types, /* away_states */
10517 sipe_blist_node_menu, /* blist_node_menu */
10518 NULL, /* chat_info */
10519 NULL, /* chat_info_defaults */
10520 sipe_login, /* login */
10521 sipe_close, /* close */
10522 sipe_im_send, /* send_im */
10523 NULL, /* set_info */ // TODO maybe
10524 sipe_send_typing, /* send_typing */
10525 sipe_get_info, /* get_info */
10526 sipe_set_status, /* set_status */
10527 sipe_set_idle, /* set_idle */
10528 NULL, /* change_passwd */
10529 sipe_add_buddy, /* add_buddy */
10530 NULL, /* add_buddies */
10531 sipe_remove_buddy, /* remove_buddy */
10532 NULL, /* remove_buddies */
10533 sipe_add_permit, /* add_permit */
10534 sipe_add_deny, /* add_deny */
10535 sipe_add_deny, /* rem_permit */
10536 sipe_add_permit, /* rem_deny */
10537 dummy_permit_deny, /* set_permit_deny */
10538 NULL, /* join_chat */
10539 NULL, /* reject_chat */
10540 NULL, /* get_chat_name */
10541 sipe_chat_invite, /* chat_invite */
10542 sipe_chat_leave, /* chat_leave */
10543 NULL, /* chat_whisper */
10544 sipe_chat_send, /* chat_send */
10545 sipe_keep_alive, /* keepalive */
10546 NULL, /* register_user */
10547 NULL, /* get_cb_info */ // deprecated
10548 NULL, /* get_cb_away */ // deprecated
10549 sipe_alias_buddy, /* alias_buddy */
10550 sipe_group_buddy, /* group_buddy */
10551 sipe_rename_group, /* rename_group */
10552 NULL, /* buddy_free */
10553 sipe_convo_closed, /* convo_closed */
10554 purple_normalize_nocase, /* normalize */
10555 NULL, /* set_buddy_icon */
10556 sipe_remove_group, /* remove_group */
10557 NULL, /* get_cb_real_name */ // TODO?
10558 NULL, /* set_chat_topic */
10559 NULL, /* find_blist_chat */
10560 NULL, /* roomlist_get_list */
10561 NULL, /* roomlist_cancel */
10562 NULL, /* roomlist_expand_category */
10563 NULL, /* can_receive_file */
10564 sipe_ft_send_file, /* send_file */
10565 sipe_ft_new_xfer, /* new_xfer */
10566 NULL, /* offline_message */
10567 NULL, /* whiteboard_prpl_ops */
10568 sipe_send_raw, /* send_raw */
10569 NULL, /* roomlist_room_serialize */
10570 NULL, /* unregister_user */
10571 NULL, /* send_attention */
10572 NULL, /* get_attention_types */
10573 #if !PURPLE_VERSION_CHECK(2,5,0)
10574 /* Backward compatibility when compiling against 2.4.x API */
10575 (void (*)(void)) /* _purple_reserved4 */
10576 #endif
10577 sizeof(PurplePluginProtocolInfo), /* struct_size */
10578 #if PURPLE_VERSION_CHECK(2,5,0)
10579 sipe_get_account_text_table, /* get_account_text_table */
10580 #if PURPLE_VERSION_CHECK(2,6,0)
10581 NULL, /* initiate_media */
10582 NULL, /* get_media_caps */
10583 #if PURPLE_VERSION_CHECK(2,7,0)
10584 NULL, /* get_moods */
10585 #endif
10586 #endif
10587 #endif
10591 PurplePluginInfo info = {
10592 PURPLE_PLUGIN_MAGIC,
10593 PURPLE_MAJOR_VERSION,
10594 PURPLE_MINOR_VERSION,
10595 PURPLE_PLUGIN_PROTOCOL, /**< type */
10596 NULL, /**< ui_requirement */
10597 0, /**< flags */
10598 NULL, /**< dependencies */
10599 PURPLE_PRIORITY_DEFAULT, /**< priority */
10600 "prpl-sipe", /**< id */
10601 "Office Communicator", /**< name */
10602 PACKAGE_VERSION, /**< version */
10603 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10604 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10605 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10606 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10607 "Gabriel Burt <gburt@novell.com>, " /**< author */
10608 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10609 "pier11 <pier11@operamail.com>", /**< author */
10610 PACKAGE_URL, /**< homepage */
10611 sipe_plugin_load, /**< load */
10612 sipe_plugin_unload, /**< unload */
10613 sipe_plugin_destroy, /**< destroy */
10614 NULL, /**< ui_info */
10615 &prpl_info, /**< extra_info */
10616 NULL,
10617 sipe_actions,
10618 NULL,
10619 NULL,
10620 NULL,
10621 NULL
10624 void sipe_core_init(void)
10626 srand(time(NULL));
10627 sip_sec_init();
10629 #ifdef ENABLE_NLS
10630 SIPE_DEBUG_INFO("bindtextdomain = %s",
10631 bindtextdomain(PACKAGE_NAME, LOCALEDIR));
10632 SIPE_DEBUG_INFO("bind_textdomain_codeset = %s",
10633 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"));
10634 textdomain(PACKAGE_NAME);
10635 #endif
10636 #ifdef HAVE_GMIME
10637 g_mime_init(0);
10638 #endif
10641 void sipe_core_destroy(void)
10643 #ifdef HAVE_GMIME
10644 g_mime_shutdown();
10645 #endif
10646 sip_sec_destroy();
10650 Local Variables:
10651 mode: c
10652 c-file-style: "bsd"
10653 indent-tabs-mode: t
10654 tab-width: 8
10655 End: