access: move Blocked level to the botton
[siplcs.git] / src / core / sipe.c
blobd4038f78cddb6f8ced1b08184f0ac3fc37a2c54e
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #ifdef _WIN32
41 #include "win32dep.h" /* for LOCALEDIR */
42 #ifdef _DLL
43 #define _WS2TCPIP_H_
44 #define _WINSOCK2API_
45 #define _LIBC_INTERNAL_
46 #endif /* _DLL */
47 /* for network */
48 #include "libc_interface.h"
49 #else
50 #include <sys/types.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #endif /* _WIN32 */
55 #include <time.h>
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <errno.h>
59 #include <string.h>
60 #include <unistd.h>
62 #include <glib.h>
63 #ifdef HAVE_GMIME
64 #include <gmime/gmime.h>
65 #endif
67 #include "sipe-common.h"
69 #include "account.h"
70 #include "blist.h"
71 #include "connection.h"
72 #include "conversation.h"
73 #include "core.h"
74 #include "cipher.h"
75 #include "circbuffer.h"
76 #include "dnsquery.h"
77 #include "dnssrv.h"
78 #include "ft.h"
79 #include "network.h"
80 #include "notify.h"
81 #include "plugin.h"
82 #include "privacy.h"
83 #include "request.h"
84 #include "savedstatuses.h"
85 #include "sslconn.h"
86 #include "version.h"
87 #include "xmlnode.h"
89 #include "core-depurple.h" /* Temporary for the core de-purple transition */
91 #include "sipmsg.h"
92 #include "sip-csta.h"
93 #include "sip-sec.h"
94 #include "sipe-backend.h"
95 #include "sipe-cal.h"
96 #include "sipe-chat.h"
97 #include "sipe-conf.h"
98 #include "sipe-core.h"
99 #include "sipe-dialog.h"
100 #include "sipe-ews.h"
101 #include "sipe-ft.h"
102 #include "sipe-mime.h"
103 #include "sipe-nls.h"
104 #include "sipe-session.h"
105 #include "sipe-sign.h"
106 #include "sipe-utils.h"
107 #include "sipe-xml.h"
108 #include "http-conn.h"
109 #include "uuid.h"
110 #include "sipe.h"
112 /* Backward compatibility when compiling against 2.4.x API */
113 #if !PURPLE_VERSION_CHECK(2,5,0)
114 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
115 #endif
117 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
119 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
120 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
122 /* Keep in sync with sipe_transport_type! */
123 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
124 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
126 /* Status identifiers (see also: sipe_status_types()) */
127 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
128 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
129 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
130 /* PURPLE_STATUS_UNAVAILABLE: */
131 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
132 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
133 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
134 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
135 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
136 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
137 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
138 /* PURPLE_STATUS_AWAY: */
139 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
140 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
141 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
142 /** Reuters status (user settable) */
143 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
144 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
145 /* ??? PURPLE_STATUS_MOBILE */
146 /* ??? PURPLE_STATUS_TUNE */
148 /* Status attributes (see also sipe_status_types() */
149 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
151 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
153 static struct sipe_activity_map_struct
155 sipe_activity type;
156 const char *token;
157 const char *desc;
158 const char *status_id;
160 } const sipe_activity_map[] =
162 /* This has nothing to do with Availability numbers, like 3500 (online).
163 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
165 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
166 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
167 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
168 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
169 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
170 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
171 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
172 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
173 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
174 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
175 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
176 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
177 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
178 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
179 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
181 /** @param x is sipe_activity */
182 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
185 /* Action name templates */
186 #define ACTION_NAME_PRESENCE "<presence><%s>"
188 static sipe_activity
189 sipe_get_activity_by_token(const char *token)
191 int i;
193 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
195 if (sipe_strequal(token, sipe_activity_map[i].token))
196 return sipe_activity_map[i].type;
199 return sipe_activity_map[0].type;
202 static const char *
203 sipe_get_activity_desc_by_token(const char *token)
205 if (!token) return NULL;
207 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
210 /** Allows to send typed messages from chat window again after account reinstantiation. */
211 static void
212 sipe_rejoin_chat(PurpleConversation *conv)
214 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
215 PURPLE_CONV_CHAT(conv)->left)
217 PURPLE_CONV_CHAT(conv)->left = FALSE;
218 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
222 static char *genbranch()
224 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
225 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
226 rand() & 0xFFFF, rand() & 0xFFFF);
230 static char *default_ua = NULL;
231 static const char*
232 sipe_get_useragent(struct sipe_account_data *sip)
234 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
235 if (is_empty(useragent)) {
236 if (!default_ua) {
237 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
238 /* ref: lzodefs.h */
239 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
240 #define SIPE_TARGET_PLATFORM "linux"
241 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
242 #define SIPE_TARGET_PLATFORM "bsd"
243 #elif defined(__APPLE__) || defined(__MACOS__)
244 #define SIPE_TARGET_PLATFORM "macosx"
245 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
246 #define SIPE_TARGET_PLATFORM "aix"
247 #elif defined(__solaris__) || defined(__sun)
248 #define SIPE_TARGET_PLATFORM "sun"
249 #elif defined(_WIN32)
250 #define SIPE_TARGET_PLATFORM "win"
251 #elif defined(__CYGWIN__)
252 #define SIPE_TARGET_PLATFORM "cygwin"
253 #elif defined(__hpux__)
254 #define SIPE_TARGET_PLATFORM "hpux"
255 #elif defined(__sgi__)
256 #define SIPE_TARGET_PLATFORM "irix"
257 #else
258 #define SIPE_TARGET_PLATFORM "unknown"
259 #endif
261 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
262 #define SIPE_TARGET_ARCH "x86_64"
263 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
264 #define SIPE_TARGET_ARCH "i386"
265 #elif defined(__ppc64__)
266 #define SIPE_TARGET_ARCH "ppc64"
267 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
268 #define SIPE_TARGET_ARCH "ppc"
269 #elif defined(__hppa__) || defined(__hppa)
270 #define SIPE_TARGET_ARCH "hppa"
271 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
272 #define SIPE_TARGET_ARCH "mips"
273 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
274 #define SIPE_TARGET_ARCH "s390"
275 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
276 #define SIPE_TARGET_ARCH "sparc"
277 #elif defined(__arm__)
278 #define SIPE_TARGET_ARCH "arm"
279 #else
280 #define SIPE_TARGET_ARCH "other"
281 #endif
283 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
284 purple_core_get_version(),
285 sip->server_version ? sip->server_version : "");
287 useragent = default_ua;
289 return useragent;
292 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
293 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
295 return "sipe";
298 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
300 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
301 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
302 gpointer data);
304 static void sipe_close(PurpleConnection *gc);
306 static void send_presence_status(struct sipe_account_data *sip);
308 static void sendout_pkt(PurpleConnection *gc, const char *buf);
310 static void sipe_keep_alive(PurpleConnection *gc)
312 struct sipe_account_data *sip = gc->proto_data;
313 if (sip->transport == SIPE_TRANSPORT_UDP) {
314 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
315 gchar buf[2] = {0, 0};
316 SIPE_DEBUG_INFO_NOFORMAT("sending keep alive");
317 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
318 } else {
319 time_t now = time(NULL);
320 if ((sip->keepalive_timeout > 0) &&
321 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
322 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
324 SIPE_DEBUG_INFO("sending keep alive %d", sip->keepalive_timeout);
325 sendout_pkt(gc, "\r\n\r\n");
326 sip->last_keepalive = now;
331 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
333 struct sip_connection *ret = NULL;
334 GSList *entry = sip->openconns;
335 while (entry) {
336 ret = entry->data;
337 if (ret->fd == fd) return ret;
338 entry = entry->next;
340 return NULL;
343 static void sipe_auth_free(struct sip_auth *auth)
345 g_free(auth->opaque);
346 auth->opaque = NULL;
347 g_free(auth->realm);
348 auth->realm = NULL;
349 g_free(auth->target);
350 auth->target = NULL;
351 auth->version = 0;
352 auth->type = AUTH_TYPE_UNSET;
353 auth->retries = 0;
354 auth->expires = 0;
355 g_free(auth->gssapi_data);
356 auth->gssapi_data = NULL;
357 sip_sec_destroy_context(auth->gssapi_context);
358 auth->gssapi_context = NULL;
361 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
363 struct sip_connection *ret = g_new0(struct sip_connection, 1);
364 ret->fd = fd;
365 sip->openconns = g_slist_append(sip->openconns, ret);
366 return ret;
369 static void connection_remove(struct sipe_account_data *sip, int fd)
371 struct sip_connection *conn = connection_find(sip, fd);
372 if (conn) {
373 sip->openconns = g_slist_remove(sip->openconns, conn);
374 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
375 g_free(conn->inbuf);
376 g_free(conn);
380 static void connection_free_all(struct sipe_account_data *sip)
382 struct sip_connection *ret = NULL;
383 GSList *entry = sip->openconns;
384 while (entry) {
385 ret = entry->data;
386 connection_remove(sip, ret->fd);
387 entry = sip->openconns;
391 static void
392 sipe_make_signature(struct sipe_account_data *sip,
393 struct sipmsg *msg);
395 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
397 gchar noncecount[9];
398 const char *authuser = sip->authuser;
399 gchar *response;
400 gchar *ret;
402 if (!authuser || strlen(authuser) < 1) {
403 authuser = sip->username;
406 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
407 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
408 gchar *version_str;
410 // If we have a signature for the message, include that
411 if (msg->signature) {
412 return g_strdup_printf("%s qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth_protocol, auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
415 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
416 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
417 gchar *gssapi_data;
418 gchar *opaque;
419 gchar *sign_str = NULL;
421 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
422 &(auth->expires),
423 auth->type,
424 purple_account_get_bool(sip->account, "sso", TRUE),
425 sip->authdomain ? sip->authdomain : "",
426 authuser,
427 sip->password,
428 auth->target,
429 auth->gssapi_data);
430 if (!gssapi_data || !auth->gssapi_context) {
431 sip->gc->wants_to_die = TRUE;
432 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
433 return NULL;
436 if (auth->version > 3) {
437 sipe_make_signature(sip, msg);
438 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
439 msg->rand, msg->num, msg->signature);
440 } else {
441 sign_str = g_strdup("");
444 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
445 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
446 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"%s%s", auth_protocol, opaque, auth->realm, auth->target, gssapi_data, version_str, sign_str);
447 g_free(opaque);
448 g_free(gssapi_data);
449 g_free(version_str);
450 g_free(sign_str);
451 return ret;
454 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
455 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
456 g_free(version_str);
457 return ret;
459 } else { /* Digest */
461 /* Calculate new session key */
462 if (!auth->opaque) {
463 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth->gssapi_data, auth->realm);
464 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
465 authuser, auth->realm, sip->password,
466 auth->gssapi_data, NULL);
469 sprintf(noncecount, "%08d", auth->nc++);
470 response = purple_cipher_http_digest_calculate_response("md5",
471 msg->method, msg->target, NULL, NULL,
472 auth->gssapi_data, noncecount, NULL,
473 auth->opaque);
474 SIPE_DEBUG_INFO("Digest response %s", response);
476 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->gssapi_data, msg->target, noncecount, response);
477 g_free(response);
478 return ret;
482 static char *parse_attribute(const char *attrname, const char *source)
484 const char *tmp, *tmp2;
485 char *retval = NULL;
486 int len = strlen(attrname);
488 if (g_str_has_prefix(source, attrname)) {
489 tmp = source + len;
490 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
491 if (tmp2)
492 retval = g_strndup(tmp, tmp2 - tmp);
493 else
494 retval = g_strdup(tmp);
497 return retval;
500 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
502 int i;
503 gchar **parts;
505 if (!hdr) {
506 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
507 return;
510 if (!g_strncasecmp(hdr, "NTLM", 4)) {
511 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
512 auth->type = AUTH_TYPE_NTLM;
513 hdr += 5;
514 auth->nc = 1;
515 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
516 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
517 auth->type = AUTH_TYPE_KERBEROS;
518 hdr += 9;
519 auth->nc = 3;
520 } else {
521 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
522 auth->type = AUTH_TYPE_DIGEST;
523 hdr += 7;
526 parts = g_strsplit(hdr, "\", ", 0);
527 for (i = 0; parts[i]; i++) {
528 char *tmp;
530 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
532 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
533 g_free(auth->gssapi_data);
534 auth->gssapi_data = tmp;
536 if (auth->type == AUTH_TYPE_NTLM) {
537 /* NTLM module extracts nonce from gssapi-data */
538 auth->nc = 3;
541 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
542 /* Only used with AUTH_TYPE_DIGEST */
543 g_free(auth->gssapi_data);
544 auth->gssapi_data = tmp;
545 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
546 g_free(auth->opaque);
547 auth->opaque = tmp;
548 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
549 g_free(auth->realm);
550 auth->realm = tmp;
552 if (auth->type == AUTH_TYPE_DIGEST) {
553 /* Throw away old session key */
554 g_free(auth->opaque);
555 auth->opaque = NULL;
556 auth->nc = 1;
558 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
559 g_free(auth->target);
560 auth->target = tmp;
561 } else if ((tmp = parse_attribute("version=", parts[i]))) {
562 auth->version = atoi(tmp);
563 g_free(tmp);
565 // uncomment to revert to previous functionality if version 3+ does not work.
566 // auth->version = 2;
568 g_strfreev(parts);
570 return;
573 static void sipe_canwrite_cb(gpointer data,
574 SIPE_UNUSED_PARAMETER gint source,
575 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
577 PurpleConnection *gc = data;
578 struct sipe_account_data *sip = gc->proto_data;
579 gsize max_write;
580 gssize written;
582 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
584 if (max_write == 0) {
585 if (sip->tx_handler != 0){
586 purple_input_remove(sip->tx_handler);
587 sip->tx_handler = 0;
589 return;
592 written = write(sip->fd, sip->txbuf->outptr, max_write);
594 if (written < 0 && errno == EAGAIN)
595 written = 0;
596 else if (written <= 0) {
597 /*TODO: do we really want to disconnect on a failure to write?*/
598 purple_connection_error(gc, _("Could not write"));
599 return;
602 purple_circ_buffer_mark_read(sip->txbuf, written);
605 static void sipe_canwrite_cb_ssl(gpointer data,
606 SIPE_UNUSED_PARAMETER gint src,
607 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
609 PurpleConnection *gc = data;
610 struct sipe_account_data *sip = gc->proto_data;
611 gsize max_write;
612 gssize written;
614 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
616 if (max_write == 0) {
617 if (sip->tx_handler != 0) {
618 purple_input_remove(sip->tx_handler);
619 sip->tx_handler = 0;
620 return;
624 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
626 if (written < 0 && errno == EAGAIN)
627 written = 0;
628 else if (written <= 0) {
629 /*TODO: do we really want to disconnect on a failure to write?*/
630 purple_connection_error(gc, _("Could not write"));
631 return;
634 purple_circ_buffer_mark_read(sip->txbuf, written);
637 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
639 static void send_later_cb(gpointer data, gint source,
640 SIPE_UNUSED_PARAMETER const gchar *error)
642 PurpleConnection *gc = data;
643 struct sipe_account_data *sip;
644 struct sip_connection *conn;
646 if (!PURPLE_CONNECTION_IS_VALID(gc))
648 if (source >= 0)
649 close(source);
650 return;
653 if (source < 0) {
654 purple_connection_error(gc, _("Could not connect"));
655 return;
658 sip = gc->proto_data;
659 sip->fd = source;
660 sip->connecting = FALSE;
661 sip->last_keepalive = time(NULL);
663 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
665 /* If there is more to write now, we need to register a handler */
666 if (sip->txbuf->bufused > 0)
667 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
669 conn = connection_create(sip, source);
670 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
673 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
675 struct sipe_account_data *sip;
677 if (!PURPLE_CONNECTION_IS_VALID(gc))
679 if (gsc) purple_ssl_close(gsc);
680 return NULL;
683 sip = gc->proto_data;
684 sip->fd = gsc->fd;
685 sip->gsc = gsc;
686 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
687 sip->connecting = FALSE;
688 sip->last_keepalive = time(NULL);
690 connection_create(sip, gsc->fd);
692 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
694 return sip;
697 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
698 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
700 PurpleConnection *gc = data;
701 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
702 if (sip == NULL) return;
704 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
706 /* If there is more to write now */
707 if (sip->txbuf->bufused > 0) {
708 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
713 static void sendlater(PurpleConnection *gc, const char *buf)
715 struct sipe_account_data *sip = gc->proto_data;
717 if (!sip->connecting) {
718 SIPE_DEBUG_INFO("connecting to %s port %d", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
719 if (sip->transport == SIPE_TRANSPORT_TLS){
720 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
721 } else {
722 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
723 purple_connection_error(gc, _("Could not create socket"));
726 sip->connecting = TRUE;
729 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
730 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
732 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
735 static void sendout_pkt(PurpleConnection *gc, const char *buf)
737 struct sipe_account_data *sip = gc->proto_data;
738 time_t currtime = time(NULL);
739 int writelen = strlen(buf);
740 char *tmp;
742 SIPE_DEBUG_INFO("sending - %s######\n%s######", ctime(&currtime), tmp = fix_newlines(buf));
743 g_free(tmp);
744 if (sip->transport == SIPE_TRANSPORT_UDP) {
745 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
746 SIPE_DEBUG_INFO_NOFORMAT("could not send packet");
748 } else {
749 int ret;
750 if (sip->fd < 0) {
751 sendlater(gc, buf);
752 return;
755 if (sip->tx_handler) {
756 ret = -1;
757 errno = EAGAIN;
758 } else{
759 if (sip->gsc){
760 ret = purple_ssl_write(sip->gsc, buf, writelen);
761 }else{
762 ret = write(sip->fd, buf, writelen);
766 if (ret < 0 && errno == EAGAIN)
767 ret = 0;
768 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
769 sendlater(gc, buf);
770 return;
773 if (ret < writelen) {
774 if (!sip->tx_handler){
775 if (sip->gsc){
776 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
778 else{
779 sip->tx_handler = purple_input_add(sip->fd,
780 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
781 gc);
785 /* XXX: is it OK to do this? You might get part of a request sent
786 with part of another. */
787 if (sip->txbuf->bufused > 0)
788 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
790 purple_circ_buffer_append(sip->txbuf, buf + ret,
791 writelen - ret);
796 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
798 sendout_pkt(gc, buf);
799 return len;
802 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
804 GSList *tmp = msg->headers;
805 gchar *name;
806 gchar *value;
807 GString *outstr = g_string_new("");
808 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
809 while (tmp) {
810 name = ((struct sipnameval*) (tmp->data))->name;
811 value = ((struct sipnameval*) (tmp->data))->value;
812 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
813 tmp = g_slist_next(tmp);
815 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
816 sendout_pkt(sip->gc, outstr->str);
817 g_string_free(outstr, TRUE);
820 static void
821 sipe_make_signature(struct sipe_account_data *sip,
822 struct sipmsg *msg)
824 if (sip->registrar.gssapi_context) {
825 struct sipmsg_breakdown msgbd;
826 gchar *signature_input_str;
827 msgbd.msg = msg;
828 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
829 msgbd.rand = g_strdup_printf("%08x", g_random_int());
830 sip->registrar.ntlm_num++;
831 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
832 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
833 if (signature_input_str != NULL) {
834 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
835 msg->signature = signature_hex;
836 msg->rand = g_strdup(msgbd.rand);
837 msg->num = g_strdup(msgbd.num);
838 g_free(signature_input_str);
840 sipmsg_breakdown_free(&msgbd);
844 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
846 gchar * buf;
848 if (sip->registrar.type == AUTH_TYPE_UNSET) {
849 return;
852 sipe_make_signature(sip, msg);
854 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
855 buf = auth_header(sip, &sip->registrar, msg);
856 if (buf) {
857 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
859 g_free(buf);
860 } else if (sipe_strequal(method,"SUBSCRIBE") || sipe_strequal(method,"SERVICE") || sipe_strequal(method,"MESSAGE") || sipe_strequal(method,"INVITE") || sipe_strequal(method, "ACK") || sipe_strequal(method, "NOTIFY") || sipe_strequal(method, "BYE") || sipe_strequal(method, "INFO") || sipe_strequal(method, "OPTIONS") || sipe_strequal(method, "REFER")) {
861 sip->registrar.nc = 3;
862 sip->registrar.type = AUTH_TYPE_NTLM;
863 #ifdef HAVE_KERBEROS
864 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
865 sip->registrar.type = AUTH_TYPE_KERBEROS;
867 #endif
870 buf = auth_header(sip, &sip->registrar, msg);
871 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
872 g_free(buf);
873 } else {
874 SIPE_DEBUG_INFO("not adding auth header to msg w/ method %s", method);
878 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
879 const char *text, const char *body)
881 gchar *name;
882 gchar *value;
883 GString *outstr = g_string_new("");
884 struct sipe_account_data *sip = gc->proto_data;
885 gchar *contact;
886 GSList *tmp;
887 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
889 /* Can return NULL! */
890 contact = get_contact(sip);
891 if (contact) {
892 sipmsg_add_header(msg, "Contact", contact);
893 g_free(contact);
896 if (body) {
897 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
898 sipmsg_add_header(msg, "Content-Length", len);
899 g_free(len);
900 } else {
901 sipmsg_add_header(msg, "Content-Length", "0");
904 msg->response = code;
906 sipmsg_strip_headers(msg, keepers);
907 sipmsg_merge_new_headers(msg);
908 sign_outgoing_message(msg, sip, msg->method);
910 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
911 tmp = msg->headers;
912 while (tmp) {
913 name = ((struct sipnameval*) (tmp->data))->name;
914 value = ((struct sipnameval*) (tmp->data))->value;
916 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
917 tmp = g_slist_next(tmp);
919 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
920 sendout_pkt(gc, outstr->str);
921 g_string_free(outstr, TRUE);
924 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
926 if (sip->transactions) {
927 sip->transactions = g_slist_remove(sip->transactions, trans);
928 SIPE_DEBUG_INFO("sip->transactions count:%d after removal", g_slist_length(sip->transactions));
930 if (trans->msg) sipmsg_free(trans->msg);
931 if (trans->payload) {
932 (*trans->payload->destroy)(trans->payload->data);
933 g_free(trans->payload);
935 g_free(trans->key);
936 g_free(trans);
940 static struct transaction *
941 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
943 const gchar *call_id;
944 const gchar *cseq;
945 struct transaction *trans = g_new0(struct transaction, 1);
947 trans->time = time(NULL);
948 trans->msg = (struct sipmsg *)msg;
949 call_id = sipmsg_find_header(trans->msg, "Call-ID");
950 cseq = sipmsg_find_header(trans->msg, "CSeq");
951 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
952 trans->callback = callback;
953 sip->transactions = g_slist_append(sip->transactions, trans);
954 SIPE_DEBUG_INFO("sip->transactions count:%d after addition", g_slist_length(sip->transactions));
955 return trans;
958 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
960 struct transaction *trans;
961 GSList *transactions = sip->transactions;
962 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
963 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
964 gchar *key;
966 if (!call_id || !cseq) {
967 SIPE_DEBUG_ERROR_NOFORMAT("transaction_find: no Call-ID or CSeq!");
968 return NULL;
971 key = g_strdup_printf("<%s><%s>", call_id, cseq);
972 while (transactions) {
973 trans = transactions->data;
974 if (!g_strcasecmp(trans->key, key)) {
975 g_free(key);
976 return trans;
978 transactions = transactions->next;
981 g_free(key);
982 return NULL;
985 struct transaction *
986 send_sip_request(PurpleConnection *gc, const gchar *method,
987 const gchar *url, const gchar *to, const gchar *addheaders,
988 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
990 struct sipe_account_data *sip = gc->proto_data;
991 const char *addh = "";
992 char *buf;
993 struct sipmsg *msg;
994 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
995 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
996 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
997 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
998 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
999 gchar *route = g_strdup("");
1000 gchar *epid = get_epid(sip);
1001 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
1002 struct transaction *trans = NULL;
1004 if (dialog && dialog->routes)
1006 GSList *iter = dialog->routes;
1008 while(iter)
1010 char *tmp = route;
1011 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1012 g_free(tmp);
1013 iter = g_slist_next(iter);
1017 if (!ourtag && !dialog) {
1018 ourtag = gentag();
1021 if (sipe_strequal(method, "REGISTER")) {
1022 if (sip->regcallid) {
1023 g_free(callid);
1024 callid = g_strdup(sip->regcallid);
1025 } else {
1026 sip->regcallid = g_strdup(callid);
1028 cseq = ++sip->cseq;
1031 if (addheaders) addh = addheaders;
1033 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1034 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1035 "From: <sip:%s>%s%s;epid=%s\r\n"
1036 "To: <%s>%s%s%s%s\r\n"
1037 "Max-Forwards: 70\r\n"
1038 "CSeq: %d %s\r\n"
1039 "User-Agent: %s\r\n"
1040 "Call-ID: %s\r\n"
1041 "%s%s"
1042 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1043 method,
1044 dialog && dialog->request ? dialog->request : url,
1045 TRANSPORT_DESCRIPTOR,
1046 purple_network_get_my_ip(-1),
1047 sip->listenport,
1048 branch ? ";branch=" : "",
1049 branch ? branch : "",
1050 sip->username,
1051 ourtag ? ";tag=" : "",
1052 ourtag ? ourtag : "",
1053 epid,
1055 theirtag ? ";tag=" : "",
1056 theirtag ? theirtag : "",
1057 theirepid ? ";epid=" : "",
1058 theirepid ? theirepid : "",
1059 cseq,
1060 method,
1061 sipe_get_useragent(sip),
1062 callid,
1063 route,
1064 addh,
1065 body ? (gsize) strlen(body) : 0,
1066 body ? body : "");
1069 //printf ("parsing msg buf:\n%s\n\n", buf);
1070 msg = sipmsg_parse_msg(buf);
1072 g_free(buf);
1073 g_free(ourtag);
1074 g_free(theirtag);
1075 g_free(theirepid);
1076 g_free(branch);
1077 g_free(callid);
1078 g_free(route);
1079 g_free(epid);
1081 sign_outgoing_message (msg, sip, method);
1083 buf = sipmsg_to_string (msg);
1085 /* add to ongoing transactions */
1086 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1087 if (!sipe_strequal(method, "ACK")) {
1088 trans = transactions_add_buf(sip, msg, tc);
1089 } else {
1090 sipmsg_free(msg);
1092 sendout_pkt(gc, buf);
1093 g_free(buf);
1095 return trans;
1099 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1101 static void
1102 send_soap_request_with_cb(struct sipe_account_data *sip,
1103 gchar *from0,
1104 gchar *body,
1105 TransCallback callback,
1106 struct transaction_payload *payload)
1108 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1109 gchar *contact = get_contact(sip);
1110 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1111 "Content-Type: application/SOAP+xml\r\n",contact);
1113 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1114 trans->payload = payload;
1116 g_free(from);
1117 g_free(contact);
1118 g_free(hdr);
1121 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1123 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1126 static char *get_contact_register(struct sipe_account_data *sip)
1128 char *epid = get_epid(sip);
1129 char *uuid = generateUUIDfromEPID(epid);
1130 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1131 g_free(uuid);
1132 g_free(epid);
1133 return(buf);
1136 static void do_register_exp(struct sipe_account_data *sip, int expire)
1138 char *uri;
1139 char *expires;
1140 char *to;
1141 char *contact;
1142 char *hdr;
1144 if (!sip->sipdomain) return;
1146 uri = sip_uri_from_name(sip->sipdomain);
1147 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1148 to = sip_uri_self(sip);
1149 contact = get_contact_register(sip);
1150 hdr = g_strdup_printf("Contact: %s\r\n"
1151 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1152 "Event: registration\r\n"
1153 "Allow-Events: presence\r\n"
1154 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1155 "%s", contact, expires);
1156 g_free(contact);
1157 g_free(expires);
1159 sip->registerstatus = 1;
1161 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1162 process_register_response);
1164 g_free(hdr);
1165 g_free(uri);
1166 g_free(to);
1169 static void do_register_cb(struct sipe_account_data *sip,
1170 SIPE_UNUSED_PARAMETER void *unused)
1172 do_register_exp(sip, -1);
1173 sip->reregister_set = FALSE;
1176 static void do_register(struct sipe_account_data *sip)
1178 do_register_exp(sip, -1);
1181 static void
1182 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1184 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1185 send_soap_request(sip, body);
1186 g_free(body);
1189 static void
1190 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1192 if (allow) {
1193 SIPE_DEBUG_INFO("Authorizing contact %s", who);
1194 } else {
1195 SIPE_DEBUG_INFO("Blocking contact %s", who);
1198 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1201 static
1202 void sipe_auth_user_cb(void * data)
1204 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1205 if (!job) return;
1207 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1208 g_free(job);
1211 static
1212 void sipe_deny_user_cb(void * data)
1214 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1215 if (!job) return;
1217 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1218 g_free(job);
1221 static void
1222 sipe_add_permit(PurpleConnection *gc, const char *name)
1224 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1225 sipe_contact_allow_deny(sip, name, TRUE);
1228 static void
1229 sipe_add_deny(PurpleConnection *gc, const char *name)
1231 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1232 sipe_contact_allow_deny(sip, name, FALSE);
1235 /*static void
1236 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1238 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1239 sipe_contact_set_acl(sip, name, "");
1242 static void
1243 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1245 xmlnode *watchers;
1246 xmlnode *watcher;
1247 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1248 if (msg->response != 0 && msg->response != 200) return;
1250 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1252 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1253 if (!watchers) return;
1255 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1256 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1257 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1258 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1260 // TODO pull out optional displayName to pass as alias
1261 if (remote_user) {
1262 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1263 job->who = remote_user;
1264 job->sip = sip;
1265 purple_account_request_authorization(
1266 sip->account,
1267 remote_user,
1268 _("you"), /* id */
1269 alias,
1270 NULL, /* message */
1271 on_list,
1272 sipe_auth_user_cb,
1273 sipe_deny_user_cb,
1274 (void *) job);
1279 xmlnode_free(watchers);
1280 return;
1283 static void
1284 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1286 PurpleGroup * purple_group = purple_find_group(group->name);
1287 if (!purple_group) {
1288 purple_group = purple_group_new(group->name);
1289 purple_blist_add_group(purple_group, NULL);
1292 if (purple_group) {
1293 group->purple_group = purple_group;
1294 sip->groups = g_slist_append(sip->groups, group);
1295 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
1296 } else {
1297 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
1301 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1303 struct sipe_group *group;
1304 GSList *entry;
1305 if (sip == NULL) {
1306 return NULL;
1309 entry = sip->groups;
1310 while (entry) {
1311 group = entry->data;
1312 if (group->id == id) {
1313 return group;
1315 entry = entry->next;
1317 return NULL;
1320 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1322 struct sipe_group *group;
1323 GSList *entry;
1324 if (!sip || !name) {
1325 return NULL;
1328 entry = sip->groups;
1329 while (entry) {
1330 group = entry->data;
1331 if (sipe_strequal(group->name, name)) {
1332 return group;
1334 entry = entry->next;
1336 return NULL;
1339 static void
1340 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1342 gchar *body;
1343 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
1344 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1345 send_soap_request(sip, body);
1346 g_free(body);
1347 g_free(group->name);
1348 group->name = g_strdup(name);
1352 * Only appends if no such value already stored.
1353 * Like Set in Java.
1355 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1356 GSList * res = list;
1357 if (!g_slist_find_custom(list, data, func)) {
1358 res = g_slist_insert_sorted(list, data, func);
1360 return res;
1363 static int
1364 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1365 return group1->id - group2->id;
1369 * Returns string like "2 4 7 8" - group ids buddy belong to.
1371 static gchar *
1372 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1373 int i = 0;
1374 gchar *res;
1375 //creating array from GList, converting int to gchar*
1376 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1377 GSList *entry = buddy->groups;
1379 if (!ids_arr) return NULL;
1381 while (entry) {
1382 struct sipe_group * group = entry->data;
1383 ids_arr[i] = g_strdup_printf("%d", group->id);
1384 entry = entry->next;
1385 i++;
1387 ids_arr[i] = NULL;
1388 res = g_strjoinv(" ", ids_arr);
1389 g_strfreev(ids_arr);
1390 return res;
1394 * Sends buddy update to server
1396 static void
1397 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1399 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1400 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1402 if (buddy && purple_buddy) {
1403 const char *alias = purple_buddy_get_alias(purple_buddy);
1404 gchar *groups = sipe_get_buddy_groups_string(buddy);
1405 if (groups) {
1406 gchar *body;
1407 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
1409 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1410 alias, groups, "true", buddy->name, sip->contacts_delta++
1412 send_soap_request(sip, body);
1413 g_free(groups);
1414 g_free(body);
1419 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1421 if (msg->response == 200) {
1422 struct sipe_group *group;
1423 struct group_user_context *ctx = trans->payload->data;
1424 xmlnode *xml;
1425 xmlnode *node;
1426 char *group_id;
1427 struct sipe_buddy *buddy;
1429 xml = xmlnode_from_str(msg->body, msg->bodylen);
1430 if (!xml) {
1431 return FALSE;
1434 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1435 if (!node) {
1436 xmlnode_free(xml);
1437 return FALSE;
1440 group_id = xmlnode_get_data(node);
1441 if (!group_id) {
1442 xmlnode_free(xml);
1443 return FALSE;
1446 group = g_new0(struct sipe_group, 1);
1447 group->id = (int)g_ascii_strtod(group_id, NULL);
1448 g_free(group_id);
1449 group->name = g_strdup(ctx->group_name);
1451 sipe_group_add(sip, group);
1453 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1454 if (buddy) {
1455 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1458 sipe_group_set_user(sip, ctx->user_name);
1460 xmlnode_free(xml);
1461 return TRUE;
1463 return FALSE;
1466 static void sipe_group_context_destroy(gpointer data)
1468 struct group_user_context *ctx = data;
1469 g_free(ctx->group_name);
1470 g_free(ctx->user_name);
1471 g_free(ctx);
1474 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1476 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1477 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1478 gchar *body;
1479 ctx->group_name = g_strdup(name);
1480 ctx->user_name = g_strdup(who);
1481 payload->destroy = sipe_group_context_destroy;
1482 payload->data = ctx;
1484 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1485 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1486 g_free(body);
1490 * Data structure for scheduled actions
1493 struct scheduled_action {
1495 * Name of action.
1496 * Format is <Event>[<Data>...]
1497 * Example: <presence><sip:user@domain.com> or <registration>
1499 gchar *name;
1500 guint timeout_handler;
1501 gboolean repetitive;
1502 Action action;
1503 GDestroyNotify destroy;
1504 struct sipe_account_data *sip;
1505 void *payload;
1509 * A timer callback
1510 * Should return FALSE if repetitive action is not needed
1512 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1514 gboolean ret;
1515 SIPE_DEBUG_INFO_NOFORMAT("sipe_scheduled_exec: executing");
1516 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1517 SIPE_DEBUG_INFO("sip->timeouts count:%d after removal", g_slist_length(sched_action->sip->timeouts));
1518 (sched_action->action)(sched_action->sip, sched_action->payload);
1519 ret = sched_action->repetitive;
1520 if (sched_action->destroy) {
1521 (*sched_action->destroy)(sched_action->payload);
1523 g_free(sched_action->name);
1524 g_free(sched_action);
1525 return ret;
1529 * Kills action timer effectively cancelling
1530 * scheduled action
1532 * @param name of action
1534 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1536 GSList *entry;
1538 if (!sip->timeouts || !name) return;
1540 entry = sip->timeouts;
1541 while (entry) {
1542 struct scheduled_action *sched_action = entry->data;
1543 if(sipe_strequal(sched_action->name, name)) {
1544 GSList *to_delete = entry;
1545 entry = entry->next;
1546 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1547 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
1548 purple_timeout_remove(sched_action->timeout_handler);
1549 if (sched_action->destroy) {
1550 (*sched_action->destroy)(sched_action->payload);
1552 g_free(sched_action->name);
1553 g_free(sched_action);
1554 } else {
1555 entry = entry->next;
1560 static void
1561 sipe_schedule_action0(const gchar *name,
1562 int timeout,
1563 gboolean isSeconds,
1564 Action action,
1565 GDestroyNotify destroy,
1566 struct sipe_account_data *sip,
1567 void *payload)
1569 struct scheduled_action *sched_action;
1571 /* Make sure each action only exists once */
1572 sipe_cancel_scheduled_action(sip, name);
1574 SIPE_DEBUG_INFO("scheduling action %s timeout:%d(%s)", name, timeout, isSeconds ? "sec" : "msec");
1575 sched_action = g_new0(struct scheduled_action, 1);
1576 sched_action->repetitive = FALSE;
1577 sched_action->name = g_strdup(name);
1578 sched_action->action = action;
1579 sched_action->destroy = destroy;
1580 sched_action->sip = sip;
1581 sched_action->payload = payload;
1582 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1583 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1584 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1585 SIPE_DEBUG_INFO("sip->timeouts count:%d after addition", g_slist_length(sip->timeouts));
1588 void
1589 sipe_schedule_action(const gchar *name,
1590 int timeout,
1591 Action action,
1592 GDestroyNotify destroy,
1593 struct sipe_account_data *sip,
1594 void *payload)
1596 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1600 * Same as sipe_schedule_action() but timeout is in milliseconds.
1602 static void
1603 sipe_schedule_action_msec(const gchar *name,
1604 int timeout,
1605 Action action,
1606 GDestroyNotify destroy,
1607 struct sipe_account_data *sip,
1608 void *payload)
1610 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1613 static void
1614 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1615 time_t calculate_from);
1617 static int
1618 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1620 static const char*
1621 sipe_get_status_by_availability(int avail,
1622 char** activity);
1624 static void
1625 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1626 const char *status_id,
1627 const char *message,
1628 time_t do_not_publish[]);
1630 static void
1631 sipe_apply_calendar_status(struct sipe_account_data *sip,
1632 struct sipe_buddy *sbuddy,
1633 const char *status_id)
1635 time_t cal_avail_since;
1636 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1637 int avail;
1638 gchar *self_uri;
1640 if (!sbuddy) return;
1642 if (cal_status < SIPE_CAL_NO_DATA) {
1643 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
1644 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1647 /* scheduled Cal update call */
1648 if (!status_id) {
1649 status_id = sbuddy->last_non_cal_status_id;
1650 g_free(sbuddy->activity);
1651 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1654 if (!status_id) {
1655 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
1656 sbuddy->name ? sbuddy->name : "" );
1657 return;
1660 /* adjust to calendar status */
1661 if (cal_status != SIPE_CAL_NO_DATA) {
1662 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1664 if (cal_status == SIPE_CAL_BUSY
1665 && cal_avail_since > sbuddy->user_avail_since
1666 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1668 status_id = SIPE_STATUS_ID_BUSY;
1669 g_free(sbuddy->activity);
1670 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1672 avail = sipe_get_availability_by_status(status_id, NULL);
1674 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1675 if (cal_avail_since > sbuddy->activity_since) {
1676 if (cal_status == SIPE_CAL_OOF
1677 && avail >= 15000) /* 12000 in 2007 */
1679 g_free(sbuddy->activity);
1680 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1685 /* then set status_id actually */
1686 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
1687 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1689 /* set our account state to the one in roaming (including calendar info) */
1690 self_uri = sip_uri_self(sip);
1691 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1692 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1693 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1696 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
1697 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1699 g_free(self_uri);
1702 static void
1703 sipe_got_user_status(struct sipe_account_data *sip,
1704 const char* uri,
1705 const char *status_id)
1707 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1709 if (!sbuddy) return;
1711 /* Check if on 2005 system contact's calendar,
1712 * then set/preserve it.
1714 if (!sip->ocs2007) {
1715 sipe_apply_calendar_status(sip, sbuddy, status_id);
1716 } else {
1717 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1721 static void
1722 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1723 struct sipe_buddy *sbuddy,
1724 struct sipe_account_data *sip)
1726 sipe_apply_calendar_status(sip, sbuddy, NULL);
1730 * Updates contact's status
1731 * based on their calendar information.
1733 * Applicability: 2005 systems
1735 static void
1736 update_calendar_status(struct sipe_account_data *sip)
1738 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
1739 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1741 /* repeat scheduling */
1742 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1746 * Schedules process of contacts' status update
1747 * based on their calendar information.
1748 * Should be scheduled to the beginning of every
1749 * 15 min interval, like:
1750 * 13:00, 13:15, 13:30, 13:45, etc.
1752 * Applicability: 2005 systems
1754 static void
1755 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1756 time_t calculate_from)
1758 int interval = 15*60;
1759 /** start of the beginning of closest 15 min interval. */
1760 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1762 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
1763 asctime(localtime(&calculate_from)));
1764 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
1765 asctime(localtime(&next_start)));
1767 sipe_schedule_action("<+2005-cal-status>",
1768 (int)(next_start - time(NULL)),
1769 (Action)update_calendar_status,
1770 NULL,
1771 sip,
1772 NULL);
1776 * Schedules process of self status publish
1777 * based on own calendar information.
1778 * Should be scheduled to the beginning of every
1779 * 15 min interval, like:
1780 * 13:00, 13:15, 13:30, 13:45, etc.
1782 * Applicability: 2007+ systems
1784 static void
1785 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1786 time_t calculate_from)
1788 int interval = 5*60;
1789 /** start of the beginning of closest 5 min interval. */
1790 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1792 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1793 asctime(localtime(&calculate_from)));
1794 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1795 asctime(localtime(&next_start)));
1797 sipe_schedule_action("<+2007-cal-status>",
1798 (int)(next_start - time(NULL)),
1799 (Action)publish_calendar_status_self,
1800 NULL,
1801 sip,
1802 NULL);
1805 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1807 /** Should be g_free()'d
1809 static gchar *
1810 sipe_get_subscription_key(const gchar *event,
1811 const gchar *with)
1813 gchar *key = NULL;
1815 if (is_empty(event)) return NULL;
1817 if (event && sipe_strcase_equal(event, "presence")) {
1818 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1819 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1821 /* @TODO drop participated buddies' just_added flag */
1822 } else if (event) {
1823 /* Subscription is identified by <event> key */
1824 key = g_strdup_printf("<%s>", event);
1827 return key;
1830 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1831 SIPE_UNUSED_PARAMETER struct transaction *trans)
1833 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1834 const gchar *event = sipmsg_find_header(msg, "Event");
1835 gchar *key;
1837 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1838 if (!event) {
1839 struct sipmsg *request_msg = trans->msg;
1840 event = sipmsg_find_header(request_msg, "Event");
1843 key = sipe_get_subscription_key(event, with);
1845 /* 200 OK; 481 Call Leg Does Not Exist */
1846 if (key && (msg->response == 200 || msg->response == 481)) {
1847 if (g_hash_table_lookup(sip->subscriptions, key)) {
1848 g_hash_table_remove(sip->subscriptions, key);
1849 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
1853 /* create/store subscription dialog if not yet */
1854 if (msg->response == 200) {
1855 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1856 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1858 if (key) {
1859 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1860 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1862 subscription->dialog.callid = g_strdup(callid);
1863 subscription->dialog.cseq = atoi(cseq);
1864 subscription->dialog.with = g_strdup(with);
1865 subscription->event = g_strdup(event);
1866 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1868 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key);
1871 g_free(cseq);
1874 g_free(key);
1875 g_free(with);
1877 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1879 process_incoming_notify(sip, msg, FALSE, FALSE);
1881 return TRUE;
1884 static void sipe_subscribe_resource_uri(const char *name,
1885 SIPE_UNUSED_PARAMETER gpointer value,
1886 gchar **resources_uri)
1888 gchar *tmp = *resources_uri;
1889 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1890 g_free(tmp);
1893 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1895 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1896 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1897 gchar *tmp = *resources_uri;
1899 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1901 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1902 g_free(tmp);
1906 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1907 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1908 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1909 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1910 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1913 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1915 gchar *key;
1916 gchar *contact = get_contact(sip);
1917 gchar *request;
1918 gchar *content;
1919 gchar *require = "";
1920 gchar *accept = "";
1921 gchar *autoextend = "";
1922 gchar *content_type;
1923 struct sip_dialog *dialog;
1925 if (sip->ocs2007) {
1926 require = ", categoryList";
1927 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1928 content_type = "application/msrtc-adrl-categorylist+xml";
1929 content = g_strdup_printf(
1930 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1931 "<action name=\"subscribe\" id=\"63792024\">\n"
1932 "<adhocList>\n%s</adhocList>\n"
1933 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1934 "<category name=\"calendarData\"/>\n"
1935 "<category name=\"contactCard\"/>\n"
1936 "<category name=\"note\"/>\n"
1937 "<category name=\"state\"/>\n"
1938 "</categoryList>\n"
1939 "</action>\n"
1940 "</batchSub>", sip->username, resources_uri);
1941 } else {
1942 autoextend = "Supported: com.microsoft.autoextend\r\n";
1943 content_type = "application/adrl+xml";
1944 content = g_strdup_printf(
1945 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1946 "<create xmlns=\"\">\n%s</create>\n"
1947 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1949 g_free(resources_uri);
1951 request = g_strdup_printf(
1952 "Require: adhoclist%s\r\n"
1953 "Supported: eventlist\r\n"
1954 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1955 "Supported: ms-piggyback-first-notify\r\n"
1956 "%sSupported: ms-benotify\r\n"
1957 "Proxy-Require: ms-benotify\r\n"
1958 "Event: presence\r\n"
1959 "Content-Type: %s\r\n"
1960 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1961 g_free(contact);
1963 /* subscribe to buddy presence */
1964 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1965 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1966 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1967 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
1969 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1971 g_free(content);
1972 g_free(to);
1973 g_free(request);
1974 g_free(key);
1977 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1978 SIPE_UNUSED_PARAMETER void *unused)
1980 gchar *to = sip_uri_self(sip);
1981 gchar *resources_uri = g_strdup("");
1982 if (sip->ocs2007) {
1983 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1984 } else {
1985 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1988 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1991 struct presence_batched_routed {
1992 gchar *host;
1993 GSList *buddies;
1996 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1998 struct presence_batched_routed *data = payload;
1999 GSList *buddies = data->buddies;
2000 while (buddies) {
2001 g_free(buddies->data);
2002 buddies = buddies->next;
2004 g_slist_free(data->buddies);
2005 g_free(data->host);
2006 g_free(payload);
2009 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
2011 struct presence_batched_routed *data = payload;
2012 GSList *buddies = data->buddies;
2013 gchar *resources_uri = g_strdup("");
2014 while (buddies) {
2015 gchar *tmp = resources_uri;
2016 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2017 g_free(tmp);
2018 buddies = buddies->next;
2020 sipe_subscribe_presence_batched_to(sip, resources_uri,
2021 g_strdup(data->host));
2025 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2026 * The user sends a single SUBSCRIBE request to the subscribed contact.
2027 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2031 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2034 gchar *key;
2035 gchar *to = sip_uri((char *)buddy_name);
2036 gchar *tmp = get_contact(sip);
2037 gchar *request;
2038 gchar *content = NULL;
2039 gchar *autoextend = "";
2040 gchar *content_type = "";
2041 struct sip_dialog *dialog;
2042 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2043 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2045 if (sbuddy) sbuddy->just_added = FALSE;
2047 if (sip->ocs2007) {
2048 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2049 } else {
2050 autoextend = "Supported: com.microsoft.autoextend\r\n";
2053 request = g_strdup_printf(
2054 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2055 "Supported: ms-piggyback-first-notify\r\n"
2056 "%s%sSupported: ms-benotify\r\n"
2057 "Proxy-Require: ms-benotify\r\n"
2058 "Event: presence\r\n"
2059 "Contact: %s\r\n", autoextend, content_type, tmp);
2061 if (sip->ocs2007) {
2062 content = g_strdup_printf(
2063 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2064 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2065 "<resource uri=\"%s\"%s\n"
2066 "</adhocList>\n"
2067 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2068 "<category name=\"calendarData\"/>\n"
2069 "<category name=\"contactCard\"/>\n"
2070 "<category name=\"note\"/>\n"
2071 "<category name=\"state\"/>\n"
2072 "</categoryList>\n"
2073 "</action>\n"
2074 "</batchSub>", sip->username, to, context);
2077 g_free(tmp);
2079 /* subscribe to buddy presence */
2080 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2081 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2082 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2083 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2085 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2087 g_free(content);
2088 g_free(to);
2089 g_free(request);
2090 g_free(key);
2093 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2095 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
2097 if (!purple_status_is_active(status))
2098 return;
2100 if (account->gc) {
2101 struct sipe_account_data *sip = account->gc->proto_data;
2103 if (sip) {
2104 gchar *action_name;
2105 gchar *tmp;
2106 time_t now = time(NULL);
2107 const char *status_id = purple_status_get_id(status);
2108 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2109 sipe_activity activity = sipe_get_activity_by_token(status_id);
2110 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2112 /* when other point of presence clears note, but we are keeping
2113 * state if OOF note.
2115 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2116 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
2117 do_not_publish = FALSE;
2120 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
2121 status_id, (int)sip->do_not_publish[activity], (int)now);
2123 sip->do_not_publish[activity] = 0;
2124 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
2125 status_id, (int)sip->do_not_publish[activity]);
2127 if (do_not_publish)
2129 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
2130 return;
2133 g_free(sip->status);
2134 sip->status = g_strdup(status_id);
2136 /* hack to escape apostrof before comparison */
2137 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
2139 /* this will preserve OOF flag as well */
2140 if (!sipe_strequal(tmp, sip->note)) {
2141 sip->is_oof_note = FALSE;
2142 g_free(sip->note);
2143 sip->note = g_strdup(note);
2144 sip->note_since = time(NULL);
2146 g_free(tmp);
2148 /* schedule 2 sec to capture idle flag */
2149 action_name = g_strdup_printf("<%s>", "+set-status");
2150 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2151 g_free(action_name);
2155 static void
2156 sipe_set_idle(PurpleConnection * gc,
2157 int interval)
2159 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
2161 if (gc) {
2162 struct sipe_account_data *sip = gc->proto_data;
2164 if (sip) {
2165 sip->idle_switch = time(NULL);
2166 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2171 static void
2172 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2173 SIPE_UNUSED_PARAMETER const char *alias)
2175 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2176 sipe_group_set_user(sip, name);
2179 static void
2180 sipe_group_buddy(PurpleConnection *gc,
2181 const char *who,
2182 const char *old_group_name,
2183 const char *new_group_name)
2185 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2186 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2187 struct sipe_group * old_group = NULL;
2188 struct sipe_group * new_group;
2190 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
2191 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2193 if(!buddy) { // buddy not in roaming list
2194 return;
2197 if (old_group_name) {
2198 old_group = sipe_group_find_by_name(sip, old_group_name);
2200 new_group = sipe_group_find_by_name(sip, new_group_name);
2202 if (old_group) {
2203 buddy->groups = g_slist_remove(buddy->groups, old_group);
2204 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
2207 if (!new_group) {
2208 sipe_group_create(sip, new_group_name, who);
2209 } else {
2210 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2211 sipe_group_set_user(sip, who);
2215 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2217 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2219 /* libpurple can call us with undefined buddy or group */
2220 if (buddy && group) {
2221 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2223 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2224 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2225 purple_blist_rename_buddy(buddy, buddy_name);
2226 g_free(buddy_name);
2228 /* Prepend sip: if needed */
2229 if (!g_str_has_prefix(buddy->name, "sip:")) {
2230 gchar *buf = sip_uri_from_name(buddy->name);
2231 purple_blist_rename_buddy(buddy, buf);
2232 g_free(buf);
2235 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2236 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2237 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
2238 b->name = g_strdup(buddy->name);
2239 b->just_added = TRUE;
2240 g_hash_table_insert(sip->buddies, b->name, b);
2241 sipe_group_buddy(gc, b->name, NULL, group->name);
2242 /* @TODO should go to callback */
2243 sipe_subscribe_presence_single(sip, b->name);
2244 } else {
2245 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
2250 static void sipe_free_buddy(struct sipe_buddy *buddy)
2252 #ifndef _WIN32
2254 * We are calling g_hash_table_foreach_steal(). That means that no
2255 * key/value deallocation functions are called. Therefore the glib
2256 * hash code does not touch the key (buddy->name) or value (buddy)
2257 * of the to-be-deleted hash node at all. It follows that we
2259 * - MUST free the memory for the key ourselves and
2260 * - ARE allowed to do it in this function
2262 * Conclusion: glib must be broken on the Windows platform if sipe
2263 * crashes with SIGTRAP when closing. You'll have to live
2264 * with the memory leak until this is fixed.
2266 g_free(buddy->name);
2267 #endif
2268 g_free(buddy->activity);
2269 g_free(buddy->meeting_subject);
2270 g_free(buddy->meeting_location);
2271 g_free(buddy->note);
2273 g_free(buddy->cal_start_time);
2274 g_free(buddy->cal_free_busy_base64);
2275 g_free(buddy->cal_free_busy);
2276 g_free(buddy->last_non_cal_activity);
2278 sipe_cal_free_working_hours(buddy->cal_working_hours);
2280 g_free(buddy->device_name);
2281 g_slist_free(buddy->groups);
2282 g_free(buddy);
2286 * Unassociates buddy from group first.
2287 * Then see if no groups left, removes buddy completely.
2288 * Otherwise updates buddy groups on server.
2290 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2292 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2293 struct sipe_buddy *b;
2294 struct sipe_group *g = NULL;
2296 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2297 if (!buddy) return;
2299 b = g_hash_table_lookup(sip->buddies, buddy->name);
2300 if (!b) return;
2302 if (group) {
2303 g = sipe_group_find_by_name(sip, group->name);
2306 if (g) {
2307 b->groups = g_slist_remove(b->groups, g);
2308 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
2311 if (g_slist_length(b->groups) < 1) {
2312 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2313 sipe_cancel_scheduled_action(sip, action_name);
2314 g_free(action_name);
2316 g_hash_table_remove(sip->buddies, buddy->name);
2318 if (b->name) {
2319 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2320 send_soap_request(sip, body);
2321 g_free(body);
2324 sipe_free_buddy(b);
2325 } else {
2326 //updates groups on server
2327 sipe_group_set_user(sip, b->name);
2332 static void
2333 sipe_rename_group(PurpleConnection *gc,
2334 const char *old_name,
2335 PurpleGroup *group,
2336 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2338 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2339 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2340 if (s_group) {
2341 sipe_group_rename(sip, s_group, group->name);
2342 } else {
2343 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
2347 static void
2348 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2350 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2351 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2352 if (s_group) {
2353 gchar *body;
2354 SIPE_DEBUG_INFO("Deleting group %s", group->name);
2355 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2356 send_soap_request(sip, body);
2357 g_free(body);
2359 sip->groups = g_slist_remove(sip->groups, s_group);
2360 g_free(s_group->name);
2361 g_free(s_group);
2362 } else {
2363 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
2367 /** All statuses need message attribute to pass Note */
2368 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2370 PurpleStatusType *type;
2371 GList *types = NULL;
2373 /* Macros to reduce code repetition.
2374 Translators: noun */
2375 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2376 prim, id, name, \
2377 TRUE, user, FALSE, \
2378 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2379 NULL); \
2380 types = g_list_append(types, type);
2382 /* Online */
2383 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2384 NULL,
2385 NULL,
2386 TRUE);
2388 /* Busy */
2389 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2390 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2391 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2392 TRUE);
2394 /* Do Not Disturb */
2395 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2396 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2397 NULL,
2398 TRUE);
2400 /* Away */
2401 /* Goes first in the list as
2402 * purple picks the first status with the AWAY type
2403 * for idle.
2405 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2406 NULL,
2407 NULL,
2408 TRUE);
2410 /* Be Right Back */
2411 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2412 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2413 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2414 TRUE);
2416 /* Appear Offline */
2417 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2418 NULL,
2419 NULL,
2420 TRUE);
2422 /* Offline */
2423 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2424 NULL,
2425 NULL,
2426 TRUE);
2427 types = g_list_append(types, type);
2429 return types;
2433 * A callback for g_hash_table_foreach
2435 static void
2436 sipe_buddy_subscribe_cb(char *buddy_name,
2437 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2438 struct sipe_account_data *sip)
2440 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2441 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2442 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2443 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2445 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2446 g_free(action_name);
2450 * Removes entries from purple buddy list
2451 * that does not correspond ones in the roaming contact list.
2453 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2454 GSList *buddies = purple_find_buddies(sip->account, NULL);
2455 GSList *entry = buddies;
2456 struct sipe_buddy *buddy;
2457 PurpleBuddy *b;
2458 PurpleGroup *g;
2460 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
2461 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sip->buddies));
2462 while (entry) {
2463 b = entry->data;
2464 g = purple_buddy_get_group(b);
2465 buddy = g_hash_table_lookup(sip->buddies, b->name);
2466 if(buddy) {
2467 gboolean in_sipe_groups = FALSE;
2468 GSList *entry2 = buddy->groups;
2469 while (entry2) {
2470 struct sipe_group *group = entry2->data;
2471 if (sipe_strequal(group->name, g->name)) {
2472 in_sipe_groups = TRUE;
2473 break;
2475 entry2 = entry2->next;
2477 if(!in_sipe_groups) {
2478 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
2479 purple_blist_remove_buddy(b);
2481 } else {
2482 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
2483 purple_blist_remove_buddy(b);
2485 entry = entry->next;
2487 g_slist_free(buddies);
2490 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2492 int len = msg->bodylen;
2494 const gchar *tmp = sipmsg_find_header(msg, "Event");
2495 xmlnode *item;
2496 xmlnode *isc;
2497 const gchar *contacts_delta;
2498 xmlnode *group_node;
2499 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2500 return FALSE;
2503 /* Convert the contact from XML to Purple Buddies */
2504 isc = xmlnode_from_str(msg->body, len);
2505 if (!isc) {
2506 return FALSE;
2509 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2510 if (contacts_delta) {
2511 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2514 if (sipe_strequal(isc->name, "contactList")) {
2516 /* Parse groups */
2517 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2518 struct sipe_group * group = g_new0(struct sipe_group, 1);
2519 const char *name = xmlnode_get_attrib(group_node, "name");
2521 if (g_str_has_prefix(name, "~")) {
2522 name = _("Other Contacts");
2524 group->name = g_strdup(name);
2525 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2527 sipe_group_add(sip, group);
2530 // Make sure we have at least one group
2531 if (g_slist_length(sip->groups) == 0) {
2532 struct sipe_group * group = g_new0(struct sipe_group, 1);
2533 PurpleGroup *purple_group;
2534 group->name = g_strdup(_("Other Contacts"));
2535 group->id = 1;
2536 purple_group = purple_group_new(group->name);
2537 purple_blist_add_group(purple_group, NULL);
2538 sip->groups = g_slist_append(sip->groups, group);
2541 /* Parse contacts */
2542 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2543 const gchar *uri = xmlnode_get_attrib(item, "uri");
2544 const gchar *name = xmlnode_get_attrib(item, "name");
2545 gchar *buddy_name;
2546 struct sipe_buddy *buddy = NULL;
2547 gchar *tmp;
2548 gchar **item_groups;
2549 int i = 0;
2551 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2552 tmp = sip_uri_from_name(uri);
2553 buddy_name = g_ascii_strdown(tmp, -1);
2554 g_free(tmp);
2556 /* assign to group Other Contacts if nothing else received */
2557 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2558 if(is_empty(tmp)) {
2559 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2560 g_free(tmp);
2561 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2563 item_groups = g_strsplit(tmp, " ", 0);
2564 g_free(tmp);
2566 while (item_groups[i]) {
2567 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2569 // If couldn't find the right group for this contact, just put them in the first group we have
2570 if (group == NULL && g_slist_length(sip->groups) > 0) {
2571 group = sip->groups->data;
2574 if (group != NULL) {
2575 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2576 if (!b){
2577 b = purple_buddy_new(sip->account, buddy_name, uri);
2578 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2580 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
2583 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2584 if (name != NULL && strlen(name) != 0) {
2585 purple_blist_alias_buddy(b, name);
2587 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
2591 if (!buddy) {
2592 buddy = g_new0(struct sipe_buddy, 1);
2593 buddy->name = g_strdup(b->name);
2594 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2597 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2599 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
2600 } else {
2601 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2602 name);
2605 i++;
2606 } // while, contact groups
2607 g_strfreev(item_groups);
2608 g_free(buddy_name);
2610 } // for, contacts
2612 sipe_cleanup_local_blist(sip);
2614 /* Add self-contact if not there yet. 2005 systems. */
2615 /* This will resemble subscription to roaming_self in 2007 systems */
2616 if (!sip->ocs2007) {
2617 gchar *self_uri = sip_uri_self(sip);
2618 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2620 if (!buddy) {
2621 buddy = g_new0(struct sipe_buddy, 1);
2622 buddy->name = g_strdup(self_uri);
2623 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2625 g_free(self_uri);
2628 xmlnode_free(isc);
2630 /* subscribe to buddies */
2631 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2632 if (sip->batched_support) {
2633 sipe_subscribe_presence_batched(sip, NULL);
2634 } else {
2635 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2637 sip->subscribed_buddies = TRUE;
2639 /* for 2005 systems schedule contacts' status update
2640 * based on their calendar information
2642 if (!sip->ocs2007) {
2643 sipe_sched_calendar_status_update(sip, time(NULL));
2646 return 0;
2650 * Subscribe roaming contacts
2652 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2654 gchar *to = sip_uri_self(sip);
2655 gchar *tmp = get_contact(sip);
2656 gchar *hdr = g_strdup_printf(
2657 "Event: vnd-microsoft-roaming-contacts\r\n"
2658 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2659 "Supported: com.microsoft.autoextend\r\n"
2660 "Supported: ms-benotify\r\n"
2661 "Proxy-Require: ms-benotify\r\n"
2662 "Supported: ms-piggyback-first-notify\r\n"
2663 "Contact: %s\r\n", tmp);
2664 g_free(tmp);
2666 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2667 g_free(to);
2668 g_free(hdr);
2671 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2672 SIPE_UNUSED_PARAMETER void *unused)
2674 gchar *key;
2675 struct sip_dialog *dialog;
2676 gchar *to = sip_uri_self(sip);
2677 gchar *tmp = get_contact(sip);
2678 gchar *hdr = g_strdup_printf(
2679 "Event: presence.wpending\r\n"
2680 "Accept: text/xml+msrtc.wpending\r\n"
2681 "Supported: com.microsoft.autoextend\r\n"
2682 "Supported: ms-benotify\r\n"
2683 "Proxy-Require: ms-benotify\r\n"
2684 "Supported: ms-piggyback-first-notify\r\n"
2685 "Contact: %s\r\n", tmp);
2686 g_free(tmp);
2688 /* Subscription is identified by <event> key */
2689 key = g_strdup_printf("<%s>", "presence.wpending");
2690 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2691 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2693 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2695 g_free(to);
2696 g_free(hdr);
2697 g_free(key);
2701 * Fires on deregistration event initiated by server.
2702 * [MS-SIPREGE] SIP extension.
2705 // 2007 Example
2707 // Content-Type: text/registration-event
2708 // subscription-state: terminated;expires=0
2709 // ms-diagnostics-public: 4141;reason="User disabled"
2711 // deregistered;event=rejected
2713 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2715 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2716 gchar *event = NULL;
2717 gchar *reason = NULL;
2718 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2719 gchar *warning;
2721 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2722 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2724 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2725 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2726 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2727 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2728 } else {
2729 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2730 return;
2733 if (diagnostics != NULL) {
2734 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2735 } else { // for LCS2005
2736 int error_id = 0;
2737 if (event && sipe_strcase_equal(event, "unregistered")) {
2738 error_id = 4140; // [MS-SIPREGE]
2739 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2740 reason = g_strdup(_("you are already signed in at another location"));
2741 } else if (event && sipe_strcase_equal(event, "rejected")) {
2742 error_id = 4141;
2743 reason = g_strdup(_("user disabled")); // [MS-OCER]
2744 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2745 error_id = 4142;
2746 reason = g_strdup(_("user moved")); // [MS-OCER]
2749 g_free(event);
2750 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2751 g_free(reason);
2753 sip->gc->wants_to_die = TRUE;
2754 purple_connection_error(sip->gc, warning);
2755 g_free(warning);
2759 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2761 xmlnode *xn_provision_group_list;
2762 xmlnode *node;
2764 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2766 /* provisionGroup */
2767 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2768 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2769 g_free(sip->focus_factory_uri);
2770 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2771 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2772 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2773 break;
2776 xmlnode_free(xn_provision_group_list);
2779 /** for 2005 system */
2780 static void
2781 sipe_process_provisioning(struct sipe_account_data *sip,
2782 struct sipmsg *msg)
2784 xmlnode *xn_provision;
2785 xmlnode *node;
2787 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2788 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2789 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", xmlnode_get_attrib(node, "uri"));
2790 if ((node = xmlnode_get_child(node, "line"))) {
2791 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2792 const gchar *server = xmlnode_get_attrib(node, "server");
2793 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2794 sip_csta_open(sip, line_uri, server);
2797 xmlnode_free(xn_provision);
2800 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2802 const gchar *contacts_delta;
2803 xmlnode *xml;
2805 xml = xmlnode_from_str(msg->body, msg->bodylen);
2806 if (!xml)
2808 return;
2811 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2812 if (contacts_delta)
2814 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2817 xmlnode_free(xml);
2820 static void
2821 free_container(struct sipe_container *container)
2823 GSList *entry;
2825 if (!container) return;
2827 entry = container->members;
2828 while (entry) {
2829 void *data = entry->data;
2830 entry = g_slist_remove(entry, data);
2831 g_free(data);
2833 g_free(container);
2836 static void
2837 sipe_send_set_container_members(struct sipe_account_data *sip,
2838 guint container_id,
2839 guint container_version,
2840 const gchar* action,
2841 const gchar* type,
2842 const gchar* value)
2844 gchar *self = sip_uri_self(sip);
2845 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2846 gchar *contact;
2847 gchar *hdr;
2848 gchar *body = g_strdup_printf(
2849 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2850 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2851 "</setContainerMembers>",
2852 container_id,
2853 container_version,
2854 action,
2855 type,
2856 value_str);
2857 g_free(value_str);
2859 contact = get_contact(sip);
2860 hdr = g_strdup_printf("Contact: %s\r\n"
2861 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2862 g_free(contact);
2864 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2866 g_free(hdr);
2867 g_free(body);
2868 g_free(self);
2872 * Finds locally stored MS-PRES container member
2874 static struct sipe_container_member *
2875 sipe_find_container_member(struct sipe_container *container,
2876 const gchar *type,
2877 const gchar *value)
2879 struct sipe_container_member *member;
2880 GSList *entry;
2882 if (container == NULL || type == NULL) {
2883 return NULL;
2886 entry = container->members;
2887 while (entry) {
2888 member = entry->data;
2889 if (sipe_strcase_equal(member->type, type) &&
2890 sipe_strcase_equal(member->value, value))
2892 return member;
2894 entry = entry->next;
2896 return NULL;
2900 * Finds locally stored MS-PRES container by id
2902 static struct sipe_container *
2903 sipe_find_container(struct sipe_account_data *sip,
2904 guint id)
2906 struct sipe_container *container;
2907 GSList *entry;
2909 if (sip == NULL) {
2910 return NULL;
2913 entry = sip->containers;
2914 while (entry) {
2915 container = entry->data;
2916 if (id == container->id) {
2917 return container;
2919 entry = entry->next;
2921 return NULL;
2924 /**
2925 * Returns pointer to domain part in provided Email URL
2927 * @param email an email URL. Example: first.last@hq.company.com
2928 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
2930 * Doesn't allocate memory
2932 static const char *
2933 sipe_get_domain(const char *email)
2935 char *tmp;
2937 if (!email) return NULL;
2939 tmp = strstr(email, "@");
2941 if (tmp && ((tmp+1) < (email + strlen(email)))) {
2942 return tmp+1;
2943 } else {
2944 return NULL;
2948 /**
2949 * Returns pointer to URI without sip: prefix if any
2951 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
2952 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
2954 * Doesn't allocate memory
2956 static const char *
2957 sipe_get_no_sip_uri(const char *sip_uri)
2959 const char *prefix = "sip:";
2960 if (!sip_uri) return NULL;
2962 if (g_str_has_prefix(sip_uri, prefix)) {
2963 return (sip_uri+strlen(prefix));
2964 } else {
2965 return sip_uri;
2970 /* @TODO: replace with binary search for faster access? */
2971 /** source: http://support.microsoft.com/kb/897567 */
2972 static const char * const public_domains [] = {
2973 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
2974 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
2975 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
2976 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
2977 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
2978 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
2979 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
2980 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
2981 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
2982 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
2983 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
2984 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
2985 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
2986 "yahoo.com",
2987 NULL};
2989 static gboolean
2990 sipe_is_public_domain(const char *domain)
2992 int i = 0;
2993 while (public_domains[i]) {
2994 if (sipe_strcase_equal(public_domains[i], domain)) {
2995 return TRUE;
2997 i++;
2999 return FALSE;
3003 * Access Levels
3004 * 32000 - Blocked
3005 * 400 - Personal
3006 * 300 - Team
3007 * 200 - Company
3008 * 100 - Public
3010 static const char *
3011 sipe_get_access_level_name(int container_id)
3013 switch(container_id) {
3014 case 32000: return _("Blocked");
3015 case 400: return _("Personal");
3016 case 300: return _("Team");
3017 case 200: return _("Company");
3018 case 100: return _("Public");
3020 return _("Unknown");
3023 static const guint containers[] = {32000, 400, 300, 200, 100};
3024 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
3026 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
3027 static int
3028 sipe_find_access_level(struct sipe_account_data *sip,
3029 const gchar *type,
3030 const gchar *value)
3032 unsigned int i = 0;
3034 for (i = 0; i < CONTAINERS_LEN; i++) {
3035 struct sipe_container_member *member;
3036 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3037 if (!container) continue;
3039 if (sipe_strequal("user", type)) {
3040 const char *domain;
3041 const char *no_sip_uri = sipe_get_no_sip_uri(value);
3043 member = sipe_find_container_member(container, "user", no_sip_uri);
3044 if (member) return containers[i];
3046 domain = sipe_get_domain(no_sip_uri);
3047 member = sipe_find_container_member(container, "domain", domain);
3048 if (member) {
3049 return containers[i];
3052 member = sipe_find_container_member(container, "sameEnterprise", NULL);
3053 if (member &&
3054 sipe_strcase_equal(sip->sipdomain, domain))
3056 return containers[i];
3059 member = sipe_find_container_member(container, "publicCloud", NULL);
3060 if (member && sipe_is_public_domain(domain))
3062 return containers[i];
3065 member = sipe_find_container_member(container, "everyone", NULL);
3066 if (member)
3068 return containers[i];
3070 } else {
3071 member = sipe_find_container_member(container, type, value);
3072 if (member) {
3073 return containers[i];
3078 return -1;
3081 static void
3082 sipe_change_access_level(struct sipe_account_data *sip,
3083 const int container_id, /* new access level*/
3084 const gchar *type,
3085 const gchar *value)
3087 unsigned int i;
3088 int current_container_id = -1;
3090 /* for each container: find/delete */
3091 for (i = 0; i < CONTAINERS_LEN; i++) {
3092 struct sipe_container_member *member;
3093 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3095 if (!container) continue;
3097 member = sipe_find_container_member(container, type, value);
3098 if (member) {
3099 current_container_id = containers[i];
3100 /* delete/publish current access level */
3101 if (container_id != current_container_id) {
3102 sipe_send_set_container_members(
3103 sip, current_container_id, container->version, "delete", type, value);
3108 /* assign/publish new access level */
3109 if (container_id != current_container_id) {
3110 struct sipe_container *container = sipe_find_container(sip, container_id);
3111 guint version = container ? container->version : 0;
3113 sipe_send_set_container_members(sip, container_id, version, "add", type, value);
3117 static void
3118 free_publication(struct sipe_publication *publication)
3120 g_free(publication->category);
3121 g_free(publication->cal_event_hash);
3122 g_free(publication->note);
3124 g_free(publication->working_hours_xml_str);
3125 g_free(publication->fb_start_str);
3126 g_free(publication->free_busy_base64);
3128 g_free(publication);
3131 /* key is <category><instance><container> */
3132 static gboolean
3133 sipe_is_our_publication(struct sipe_account_data *sip,
3134 const gchar *key)
3136 GSList *entry;
3138 /* filling keys for our publications if not yet cached */
3139 if (!sip->our_publication_keys) {
3140 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
3141 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
3142 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
3143 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
3144 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
3145 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
3146 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
3148 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
3149 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
3150 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
3151 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
3152 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
3153 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
3154 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
3155 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
3156 SIPE_DEBUG_INFO("\tNote : %u", 0);
3157 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
3159 /* device */
3160 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3161 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
3163 /* state:machineState */
3164 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3165 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
3166 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3167 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3169 /* state:userState */
3170 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3171 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3172 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3173 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3175 /* state:calendarState */
3176 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3177 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3178 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3179 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3181 /* state:calendarState OOF */
3182 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3183 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3184 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3185 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3187 /* note */
3188 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3189 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3190 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3191 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3192 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3193 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3195 /* note OOF */
3196 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3197 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3198 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3199 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3200 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3201 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3203 /* calendarData:WorkingHours */
3204 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3205 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3206 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3207 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3208 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3209 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3210 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3211 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3212 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3213 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3214 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3215 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3217 /* calendarData:FreeBusy */
3218 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3219 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3220 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3221 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3222 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3223 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3224 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3225 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3226 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3227 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3228 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3229 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3231 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3232 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3235 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3237 entry = sip->our_publication_keys;
3238 while (entry) {
3239 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3240 if (sipe_strequal(entry->data, key)) {
3241 return TRUE;
3243 entry = entry->next;
3245 return FALSE;
3248 /** Property names to store in blist.xml */
3249 #define ALIAS_PROP "alias"
3250 #define EMAIL_PROP "email"
3251 #define PHONE_PROP "phone"
3252 #define PHONE_DISPLAY_PROP "phone-display"
3253 #define PHONE_MOBILE_PROP "phone-mobile"
3254 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3255 #define PHONE_HOME_PROP "phone-home"
3256 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3257 #define PHONE_OTHER_PROP "phone-other"
3258 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3259 #define PHONE_CUSTOM1_PROP "phone-custom1"
3260 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3261 #define SITE_PROP "site"
3262 #define COMPANY_PROP "company"
3263 #define DEPARTMENT_PROP "department"
3264 #define TITLE_PROP "title"
3265 #define OFFICE_PROP "office"
3266 /** implies work address */
3267 #define ADDRESS_STREET_PROP "address-street"
3268 #define ADDRESS_CITY_PROP "address-city"
3269 #define ADDRESS_STATE_PROP "address-state"
3270 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3271 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3274 * Tries to figure out user first and last name
3275 * based on Display Name and email properties.
3277 * Allocates memory - must be g_free()'d
3279 * Examples to parse:
3280 * First Last
3281 * First Last - Company Name
3282 * Last, First
3283 * Last, First M.
3284 * Last, First (C)(STP) (Company)
3285 * first.last@company.com (preprocessed as "first last")
3286 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3288 * Unusable examples:
3289 * user@company.com (preprocessed as "user")
3290 * first.m.last@company.com (preprocessed as "first m last")
3291 * user.company.com@reuters.net (preprocessed as "user company com")
3293 static void
3294 sipe_get_first_last_names(struct sipe_account_data *sip,
3295 const char *uri,
3296 char **first_name,
3297 char **last_name)
3299 PurpleBuddy *p_buddy;
3300 char *display_name;
3301 const char *email;
3302 const char *first, *last;
3303 char *tmp;
3304 char **parts;
3305 gboolean has_comma = FALSE;
3307 if (!sip || !uri) return;
3309 p_buddy = purple_find_buddy(sip->account, uri);
3311 if (!p_buddy) return;
3313 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3314 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3316 if (!display_name && !email) return;
3318 /* if no display name, make "first last anything_else" out of email */
3319 if (email && !display_name) {
3320 display_name = g_strndup(email, strstr(email, "@") - email);
3321 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
3322 g_free(tmp);
3325 if (display_name) {
3326 has_comma = (strstr(display_name, ",") != NULL);
3327 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
3328 g_free(tmp);
3329 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
3330 g_free(tmp);
3333 parts = g_strsplit(display_name, " ", 0);
3335 if (!parts[0] || !parts[1]) {
3336 g_free(display_name);
3337 g_strfreev(parts);
3338 return;
3341 if (has_comma) {
3342 last = parts[0];
3343 first = parts[1];
3344 } else {
3345 first = parts[0];
3346 last = parts[1];
3349 if (first_name) {
3350 *first_name = g_strstrip(g_strdup(first));
3353 if (last_name) {
3354 *last_name = g_strstrip(g_strdup(last));
3357 g_free(display_name);
3358 g_strfreev(parts);
3362 * Update user information
3364 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3365 * @param property_name
3366 * @param property_value may be modified to strip white space
3368 static void
3369 sipe_update_user_info(struct sipe_account_data *sip,
3370 const char *uri,
3371 const char *property_name,
3372 char *property_value)
3374 GSList *buddies, *entry;
3376 if (!property_name || strlen(property_name) == 0) return;
3378 if (property_value)
3379 property_value = g_strstrip(property_value);
3381 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3382 while (entry) {
3383 const char *prop_str;
3384 const char *server_alias;
3385 PurpleBuddy *p_buddy = entry->data;
3387 /* for Display Name */
3388 if (sipe_strequal(property_name, ALIAS_PROP)) {
3389 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3390 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
3391 purple_blist_alias_buddy(p_buddy, property_value);
3394 server_alias = purple_buddy_get_server_alias(p_buddy);
3395 if (!is_empty(property_value) &&
3396 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3398 purple_blist_server_alias_buddy(p_buddy, property_value);
3401 /* for other properties */
3402 else {
3403 if (!is_empty(property_value)) {
3404 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3405 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3406 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3411 entry = entry->next;
3413 g_slist_free(buddies);
3417 * Update user phone
3418 * Suitable for both 2005 and 2007 systems.
3420 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3421 * @param phone_type
3422 * @param phone may be modified to strip white space
3423 * @param phone_display_string may be modified to strip white space
3425 static void
3426 sipe_update_user_phone(struct sipe_account_data *sip,
3427 const char *uri,
3428 const gchar *phone_type,
3429 gchar *phone,
3430 gchar *phone_display_string)
3432 const char *phone_node = PHONE_PROP; /* work phone by default */
3433 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3435 if(!phone || strlen(phone) == 0) return;
3437 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3438 phone_node = PHONE_MOBILE_PROP;
3439 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3440 } else if (sipe_strequal(phone_type, "home")) {
3441 phone_node = PHONE_HOME_PROP;
3442 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3443 } else if (sipe_strequal(phone_type, "other")) {
3444 phone_node = PHONE_OTHER_PROP;
3445 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3446 } else if (sipe_strequal(phone_type, "custom1")) {
3447 phone_node = PHONE_CUSTOM1_PROP;
3448 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3451 sipe_update_user_info(sip, uri, phone_node, phone);
3452 if (phone_display_string) {
3453 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3457 static void
3458 sipe_update_calendar(struct sipe_account_data *sip)
3460 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3462 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: started.");
3464 if (sipe_strequal(calendar, "EXCH")) {
3465 sipe_ews_update_calendar(sip);
3468 /* schedule repeat */
3469 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3471 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: finished.");
3475 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3476 * by using standard Purple's means of signals and saved statuses.
3478 * Thus all UI elements get updated: Status Button with Note, docklet.
3479 * This is ablolutely important as both our status and note can come
3480 * inbound (roaming) or be updated programmatically (e.g. based on our
3481 * calendar data).
3483 static void
3484 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3485 const char *status_id,
3486 const char *message,
3487 time_t do_not_publish[])
3489 PurpleStatus *status = purple_account_get_active_status(account);
3490 gboolean changed = TRUE;
3492 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3493 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3495 changed = FALSE;
3498 if (purple_savedstatus_is_idleaway()) {
3499 changed = FALSE;
3502 if (changed) {
3503 PurpleSavedStatus *saved_status;
3504 const PurpleStatusType *acct_status_type =
3505 purple_status_type_find_with_id(account->status_types, status_id);
3506 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3507 sipe_activity activity = sipe_get_activity_by_token(status_id);
3509 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3510 if (saved_status) {
3511 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3514 /* If this type+message is unique then create a new transient saved status
3515 * Ref: gtkstatusbox.c
3517 if (!saved_status) {
3518 GList *tmp;
3519 GList *active_accts = purple_accounts_get_all_active();
3521 saved_status = purple_savedstatus_new(NULL, primitive);
3522 purple_savedstatus_set_message(saved_status, message);
3524 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3525 purple_savedstatus_set_substatus(saved_status,
3526 (PurpleAccount *)tmp->data, acct_status_type, message);
3528 g_list_free(active_accts);
3531 do_not_publish[activity] = time(NULL);
3532 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3533 status_id, (int)do_not_publish[activity]);
3535 /* Set the status for each account */
3536 purple_savedstatus_activate(saved_status);
3540 struct hash_table_delete_payload {
3541 GHashTable *hash_table;
3542 guint container;
3545 static void
3546 sipe_remove_category_container_publications_cb(const char *name,
3547 struct sipe_publication *publication,
3548 struct hash_table_delete_payload *payload)
3550 if (publication->container == payload->container) {
3551 g_hash_table_remove(payload->hash_table, name);
3554 static void
3555 sipe_remove_category_container_publications(GHashTable *our_publications,
3556 const char *category,
3557 guint container)
3559 struct hash_table_delete_payload payload;
3560 payload.hash_table = g_hash_table_lookup(our_publications, category);
3562 if (!payload.hash_table) return;
3564 payload.container = container;
3565 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3568 static void
3569 send_publish_category_initial(struct sipe_account_data *sip);
3572 * When we receive some self (BE) NOTIFY with a new subscriber
3573 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3576 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3578 gchar *contact;
3579 gchar *to;
3580 xmlnode *xml;
3581 xmlnode *node;
3582 xmlnode *node2;
3583 char *display_name = NULL;
3584 char *uri;
3585 GSList *category_names = NULL;
3586 int aggreg_avail = 0;
3587 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3588 gboolean do_update_status = FALSE;
3589 gboolean has_note_cleaned = FALSE;
3591 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3593 xml = xmlnode_from_str(msg->body, msg->bodylen);
3594 if (!xml) return;
3596 contact = get_contact(sip);
3597 to = sip_uri_self(sip);
3600 /* categories */
3601 /* set list of categories participating in this XML */
3602 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3603 const gchar *name = xmlnode_get_attrib(node, "name");
3604 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3606 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3607 category_names ? (int) g_slist_length(category_names) : -1);
3608 /* drop category information */
3609 if (category_names) {
3610 GSList *entry = category_names;
3611 while (entry) {
3612 GHashTable *cat_publications;
3613 const gchar *category = entry->data;
3614 entry = entry->next;
3615 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
3616 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3617 if (cat_publications) {
3618 g_hash_table_remove(sip->our_publications, category);
3619 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
3623 g_slist_free(category_names);
3624 /* filling our categories reflected in roaming data */
3625 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3626 const char *tmp;
3627 const gchar *name = xmlnode_get_attrib(node, "name");
3628 guint container = xmlnode_get_int_attrib(node, "container", -1);
3629 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3630 guint version = xmlnode_get_int_attrib(node, "version", 0);
3631 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3632 sipe_utils_str_to_time(tmp) : 0;
3633 gchar *key;
3634 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3636 /* Ex. clear note: <category name="note"/> */
3637 if (container == (guint)-1) {
3638 g_free(sip->note);
3639 sip->note = NULL;
3640 do_update_status = TRUE;
3641 continue;
3644 /* Ex. clear note: <category name="note" container="200"/> */
3645 if (instance == (guint)-1) {
3646 if (container == 200) {
3647 g_free(sip->note);
3648 sip->note = NULL;
3649 do_update_status = TRUE;
3651 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
3652 sipe_remove_category_container_publications(
3653 sip->our_publications, name, container);
3654 continue;
3657 /* key is <category><instance><container> */
3658 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3659 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
3661 /* capture all userState publication for later clean up if required */
3662 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3663 xmlnode *xn_state = xmlnode_get_child(node, "state");
3665 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3666 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3667 publication->category = g_strdup(name);
3668 publication->instance = instance;
3669 publication->container = container;
3670 publication->version = version;
3672 if (!sip->user_state_publications) {
3673 sip->user_state_publications = g_hash_table_new_full(
3674 g_str_hash, g_str_equal,
3675 g_free, (GDestroyNotify)free_publication);
3677 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3678 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3679 key, version);
3683 if (sipe_is_our_publication(sip, key)) {
3684 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3686 publication->category = g_strdup(name);
3687 publication->instance = instance;
3688 publication->container = container;
3689 publication->version = version;
3691 /* filling publication->availability */
3692 if (sipe_strequal(name, "state")) {
3693 xmlnode *xn_state = xmlnode_get_child(node, "state");
3694 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3696 if (xn_avail) {
3697 gchar *avail_str = xmlnode_get_data(xn_avail);
3698 if (avail_str) {
3699 publication->availability = atoi(avail_str);
3701 g_free(avail_str);
3703 /* for calendarState */
3704 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3705 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3706 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3708 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3709 if (xn_activity) {
3710 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3711 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3713 event->is_meeting = TRUE;
3716 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3717 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3719 publication->cal_event_hash = sipe_cal_event_hash(event);
3720 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3721 publication->cal_event_hash);
3722 sipe_cal_event_free(event);
3725 /* filling publication->note */
3726 if (sipe_strequal(name, "note")) {
3727 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3729 if (!has_note_cleaned) {
3730 has_note_cleaned = TRUE;
3732 g_free(sip->note);
3733 sip->note = NULL;
3734 sip->note_since = publish_time;
3736 do_update_status = TRUE;
3739 g_free(publication->note);
3740 publication->note = NULL;
3741 if (xn_body) {
3742 char *tmp;
3744 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3745 g_free(tmp);
3746 if (publish_time >= sip->note_since) {
3747 g_free(sip->note);
3748 sip->note = g_strdup(publication->note);
3749 sip->note_since = publish_time;
3750 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3752 do_update_status = TRUE;
3757 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3758 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3759 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3760 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3761 if (xn_free_busy) {
3762 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3763 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3765 if (xn_working_hours) {
3766 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3770 if (!cat_publications) {
3771 cat_publications = g_hash_table_new_full(
3772 g_str_hash, g_str_equal,
3773 g_free, (GDestroyNotify)free_publication);
3774 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3775 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3777 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3778 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3780 g_free(key);
3782 /* aggregateState (not an our publication) from 2-nd container */
3783 if (sipe_strequal(name, "state") && container == 2) {
3784 xmlnode *xn_state = xmlnode_get_child(node, "state");
3786 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3787 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3788 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3790 if (xn_avail) {
3791 gchar *avail_str = xmlnode_get_data(xn_avail);
3792 if (avail_str) {
3793 aggreg_avail = atoi(avail_str);
3795 g_free(avail_str);
3798 if (xn_activity) {
3799 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3801 aggreg_activity = sipe_get_activity_by_token(activity_token);
3804 do_update_status = TRUE;
3808 /* userProperties published by server from AD */
3809 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3810 xmlnode *line;
3811 /* line, for Remote Call Control (RCC) */
3812 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3813 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3814 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3815 gchar *line_uri;
3817 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3819 line_uri = xmlnode_get_data(line);
3820 if (line_uri) {
3821 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
3822 sip_csta_open(sip, line_uri, line_server);
3824 g_free(line_uri);
3826 break;
3830 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
3831 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3833 /* containers */
3834 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3835 guint id = xmlnode_get_int_attrib(node, "id", 0);
3836 struct sipe_container *container = sipe_find_container(sip, id);
3838 if (container) {
3839 sip->containers = g_slist_remove(sip->containers, container);
3840 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
3841 free_container(container);
3843 container = g_new0(struct sipe_container, 1);
3844 container->id = id;
3845 container->version = xmlnode_get_int_attrib(node, "version", 0);
3846 sip->containers = g_slist_append(sip->containers, container);
3847 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
3849 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3850 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3851 member->type = xmlnode_get_attrib(node2, "type");
3852 member->value = xmlnode_get_attrib(node2, "value");
3853 container->members = g_slist_append(container->members, member);
3854 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
3855 member->type, member->value ? member->value : "");
3859 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
3860 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3861 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3862 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3863 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
3864 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
3865 /* initial set-up to let counterparties see your status */
3866 if (sameEnterpriseAL < 0) {
3867 struct sipe_container *container = sipe_find_container(sip, 200);
3868 guint version = container ? container->version : 0;
3869 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3871 if (federatedAL < 0) {
3872 struct sipe_container *container = sipe_find_container(sip, 100);
3873 guint version = container ? container->version : 0;
3874 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3876 sip->access_level_set = TRUE;
3879 /* subscribers */
3880 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3881 const char *user;
3882 const char *acknowledged;
3883 gchar *hdr;
3884 gchar *body;
3886 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3887 if (!user) continue;
3888 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
3889 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3890 uri = sip_uri_from_name(user);
3892 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3894 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3895 if(sipe_strcase_equal(acknowledged,"false")){
3896 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
3897 if (!purple_find_buddy(sip->account, uri)) {
3898 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3901 hdr = g_strdup_printf(
3902 "Contact: %s\r\n"
3903 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3905 body = g_strdup_printf(
3906 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3907 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3908 "</setSubscribers>", user);
3910 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3911 g_free(body);
3912 g_free(hdr);
3914 g_free(display_name);
3915 g_free(uri);
3918 g_free(contact);
3919 xmlnode_free(xml);
3921 /* Publish initial state if not yet.
3922 * Assuming this happens on initial responce to subscription to roaming-self
3923 * so we've already updated our roaming data in full.
3924 * Only for 2007+
3926 if (!sip->initial_state_published) {
3927 send_publish_category_initial(sip);
3928 sip->initial_state_published = TRUE;
3929 /* dalayed run */
3930 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3931 do_update_status = FALSE;
3932 } else if (aggreg_avail) {
3934 g_free(sip->status);
3935 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3936 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3937 } else {
3938 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3942 if (do_update_status) {
3943 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
3944 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3947 g_free(to);
3950 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3952 gchar *to = sip_uri_self(sip);
3953 gchar *tmp = get_contact(sip);
3954 gchar *hdr = g_strdup_printf(
3955 "Event: vnd-microsoft-roaming-ACL\r\n"
3956 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3957 "Supported: com.microsoft.autoextend\r\n"
3958 "Supported: ms-benotify\r\n"
3959 "Proxy-Require: ms-benotify\r\n"
3960 "Supported: ms-piggyback-first-notify\r\n"
3961 "Contact: %s\r\n", tmp);
3962 g_free(tmp);
3964 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3965 g_free(to);
3966 g_free(hdr);
3970 * To request for presence information about the user, access level settings that have already been configured by the user
3971 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3972 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3975 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3977 gchar *to = sip_uri_self(sip);
3978 gchar *tmp = get_contact(sip);
3979 gchar *hdr = g_strdup_printf(
3980 "Event: vnd-microsoft-roaming-self\r\n"
3981 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3982 "Supported: ms-benotify\r\n"
3983 "Proxy-Require: ms-benotify\r\n"
3984 "Supported: ms-piggyback-first-notify\r\n"
3985 "Contact: %s\r\n"
3986 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3988 gchar *body=g_strdup(
3989 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3990 "<roaming type=\"categories\"/>"
3991 "<roaming type=\"containers\"/>"
3992 "<roaming type=\"subscribers\"/></roamingList>");
3994 g_free(tmp);
3995 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3996 g_free(body);
3997 g_free(to);
3998 g_free(hdr);
4002 * For 2005 version
4004 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
4006 gchar *to = sip_uri_self(sip);
4007 gchar *tmp = get_contact(sip);
4008 gchar *hdr = g_strdup_printf(
4009 "Event: vnd-microsoft-provisioning\r\n"
4010 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
4011 "Supported: com.microsoft.autoextend\r\n"
4012 "Supported: ms-benotify\r\n"
4013 "Proxy-Require: ms-benotify\r\n"
4014 "Supported: ms-piggyback-first-notify\r\n"
4015 "Expires: 0\r\n"
4016 "Contact: %s\r\n", tmp);
4018 g_free(tmp);
4019 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
4020 g_free(to);
4021 g_free(hdr);
4024 /** Subscription for provisioning information to help with initial
4025 * configuration. This subscription is a one-time query (denoted by the Expires header,
4026 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
4027 * configuration, meeting policies, and policy settings that Communicator must enforce.
4028 * TODO: for what we need this information.
4031 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
4033 gchar *to = sip_uri_self(sip);
4034 gchar *tmp = get_contact(sip);
4035 gchar *hdr = g_strdup_printf(
4036 "Event: vnd-microsoft-provisioning-v2\r\n"
4037 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
4038 "Supported: com.microsoft.autoextend\r\n"
4039 "Supported: ms-benotify\r\n"
4040 "Proxy-Require: ms-benotify\r\n"
4041 "Supported: ms-piggyback-first-notify\r\n"
4042 "Expires: 0\r\n"
4043 "Contact: %s\r\n"
4044 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
4045 gchar *body = g_strdup(
4046 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
4047 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
4048 "<provisioningGroup name=\"ucPolicy\"/>"
4049 "</provisioningGroupList>");
4051 g_free(tmp);
4052 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4053 g_free(body);
4054 g_free(to);
4055 g_free(hdr);
4058 static void
4059 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
4060 gpointer value, gpointer user_data)
4062 struct sip_subscription *subscription = value;
4063 struct sip_dialog *dialog = &subscription->dialog;
4064 struct sipe_account_data *sip = user_data;
4065 gchar *tmp = get_contact(sip);
4066 gchar *hdr = g_strdup_printf(
4067 "Event: %s\r\n"
4068 "Expires: 0\r\n"
4069 "Contact: %s\r\n", subscription->event, tmp);
4070 g_free(tmp);
4072 /* Rate limit to max. 25 requests per seconds */
4073 g_usleep(1000000 / 25);
4075 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
4076 g_free(hdr);
4079 /* IM Session (INVITE and MESSAGE methods) */
4081 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
4082 static gchar *
4083 get_end_points (struct sipe_account_data *sip,
4084 struct sip_session *session)
4086 gchar *res;
4088 if (session == NULL) {
4089 return NULL;
4092 res = g_strdup_printf("<sip:%s>", sip->username);
4094 SIPE_DIALOG_FOREACH {
4095 gchar *tmp = res;
4096 res = g_strdup_printf("%s, <%s>", res, dialog->with);
4097 g_free(tmp);
4099 if (dialog->theirepid) {
4100 tmp = res;
4101 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
4102 g_free(tmp);
4104 } SIPE_DIALOG_FOREACH_END;
4106 return res;
4109 static gboolean
4110 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
4111 struct sipmsg *msg,
4112 SIPE_UNUSED_PARAMETER struct transaction *trans)
4114 gboolean ret = TRUE;
4116 if (msg->response != 200) {
4117 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
4118 return FALSE;
4121 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
4123 return ret;
4127 * Asks UA/proxy about its capabilities.
4129 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
4131 gchar *to = sip_uri(who);
4132 gchar *contact = get_contact(sip);
4133 gchar *request = g_strdup_printf(
4134 "Accept: application/sdp\r\n"
4135 "Contact: %s\r\n", contact);
4136 g_free(contact);
4138 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
4140 g_free(to);
4141 g_free(request);
4144 static void
4145 sipe_notify_user(struct sipe_account_data *sip,
4146 struct sip_session *session,
4147 PurpleMessageFlags flags,
4148 const gchar *message)
4150 PurpleConversation *conv;
4152 if (!session->conv) {
4153 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
4154 } else {
4155 conv = session->conv;
4157 purple_conversation_write(conv, NULL, message, flags, time(NULL));
4160 void
4161 sipe_present_info(struct sipe_account_data *sip,
4162 struct sip_session *session,
4163 const gchar *message)
4165 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4168 static void
4169 sipe_present_err(struct sipe_account_data *sip,
4170 struct sip_session *session,
4171 const gchar *message)
4173 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4176 void
4177 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4178 struct sip_session *session,
4179 int sip_error,
4180 int sip_warning,
4181 const gchar *who,
4182 const gchar *message)
4184 char *msg, *msg_tmp, *msg_tmp2;
4185 const char *label;
4187 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
4188 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4189 g_free(msg_tmp);
4190 /* Service unavailable; Server Internal Error; Server Time-out */
4191 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4192 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4193 g_free(msg);
4194 msg = NULL;
4195 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4196 label = _("This message was not delivered to %s because the service is not available");
4197 } else if (sip_error == 486) { /* Busy Here */
4198 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4199 } else if (sip_error == 415) { /* Unsupported media type */
4200 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4201 } else {
4202 label = _("This message was not delivered to %s because one or more recipients are offline");
4205 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4206 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4207 msg ? ":" : "",
4208 msg ? msg : "");
4209 sipe_present_err(sip, session, msg_tmp);
4210 g_free(msg_tmp2);
4211 g_free(msg_tmp);
4212 g_free(msg);
4216 static gboolean
4217 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4218 SIPE_UNUSED_PARAMETER struct transaction *trans)
4220 gboolean ret = TRUE;
4221 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4222 struct sip_session *session = sipe_session_find_im(sip, with);
4223 struct sip_dialog *dialog;
4224 gchar *cseq;
4225 char *key;
4226 struct queued_message *message;
4228 if (!session) {
4229 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4230 g_free(with);
4231 return FALSE;
4234 dialog = sipe_dialog_find(session, with);
4235 if (!dialog) {
4236 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4237 g_free(with);
4238 return FALSE;
4241 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4242 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4243 g_free(cseq);
4244 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4246 if (msg->response >= 400) {
4247 PurpleBuddy *pbuddy;
4248 const char *alias = with;
4249 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4250 int warning = -1;
4252 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4254 if (warn_hdr) {
4255 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4256 if (parts[0]) {
4257 warning = atoi(parts[0]);
4259 g_strfreev(parts);
4262 /* cancel file transfer as rejected by server */
4263 if (msg->response == 606 && /* Not acceptable all. */
4264 warning == 309 && /* Message contents not allowed by policy */
4265 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4267 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4268 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4269 sipe_utils_nameval_free(parsed_body);
4272 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4273 alias = purple_buddy_get_alias(pbuddy);
4276 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4278 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4279 if (msg->response == 408 || /* Request timeout */
4280 msg->response == 480 || /* Temporarily Unavailable */
4281 msg->response == 481) { /* Call/Transaction Does Not Exist */
4282 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4283 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4286 ret = FALSE;
4287 } else {
4288 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4289 if (message_id) {
4290 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4291 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4292 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4295 g_hash_table_remove(session->unconfirmed_messages, key);
4296 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4297 key, g_hash_table_size(session->unconfirmed_messages));
4300 g_free(key);
4301 g_free(with);
4303 if (ret) sipe_im_process_queue(sip, session);
4304 return ret;
4307 static gboolean
4308 sipe_is_election_finished(struct sip_session *session);
4310 static void
4311 sipe_election_result(struct sipe_account_data *sip,
4312 void *sess);
4314 static gboolean
4315 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4316 SIPE_UNUSED_PARAMETER struct transaction *trans)
4318 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4319 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4320 struct sip_dialog *dialog;
4321 struct sip_session *session;
4323 session = sipe_session_find_chat_by_callid(sip, callid);
4324 if (!session) {
4325 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
4326 return FALSE;
4329 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4330 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4331 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4332 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4334 if (xn_request_rm_response) {
4335 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4336 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4338 dialog = sipe_dialog_find(session, with);
4339 if (!dialog) {
4340 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
4341 xmlnode_free(xn_action);
4342 return FALSE;
4345 if (allow && !g_strcasecmp(allow, "true")) {
4346 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
4347 dialog->election_vote = 1;
4348 } else if (allow && !g_strcasecmp(allow, "false")) {
4349 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
4350 dialog->election_vote = -1;
4353 if (sipe_is_election_finished(session)) {
4354 sipe_election_result(sip, session);
4357 } else if (xn_set_rm_response) {
4360 xmlnode_free(xn_action);
4364 return TRUE;
4367 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4369 gchar *hdr;
4370 gchar *tmp;
4371 char *msgtext = NULL;
4372 const gchar *msgr = "";
4373 gchar *tmp2 = NULL;
4375 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4376 char *msgformat;
4377 gchar *msgr_value;
4379 sipe_parse_html(msg, &msgformat, &msgtext);
4380 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
4382 msgr_value = sipmsg_get_msgr_string(msgformat);
4383 g_free(msgformat);
4384 if (msgr_value) {
4385 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4386 g_free(msgr_value);
4388 } else {
4389 msgtext = g_strdup(msg);
4392 tmp = get_contact(sip);
4393 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4394 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4395 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4396 if (content_type == NULL)
4397 content_type = "text/plain";
4399 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4400 g_free(tmp);
4401 g_free(tmp2);
4403 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4404 g_free(msgtext);
4405 g_free(hdr);
4409 void
4410 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4412 GSList *entry2 = session->outgoing_message_queue;
4413 while (entry2) {
4414 struct queued_message *msg = entry2->data;
4416 /* for multiparty chat or conference */
4417 if (session->is_multiparty || session->focus_uri) {
4418 gchar *who = sip_uri_self(sip);
4419 serv_got_chat_in(sip->gc, session->chat_id, who,
4420 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4421 g_free(who);
4424 SIPE_DIALOG_FOREACH {
4425 char *key;
4426 struct queued_message *message;
4428 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4430 message = g_new0(struct queued_message,1);
4431 message->body = g_strdup(msg->body);
4432 if (msg->content_type != NULL)
4433 message->content_type = g_strdup(msg->content_type);
4435 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4436 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4437 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4438 key, g_hash_table_size(session->unconfirmed_messages));
4439 g_free(key);
4441 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4442 } SIPE_DIALOG_FOREACH_END;
4444 entry2 = sipe_session_dequeue_message(session);
4448 static void
4449 sipe_refer_notify(struct sipe_account_data *sip,
4450 struct sip_session *session,
4451 const gchar *who,
4452 int status,
4453 const gchar *desc)
4455 gchar *hdr;
4456 gchar *body;
4457 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4459 hdr = g_strdup_printf(
4460 "Event: refer\r\n"
4461 "Subscription-State: %s\r\n"
4462 "Content-Type: message/sipfrag\r\n",
4463 status >= 200 ? "terminated" : "active");
4465 body = g_strdup_printf(
4466 "SIP/2.0 %d %s\r\n",
4467 status, desc);
4469 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4471 g_free(hdr);
4472 g_free(body);
4475 static gboolean
4476 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4478 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4479 struct sip_session *session;
4480 struct sip_dialog *dialog;
4481 char *cseq;
4482 char *key;
4483 struct queued_message *message;
4484 struct sipmsg *request_msg = trans->msg;
4486 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4487 gchar *referred_by;
4489 session = sipe_session_find_chat_by_callid(sip, callid);
4490 if (!session) {
4491 session = sipe_session_find_im(sip, with);
4493 if (!session) {
4494 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4495 g_free(with);
4496 return FALSE;
4499 dialog = sipe_dialog_find(session, with);
4500 if (!dialog) {
4501 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4502 g_free(with);
4503 return FALSE;
4506 sipe_dialog_parse(dialog, msg, TRUE);
4508 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4509 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4510 g_free(cseq);
4511 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4513 if (msg->response != 200) {
4514 PurpleBuddy *pbuddy;
4515 const char *alias = with;
4516 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4517 int warning = -1;
4519 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4521 if (warn_hdr) {
4522 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4523 if (parts[0]) {
4524 warning = atoi(parts[0]);
4526 g_strfreev(parts);
4529 /* cancel file transfer as rejected by server */
4530 if (msg->response == 606 && /* Not acceptable all. */
4531 warning == 309 && /* Message contents not allowed by policy */
4532 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4534 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4535 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4536 sipe_utils_nameval_free(parsed_body);
4539 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4540 alias = purple_buddy_get_alias(pbuddy);
4543 if (message) {
4544 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4545 } else {
4546 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4547 sipe_present_err(sip, session, tmp_msg);
4548 g_free(tmp_msg);
4551 sipe_dialog_remove(session, with);
4553 g_free(key);
4554 g_free(with);
4555 return FALSE;
4558 dialog->cseq = 0;
4559 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4560 dialog->outgoing_invite = NULL;
4561 dialog->is_established = TRUE;
4563 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4564 if (referred_by) {
4565 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4566 g_free(referred_by);
4569 /* add user to chat if it is a multiparty session */
4570 if (session->is_multiparty) {
4571 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4572 with, NULL,
4573 PURPLE_CBFLAGS_NONE, TRUE);
4576 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4577 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4578 sipe_session_dequeue_message(session);
4581 sipe_im_process_queue(sip, session);
4583 g_hash_table_remove(session->unconfirmed_messages, key);
4584 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4585 key, g_hash_table_size(session->unconfirmed_messages));
4587 g_free(key);
4588 g_free(with);
4589 return TRUE;
4593 void
4594 sipe_invite(struct sipe_account_data *sip,
4595 struct sip_session *session,
4596 const gchar *who,
4597 const gchar *msg_body,
4598 const gchar *msg_content_type,
4599 const gchar *referred_by,
4600 const gboolean is_triggered)
4602 gchar *hdr;
4603 gchar *to;
4604 gchar *contact;
4605 gchar *body;
4606 gchar *self;
4607 char *ms_text_format = NULL;
4608 gchar *roster_manager;
4609 gchar *end_points;
4610 gchar *referred_by_str;
4611 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4613 if (dialog && dialog->is_established) {
4614 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
4615 return;
4618 if (!dialog) {
4619 dialog = sipe_dialog_add(session);
4620 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4621 dialog->with = g_strdup(who);
4624 if (!(dialog->ourtag)) {
4625 dialog->ourtag = gentag();
4628 to = sip_uri(who);
4630 if (msg_body) {
4631 char *msgtext = NULL;
4632 char *base64_msg;
4633 const gchar *msgr = "";
4634 char *key;
4635 struct queued_message *message;
4636 gchar *tmp = NULL;
4638 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4639 char *msgformat;
4640 gchar *msgr_value;
4642 sipe_parse_html(msg_body, &msgformat, &msgtext);
4643 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
4645 msgr_value = sipmsg_get_msgr_string(msgformat);
4646 g_free(msgformat);
4647 if (msgr_value) {
4648 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4649 g_free(msgr_value);
4651 } else {
4652 msgtext = g_strdup(msg_body);
4655 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4656 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4657 msg_content_type ? msg_content_type : "text/plain",
4658 msgr,
4659 base64_msg);
4660 g_free(msgtext);
4661 g_free(tmp);
4662 g_free(base64_msg);
4664 message = g_new0(struct queued_message,1);
4665 message->body = g_strdup(msg_body);
4666 if (msg_content_type != NULL)
4667 message->content_type = g_strdup(msg_content_type);
4669 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4670 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4671 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4672 key, g_hash_table_size(session->unconfirmed_messages));
4673 g_free(key);
4676 contact = get_contact(sip);
4677 end_points = get_end_points(sip, session);
4678 self = sip_uri_self(sip);
4679 roster_manager = g_strdup_printf(
4680 "Roster-Manager: %s\r\n"
4681 "EndPoints: %s\r\n",
4682 self,
4683 end_points);
4684 referred_by_str = referred_by ?
4685 g_strdup_printf(
4686 "Referred-By: %s\r\n",
4687 referred_by)
4688 : g_strdup("");
4689 hdr = g_strdup_printf(
4690 "Supported: ms-sender\r\n"
4691 "%s"
4692 "%s"
4693 "%s"
4694 "%s"
4695 "Contact: %s\r\n%s"
4696 "Content-Type: application/sdp\r\n",
4697 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4698 referred_by_str,
4699 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4700 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4701 contact,
4702 ms_text_format ? ms_text_format : "");
4703 g_free(ms_text_format);
4704 g_free(self);
4706 body = g_strdup_printf(
4707 "v=0\r\n"
4708 "o=- 0 0 IN IP4 %s\r\n"
4709 "s=session\r\n"
4710 "c=IN IP4 %s\r\n"
4711 "t=0 0\r\n"
4712 "m=%s %d sip null\r\n"
4713 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4714 purple_network_get_my_ip(-1),
4715 purple_network_get_my_ip(-1),
4716 sip->ocs2007 ? "message" : "x-ms-message",
4717 sip->realport);
4719 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4720 to, to, hdr, body, dialog, process_invite_response);
4722 g_free(to);
4723 g_free(roster_manager);
4724 g_free(end_points);
4725 g_free(referred_by_str);
4726 g_free(body);
4727 g_free(hdr);
4728 g_free(contact);
4731 static void
4732 sipe_refer(struct sipe_account_data *sip,
4733 struct sip_session *session,
4734 const gchar *who)
4736 gchar *hdr;
4737 gchar *contact;
4738 gchar *epid = get_epid(sip);
4739 struct sip_dialog *dialog = sipe_dialog_find(session,
4740 session->roster_manager);
4741 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4743 contact = get_contact(sip);
4744 hdr = g_strdup_printf(
4745 "Contact: %s\r\n"
4746 "Refer-to: <%s>\r\n"
4747 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4748 "Require: com.microsoft.rtc-multiparty\r\n",
4749 contact,
4750 who,
4751 sip->username,
4752 ourtag ? ";tag=" : "",
4753 ourtag ? ourtag : "",
4754 epid);
4755 g_free(epid);
4757 send_sip_request(sip->gc, "REFER",
4758 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4760 g_free(hdr);
4761 g_free(contact);
4764 static void
4765 sipe_send_election_request_rm(struct sipe_account_data *sip,
4766 struct sip_dialog *dialog,
4767 int bid)
4769 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4771 gchar *body = g_strdup_printf(
4772 "<?xml version=\"1.0\"?>\r\n"
4773 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4774 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4775 sip->username, bid);
4777 send_sip_request(sip->gc, "INFO",
4778 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4780 g_free(body);
4783 static void
4784 sipe_send_election_set_rm(struct sipe_account_data *sip,
4785 struct sip_dialog *dialog)
4787 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4789 gchar *body = g_strdup_printf(
4790 "<?xml version=\"1.0\"?>\r\n"
4791 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4792 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4793 sip->username);
4795 send_sip_request(sip->gc, "INFO",
4796 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4798 g_free(body);
4801 static void
4802 sipe_session_close(struct sipe_account_data *sip,
4803 struct sip_session * session)
4805 if (session && session->focus_uri) {
4806 sipe_conf_immcu_closed(sip, session);
4807 conf_session_close(sip, session);
4810 if (session) {
4811 SIPE_DIALOG_FOREACH {
4812 /* @TODO slow down BYE message sending rate */
4813 /* @see single subscription code */
4814 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4815 } SIPE_DIALOG_FOREACH_END;
4817 sipe_session_remove(sip, session);
4821 static void
4822 sipe_session_close_all(struct sipe_account_data *sip)
4824 GSList *entry;
4825 while ((entry = sip->sessions) != NULL) {
4826 sipe_session_close(sip, entry->data);
4830 static void
4831 sipe_convo_closed(PurpleConnection * gc, const char *who)
4833 struct sipe_account_data *sip = gc->proto_data;
4835 SIPE_DEBUG_INFO("conversation with %s closed", who);
4836 sipe_session_close(sip, sipe_session_find_im(sip, who));
4839 static void
4840 sipe_chat_invite(PurpleConnection *gc, int id,
4841 SIPE_UNUSED_PARAMETER const char *message,
4842 const char *name)
4844 sipe_chat_create(gc->proto_data, id, name);
4847 static void
4848 sipe_chat_leave (PurpleConnection *gc, int id)
4850 struct sipe_account_data *sip = gc->proto_data;
4851 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4853 sipe_session_close(sip, session);
4856 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4857 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4859 struct sipe_account_data *sip = gc->proto_data;
4860 struct sip_session *session;
4861 struct sip_dialog *dialog;
4862 gchar *uri = sip_uri(who);
4864 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
4866 session = sipe_session_find_or_add_im(sip, uri);
4867 dialog = sipe_dialog_find(session, uri);
4869 // Queue the message
4870 sipe_session_enqueue_message(session, what, NULL);
4872 if (dialog && !dialog->outgoing_invite) {
4873 sipe_im_process_queue(sip, session);
4874 } else if (!dialog || !dialog->outgoing_invite) {
4875 // Need to send the INVITE to get the outgoing dialog setup
4876 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4879 g_free(uri);
4880 return 1;
4883 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4884 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4886 struct sipe_account_data *sip = gc->proto_data;
4887 struct sip_session *session;
4889 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
4891 session = sipe_session_find_chat_by_id(sip, id);
4893 // Queue the message
4894 if (session && session->dialogs) {
4895 sipe_session_enqueue_message(session,what,NULL);
4896 sipe_im_process_queue(sip, session);
4897 } else if (sip) {
4898 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4899 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4901 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
4902 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
4904 if (sip->ocs2007) {
4905 struct sip_session *session = sipe_session_add_chat(sip);
4907 session->is_multiparty = FALSE;
4908 session->focus_uri = g_strdup(proto_chat_id);
4909 sipe_session_enqueue_message(session, what, NULL);
4910 sipe_invite_conf_focus(sip, session);
4914 return 1;
4917 /* End IM Session (INVITE and MESSAGE methods) */
4919 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4921 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4922 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4923 gchar *from;
4924 struct sip_session *session;
4926 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
4928 /* Call Control protocol */
4929 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4931 process_incoming_info_csta(sip, msg);
4932 return;
4935 from = parse_from(sipmsg_find_header(msg, "From"));
4936 session = sipe_session_find_chat_by_callid(sip, callid);
4937 if (!session) {
4938 session = sipe_session_find_im(sip, from);
4940 if (!session) {
4941 g_free(from);
4942 return;
4945 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4947 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4948 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4949 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4951 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4953 if (xn_request_rm) {
4954 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4955 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4956 gchar *body = g_strdup_printf(
4957 "<?xml version=\"1.0\"?>\r\n"
4958 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4959 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4960 sip->username,
4961 session->bid < bid ? "true" : "false");
4962 send_sip_response(sip->gc, msg, 200, "OK", body);
4963 g_free(body);
4964 } else if (xn_set_rm) {
4965 gchar *body;
4966 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4967 g_free(session->roster_manager);
4968 session->roster_manager = g_strdup(rm);
4970 body = g_strdup_printf(
4971 "<?xml version=\"1.0\"?>\r\n"
4972 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4973 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4974 sip->username);
4975 send_sip_response(sip->gc, msg, 200, "OK", body);
4976 g_free(body);
4978 xmlnode_free(xn_action);
4981 else
4983 /* looks like purple lacks typing notification for chat */
4984 if (!session->is_multiparty && !session->focus_uri) {
4985 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4986 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4987 "status");
4988 if (sipe_strequal(status, "type")) {
4989 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4990 } else if (sipe_strequal(status, "idle")) {
4991 serv_got_typing_stopped(sip->gc, from);
4993 xmlnode_free(xn_keyboard_activity);
4996 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4998 g_free(from);
5001 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
5003 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5004 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5005 struct sip_session *session;
5006 struct sip_dialog *dialog;
5008 /* collect dialog identification
5009 * we need callid, ourtag and theirtag to unambiguously identify dialog
5011 /* take data before 'msg' will be modified by send_sip_response */
5012 dialog = g_new0(struct sip_dialog, 1);
5013 dialog->callid = g_strdup(callid);
5014 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
5015 dialog->with = g_strdup(from);
5016 sipe_dialog_parse(dialog, msg, FALSE);
5018 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5020 session = sipe_session_find_chat_by_callid(sip, callid);
5021 if (!session) {
5022 session = sipe_session_find_im(sip, from);
5024 if (!session) {
5025 sipe_dialog_free(dialog);
5026 g_free(from);
5027 return;
5030 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
5031 g_free(session->roster_manager);
5032 session->roster_manager = NULL;
5035 /* This what BYE is essentially for - terminating dialog */
5036 sipe_dialog_remove_3(session, dialog);
5037 sipe_dialog_free(dialog);
5038 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
5039 sipe_conf_immcu_closed(sip, session);
5040 } else if (session->is_multiparty) {
5041 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
5044 g_free(from);
5047 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
5049 gchar *self = sip_uri_self(sip);
5050 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5051 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5052 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
5053 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
5054 struct sip_session *session;
5055 struct sip_dialog *dialog;
5057 session = sipe_session_find_chat_by_callid(sip, callid);
5058 dialog = sipe_dialog_find(session, from);
5060 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
5061 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
5062 } else {
5063 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
5065 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
5068 g_free(self);
5069 g_free(from);
5070 g_free(refer_to);
5071 g_free(referred_by);
5074 static unsigned int
5075 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
5077 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
5078 struct sip_session *session;
5079 struct sip_dialog *dialog;
5081 if (state == PURPLE_NOT_TYPING)
5082 return 0;
5084 session = sipe_session_find_im(sip, who);
5085 dialog = sipe_dialog_find(session, who);
5087 if (session && dialog && dialog->is_established) {
5088 send_sip_request(gc, "INFO", who, who,
5089 "Content-Type: application/xml\r\n",
5090 SIPE_SEND_TYPING, dialog, NULL);
5092 return SIPE_TYPING_SEND_TIMEOUT;
5095 static gboolean resend_timeout(struct sipe_account_data *sip)
5097 GSList *tmp = sip->transactions;
5098 time_t currtime = time(NULL);
5099 while (tmp) {
5100 struct transaction *trans = tmp->data;
5101 tmp = tmp->next;
5102 SIPE_DEBUG_INFO("have open transaction age: %ld", (long int)currtime-trans->time);
5103 if ((currtime - trans->time > 5) && trans->retries >= 1) {
5104 /* TODO 408 */
5105 } else {
5106 if ((currtime - trans->time > 2) && trans->retries == 0) {
5107 trans->retries++;
5108 sendout_sipmsg(sip, trans->msg);
5112 return TRUE;
5115 static void do_reauthenticate_cb(struct sipe_account_data *sip,
5116 SIPE_UNUSED_PARAMETER void *unused)
5118 /* register again when security token expires */
5119 /* we have to start a new authentication as the security token
5120 * is almost expired by sending a not signed REGISTER message */
5121 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
5122 sipe_auth_free(&sip->registrar);
5123 sipe_auth_free(&sip->proxy);
5124 sip->registerstatus = 0;
5125 do_register(sip);
5126 sip->reauthenticate_set = FALSE;
5129 static gboolean
5130 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
5131 struct sipmsg *msg,
5132 GSList *parsed_body)
5134 gboolean found = FALSE;
5136 if (parsed_body) {
5137 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
5139 if (sipe_strequal(invitation_command, "INVITE")) {
5140 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
5141 found = TRUE;
5142 } else if (sipe_strequal(invitation_command, "CANCEL")) {
5143 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
5144 found = TRUE;
5145 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
5146 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
5147 found = TRUE;
5150 return found;
5153 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
5155 gchar *from;
5156 const gchar *contenttype;
5157 gboolean found = FALSE;
5159 from = parse_from(sipmsg_find_header(msg, "From"));
5161 if (!from) return;
5163 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
5165 contenttype = sipmsg_find_header(msg, "Content-Type");
5166 if (g_str_has_prefix(contenttype, "text/plain")
5167 || g_str_has_prefix(contenttype, "text/html")
5168 || g_str_has_prefix(contenttype, "multipart/related")
5169 || g_str_has_prefix(contenttype, "multipart/alternative"))
5171 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5172 gchar *html = get_html_message(contenttype, msg->body);
5174 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5175 if (!session) {
5176 session = sipe_session_find_im(sip, from);
5179 if (session && session->focus_uri) { /* a conference */
5180 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5181 gchar *sender = parse_from(tmp);
5182 g_free(tmp);
5183 serv_got_chat_in(sip->gc, session->chat_id, sender,
5184 PURPLE_MESSAGE_RECV, html, time(NULL));
5185 g_free(sender);
5186 } else if (session && session->is_multiparty) { /* a multiparty chat */
5187 serv_got_chat_in(sip->gc, session->chat_id, from,
5188 PURPLE_MESSAGE_RECV, html, time(NULL));
5189 } else {
5190 serv_got_im(sip->gc, from, html, 0, time(NULL));
5192 g_free(html);
5193 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5194 found = TRUE;
5196 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5197 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5198 xmlnode *state;
5199 gchar *statedata;
5201 if (!isc) {
5202 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
5203 g_free(from);
5204 return;
5207 state = xmlnode_get_child(isc, "state");
5209 if (!state) {
5210 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
5211 xmlnode_free(isc);
5212 g_free(from);
5213 return;
5216 statedata = xmlnode_get_data(state);
5217 if (statedata) {
5218 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5219 else serv_got_typing_stopped(sip->gc, from);
5221 g_free(statedata);
5223 xmlnode_free(isc);
5224 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5225 found = TRUE;
5226 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5227 GSList *body = sipe_ft_parse_msg_body(msg->body);
5228 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5229 sipe_utils_nameval_free(body);
5230 if (found) {
5231 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5234 if (!found) {
5235 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5236 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5237 if (!session) {
5238 session = sipe_session_find_im(sip, from);
5240 if (session) {
5241 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5242 from);
5243 sipe_present_err(sip, session, errmsg);
5244 g_free(errmsg);
5247 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
5248 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5250 g_free(from);
5253 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5255 gchar *body;
5256 gchar *newTag;
5257 const gchar *oldHeader;
5258 gchar *newHeader;
5259 gboolean is_multiparty = FALSE;
5260 gboolean is_triggered = FALSE;
5261 gboolean was_multiparty = TRUE;
5262 gboolean just_joined = FALSE;
5263 gchar *from;
5264 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5265 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5266 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5267 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5268 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5269 GSList *end_points = NULL;
5270 char *tmp = NULL;
5271 struct sip_session *session;
5272 const gchar *ms_text_format;
5274 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
5275 g_free(tmp);
5277 /* Invitation to join conference */
5278 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5279 process_incoming_invite_conf(sip, msg);
5280 return;
5283 /* Only accept text invitations */
5284 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5285 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5286 return;
5289 // TODO There *must* be a better way to clean up the To header to add a tag...
5290 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5291 oldHeader = sipmsg_find_header(msg, "To");
5292 newTag = gentag();
5293 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5294 sipmsg_remove_header_now(msg, "To");
5295 sipmsg_add_header_now(msg, "To", newHeader);
5296 g_free(newHeader);
5298 if (end_points_hdr) {
5299 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5301 if (g_slist_length(end_points) > 2) {
5302 is_multiparty = TRUE;
5305 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5306 is_triggered = TRUE;
5307 is_multiparty = TRUE;
5310 session = sipe_session_find_chat_by_callid(sip, callid);
5311 /* Convert to multiparty */
5312 if (session && is_multiparty && !session->is_multiparty) {
5313 g_free(session->with);
5314 session->with = NULL;
5315 was_multiparty = FALSE;
5316 session->is_multiparty = TRUE;
5317 session->chat_id = rand();
5320 if (!session && is_multiparty) {
5321 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5323 /* IM session */
5324 from = parse_from(sipmsg_find_header(msg, "From"));
5325 if (!session) {
5326 session = sipe_session_find_or_add_im(sip, from);
5329 if (session) {
5330 g_free(session->callid);
5331 session->callid = g_strdup(callid);
5333 session->is_multiparty = is_multiparty;
5334 if (roster_manager) {
5335 session->roster_manager = g_strdup(roster_manager);
5339 if (is_multiparty && end_points) {
5340 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5341 GSList *entry = end_points;
5342 while (entry) {
5343 struct sip_dialog *dialog;
5344 struct sipendpoint *end_point = entry->data;
5345 entry = entry->next;
5347 if (!g_strcasecmp(from, end_point->contact) ||
5348 !g_strcasecmp(to, end_point->contact))
5349 continue;
5351 dialog = sipe_dialog_find(session, end_point->contact);
5352 if (dialog) {
5353 g_free(dialog->theirepid);
5354 dialog->theirepid = end_point->epid;
5355 end_point->epid = NULL;
5356 } else {
5357 dialog = sipe_dialog_add(session);
5359 dialog->callid = g_strdup(session->callid);
5360 dialog->with = end_point->contact;
5361 end_point->contact = NULL;
5362 dialog->theirepid = end_point->epid;
5363 end_point->epid = NULL;
5365 just_joined = TRUE;
5367 /* send triggered INVITE */
5368 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5371 g_free(to);
5374 if (end_points) {
5375 GSList *entry = end_points;
5376 while (entry) {
5377 struct sipendpoint *end_point = entry->data;
5378 entry = entry->next;
5379 g_free(end_point->contact);
5380 g_free(end_point->epid);
5381 g_free(end_point);
5383 g_slist_free(end_points);
5386 if (session) {
5387 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5388 if (dialog) {
5389 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5390 sipe_dialog_parse_routes(dialog, msg, FALSE);
5391 } else {
5392 dialog = sipe_dialog_add(session);
5394 dialog->callid = g_strdup(session->callid);
5395 dialog->with = g_strdup(from);
5396 sipe_dialog_parse(dialog, msg, FALSE);
5398 if (!dialog->ourtag) {
5399 dialog->ourtag = newTag;
5400 newTag = NULL;
5403 just_joined = TRUE;
5405 } else {
5406 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5408 g_free(newTag);
5410 if (is_multiparty && !session->conv) {
5411 gchar *chat_title = sipe_chat_get_name(callid);
5412 gchar *self = sip_uri_self(sip);
5413 /* create prpl chat */
5414 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5415 session->chat_title = g_strdup(chat_title);
5416 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5417 /* add self */
5418 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5419 self, NULL,
5420 PURPLE_CBFLAGS_NONE, FALSE);
5421 g_free(chat_title);
5422 g_free(self);
5425 if (is_multiparty && !was_multiparty) {
5426 /* add current IM counterparty to chat */
5427 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5428 sipe_dialog_first(session)->with, NULL,
5429 PURPLE_CBFLAGS_NONE, FALSE);
5432 /* add inviting party to chat */
5433 if (just_joined && session->conv) {
5434 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5435 from, NULL,
5436 PURPLE_CBFLAGS_NONE, TRUE);
5439 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5441 /* This used only in 2005 official client, not 2007 or Reuters.
5442 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5443 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5445 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5446 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5447 if (is_multiparty ||
5448 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5450 if (ms_text_format) {
5451 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5453 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5454 if (tmp) {
5455 gsize len;
5456 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5458 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5460 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5461 sipe_utils_nameval_free(parsed_body);
5462 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5464 g_free(tmp);
5466 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5468 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5469 gchar *html = get_html_message(ms_text_format, NULL);
5470 if (html) {
5471 if (is_multiparty) {
5472 serv_got_chat_in(sip->gc, session->chat_id, from,
5473 PURPLE_MESSAGE_RECV, html, time(NULL));
5474 } else {
5475 serv_got_im(sip->gc, from, html, 0, time(NULL));
5477 g_free(html);
5478 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5484 g_free(from);
5486 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5487 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5488 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5490 body = g_strdup_printf(
5491 "v=0\r\n"
5492 "o=- 0 0 IN IP4 %s\r\n"
5493 "s=session\r\n"
5494 "c=IN IP4 %s\r\n"
5495 "t=0 0\r\n"
5496 "m=%s %d sip sip:%s\r\n"
5497 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5498 purple_network_get_my_ip(-1),
5499 purple_network_get_my_ip(-1),
5500 sip->ocs2007 ? "message" : "x-ms-message",
5501 sip->realport,
5502 sip->username);
5503 send_sip_response(sip->gc, msg, 200, "OK", body);
5504 g_free(body);
5507 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5509 gchar *body;
5511 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5512 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5513 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5515 body = g_strdup_printf(
5516 "v=0\r\n"
5517 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5518 "s=session\r\n"
5519 "c=IN IP4 0.0.0.0\r\n"
5520 "t=0 0\r\n"
5521 "m=%s %d sip sip:%s\r\n"
5522 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5523 sip->ocs2007 ? "message" : "x-ms-message",
5524 sip->realport,
5525 sip->username);
5526 send_sip_response(sip->gc, msg, 200, "OK", body);
5527 g_free(body);
5530 static const char*
5531 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5533 const char *res = "NTLM";
5534 #ifdef HAVE_KERBEROS
5535 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5536 res = "Kerberos";
5538 #else
5539 (void) sip; /* make compiler happy */
5540 #endif
5541 return res;
5544 static void sipe_connection_cleanup(struct sipe_account_data *);
5545 static void create_connection(struct sipe_account_data *, gchar *, int);
5547 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5548 SIPE_UNUSED_PARAMETER struct transaction *trans)
5550 gchar *tmp;
5551 const gchar *expires_header;
5552 int expires, i;
5553 GSList *hdr = msg->headers;
5554 struct sipnameval *elem;
5556 expires_header = sipmsg_find_header(msg, "Expires");
5557 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5558 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
5560 switch (msg->response) {
5561 case 200:
5562 if (expires == 0) {
5563 sip->registerstatus = 0;
5564 } else {
5565 const gchar *contact_hdr;
5566 gchar *gruu = NULL;
5567 gchar *epid;
5568 gchar *uuid;
5569 gchar *timeout;
5570 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5571 const char *auth_scheme;
5573 if (!sip->reregister_set) {
5574 gchar *action_name = g_strdup_printf("<%s>", "registration");
5575 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5576 g_free(action_name);
5577 sip->reregister_set = TRUE;
5580 sip->registerstatus = 3;
5582 if (server_hdr && !sip->server_version) {
5583 sip->server_version = g_strdup(server_hdr);
5584 g_free(default_ua);
5585 default_ua = NULL;
5588 auth_scheme = sipe_get_auth_scheme_name(sip);
5589 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5591 if (tmp) {
5592 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
5593 fill_auth(tmp, &sip->registrar);
5596 if (!sip->reauthenticate_set) {
5597 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5598 guint reauth_timeout;
5599 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5600 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5601 reauth_timeout = sip->registrar.expires - 300;
5602 } else {
5603 /* NTLM: we have to reauthenticate as our security token expires
5604 after eight hours (be five minutes early) */
5605 reauth_timeout = (8 * 3600) - 300;
5607 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5608 g_free(action_name);
5609 sip->reauthenticate_set = TRUE;
5612 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5614 epid = get_epid(sip);
5615 uuid = generateUUIDfromEPID(epid);
5616 g_free(epid);
5618 // There can be multiple Contact headers (one per location where the user is logged in) so
5619 // make sure to only get the one for this uuid
5620 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5621 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5622 if (valid_contact) {
5623 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5624 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5625 g_free(valid_contact);
5626 break;
5627 } else {
5628 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5631 g_free(uuid);
5633 g_free(sip->contact);
5634 if(gruu) {
5635 sip->contact = g_strdup_printf("<%s>", gruu);
5636 g_free(gruu);
5637 } else {
5638 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5639 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
5641 sip->ocs2007 = FALSE;
5642 sip->batched_support = FALSE;
5644 while(hdr)
5646 elem = hdr->data;
5647 if (sipe_strcase_equal(elem->name, "Supported")) {
5648 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5649 /* We interpret this as OCS2007+ indicator */
5650 sip->ocs2007 = TRUE;
5651 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5653 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5654 sip->batched_support = TRUE;
5655 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5658 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5659 gchar **caps = g_strsplit(elem->value,",",0);
5660 i = 0;
5661 while (caps[i]) {
5662 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5663 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5664 i++;
5666 g_strfreev(caps);
5668 hdr = g_slist_next(hdr);
5671 /* rejoin open chats to be able to use them by continue to send messages */
5672 purple_conversation_foreach(sipe_rejoin_chat);
5674 /* subscriptions */
5675 if (!sip->subscribed) { //do it just once, not every re-register
5677 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5678 (GCompareFunc)g_ascii_strcasecmp)) {
5679 sipe_subscribe_roaming_contacts(sip);
5682 /* For 2007+ it does not make sence to subscribe to:
5683 * vnd-microsoft-roaming-ACL
5684 * vnd-microsoft-provisioning (not v2)
5685 * presence.wpending
5686 * These are for backward compatibility.
5688 if (sip->ocs2007)
5690 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5691 (GCompareFunc)g_ascii_strcasecmp)) {
5692 sipe_subscribe_roaming_self(sip);
5694 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5695 (GCompareFunc)g_ascii_strcasecmp)) {
5696 sipe_subscribe_roaming_provisioning_v2(sip);
5699 /* For 2005- servers */
5700 else
5702 //sipe_options_request(sip, sip->sipdomain);
5704 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5705 (GCompareFunc)g_ascii_strcasecmp)) {
5706 sipe_subscribe_roaming_acl(sip);
5708 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5709 (GCompareFunc)g_ascii_strcasecmp)) {
5710 sipe_subscribe_roaming_provisioning(sip);
5712 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5713 (GCompareFunc)g_ascii_strcasecmp)) {
5714 sipe_subscribe_presence_wpending(sip, msg);
5717 /* For 2007+ we publish our initial statuses and calendar data only after
5718 * received our existing publications in sipe_process_roaming_self()
5719 * Only in this case we know versions of current publications made
5720 * on our behalf.
5722 /* For 2005- we publish our initial statuses only after
5723 * received our existing UserInfo data in response to
5724 * self subscription.
5725 * Only in this case we won't override existing UserInfo data
5726 * set earlier or by other client on our behalf.
5730 sip->subscribed = TRUE;
5733 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5734 "timeout=", ";", NULL);
5735 if (timeout != NULL) {
5736 sscanf(timeout, "%u", &sip->keepalive_timeout);
5737 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5738 sip->keepalive_timeout);
5739 g_free(timeout);
5742 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5744 break;
5745 case 301:
5747 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5749 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5750 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5751 gchar **tmp;
5752 gchar *hostname;
5753 int port = 0;
5754 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5755 int i = 1;
5757 tmp = g_strsplit(parts[0], ":", 0);
5758 hostname = g_strdup(tmp[0]);
5759 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5760 g_strfreev(tmp);
5762 while (parts[i]) {
5763 tmp = g_strsplit(parts[i], "=", 0);
5764 if (tmp[1]) {
5765 if (g_strcasecmp("transport", tmp[0]) == 0) {
5766 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5767 transport = SIPE_TRANSPORT_TCP;
5768 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5769 transport = SIPE_TRANSPORT_UDP;
5773 g_strfreev(tmp);
5774 i++;
5776 g_strfreev(parts);
5778 /* Close old connection */
5779 sipe_connection_cleanup(sip);
5781 /* Create new connection */
5782 sip->transport = transport;
5783 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5784 hostname, port, TRANSPORT_DESCRIPTOR);
5785 create_connection(sip, hostname, port);
5787 g_free(redirect);
5789 break;
5790 case 401:
5791 if (sip->registerstatus != 2) {
5792 const char *auth_scheme;
5793 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5794 if (sip->registrar.retries > 3) {
5795 sip->gc->wants_to_die = TRUE;
5796 purple_connection_error(sip->gc, _("Authentication failed"));
5797 return TRUE;
5800 auth_scheme = sipe_get_auth_scheme_name(sip);
5801 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5803 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
5804 if (!tmp) {
5805 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5806 sip->gc->wants_to_die = TRUE;
5807 purple_connection_error(sip->gc, tmp2);
5808 g_free(tmp2);
5809 return TRUE;
5811 fill_auth(tmp, &sip->registrar);
5812 sip->registerstatus = 2;
5813 if (sip->account->disconnecting) {
5814 do_register_exp(sip, 0);
5815 } else {
5816 do_register(sip);
5819 break;
5820 case 403:
5822 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5823 gchar **reason = NULL;
5824 gchar *warning;
5825 if (diagnostics != NULL) {
5826 /* Example header:
5827 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5829 reason = g_strsplit(diagnostics, "\"", 0);
5831 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5832 (reason && reason[1]) ? reason[1] : _("no reason given"));
5833 g_strfreev(reason);
5835 sip->gc->wants_to_die = TRUE;
5836 purple_connection_error(sip->gc, warning);
5837 g_free(warning);
5838 return TRUE;
5840 break;
5841 case 404:
5843 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5844 gchar *reason = NULL;
5845 gchar *warning;
5846 if (diagnostics != NULL) {
5847 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5849 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5850 diagnostics ? (reason ? reason : _("no reason given")) :
5851 _("SIP is either not enabled for the destination URI or it does not exist"));
5852 g_free(reason);
5854 sip->gc->wants_to_die = TRUE;
5855 purple_connection_error(sip->gc, warning);
5856 g_free(warning);
5857 return TRUE;
5859 break;
5860 case 503:
5861 case 504: /* Server time-out */
5863 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5864 gchar *reason = NULL;
5865 gchar *warning;
5866 if (diagnostics != NULL) {
5867 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5869 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5870 g_free(reason);
5872 sip->gc->wants_to_die = TRUE;
5873 purple_connection_error(sip->gc, warning);
5874 g_free(warning);
5875 return TRUE;
5877 break;
5879 return TRUE;
5883 * Returns 2005-style activity and Availability.
5885 * @param status Sipe statis id.
5887 static void
5888 sipe_get_act_avail_by_status_2005(const char *status,
5889 int *activity,
5890 int *availability)
5892 int avail = 300; /* online */
5893 int act = 400; /* Available */
5895 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5896 act = 100;
5897 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5898 // act = 150;
5899 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5900 act = 300;
5901 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5902 act = 400;
5903 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5904 // act = 500;
5905 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5906 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5907 act = 600;
5908 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5909 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5910 avail = 0; /* offline */
5911 act = 100;
5912 } else {
5913 act = 400; /* Available */
5916 if (activity) *activity = act;
5917 if (availability) *availability = avail;
5921 * [MS-SIP] 2.2.1
5923 * @param activity 2005 aggregated activity. Ex.: 600
5924 * @param availablity 2005 aggregated availablity. Ex.: 300
5926 static const char *
5927 sipe_get_status_by_act_avail_2005(const int activity,
5928 const int availablity,
5929 char **activity_desc)
5931 const char *status_id = NULL;
5932 const char *act = NULL;
5934 if (activity < 150) {
5935 status_id = SIPE_STATUS_ID_AWAY;
5936 } else if (activity < 200) {
5937 //status_id = SIPE_STATUS_ID_LUNCH;
5938 status_id = SIPE_STATUS_ID_AWAY;
5939 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5940 } else if (activity < 300) {
5941 //status_id = SIPE_STATUS_ID_IDLE;
5942 status_id = SIPE_STATUS_ID_AWAY;
5943 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5944 } else if (activity < 400) {
5945 status_id = SIPE_STATUS_ID_BRB;
5946 } else if (activity < 500) {
5947 status_id = SIPE_STATUS_ID_AVAILABLE;
5948 } else if (activity < 600) {
5949 //status_id = SIPE_STATUS_ID_ON_PHONE;
5950 status_id = SIPE_STATUS_ID_BUSY;
5951 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5952 } else if (activity < 700) {
5953 status_id = SIPE_STATUS_ID_BUSY;
5954 } else if (activity < 800) {
5955 status_id = SIPE_STATUS_ID_AWAY;
5956 } else {
5957 status_id = SIPE_STATUS_ID_AVAILABLE;
5960 if (availablity < 100)
5961 status_id = SIPE_STATUS_ID_OFFLINE;
5963 if (activity_desc && act) {
5964 g_free(*activity_desc);
5965 *activity_desc = g_strdup(act);
5968 return status_id;
5972 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5974 static const char*
5975 sipe_get_status_by_availability(int avail,
5976 char** activity_desc)
5978 const char *status;
5979 const char *act = NULL;
5981 if (avail < 3000) {
5982 status = SIPE_STATUS_ID_OFFLINE;
5983 } else if (avail < 4500) {
5984 status = SIPE_STATUS_ID_AVAILABLE;
5985 } else if (avail < 6000) {
5986 //status = SIPE_STATUS_ID_IDLE;
5987 status = SIPE_STATUS_ID_AVAILABLE;
5988 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5989 } else if (avail < 7500) {
5990 status = SIPE_STATUS_ID_BUSY;
5991 } else if (avail < 9000) {
5992 //status = SIPE_STATUS_ID_BUSYIDLE;
5993 status = SIPE_STATUS_ID_BUSY;
5994 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5995 } else if (avail < 12000) {
5996 status = SIPE_STATUS_ID_DND;
5997 } else if (avail < 15000) {
5998 status = SIPE_STATUS_ID_BRB;
5999 } else if (avail < 18000) {
6000 status = SIPE_STATUS_ID_AWAY;
6001 } else {
6002 status = SIPE_STATUS_ID_OFFLINE;
6005 if (activity_desc && act) {
6006 g_free(*activity_desc);
6007 *activity_desc = g_strdup(act);
6010 return status;
6014 * Returns 2007-style availability value
6016 * @param sipe_status_id (in)
6017 * @param activity_token (out) Must be g_free()'d after use if consumed.
6019 static int
6020 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
6022 int availability;
6023 sipe_activity activity = SIPE_ACTIVITY_UNSET;
6025 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
6026 availability = 15500;
6027 if (!activity_token || !(*activity_token)) {
6028 activity = SIPE_ACTIVITY_AWAY;
6030 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
6031 availability = 12500;
6032 activity = SIPE_ACTIVITY_BRB;
6033 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
6034 availability = 9500;
6035 activity = SIPE_ACTIVITY_DND;
6036 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
6037 availability = 6500;
6038 if (!activity_token || !(*activity_token)) {
6039 activity = SIPE_ACTIVITY_BUSY;
6041 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
6042 availability = 3500;
6043 activity = SIPE_ACTIVITY_ONLINE;
6044 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
6045 availability = 0;
6046 } else {
6047 // Offline or invisible
6048 availability = 18500;
6049 activity = SIPE_ACTIVITY_OFFLINE;
6052 if (activity_token) {
6053 *activity_token = g_strdup(sipe_activity_map[activity].token);
6055 return availability;
6058 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
6060 const char *uri;
6061 sipe_xml *xn_categories;
6062 const sipe_xml *xn_category;
6063 const char *status = NULL;
6064 gboolean do_update_status = FALSE;
6065 gboolean has_note_cleaned = FALSE;
6066 gboolean has_free_busy_cleaned = FALSE;
6068 xn_categories = sipe_xml_parse(data, len);
6069 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
6071 for (xn_category = sipe_xml_child(xn_categories, "category");
6072 xn_category ;
6073 xn_category = sipe_xml_twin(xn_category) )
6075 const sipe_xml *xn_node;
6076 const char *tmp;
6077 const char *attrVar = sipe_xml_attribute(xn_category, "name");
6078 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
6079 sipe_utils_str_to_time(tmp) : 0;
6081 /* contactCard */
6082 if (sipe_strequal(attrVar, "contactCard"))
6084 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
6086 if (card) {
6087 const sipe_xml *node;
6088 /* identity - Display Name and email */
6089 node = sipe_xml_child(card, "identity");
6090 if (node) {
6091 char* display_name = sipe_xml_data(
6092 sipe_xml_child(node, "name/displayName"));
6093 char* email = sipe_xml_data(
6094 sipe_xml_child(node, "email"));
6096 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6097 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6099 g_free(display_name);
6100 g_free(email);
6102 /* company */
6103 node = sipe_xml_child(card, "company");
6104 if (node) {
6105 char* company = sipe_xml_data(node);
6106 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
6107 g_free(company);
6109 /* department */
6110 node = sipe_xml_child(card, "department");
6111 if (node) {
6112 char* department = sipe_xml_data(node);
6113 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
6114 g_free(department);
6116 /* title */
6117 node = sipe_xml_child(card, "title");
6118 if (node) {
6119 char* title = sipe_xml_data(node);
6120 sipe_update_user_info(sip, uri, TITLE_PROP, title);
6121 g_free(title);
6123 /* office */
6124 node = sipe_xml_child(card, "office");
6125 if (node) {
6126 char* office = sipe_xml_data(node);
6127 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
6128 g_free(office);
6130 /* site (url) */
6131 node = sipe_xml_child(card, "url");
6132 if (node) {
6133 char* site = sipe_xml_data(node);
6134 sipe_update_user_info(sip, uri, SITE_PROP, site);
6135 g_free(site);
6137 /* phone */
6138 for (node = sipe_xml_child(card, "phone");
6139 node;
6140 node = sipe_xml_twin(node))
6142 const char *phone_type = sipe_xml_attribute(node, "type");
6143 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
6144 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
6146 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
6148 g_free(phone);
6149 g_free(phone_display_string);
6151 /* address */
6152 for (node = sipe_xml_child(card, "address");
6153 node;
6154 node = sipe_xml_twin(node))
6156 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
6157 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
6158 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
6159 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
6160 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
6161 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
6163 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
6164 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
6165 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
6166 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
6167 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
6169 g_free(street);
6170 g_free(city);
6171 g_free(state);
6172 g_free(zipcode);
6173 g_free(country_code);
6175 break;
6180 /* note */
6181 else if (sipe_strequal(attrVar, "note"))
6183 if (uri) {
6184 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6186 if (!has_note_cleaned) {
6187 has_note_cleaned = TRUE;
6189 g_free(sbuddy->note);
6190 sbuddy->note = NULL;
6191 sbuddy->is_oof_note = FALSE;
6192 sbuddy->note_since = publish_time;
6194 do_update_status = TRUE;
6196 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6197 /* clean up in case no 'note' element is supplied
6198 * which indicate note removal in client
6200 g_free(sbuddy->note);
6201 sbuddy->note = NULL;
6202 sbuddy->is_oof_note = FALSE;
6203 sbuddy->note_since = publish_time;
6205 xn_node = sipe_xml_child(xn_category, "note/body");
6206 if (xn_node) {
6207 char *tmp;
6208 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6209 g_free(tmp);
6210 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6211 sbuddy->note_since = publish_time;
6213 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6214 uri, sbuddy->note ? sbuddy->note : "");
6216 /* to trigger UI refresh in case no status info is supplied in this update */
6217 do_update_status = TRUE;
6221 /* state */
6222 else if(sipe_strequal(attrVar, "state"))
6224 char *tmp;
6225 int availability;
6226 const sipe_xml *xn_availability;
6227 const sipe_xml *xn_activity;
6228 const sipe_xml *xn_meeting_subject;
6229 const sipe_xml *xn_meeting_location;
6230 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6232 xn_node = sipe_xml_child(xn_category, "state");
6233 if (!xn_node) continue;
6234 xn_availability = sipe_xml_child(xn_node, "availability");
6235 if (!xn_availability) continue;
6236 xn_activity = sipe_xml_child(xn_node, "activity");
6237 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6238 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6240 tmp = sipe_xml_data(xn_availability);
6241 availability = atoi(tmp);
6242 g_free(tmp);
6244 /* activity, meeting_subject, meeting_location */
6245 if (sbuddy) {
6246 char *tmp = NULL;
6248 /* activity */
6249 g_free(sbuddy->activity);
6250 sbuddy->activity = NULL;
6251 if (xn_activity) {
6252 const char *token = sipe_xml_attribute(xn_activity, "token");
6253 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6255 /* from token */
6256 if (!is_empty(token)) {
6257 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6259 /* from custom element */
6260 if (xn_custom) {
6261 char *custom = sipe_xml_data(xn_custom);
6263 if (!is_empty(custom)) {
6264 sbuddy->activity = custom;
6265 custom = NULL;
6267 g_free(custom);
6270 /* meeting_subject */
6271 g_free(sbuddy->meeting_subject);
6272 sbuddy->meeting_subject = NULL;
6273 if (xn_meeting_subject) {
6274 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6276 if (!is_empty(meeting_subject)) {
6277 sbuddy->meeting_subject = meeting_subject;
6278 meeting_subject = NULL;
6280 g_free(meeting_subject);
6282 /* meeting_location */
6283 g_free(sbuddy->meeting_location);
6284 sbuddy->meeting_location = NULL;
6285 if (xn_meeting_location) {
6286 char *meeting_location = sipe_xml_data(xn_meeting_location);
6288 if (!is_empty(meeting_location)) {
6289 sbuddy->meeting_location = meeting_location;
6290 meeting_location = NULL;
6292 g_free(meeting_location);
6295 status = sipe_get_status_by_availability(availability, &tmp);
6296 if (sbuddy->activity && tmp) {
6297 char *tmp2 = sbuddy->activity;
6299 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6300 g_free(tmp);
6301 g_free(tmp2);
6302 } else if (tmp) {
6303 sbuddy->activity = tmp;
6307 do_update_status = TRUE;
6309 /* calendarData */
6310 else if(sipe_strequal(attrVar, "calendarData"))
6312 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6313 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6314 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6316 if (sbuddy && xn_free_busy) {
6317 if (!has_free_busy_cleaned) {
6318 has_free_busy_cleaned = TRUE;
6320 g_free(sbuddy->cal_start_time);
6321 sbuddy->cal_start_time = NULL;
6323 g_free(sbuddy->cal_free_busy_base64);
6324 sbuddy->cal_free_busy_base64 = NULL;
6326 g_free(sbuddy->cal_free_busy);
6327 sbuddy->cal_free_busy = NULL;
6329 sbuddy->cal_free_busy_published = publish_time;
6332 if (publish_time >= sbuddy->cal_free_busy_published) {
6333 g_free(sbuddy->cal_start_time);
6334 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6336 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6337 15 : 0;
6339 g_free(sbuddy->cal_free_busy_base64);
6340 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6342 g_free(sbuddy->cal_free_busy);
6343 sbuddy->cal_free_busy = NULL;
6345 sbuddy->cal_free_busy_published = publish_time;
6347 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);
6351 if (sbuddy && xn_working_hours) {
6352 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6357 if (do_update_status) {
6358 if (!status) { /* no status category in this update, using contact's current status */
6359 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6360 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6361 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6362 status = purple_status_get_id(pstatus);
6365 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
6366 sipe_got_user_status(sip, uri, status);
6369 sipe_xml_free(xn_categories);
6372 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6374 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6375 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
6376 payload->host = g_strdup(host);
6377 payload->buddies = server;
6378 sipe_subscribe_presence_batched_routed(sip, payload);
6379 sipe_subscribe_presence_batched_routed_free(payload);
6382 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6384 xmlnode *xn_list;
6385 xmlnode *xn_resource;
6386 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6387 g_free, NULL);
6388 GSList *server;
6389 gchar *host;
6391 xn_list = xmlnode_from_str(data, len);
6393 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6394 xn_resource;
6395 xn_resource = xmlnode_get_next_twin(xn_resource) )
6397 const char *uri, *state;
6398 xmlnode *xn_instance;
6400 xn_instance = xmlnode_get_child(xn_resource, "instance");
6401 if (!xn_instance) continue;
6403 uri = xmlnode_get_attrib(xn_resource, "uri");
6404 state = xmlnode_get_attrib(xn_instance, "state");
6405 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
6407 if (strstr(state, "resubscribe")) {
6408 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6410 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6411 gchar *user = g_strdup(uri);
6412 host = g_strdup(poolFqdn);
6413 server = g_hash_table_lookup(servers, host);
6414 server = g_slist_append(server, user);
6415 g_hash_table_insert(servers, host, server);
6416 } else {
6417 sipe_subscribe_presence_single(sip, (void *) uri);
6422 /* Send out any deferred poolFqdn subscriptions */
6423 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6424 g_hash_table_destroy(servers);
6426 xmlnode_free(xn_list);
6429 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6431 gchar *uri;
6432 gchar *getbasic;
6433 gchar *activity = NULL;
6434 xmlnode *pidf;
6435 xmlnode *basicstatus = NULL, *tuple, *status;
6436 gboolean isonline = FALSE;
6437 xmlnode *display_name_node;
6439 pidf = xmlnode_from_str(data, len);
6440 if (!pidf) {
6441 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
6442 return;
6445 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6447 if ((status = xmlnode_get_child(tuple, "status"))) {
6448 basicstatus = xmlnode_get_child(status, "basic");
6452 if (!basicstatus) {
6453 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6454 xmlnode_free(pidf);
6455 return;
6458 getbasic = xmlnode_get_data(basicstatus);
6459 if (!getbasic) {
6460 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6461 xmlnode_free(pidf);
6462 return;
6465 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
6466 if (strstr(getbasic, "open")) {
6467 isonline = TRUE;
6469 g_free(getbasic);
6471 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6473 display_name_node = xmlnode_get_child(pidf, "display-name");
6474 if (display_name_node) {
6475 char * display_name = xmlnode_get_data(display_name_node);
6477 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6478 g_free(display_name);
6481 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6482 if ((status = xmlnode_get_child(tuple, "status"))) {
6483 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6484 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6485 activity = xmlnode_get_data(basicstatus);
6486 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
6492 if (isonline) {
6493 const gchar * status_id = NULL;
6494 if (activity) {
6495 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6496 status_id = SIPE_STATUS_ID_BUSY;
6497 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6498 status_id = SIPE_STATUS_ID_AWAY;
6502 if (!status_id) {
6503 status_id = SIPE_STATUS_ID_AVAILABLE;
6506 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
6507 sipe_got_user_status(sip, uri, status_id);
6508 } else {
6509 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6512 g_free(activity);
6513 g_free(uri);
6514 xmlnode_free(pidf);
6517 /** 2005 */
6518 static void
6519 sipe_user_info_has_updated(struct sipe_account_data *sip,
6520 xmlnode *xn_userinfo)
6522 xmlnode *xn_states;
6524 g_free(sip->user_states);
6525 sip->user_states = NULL;
6526 if ((xn_states = xmlnode_get_child(xn_userinfo, "states")) != NULL) {
6527 gchar *orig = sip->user_states = xmlnode_to_str(xn_states, NULL);
6529 /* this is a hack-around to remove added newline after inner element,
6530 * state in this case, where it shouldn't be.
6531 * After several use of xmlnode_to_str, amount of added newlines
6532 * grows significantly.
6534 if (orig) {
6535 gchar c, *stripped = orig;
6536 while ((c = *orig++)) {
6537 if ((c != '\n') /* && (c != '\r') */) {
6538 *stripped++ = c;
6541 *stripped = '\0';
6545 /* Publish initial state if not yet.
6546 * Assuming this happens on initial responce to self subscription
6547 * so we've already updated our UserInfo.
6549 if (!sip->initial_state_published) {
6550 send_presence_soap(sip, FALSE);
6551 /* dalayed run */
6552 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6556 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6558 char *activity = NULL;
6559 const char *epid;
6560 const char *status_id = NULL;
6561 const char *name;
6562 char *uri;
6563 char *self_uri = sip_uri_self(sip);
6564 int avl;
6565 int act;
6566 const char *device_name = NULL;
6567 const char *cal_start_time = NULL;
6568 const char *cal_granularity = NULL;
6569 char *cal_free_busy_base64 = NULL;
6570 struct sipe_buddy *sbuddy;
6571 xmlnode *node;
6572 xmlnode *xn_presentity;
6573 xmlnode *xn_availability;
6574 xmlnode *xn_activity;
6575 xmlnode *xn_display_name;
6576 xmlnode *xn_email;
6577 xmlnode *xn_phone_number;
6578 xmlnode *xn_userinfo;
6579 xmlnode *xn_note;
6580 xmlnode *xn_oof;
6581 xmlnode *xn_state;
6582 xmlnode *xn_contact;
6583 char *note;
6584 char *free_activity;
6585 int user_avail;
6586 const char *user_avail_nil;
6587 int res_avail;
6588 time_t user_avail_since = 0;
6589 time_t activity_since = 0;
6591 /* fix for Reuters environment on Linux */
6592 if (data && strstr(data, "encoding=\"utf-16\"")) {
6593 char *tmp_data;
6594 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6595 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6596 g_free(tmp_data);
6597 } else {
6598 xn_presentity = xmlnode_from_str(data, len);
6601 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6602 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6603 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6604 xn_email = xmlnode_get_child(xn_presentity, "email");
6605 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6606 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6607 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6608 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6609 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6610 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6611 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6612 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6613 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6614 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6616 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6617 user_avail = 0;
6618 user_avail_since = 0;
6621 free_activity = NULL;
6623 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6624 uri = sip_uri_from_name(name);
6625 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6626 epid = xmlnode_get_attrib(xn_availability, "epid");
6627 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6629 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6630 res_avail = sipe_get_availability_by_status(status_id, NULL);
6631 if (user_avail > res_avail) {
6632 res_avail = user_avail;
6633 status_id = sipe_get_status_by_availability(user_avail, NULL);
6636 if (xn_display_name) {
6637 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6638 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6639 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6640 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6641 char *tel_uri = sip_to_tel_uri(phone_number);
6643 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6644 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6645 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6646 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6648 g_free(tel_uri);
6649 g_free(phone_label);
6650 g_free(phone_number);
6651 g_free(email);
6652 g_free(display_name);
6655 if (xn_contact) {
6656 /* tel */
6657 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6659 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6660 const char *phone_type = xmlnode_get_attrib(node, "type");
6661 char* phone = xmlnode_get_data(node);
6663 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6665 g_free(phone);
6669 /* devicePresence */
6670 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6671 xmlnode *xn_device_name;
6672 xmlnode *xn_calendar_info;
6673 xmlnode *xn_state;
6674 char *state;
6676 /* deviceName */
6677 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6678 xn_device_name = xmlnode_get_child(node, "deviceName");
6679 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6682 /* calendarInfo */
6683 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6684 if (xn_calendar_info) {
6685 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6687 if (cal_start_time) {
6688 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6689 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6691 if (cal_start_time_t_tmp > cal_start_time_t) {
6692 cal_start_time = cal_start_time_tmp;
6693 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6694 g_free(cal_free_busy_base64);
6695 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6697 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);
6699 } else {
6700 cal_start_time = cal_start_time_tmp;
6701 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6702 g_free(cal_free_busy_base64);
6703 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6705 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);
6709 /* state */
6710 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6711 if (xn_state) {
6712 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6713 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6715 state = xmlnode_get_data(xn_state);
6716 if (dev_avail_since > user_avail_since &&
6717 dev_avail >= res_avail)
6719 res_avail = dev_avail;
6720 if (!is_empty(state))
6722 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6723 g_free(activity);
6724 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6725 } else if (sipe_strequal(state, "presenting")) {
6726 g_free(activity);
6727 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6728 } else {
6729 activity = state;
6730 state = NULL;
6732 activity_since = dev_avail_since;
6734 status_id = sipe_get_status_by_availability(res_avail, &activity);
6736 g_free(state);
6740 /* oof */
6741 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6742 g_free(activity);
6743 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6744 activity_since = 0;
6747 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6748 if (sbuddy)
6750 g_free(sbuddy->activity);
6751 sbuddy->activity = activity;
6752 activity = NULL;
6754 sbuddy->activity_since = activity_since;
6756 sbuddy->user_avail = user_avail;
6757 sbuddy->user_avail_since = user_avail_since;
6759 g_free(sbuddy->note);
6760 sbuddy->note = NULL;
6761 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6763 sbuddy->is_oof_note = (xn_oof != NULL);
6765 g_free(sbuddy->device_name);
6766 sbuddy->device_name = NULL;
6767 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6769 if (!is_empty(cal_free_busy_base64)) {
6770 g_free(sbuddy->cal_start_time);
6771 sbuddy->cal_start_time = g_strdup(cal_start_time);
6773 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6775 g_free(sbuddy->cal_free_busy_base64);
6776 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6777 cal_free_busy_base64 = NULL;
6779 g_free(sbuddy->cal_free_busy);
6780 sbuddy->cal_free_busy = NULL;
6783 sbuddy->last_non_cal_status_id = status_id;
6784 g_free(sbuddy->last_non_cal_activity);
6785 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6787 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6788 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6790 sip->is_oof_note = sbuddy->is_oof_note;
6792 g_free(sip->note);
6793 sip->note = g_strdup(sbuddy->note);
6795 sip->note_since = time(NULL);
6798 g_free(sip->status);
6799 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6802 g_free(cal_free_busy_base64);
6803 g_free(activity);
6805 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
6806 sipe_got_user_status(sip, uri, status_id);
6808 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6809 sipe_user_info_has_updated(sip, xn_userinfo);
6812 g_free(note);
6813 xmlnode_free(xn_presentity);
6814 g_free(uri);
6815 g_free(self_uri);
6818 static void sipe_presence_mime_cb(gpointer user_data,
6819 const gchar *type,
6820 const gchar *body,
6821 gsize length)
6823 if (strstr(type,"application/rlmi+xml")) {
6824 process_incoming_notify_rlmi_resub(user_data, body, length);
6825 } else if (strstr(type, "text/xml+msrtc.pidf")) {
6826 process_incoming_notify_msrtc(user_data, body, length);
6827 } else {
6828 process_incoming_notify_rlmi(user_data, body, length);
6832 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6834 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6836 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
6838 if (ctype &&
6839 (strstr(ctype, "application/rlmi+xml") ||
6840 strstr(ctype, "application/msrtc-event-categories+xml")))
6842 if (strstr(ctype, "multipart"))
6844 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sip);
6846 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6848 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6850 else if(strstr(ctype, "application/rlmi+xml"))
6852 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6855 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6857 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6859 else
6861 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6865 static void sipe_presence_timeout_mime_cb(gpointer user_data,
6866 SIPE_UNUSED_PARAMETER const gchar *type,
6867 const gchar *body,
6868 gsize length)
6870 GSList **buddies = user_data;
6871 xmlnode *xml = xmlnode_from_str(body, length);
6873 if (xml && !sipe_strequal(xml->name, "list")) {
6874 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6875 *buddies = g_slist_append(*buddies, uri);
6878 xmlnode_free(xml);
6881 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6883 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6884 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6886 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
6888 if (ctype &&
6889 strstr(ctype, "multipart") &&
6890 (strstr(ctype, "application/rlmi+xml") ||
6891 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6892 GSList *buddies = NULL;
6893 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6895 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
6897 payload->host = g_strdup(who);
6898 payload->buddies = buddies;
6899 sipe_schedule_action(action_name, timeout,
6900 sipe_subscribe_presence_batched_routed,
6901 sipe_subscribe_presence_batched_routed_free,
6902 sip, payload);
6903 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
6905 } else {
6906 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6907 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
6909 g_free(action_name);
6913 * Dispatcher for all incoming subscription information
6914 * whether it comes from NOTIFY, BENOTIFY requests or
6915 * piggy-backed to subscription's OK responce.
6917 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6918 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6920 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6922 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6923 const gchar *event = sipmsg_find_header(msg, "Event");
6924 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6925 char *tmp;
6927 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
6928 event ? event : "",
6929 tmp = fix_newlines(msg->body));
6930 g_free(tmp);
6931 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
6933 /* implicit subscriptions */
6934 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6935 sipe_process_imdn(sip, msg);
6938 if (event) {
6939 /* for one off subscriptions (send with Expire: 0) */
6940 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
6942 sipe_process_provisioning_v2(sip, msg);
6944 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
6946 sipe_process_provisioning(sip, msg);
6948 else if (sipe_strcase_equal(event, "presence"))
6950 sipe_process_presence(sip, msg);
6952 else if (sipe_strcase_equal(event, "registration-notify"))
6954 sipe_process_registration_notify(sip, msg);
6957 if (!subscription_state || strstr(subscription_state, "active"))
6959 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
6961 sipe_process_roaming_contacts(sip, msg);
6963 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
6965 sipe_process_roaming_self(sip, msg);
6967 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
6969 sipe_process_roaming_acl(sip, msg);
6971 else if (sipe_strcase_equal(event, "presence.wpending"))
6973 sipe_process_presence_wpending(sip, msg);
6975 else if (sipe_strcase_equal(event, "conference"))
6977 sipe_process_conference(sip, msg);
6982 /* The server sends status 'terminated' */
6983 if (subscription_state && strstr(subscription_state, "terminated") ) {
6984 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6985 gchar *key = sipe_get_subscription_key(event, who);
6987 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
6988 g_free(who);
6990 if (g_hash_table_lookup(sip->subscriptions, key)) {
6991 g_hash_table_remove(sip->subscriptions, key);
6992 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
6995 g_free(key);
6998 if (!request && event) {
6999 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
7000 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
7001 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
7003 if (timeout) {
7004 /* 2 min ahead of expiration */
7005 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
7007 if (sipe_strcase_equal(event, "presence.wpending") &&
7008 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
7010 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
7011 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
7012 g_free(action_name);
7014 else if (sipe_strcase_equal(event, "presence") &&
7015 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
7017 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
7018 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7020 if (sip->batched_support) {
7021 sipe_process_presence_timeout(sip, msg, who, timeout);
7023 else {
7024 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7025 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
7027 g_free(action_name);
7028 g_free(who);
7033 /* The client responses on received a NOTIFY message */
7034 if (request && !benotify)
7036 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7041 * Whether user manually changed status or
7042 * it was changed automatically due to user
7043 * became inactive/active again
7045 static gboolean
7046 sipe_is_user_state(struct sipe_account_data *sip)
7048 gboolean res;
7049 time_t now = time(NULL);
7051 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
7052 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
7054 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
7056 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
7057 return res;
7060 static void
7061 send_presence_soap0(struct sipe_account_data *sip,
7062 gboolean do_publish_calendar,
7063 gboolean do_reset_status)
7065 struct sipe_ews* ews = sip->ews;
7066 int availability = 0;
7067 int activity = 0;
7068 gchar *body;
7069 gchar *tmp;
7070 gchar *tmp2 = NULL;
7071 gchar *res_note = NULL;
7072 gchar *res_oof = NULL;
7073 const gchar *note_pub = NULL;
7074 gchar *states = NULL;
7075 gchar *calendar_data = NULL;
7076 gchar *epid = get_epid(sip);
7077 time_t now = time(NULL);
7078 gchar *since_time_str = sipe_utils_time_to_str(now);
7079 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
7080 const char *user_input;
7081 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
7083 if (oof_note && sip->note) {
7084 SIPE_DEBUG_INFO("ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
7085 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
7088 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
7090 if (!sip->initial_state_published ||
7091 do_reset_status)
7093 g_free(sip->status);
7094 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
7097 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
7099 /* Note */
7100 if (pub_oof) {
7101 note_pub = oof_note;
7102 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
7103 ews->published = TRUE;
7104 } else if (sip->note) {
7105 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
7106 g_free(sip->note);
7107 sip->note = NULL;
7108 sip->is_oof_note = FALSE;
7109 sip->note_since = 0;
7110 } else {
7111 note_pub = sip->note;
7112 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
7116 if (note_pub)
7118 /* to protocol internal plain text format */
7119 tmp = sipe_backend_markup_strip_html(note_pub);
7120 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
7121 g_free(tmp);
7124 /* User State */
7125 if (!do_reset_status) {
7126 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
7128 gchar *activity_token = NULL;
7129 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
7131 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
7132 avail_2007,
7133 since_time_str,
7134 epid,
7135 activity_token);
7136 g_free(activity_token);
7138 else /* preserve existing publication */
7140 if (sip->user_states) {
7141 states = g_strdup(sip->user_states);
7144 } else {
7145 /* do nothing - then User state will be erased */
7147 sip->initial_state_published = TRUE;
7149 /* CalendarInfo */
7150 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
7152 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7153 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7154 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
7155 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
7156 fb_start_str,
7157 free_busy_base64);
7158 g_free(fb_start_str);
7159 g_free(free_busy_base64);
7162 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
7164 /* forming resulting XML */
7165 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7166 sip->username,
7167 availability,
7168 activity,
7169 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7170 res_note ? res_note : "",
7171 res_oof ? res_oof : "",
7172 states ? states : "",
7173 calendar_data ? calendar_data : "",
7174 epid,
7175 since_time_str,
7176 since_time_str,
7177 user_input);
7178 g_free(tmp);
7179 g_free(tmp2);
7180 g_free(res_note);
7181 g_free(states);
7182 g_free(calendar_data);
7184 send_soap_request(sip, body);
7186 g_free(body);
7187 g_free(since_time_str);
7188 g_free(epid);
7191 void
7192 send_presence_soap(struct sipe_account_data *sip,
7193 gboolean do_publish_calendar)
7195 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7199 static gboolean
7200 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7201 struct sipmsg *msg,
7202 struct transaction *trans)
7204 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7206 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7207 xmlnode *xml;
7208 xmlnode *node;
7209 gchar *fault_code;
7210 GHashTable *faults;
7211 int index_our;
7212 gboolean has_device_publication = FALSE;
7214 xml = xmlnode_from_str(msg->body, msg->bodylen);
7216 /* test if version mismatch fault */
7217 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7218 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7219 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
7220 g_free(fault_code);
7221 xmlnode_free(xml);
7222 return TRUE;
7224 g_free(fault_code);
7226 /* accumulating information about faulty versions */
7227 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7228 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7229 node;
7230 node = xmlnode_get_next_twin(node))
7232 const gchar *index = xmlnode_get_attrib(node, "index");
7233 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7235 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7236 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
7238 xmlnode_free(xml);
7240 /* here we are parsing own request to figure out what publication
7241 * referensed here only by index went wrong
7243 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7245 /* publication */
7246 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7247 index_our = 1; /* starts with 1 - our first publication */
7248 node;
7249 node = xmlnode_get_next_twin(node), index_our++)
7251 gchar *idx = g_strdup_printf("%d", index_our);
7252 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7253 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7254 g_free(idx);
7256 if (sipe_strequal("device", categoryName)) {
7257 has_device_publication = TRUE;
7260 if (curVersion) { /* fault exist on this index */
7261 const gchar *container = xmlnode_get_attrib(node, "container");
7262 const gchar *instance = xmlnode_get_attrib(node, "instance");
7263 /* key is <category><instance><container> */
7264 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7265 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
7267 if (category) {
7268 struct sipe_publication *publication =
7269 g_hash_table_lookup(category, key);
7271 SIPE_DEBUG_INFO("key is %s", key);
7273 if (publication) {
7274 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7275 key, curVersion, publication->version);
7276 /* updating publication's version to the correct one */
7277 publication->version = atoi(curVersion);
7279 } else {
7280 /* We somehow lost this category from our publications... */
7281 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
7282 publication->category = g_strdup(categoryName);
7283 publication->instance = atoi(instance);
7284 publication->container = atoi(container);
7285 publication->version = atoi(curVersion);
7286 category = g_hash_table_new_full(g_str_hash, g_str_equal,
7287 g_free, (GDestroyNotify)free_publication);
7288 g_hash_table_insert(category, g_strdup(key), publication);
7289 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
7290 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
7292 g_free(key);
7295 xmlnode_free(xml);
7296 g_hash_table_destroy(faults);
7298 /* rebublishing with right versions */
7299 if (has_device_publication) {
7300 send_publish_category_initial(sip);
7301 } else {
7302 send_presence_status(sip);
7305 return TRUE;
7309 * Returns 'device' XML part for publication.
7310 * Must be g_free'd after use.
7312 static gchar *
7313 sipe_publish_get_category_device(struct sipe_account_data *sip)
7315 gchar *uri;
7316 gchar *doc;
7317 gchar *epid = get_epid(sip);
7318 gchar *uuid = generateUUIDfromEPID(epid);
7319 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7320 /* key is <category><instance><container> */
7321 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7322 struct sipe_publication *publication =
7323 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7325 g_free(key);
7326 g_free(epid);
7328 uri = sip_uri_self(sip);
7329 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7330 device_instance,
7331 publication ? publication->version : 0,
7332 uuid,
7333 uri,
7334 "00:00:00+01:00", /* @TODO make timezone real*/
7335 g_get_host_name()
7338 g_free(uri);
7339 g_free(uuid);
7341 return doc;
7345 * A service method - use
7346 * - send_publish_get_category_state_machine and
7347 * - send_publish_get_category_state_user instead.
7348 * Must be g_free'd after use.
7350 static gchar *
7351 sipe_publish_get_category_state(struct sipe_account_data *sip,
7352 gboolean is_user_state)
7354 int availability = sipe_get_availability_by_status(sip->status, NULL);
7355 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7356 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7357 /* key is <category><instance><container> */
7358 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7359 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7360 struct sipe_publication *publication_2 =
7361 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7362 struct sipe_publication *publication_3 =
7363 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7365 g_free(key_2);
7366 g_free(key_3);
7368 if (publication_2 && (publication_2->availability == availability))
7370 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7371 return NULL; /* nothing to update */
7374 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7375 instance,
7376 publication_2 ? publication_2->version : 0,
7377 availability,
7378 instance,
7379 publication_3 ? publication_3->version : 0,
7380 availability);
7384 * Only Busy and OOF calendar event are published.
7385 * Different instances are used for that.
7387 * Must be g_free'd after use.
7389 static gchar *
7390 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7391 struct sipe_cal_event *event,
7392 const char *uri,
7393 int cal_satus)
7395 gchar *start_time_str;
7396 int availability = 0;
7397 gchar *res;
7398 gchar *tmp = NULL;
7399 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7400 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7401 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7403 /* key is <category><instance><container> */
7404 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7405 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7406 struct sipe_publication *publication_2 =
7407 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7408 struct sipe_publication *publication_3 =
7409 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7411 g_free(key_2);
7412 g_free(key_3);
7414 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7415 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7416 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
7417 return NULL;
7420 if (event &&
7421 publication_3 &&
7422 (publication_3->availability == availability) &&
7423 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7425 g_free(tmp);
7426 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7427 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
7428 return NULL; /* nothing to update */
7430 g_free(tmp);
7432 if (event &&
7433 (event->cal_status == SIPE_CAL_BUSY ||
7434 event->cal_status == SIPE_CAL_OOF))
7436 gchar *availability_xml_str = NULL;
7437 gchar *activity_xml_str = NULL;
7439 if (event->cal_status == SIPE_CAL_BUSY) {
7440 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7443 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7444 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7445 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7446 "minAvailability=\"6500\"",
7447 "maxAvailability=\"8999\"");
7448 } else if (event->cal_status == SIPE_CAL_OOF) {
7449 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7450 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7451 "minAvailability=\"12000\"",
7452 "");
7454 start_time_str = sipe_utils_time_to_str(event->start_time);
7456 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7457 instance,
7458 publication_2 ? publication_2->version : 0,
7459 uri,
7460 start_time_str,
7461 availability_xml_str ? availability_xml_str : "",
7462 activity_xml_str ? activity_xml_str : "",
7463 event->subject ? event->subject : "",
7464 event->location ? event->location : "",
7466 instance,
7467 publication_3 ? publication_3->version : 0,
7468 uri,
7469 start_time_str,
7470 availability_xml_str ? availability_xml_str : "",
7471 activity_xml_str ? activity_xml_str : "",
7472 event->subject ? event->subject : "",
7473 event->location ? event->location : ""
7475 g_free(start_time_str);
7476 g_free(availability_xml_str);
7477 g_free(activity_xml_str);
7480 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7482 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7483 instance,
7484 publication_2 ? publication_2->version : 0,
7486 instance,
7487 publication_3 ? publication_3->version : 0
7491 return res;
7495 * Returns 'machineState' XML part for publication.
7496 * Must be g_free'd after use.
7498 static gchar *
7499 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7501 return sipe_publish_get_category_state(sip, FALSE);
7505 * Returns 'userState' XML part for publication.
7506 * Must be g_free'd after use.
7508 static gchar *
7509 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7511 return sipe_publish_get_category_state(sip, TRUE);
7515 * Returns 'note' XML part for publication.
7516 * Must be g_free'd after use.
7518 * Protocol format for Note is plain text.
7520 * @param note a note in Sipe internal HTML format
7521 * @param note_type either personal or OOF
7523 static gchar *
7524 sipe_publish_get_category_note(struct sipe_account_data *sip,
7525 const char *note, /* html */
7526 const char *note_type,
7527 time_t note_start,
7528 time_t note_end)
7530 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7531 /* key is <category><instance><container> */
7532 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7533 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7534 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7536 struct sipe_publication *publication_note_200 =
7537 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7538 struct sipe_publication *publication_note_300 =
7539 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7540 struct sipe_publication *publication_note_400 =
7541 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7543 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7544 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7545 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7546 char *res, *tmp1, *tmp2, *tmp3;
7547 char *start_time_attr;
7548 char *end_time_attr;
7550 g_free(tmp);
7551 tmp = NULL;
7552 g_free(key_note_200);
7553 g_free(key_note_300);
7554 g_free(key_note_400);
7556 /* we even need to republish empty note */
7557 if (sipe_strequal(n1, n2))
7559 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7560 g_free(n1);
7561 return NULL; /* nothing to update */
7564 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7565 g_free(tmp);
7566 tmp = NULL;
7567 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7568 g_free(tmp);
7570 if (n1) {
7571 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7572 instance,
7573 200,
7574 publication_note_200 ? publication_note_200->version : 0,
7575 note_type,
7576 start_time_attr ? start_time_attr : "",
7577 end_time_attr ? end_time_attr : "",
7578 n1);
7580 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7581 instance,
7582 300,
7583 publication_note_300 ? publication_note_300->version : 0,
7584 note_type,
7585 start_time_attr ? start_time_attr : "",
7586 end_time_attr ? end_time_attr : "",
7587 n1);
7589 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7590 instance,
7591 400,
7592 publication_note_400 ? publication_note_400->version : 0,
7593 note_type,
7594 start_time_attr ? start_time_attr : "",
7595 end_time_attr ? end_time_attr : "",
7596 n1);
7597 } else {
7598 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7599 "note",
7600 instance,
7601 200,
7602 publication_note_200 ? publication_note_200->version : 0,
7603 "static");
7604 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7605 "note",
7606 instance,
7607 300,
7608 publication_note_200 ? publication_note_200->version : 0,
7609 "static");
7610 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7611 "note",
7612 instance,
7613 400,
7614 publication_note_200 ? publication_note_200->version : 0,
7615 "static");
7617 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7619 g_free(start_time_attr);
7620 g_free(end_time_attr);
7621 g_free(tmp1);
7622 g_free(tmp2);
7623 g_free(tmp3);
7624 g_free(n1);
7626 return res;
7630 * Returns 'calendarData' XML part with WorkingHours for publication.
7631 * Must be g_free'd after use.
7633 static gchar *
7634 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7636 struct sipe_ews* ews = sip->ews;
7638 /* key is <category><instance><container> */
7639 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7640 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7641 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7642 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7643 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7644 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7646 struct sipe_publication *publication_cal_1 =
7647 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7648 struct sipe_publication *publication_cal_100 =
7649 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7650 struct sipe_publication *publication_cal_200 =
7651 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7652 struct sipe_publication *publication_cal_300 =
7653 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7654 struct sipe_publication *publication_cal_400 =
7655 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7656 struct sipe_publication *publication_cal_32000 =
7657 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7659 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7660 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7662 g_free(key_cal_1);
7663 g_free(key_cal_100);
7664 g_free(key_cal_200);
7665 g_free(key_cal_300);
7666 g_free(key_cal_400);
7667 g_free(key_cal_32000);
7669 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7670 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7671 return NULL;
7674 if (sipe_strequal(n1, n2))
7676 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7677 return NULL; /* nothing to update */
7680 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7681 /* 1 */
7682 publication_cal_1 ? publication_cal_1->version : 0,
7683 ews->email,
7684 ews->working_hours_xml_str,
7685 /* 100 - Public */
7686 publication_cal_100 ? publication_cal_100->version : 0,
7687 /* 200 - Company */
7688 publication_cal_200 ? publication_cal_200->version : 0,
7689 ews->email,
7690 ews->working_hours_xml_str,
7691 /* 300 - Team */
7692 publication_cal_300 ? publication_cal_300->version : 0,
7693 ews->email,
7694 ews->working_hours_xml_str,
7695 /* 400 - Personal */
7696 publication_cal_400 ? publication_cal_400->version : 0,
7697 ews->email,
7698 ews->working_hours_xml_str,
7699 /* 32000 - Blocked */
7700 publication_cal_32000 ? publication_cal_32000->version : 0
7705 * Returns 'calendarData' XML part with FreeBusy for publication.
7706 * Must be g_free'd after use.
7708 static gchar *
7709 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7711 struct sipe_ews* ews = sip->ews;
7712 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7713 char *fb_start_str;
7714 char *free_busy_base64;
7715 const char *st;
7716 const char *fb;
7717 char *res;
7719 /* key is <category><instance><container> */
7720 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7721 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7722 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7723 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7724 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7725 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7727 struct sipe_publication *publication_cal_1 =
7728 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7729 struct sipe_publication *publication_cal_100 =
7730 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7731 struct sipe_publication *publication_cal_200 =
7732 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7733 struct sipe_publication *publication_cal_300 =
7734 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7735 struct sipe_publication *publication_cal_400 =
7736 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7737 struct sipe_publication *publication_cal_32000 =
7738 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7740 g_free(key_cal_1);
7741 g_free(key_cal_100);
7742 g_free(key_cal_200);
7743 g_free(key_cal_300);
7744 g_free(key_cal_400);
7745 g_free(key_cal_32000);
7747 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7748 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7749 return NULL;
7752 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7753 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7755 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7756 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7758 /* we will rebuplish the same data to refresh publication time,
7759 * so if data from multiple sources, most recent will be choosen
7761 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7763 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7764 // g_free(fb_start_str);
7765 // g_free(free_busy_base64);
7766 // return NULL; /* nothing to update */
7769 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7770 /* 1 */
7771 cal_data_instance,
7772 publication_cal_1 ? publication_cal_1->version : 0,
7773 /* 100 - Public */
7774 cal_data_instance,
7775 publication_cal_100 ? publication_cal_100->version : 0,
7776 /* 200 - Company */
7777 cal_data_instance,
7778 publication_cal_200 ? publication_cal_200->version : 0,
7779 ews->email,
7780 fb_start_str,
7781 free_busy_base64,
7782 /* 300 - Team */
7783 cal_data_instance,
7784 publication_cal_300 ? publication_cal_300->version : 0,
7785 ews->email,
7786 fb_start_str,
7787 free_busy_base64,
7788 /* 400 - Personal */
7789 cal_data_instance,
7790 publication_cal_400 ? publication_cal_400->version : 0,
7791 ews->email,
7792 fb_start_str,
7793 free_busy_base64,
7794 /* 32000 - Blocked */
7795 cal_data_instance,
7796 publication_cal_32000 ? publication_cal_32000->version : 0
7799 g_free(fb_start_str);
7800 g_free(free_busy_base64);
7801 return res;
7804 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7806 gchar *uri;
7807 gchar *doc;
7808 gchar *tmp;
7809 gchar *hdr;
7811 uri = sip_uri_self(sip);
7812 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7813 uri,
7814 publications);
7816 tmp = get_contact(sip);
7817 hdr = g_strdup_printf("Contact: %s\r\n"
7818 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7820 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7822 g_free(tmp);
7823 g_free(hdr);
7824 g_free(uri);
7825 g_free(doc);
7828 static void
7829 send_publish_category_initial(struct sipe_account_data *sip)
7831 gchar *pub_device = sipe_publish_get_category_device(sip);
7832 gchar *pub_machine;
7833 gchar *publications;
7835 g_free(sip->status);
7836 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7838 pub_machine = sipe_publish_get_category_state_machine(sip);
7839 publications = g_strdup_printf("%s%s",
7840 pub_device,
7841 pub_machine ? pub_machine : "");
7842 g_free(pub_device);
7843 g_free(pub_machine);
7845 send_presence_publish(sip, publications);
7846 g_free(publications);
7849 static void
7850 send_presence_category_publish(struct sipe_account_data *sip)
7852 gchar *pub_state = sipe_is_user_state(sip) ?
7853 sipe_publish_get_category_state_user(sip) :
7854 sipe_publish_get_category_state_machine(sip);
7855 gchar *pub_note = sipe_publish_get_category_note(sip,
7856 sip->note,
7857 sip->is_oof_note ? "OOF" : "personal",
7860 gchar *publications;
7862 if (!pub_state && !pub_note) {
7863 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
7864 return;
7867 publications = g_strdup_printf("%s%s",
7868 pub_state ? pub_state : "",
7869 pub_note ? pub_note : "");
7871 g_free(pub_state);
7872 g_free(pub_note);
7874 send_presence_publish(sip, publications);
7875 g_free(publications);
7879 * Publishes self status
7880 * based on own calendar information.
7882 * For 2007+
7884 void
7885 publish_calendar_status_self(struct sipe_account_data *sip)
7887 struct sipe_cal_event* event = NULL;
7888 gchar *pub_cal_working_hours = NULL;
7889 gchar *pub_cal_free_busy = NULL;
7890 gchar *pub_calendar = NULL;
7891 gchar *pub_calendar2 = NULL;
7892 gchar *pub_oof_note = NULL;
7893 const gchar *oof_note;
7894 time_t oof_start = 0;
7895 time_t oof_end = 0;
7897 if (!sip->ews) {
7898 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
7899 return;
7902 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
7903 if (sip->ews->cal_events) {
7904 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7907 if (!event) {
7908 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
7909 } else {
7910 char *desc = sipe_cal_event_describe(event);
7911 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7912 g_free(desc);
7915 /* Logic
7916 if OOF
7917 OOF publish, Busy clean
7918 ilse if Busy
7919 OOF clean, Busy publish
7920 else
7921 OOF clean, Busy clean
7923 if (event && event->cal_status == SIPE_CAL_OOF) {
7924 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7925 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7926 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7927 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7928 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7929 } else {
7930 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7931 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7934 oof_note = sipe_ews_get_oof_note(sip->ews);
7935 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7936 oof_start = sip->ews->oof_start;
7937 oof_end = sip->ews->oof_end;
7939 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7941 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7942 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7944 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7945 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
7946 } else {
7947 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7948 pub_cal_working_hours ? pub_cal_working_hours : "",
7949 pub_cal_free_busy ? pub_cal_free_busy : "",
7950 pub_calendar ? pub_calendar : "",
7951 pub_calendar2 ? pub_calendar2 : "",
7952 pub_oof_note ? pub_oof_note : "");
7954 send_presence_publish(sip, publications);
7955 g_free(publications);
7958 g_free(pub_cal_working_hours);
7959 g_free(pub_cal_free_busy);
7960 g_free(pub_calendar);
7961 g_free(pub_calendar2);
7962 g_free(pub_oof_note);
7964 /* repeat scheduling */
7965 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7968 static void send_presence_status(struct sipe_account_data *sip)
7970 PurpleStatus * status = purple_account_get_active_status(sip->account);
7972 if (!status) return;
7974 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
7975 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7976 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7978 if (sip->ocs2007) {
7979 send_presence_category_publish(sip);
7980 } else {
7981 send_presence_soap(sip, FALSE);
7985 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7987 gboolean found = FALSE;
7988 const char *method = msg->method ? msg->method : "NOT FOUND";
7989 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
7990 if (msg->response == 0) { /* request */
7991 if (sipe_strequal(method, "MESSAGE")) {
7992 process_incoming_message(sip, msg);
7993 found = TRUE;
7994 } else if (sipe_strequal(method, "NOTIFY")) {
7995 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
7996 process_incoming_notify(sip, msg, TRUE, FALSE);
7997 found = TRUE;
7998 } else if (sipe_strequal(method, "BENOTIFY")) {
7999 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
8000 process_incoming_notify(sip, msg, TRUE, TRUE);
8001 found = TRUE;
8002 } else if (sipe_strequal(method, "INVITE")) {
8003 process_incoming_invite(sip, msg);
8004 found = TRUE;
8005 } else if (sipe_strequal(method, "REFER")) {
8006 process_incoming_refer(sip, msg);
8007 found = TRUE;
8008 } else if (sipe_strequal(method, "OPTIONS")) {
8009 process_incoming_options(sip, msg);
8010 found = TRUE;
8011 } else if (sipe_strequal(method, "INFO")) {
8012 process_incoming_info(sip, msg);
8013 found = TRUE;
8014 } else if (sipe_strequal(method, "ACK")) {
8015 // ACK's don't need any response
8016 found = TRUE;
8017 } else if (sipe_strequal(method, "SUBSCRIBE")) {
8018 // LCS 2005 sends us these - just respond 200 OK
8019 found = TRUE;
8020 send_sip_response(sip->gc, msg, 200, "OK", NULL);
8021 } else if (sipe_strequal(method, "BYE")) {
8022 process_incoming_bye(sip, msg);
8023 found = TRUE;
8024 } else {
8025 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
8027 } else { /* response */
8028 struct transaction *trans = transactions_find(sip, msg);
8029 if (trans) {
8030 if (msg->response == 407) {
8031 gchar *resend, *auth;
8032 const gchar *ptmp;
8034 if (sip->proxy.retries > 30) return;
8035 sip->proxy.retries++;
8036 /* do proxy authentication */
8038 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
8040 fill_auth(ptmp, &sip->proxy);
8041 auth = auth_header(sip, &sip->proxy, trans->msg);
8042 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
8043 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
8044 g_free(auth);
8045 resend = sipmsg_to_string(trans->msg);
8046 /* resend request */
8047 sendout_pkt(sip->gc, resend);
8048 g_free(resend);
8049 } else {
8050 if (msg->response < 200) {
8051 /* ignore provisional response */
8052 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
8053 } else {
8054 sip->proxy.retries = 0;
8055 if (sipe_strequal(trans->msg->method, "REGISTER")) {
8056 if (msg->response == 401)
8058 sip->registrar.retries++;
8060 else
8062 sip->registrar.retries = 0;
8064 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
8065 } else {
8066 if (msg->response == 401) {
8067 gchar *resend, *auth, *ptmp;
8068 const char* auth_scheme;
8070 if (sip->registrar.retries > 4) return;
8071 sip->registrar.retries++;
8073 auth_scheme = sipe_get_auth_scheme_name(sip);
8074 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
8076 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
8077 if (!ptmp) {
8078 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
8079 sip->gc->wants_to_die = TRUE;
8080 purple_connection_error(sip->gc, tmp2);
8081 g_free(tmp2);
8082 return;
8085 fill_auth(ptmp, &sip->registrar);
8086 auth = auth_header(sip, &sip->registrar, trans->msg);
8087 sipmsg_remove_header_now(trans->msg, "Authorization");
8088 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
8089 g_free(auth);
8090 resend = sipmsg_to_string(trans->msg);
8091 /* resend request */
8092 sendout_pkt(sip->gc, resend);
8093 g_free(resend);
8097 if (trans->callback) {
8098 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
8099 /* call the callback to process response*/
8100 (trans->callback)(sip, msg, trans);
8103 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
8104 transactions_remove(sip, trans);
8108 found = TRUE;
8109 } else {
8110 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
8113 if (!found) {
8114 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
8118 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
8120 char *cur;
8121 char *dummy;
8122 char *tmp;
8123 struct sipmsg *msg;
8124 int restlen;
8125 cur = conn->inbuf;
8127 /* according to the RFC remove CRLF at the beginning */
8128 while (*cur == '\r' || *cur == '\n') {
8129 cur++;
8131 if (cur != conn->inbuf) {
8132 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
8133 conn->inbufused = strlen(conn->inbuf);
8136 /* Received a full Header? */
8137 sip->processing_input = TRUE;
8138 while (sip->processing_input &&
8139 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
8140 time_t currtime = time(NULL);
8141 cur += 2;
8142 cur[0] = '\0';
8143 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
8144 g_free(tmp);
8145 msg = sipmsg_parse_header(conn->inbuf);
8146 cur[0] = '\r';
8147 cur += 2;
8148 restlen = conn->inbufused - (cur - conn->inbuf);
8149 if (msg && restlen >= msg->bodylen) {
8150 dummy = g_malloc(msg->bodylen + 1);
8151 memcpy(dummy, cur, msg->bodylen);
8152 dummy[msg->bodylen] = '\0';
8153 msg->body = dummy;
8154 cur += msg->bodylen;
8155 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
8156 conn->inbufused = strlen(conn->inbuf);
8157 } else {
8158 if (msg){
8159 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen, msg->bodylen, (int)strlen(conn->inbuf));
8160 sipmsg_free(msg);
8162 return;
8165 /*if (msg->body) {
8166 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8169 // Verify the signature before processing it
8170 if (sip->registrar.gssapi_context) {
8171 struct sipmsg_breakdown msgbd;
8172 gchar *signature_input_str;
8173 gchar *rspauth;
8174 msgbd.msg = msg;
8175 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8176 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8178 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8180 if (rspauth != NULL) {
8181 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8182 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8183 process_input_message(sip, msg);
8184 } else {
8185 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8186 purple_connection_error(sip->gc, _("Invalid message signature received"));
8187 sip->gc->wants_to_die = TRUE;
8189 } else if (msg->response == 401) {
8190 purple_connection_error(sip->gc, _("Authentication failed"));
8191 sip->gc->wants_to_die = TRUE;
8193 g_free(signature_input_str);
8195 g_free(rspauth);
8196 sipmsg_breakdown_free(&msgbd);
8197 } else {
8198 process_input_message(sip, msg);
8201 sipmsg_free(msg);
8205 static void sipe_udp_process(gpointer data, gint source,
8206 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8208 PurpleConnection *gc = data;
8209 struct sipe_account_data *sip = gc->proto_data;
8210 int len;
8212 static char buffer[65536];
8213 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8214 time_t currtime = time(NULL);
8215 struct sipmsg *msg;
8216 buffer[len] = '\0';
8217 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), buffer);
8218 msg = sipmsg_parse_msg(buffer);
8219 if (msg) process_input_message(sip, msg);
8223 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8225 struct sipe_account_data *sip = gc->proto_data;
8226 PurpleSslConnection *gsc = sip->gsc;
8228 SIPE_DEBUG_ERROR("%s", debug);
8229 purple_connection_error(gc, msg);
8231 /* Invalidate this connection. Next send will open a new one */
8232 if (gsc) {
8233 connection_remove(sip, gsc->fd);
8234 purple_ssl_close(gsc);
8236 sip->gsc = NULL;
8237 sip->fd = -1;
8240 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8241 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8243 PurpleConnection *gc = data;
8244 struct sipe_account_data *sip;
8245 struct sip_connection *conn;
8246 int readlen, len;
8247 gboolean firstread = TRUE;
8249 /* NOTE: This check *IS* necessary */
8250 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8251 purple_ssl_close(gsc);
8252 return;
8255 sip = gc->proto_data;
8256 conn = connection_find(sip, gsc->fd);
8257 if (conn == NULL) {
8258 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
8259 gc->wants_to_die = TRUE;
8260 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8261 return;
8264 /* Read all available data from the SSL connection */
8265 do {
8266 /* Increase input buffer size as needed */
8267 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8268 conn->inbuflen += SIMPLE_BUF_INC;
8269 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8270 SIPE_DEBUG_INFO("sipe_input_cb_ssl: new input buffer length %d", conn->inbuflen);
8273 /* Try to read as much as there is space left in the buffer */
8274 readlen = conn->inbuflen - conn->inbufused - 1;
8275 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8277 if (len < 0 && errno == EAGAIN) {
8278 /* Try again later */
8279 return;
8280 } else if (len < 0) {
8281 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8282 return;
8283 } else if (firstread && (len == 0)) {
8284 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8285 return;
8288 conn->inbufused += len;
8289 firstread = FALSE;
8291 /* Equivalence indicates that there is possibly more data to read */
8292 } while (len == readlen);
8294 conn->inbuf[conn->inbufused] = '\0';
8295 process_input(sip, conn);
8299 static void sipe_input_cb(gpointer data, gint source,
8300 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8302 PurpleConnection *gc = data;
8303 struct sipe_account_data *sip = gc->proto_data;
8304 int len;
8305 struct sip_connection *conn = connection_find(sip, source);
8306 if (!conn) {
8307 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found!");
8308 return;
8311 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8312 conn->inbuflen += SIMPLE_BUF_INC;
8313 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8316 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8318 if (len < 0 && errno == EAGAIN)
8319 return;
8320 else if (len <= 0) {
8321 SIPE_DEBUG_INFO_NOFORMAT("sipe_input_cb: read error");
8322 connection_remove(sip, source);
8323 if (sip->fd == source) sip->fd = -1;
8324 return;
8327 conn->inbufused += len;
8328 conn->inbuf[conn->inbufused] = '\0';
8330 process_input(sip, conn);
8333 /* Callback for new connections on incoming TCP port */
8334 static void sipe_newconn_cb(gpointer data, gint source,
8335 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8337 PurpleConnection *gc = data;
8338 struct sipe_account_data *sip = gc->proto_data;
8339 struct sip_connection *conn;
8341 int newfd = accept(source, NULL, NULL);
8343 conn = connection_create(sip, newfd);
8345 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8348 static void login_cb(gpointer data, gint source,
8349 SIPE_UNUSED_PARAMETER const gchar *error_message)
8351 PurpleConnection *gc = data;
8352 struct sipe_account_data *sip;
8353 struct sip_connection *conn;
8355 if (!PURPLE_CONNECTION_IS_VALID(gc))
8357 if (source >= 0)
8358 close(source);
8359 return;
8362 if (source < 0) {
8363 purple_connection_error(gc, _("Could not connect"));
8364 return;
8367 sip = gc->proto_data;
8368 sip->fd = source;
8369 sip->last_keepalive = time(NULL);
8371 conn = connection_create(sip, source);
8373 do_register(sip);
8375 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8378 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8379 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8381 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8382 if (sip == NULL) return;
8384 do_register(sip);
8387 static guint sipe_ht_hash_nick(const char *nick)
8389 char *lc = g_utf8_strdown(nick, -1);
8390 guint bucket = g_str_hash(lc);
8391 g_free(lc);
8393 return bucket;
8396 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8398 char *nick1_norm = NULL;
8399 char *nick2_norm = NULL;
8400 gboolean equal;
8402 if (nick1 == NULL && nick2 == NULL) return TRUE;
8403 if (nick1 == NULL || nick2 == NULL ||
8404 !g_utf8_validate(nick1, -1, NULL) ||
8405 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
8407 nick1_norm = g_utf8_casefold(nick1, -1);
8408 nick2_norm = g_utf8_casefold(nick2, -1);
8409 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
8410 g_free(nick2_norm);
8411 g_free(nick1_norm);
8413 return equal;
8416 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8418 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8420 sip->listen_data = NULL;
8422 if (listenfd == -1) {
8423 purple_connection_error(sip->gc, _("Could not create listen socket"));
8424 return;
8427 sip->fd = listenfd;
8429 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8430 sip->listenfd = sip->fd;
8432 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8434 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8435 do_register(sip);
8438 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8439 SIPE_UNUSED_PARAMETER const char *error_message)
8441 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8443 sip->query_data = NULL;
8445 if (!hosts || !hosts->data) {
8446 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8447 return;
8450 hosts = g_slist_remove(hosts, hosts->data);
8451 g_free(sip->serveraddr);
8452 sip->serveraddr = hosts->data;
8453 hosts = g_slist_remove(hosts, hosts->data);
8454 while (hosts) {
8455 void *tmp = hosts->data;
8456 hosts = g_slist_remove(hosts, tmp);
8457 hosts = g_slist_remove(hosts, tmp);
8458 g_free(tmp);
8461 /* create socket for incoming connections */
8462 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8463 sipe_udp_host_resolved_listen_cb, sip);
8464 if (sip->listen_data == NULL) {
8465 purple_connection_error(sip->gc, _("Could not create listen socket"));
8466 return;
8470 static const struct sipe_service_data *current_service = NULL;
8472 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8473 PurpleSslErrorType error,
8474 gpointer data)
8476 PurpleConnection *gc = data;
8477 struct sipe_account_data *sip;
8479 /* If the connection is already disconnected, we don't need to do anything else */
8480 if (!PURPLE_CONNECTION_IS_VALID(gc))
8481 return;
8483 sip = gc->proto_data;
8484 current_service = sip->service_data;
8485 if (current_service) {
8486 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8487 current_service->transport ? current_service->transport : "NULL",
8488 current_service->service ? current_service->service : "NULL");
8491 sip->fd = -1;
8492 sip->gsc = NULL;
8494 switch(error) {
8495 case PURPLE_SSL_CONNECT_FAILED:
8496 purple_connection_error(gc, _("Connection failed"));
8497 break;
8498 case PURPLE_SSL_HANDSHAKE_FAILED:
8499 purple_connection_error(gc, _("SSL handshake failed"));
8500 break;
8501 case PURPLE_SSL_CERTIFICATE_INVALID:
8502 purple_connection_error(gc, _("SSL certificate invalid"));
8503 break;
8507 static void
8508 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8510 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8511 PurpleProxyConnectData *connect_data;
8513 sip->listen_data = NULL;
8515 sip->listenfd = listenfd;
8516 if (sip->listenfd == -1) {
8517 purple_connection_error(sip->gc, _("Could not create listen socket"));
8518 return;
8521 SIPE_DEBUG_INFO("listenfd: %d", sip->listenfd);
8522 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8523 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8524 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8525 sipe_newconn_cb, sip->gc);
8526 SIPE_DEBUG_INFO("connecting to %s port %d",
8527 sip->realhostname, sip->realport);
8528 /* open tcp connection to the server */
8529 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8530 sip->realport, login_cb, sip->gc);
8532 if (connect_data == NULL) {
8533 purple_connection_error(sip->gc, _("Could not create socket"));
8537 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8539 PurpleAccount *account = sip->account;
8540 PurpleConnection *gc = sip->gc;
8542 if (port == 0) {
8543 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8546 sip->realhostname = hostname;
8547 sip->realport = port;
8549 SIPE_DEBUG_INFO("create_connection - hostname: %s port: %d",
8550 hostname, port);
8552 /* TODO: is there a good default grow size? */
8553 if (sip->transport != SIPE_TRANSPORT_UDP)
8554 sip->txbuf = purple_circ_buffer_new(0);
8556 if (sip->transport == SIPE_TRANSPORT_TLS) {
8557 /* SSL case */
8558 if (!purple_ssl_is_supported()) {
8559 gc->wants_to_die = TRUE;
8560 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8561 return;
8564 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
8566 sip->gsc = purple_ssl_connect(account, hostname, port,
8567 login_cb_ssl, sipe_ssl_connect_failure, gc);
8568 if (sip->gsc == NULL) {
8569 purple_connection_error(gc, _("Could not create SSL context"));
8570 return;
8572 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8573 /* UDP case */
8574 SIPE_DEBUG_INFO_NOFORMAT("using UDP");
8576 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8577 if (sip->query_data == NULL) {
8578 purple_connection_error(gc, _("Could not resolve hostname"));
8580 } else {
8581 /* TCP case */
8582 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
8583 /* create socket for incoming connections */
8584 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8585 sipe_tcp_connect_listen_cb, sip);
8586 if (sip->listen_data == NULL) {
8587 purple_connection_error(gc, _("Could not create listen socket"));
8588 return;
8593 /* Service list for autodection */
8594 static const struct sipe_service_data service_autodetect[] = {
8595 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8596 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8597 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8598 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8599 { NULL, NULL, 0 }
8602 /* Service list for SSL/TLS */
8603 static const struct sipe_service_data service_tls[] = {
8604 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8605 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8606 { NULL, NULL, 0 }
8609 /* Service list for TCP */
8610 static const struct sipe_service_data service_tcp[] = {
8611 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8612 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8613 { NULL, NULL, 0 }
8616 /* Service list for UDP */
8617 static const struct sipe_service_data service_udp[] = {
8618 { "sip", "udp", SIPE_TRANSPORT_UDP },
8619 { NULL, NULL, 0 }
8622 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8623 static void resolve_next_service(struct sipe_account_data *sip,
8624 const struct sipe_service_data *start)
8626 if (start) {
8627 sip->service_data = start;
8628 } else {
8629 sip->service_data++;
8630 if (sip->service_data->service == NULL) {
8631 gchar *hostname;
8632 /* Try connecting to the SIP hostname directly */
8633 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8634 if (sip->auto_transport) {
8635 // If SSL is supported, default to using it; OCS servers aren't configured
8636 // by default to accept TCP
8637 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8638 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8639 SIPE_DEBUG_INFO_NOFORMAT("set transport type..");
8642 hostname = g_strdup(sip->sipdomain);
8643 create_connection(sip, hostname, 0);
8644 return;
8648 /* Try to resolve next service */
8649 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8650 sip->service_data->transport,
8651 sip->sipdomain,
8652 srvresolved, sip);
8655 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8657 struct sipe_account_data *sip = data;
8659 sip->srv_query_data = NULL;
8661 /* find the host to connect to */
8662 if (results) {
8663 gchar *hostname = g_strdup(resp->hostname);
8664 int port = resp->port;
8665 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8666 hostname, port);
8667 g_free(resp);
8669 sip->transport = sip->service_data->type;
8671 create_connection(sip, hostname, port);
8672 } else {
8673 resolve_next_service(sip, NULL);
8677 static void sipe_login(PurpleAccount *account)
8679 PurpleConnection *gc;
8680 struct sipe_account_data *sip;
8681 gchar **signinname_login, **userserver;
8682 const char *transport;
8683 const char *email;
8685 const char *username = purple_account_get_username(account);
8686 gc = purple_account_get_connection(account);
8688 SIPE_DEBUG_INFO("sipe_login: username '%s'", username);
8690 if (strpbrk(username, "\t\v\r\n") != NULL) {
8691 gc->wants_to_die = TRUE;
8692 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8693 return;
8696 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8697 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8698 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8699 sip->gc = gc;
8700 sip->account = account;
8701 sip->reregister_set = FALSE;
8702 sip->reauthenticate_set = FALSE;
8703 sip->subscribed = FALSE;
8704 sip->subscribed_buddies = FALSE;
8705 sip->initial_state_published = FALSE;
8707 /* username format: <username>,[<optional login>] */
8708 signinname_login = g_strsplit(username, ",", 2);
8709 SIPE_DEBUG_INFO("sipe_login: signinname[0] '%s'", signinname_login[0]);
8711 /* ensure that username format is name@domain */
8712 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8713 g_strfreev(signinname_login);
8714 gc->wants_to_die = TRUE;
8715 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8716 return;
8718 sip->username = g_strdup(signinname_login[0]);
8720 /* ensure that email format is name@domain if provided */
8721 email = purple_account_get_string(sip->account, "email", NULL);
8722 if (!is_empty(email) &&
8723 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8725 gc->wants_to_die = TRUE;
8726 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8727 return;
8729 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8731 /* login name specified? */
8732 if (signinname_login[1] && strlen(signinname_login[1])) {
8733 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8734 gboolean has_domain = domain_user[1] != NULL;
8735 SIPE_DEBUG_INFO("sipe_login: signinname[1] '%s'", signinname_login[1]);
8736 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8737 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8738 SIPE_DEBUG_INFO("sipe_login: auth domain '%s' user '%s'",
8739 sip->authdomain ? sip->authdomain : "", sip->authuser);
8740 g_strfreev(domain_user);
8743 userserver = g_strsplit(signinname_login[0], "@", 2);
8744 SIPE_DEBUG_INFO("sipe_login: user '%s' server '%s'", userserver[0], userserver[1]);
8745 purple_connection_set_display_name(gc, userserver[0]);
8746 sip->sipdomain = g_strdup(userserver[1]);
8747 g_strfreev(userserver);
8748 g_strfreev(signinname_login);
8750 if (strchr(sip->username, ' ') != NULL) {
8751 gc->wants_to_die = TRUE;
8752 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8753 return;
8756 sip->password = g_strdup(purple_connection_get_password(gc));
8758 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8759 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8760 g_free, (GDestroyNotify)g_hash_table_destroy);
8761 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8762 g_free, (GDestroyNotify)sipe_subscription_free);
8764 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8766 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8768 g_free(sip->status);
8769 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8771 sip->auto_transport = FALSE;
8772 transport = purple_account_get_string(account, "transport", "auto");
8773 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8774 if (userserver[0]) {
8775 /* Use user specified server[:port] */
8776 int port = 0;
8778 if (userserver[1])
8779 port = atoi(userserver[1]);
8781 SIPE_DEBUG_INFO("sipe_login: user specified SIP server %s:%d",
8782 userserver[0], port);
8784 if (sipe_strequal(transport, "auto")) {
8785 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8786 } else if (sipe_strequal(transport, "tls")) {
8787 sip->transport = SIPE_TRANSPORT_TLS;
8788 } else if (sipe_strequal(transport, "tcp")) {
8789 sip->transport = SIPE_TRANSPORT_TCP;
8790 } else {
8791 sip->transport = SIPE_TRANSPORT_UDP;
8794 create_connection(sip, g_strdup(userserver[0]), port);
8795 } else {
8796 /* Server auto-discovery */
8797 if (sipe_strequal(transport, "auto")) {
8798 sip->auto_transport = TRUE;
8799 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8800 current_service++;
8801 resolve_next_service(sip, current_service);
8802 } else {
8803 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8805 } else if (sipe_strequal(transport, "tls")) {
8806 resolve_next_service(sip, service_tls);
8807 } else if (sipe_strequal(transport, "tcp")) {
8808 resolve_next_service(sip, service_tcp);
8809 } else {
8810 resolve_next_service(sip, service_udp);
8813 g_strfreev(userserver);
8816 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8818 connection_free_all(sip);
8820 g_free(sip->epid);
8821 sip->epid = NULL;
8823 if (sip->query_data != NULL)
8824 purple_dnsquery_destroy(sip->query_data);
8825 sip->query_data = NULL;
8827 if (sip->srv_query_data != NULL)
8828 purple_srv_cancel(sip->srv_query_data);
8829 sip->srv_query_data = NULL;
8831 if (sip->listen_data != NULL)
8832 purple_network_listen_cancel(sip->listen_data);
8833 sip->listen_data = NULL;
8835 if (sip->gsc != NULL)
8836 purple_ssl_close(sip->gsc);
8837 sip->gsc = NULL;
8839 sipe_auth_free(&sip->registrar);
8840 sipe_auth_free(&sip->proxy);
8842 if (sip->txbuf)
8843 purple_circ_buffer_destroy(sip->txbuf);
8844 sip->txbuf = NULL;
8846 g_free(sip->realhostname);
8847 sip->realhostname = NULL;
8849 g_free(sip->server_version);
8850 sip->server_version = NULL;
8852 if (sip->listenpa)
8853 purple_input_remove(sip->listenpa);
8854 sip->listenpa = 0;
8855 if (sip->tx_handler)
8856 purple_input_remove(sip->tx_handler);
8857 sip->tx_handler = 0;
8858 if (sip->resendtimeout)
8859 purple_timeout_remove(sip->resendtimeout);
8860 sip->resendtimeout = 0;
8861 if (sip->timeouts) {
8862 GSList *entry = sip->timeouts;
8863 while (entry) {
8864 struct scheduled_action *sched_action = entry->data;
8865 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
8866 purple_timeout_remove(sched_action->timeout_handler);
8867 if (sched_action->destroy) {
8868 (*sched_action->destroy)(sched_action->payload);
8870 g_free(sched_action->name);
8871 g_free(sched_action);
8872 entry = entry->next;
8875 g_slist_free(sip->timeouts);
8877 if (sip->allow_events) {
8878 GSList *entry = sip->allow_events;
8879 while (entry) {
8880 g_free(entry->data);
8881 entry = entry->next;
8884 g_slist_free(sip->allow_events);
8886 if (sip->containers) {
8887 GSList *entry = sip->containers;
8888 while (entry) {
8889 free_container((struct sipe_container *)entry->data);
8890 entry = entry->next;
8893 g_slist_free(sip->containers);
8895 if (sip->contact)
8896 g_free(sip->contact);
8897 sip->contact = NULL;
8898 if (sip->regcallid)
8899 g_free(sip->regcallid);
8900 sip->regcallid = NULL;
8902 if (sip->serveraddr)
8903 g_free(sip->serveraddr);
8904 sip->serveraddr = NULL;
8906 if (sip->focus_factory_uri)
8907 g_free(sip->focus_factory_uri);
8908 sip->focus_factory_uri = NULL;
8910 sip->fd = -1;
8911 sip->processing_input = FALSE;
8913 if (sip->ews) {
8914 sipe_ews_free(sip->ews);
8916 sip->ews = NULL;
8920 * A callback for g_hash_table_foreach_remove
8922 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8923 SIPE_UNUSED_PARAMETER gpointer user_data)
8925 sipe_free_buddy((struct sipe_buddy *) buddy);
8927 /* We must return TRUE as the key/value have already been deleted */
8928 return(TRUE);
8931 static void sipe_close(PurpleConnection *gc)
8933 struct sipe_account_data *sip = gc->proto_data;
8935 if (sip) {
8936 /* leave all conversations */
8937 sipe_session_close_all(sip);
8938 sipe_session_remove_all(sip);
8940 if (sip->csta) {
8941 sip_csta_close(sip);
8944 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8945 /* unsubscribe all */
8946 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8948 /* unregister */
8949 do_register_exp(sip, 0);
8952 sipe_connection_cleanup(sip);
8953 g_free(sip->sipdomain);
8954 g_free(sip->username);
8955 g_free(sip->email);
8956 g_free(sip->password);
8957 g_free(sip->authdomain);
8958 g_free(sip->authuser);
8959 g_free(sip->status);
8960 g_free(sip->note);
8961 g_free(sip->user_states);
8963 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8964 g_hash_table_destroy(sip->buddies);
8965 g_hash_table_destroy(sip->our_publications);
8966 g_hash_table_destroy(sip->user_state_publications);
8967 g_hash_table_destroy(sip->subscriptions);
8968 g_hash_table_destroy(sip->filetransfers);
8970 if (sip->groups) {
8971 GSList *entry = sip->groups;
8972 while (entry) {
8973 struct sipe_group *group = entry->data;
8974 g_free(group->name);
8975 g_free(group);
8976 entry = entry->next;
8979 g_slist_free(sip->groups);
8981 if (sip->our_publication_keys) {
8982 GSList *entry = sip->our_publication_keys;
8983 while (entry) {
8984 g_free(entry->data);
8985 entry = entry->next;
8988 g_slist_free(sip->our_publication_keys);
8990 while (sip->transactions)
8991 transactions_remove(sip, sip->transactions->data);
8993 g_free(gc->proto_data);
8994 gc->proto_data = NULL;
8997 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8998 SIPE_UNUSED_PARAMETER void *user_data)
9000 PurpleAccount *acct = purple_connection_get_account(gc);
9001 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
9002 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
9003 if (conv == NULL)
9004 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
9005 purple_conversation_present(conv);
9006 g_free(id);
9009 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
9010 SIPE_UNUSED_PARAMETER void *user_data)
9013 purple_blist_request_add_buddy(purple_connection_get_account(gc),
9014 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
9017 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
9018 SIPE_UNUSED_PARAMETER struct transaction *trans)
9020 PurpleNotifySearchResults *results;
9021 PurpleNotifySearchColumn *column;
9022 xmlnode *searchResults;
9023 xmlnode *mrow;
9024 int match_count = 0;
9025 gboolean more = FALSE;
9026 gchar *secondary;
9028 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
9030 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9031 if (!searchResults) {
9032 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
9033 return FALSE;
9036 results = purple_notify_searchresults_new();
9038 if (results == NULL) {
9039 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
9040 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
9042 xmlnode_free(searchResults);
9043 return FALSE;
9046 column = purple_notify_searchresults_column_new(_("User name"));
9047 purple_notify_searchresults_column_add(results, column);
9049 column = purple_notify_searchresults_column_new(_("Name"));
9050 purple_notify_searchresults_column_add(results, column);
9052 column = purple_notify_searchresults_column_new(_("Company"));
9053 purple_notify_searchresults_column_add(results, column);
9055 column = purple_notify_searchresults_column_new(_("Country"));
9056 purple_notify_searchresults_column_add(results, column);
9058 column = purple_notify_searchresults_column_new(_("Email"));
9059 purple_notify_searchresults_column_add(results, column);
9061 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
9062 GList *row = NULL;
9064 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
9065 row = g_list_append(row, g_strdup(uri_parts[1]));
9066 g_strfreev(uri_parts);
9068 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
9069 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
9070 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
9071 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
9073 purple_notify_searchresults_row_add(results, row);
9074 match_count++;
9077 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
9078 char *data = xmlnode_get_data(mrow);
9079 more = (g_strcasecmp(data, "true") == 0);
9080 g_free(data);
9083 secondary = g_strdup_printf(
9084 dngettext(PACKAGE_NAME,
9085 "Found %d contact%s:",
9086 "Found %d contacts%s:", match_count),
9087 match_count, more ? _(" (more matched your query)") : "");
9089 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
9090 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
9091 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
9093 g_free(secondary);
9094 xmlnode_free(searchResults);
9095 return TRUE;
9098 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
9100 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
9101 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
9102 unsigned i = 0;
9104 if (!attrs) return;
9106 do {
9107 PurpleRequestField *field = entries->data;
9108 const char *id = purple_request_field_get_id(field);
9109 const char *value = purple_request_field_string_get_value(field);
9111 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
9113 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
9114 } while ((entries = g_list_next(entries)) != NULL);
9115 attrs[i] = NULL;
9117 if (i > 0) {
9118 struct sipe_account_data *sip = gc->proto_data;
9119 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9120 gchar *query = g_strjoinv(NULL, attrs);
9121 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
9122 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
9123 send_soap_request_with_cb(sip, domain_uri, body,
9124 (TransCallback) process_search_contact_response, NULL);
9125 g_free(domain_uri);
9126 g_free(body);
9127 g_free(query);
9130 g_strfreev(attrs);
9133 static void sipe_show_find_contact(PurplePluginAction *action)
9135 PurpleConnection *gc = (PurpleConnection *) action->context;
9136 PurpleRequestFields *fields;
9137 PurpleRequestFieldGroup *group;
9138 PurpleRequestField *field;
9140 fields = purple_request_fields_new();
9141 group = purple_request_field_group_new(NULL);
9142 purple_request_fields_add_group(fields, group);
9144 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
9145 purple_request_field_group_add_field(group, field);
9146 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
9147 purple_request_field_group_add_field(group, field);
9148 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
9149 purple_request_field_group_add_field(group, field);
9150 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
9151 purple_request_field_group_add_field(group, field);
9153 purple_request_fields(gc,
9154 _("Search"),
9155 _("Search for a contact"),
9156 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
9157 fields,
9158 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
9159 _("_Cancel"), NULL,
9160 purple_connection_get_account(gc), NULL, NULL, gc);
9163 static void sipe_show_about_plugin(PurplePluginAction *action)
9165 PurpleConnection *gc = (PurpleConnection *) action->context;
9166 char *tmp = g_strdup_printf(
9168 * Non-translatable parts, like markup, are hard-coded
9169 * into the format string. This requires more translatable
9170 * texts but it makes the translations less error prone.
9172 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
9173 "<br/>"
9174 /* 1 */ "%s:<br/>"
9175 "<li> - MS Office Communications Server 2007 R2</li><br/>"
9176 "<li> - MS Office Communications Server 2007</li><br/>"
9177 "<li> - MS Live Communications Server 2005</li><br/>"
9178 "<li> - MS Live Communications Server 2003</li><br/>"
9179 "<li> - Reuters Messaging</li><br/>"
9180 "<br/>"
9181 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
9182 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
9183 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
9184 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
9185 /* 8 */ "%s: GPLv2+<br/>"
9186 "<br/>"
9187 /* 9 */ "%s:<br/>"
9188 " - CERN<br/>"
9189 " - Reuters Messaging network<br/>"
9190 " - Deutsche Bank<br/>"
9191 " - Merrill Lynch<br/>"
9192 " - Wachovia<br/>"
9193 " - Intel<br/>"
9194 " - Nokia<br/>"
9195 " - HP<br/>"
9196 " - Symantec<br/>"
9197 " - Accenture<br/>"
9198 " - Capgemini<br/>"
9199 " - Siemens<br/>"
9200 " - Alcatel-Lucent<br/>"
9201 " - BT<br/>"
9202 "<br/>"
9203 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9204 "<br/>"
9205 /* 12 */ "<b>%s:</b><br/>"
9206 " - Anibal Avelar<br/>"
9207 " - Gabriel Burt<br/>"
9208 " - Stefan Becker<br/>"
9209 " - pier11<br/>"
9210 " - Jakub Adam<br/>"
9211 " - Tomáš Hrabčík<br/>"
9212 "<br/>"
9213 /* 13 */ "%s<br/>"
9215 /* The next 13 texts make up the SIPE about note text */
9216 /* About note, part 1/13: introduction */
9217 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9218 /* About note, part 2/13: home page URL (label) */
9219 _("Home"),
9220 /* About note, part 3/13: support forum URL (label) */
9221 _("Support"),
9222 /* About note, part 4/13: support forum name (hyperlink text) */
9223 _("Help Forum"),
9224 /* About note, part 5/13: bug tracker URL (label) */
9225 _("Report Problems"),
9226 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9227 _("Bug Tracker"),
9228 /* About note, part 7/13: translation service URL (label) */
9229 _("Translations"),
9230 /* About note, part 8/13: license type (label) */
9231 _("License"),
9232 /* About note, part 9/13: known users */
9233 _("We support users in such organizations as"),
9234 /* About note, part 10/13: translation request, text before Transifex.net URL */
9235 /* append a space if text is not empty */
9236 _("Please help us to translate SIPE to your native language here at "),
9237 /* About note, part 11/13: translation request, text after Transifex.net URL */
9238 /* start with a space if text is not empty */
9239 _(" using convenient web interface"),
9240 /* About note, part 12/13: author list (header) */
9241 _("Authors"),
9242 /* About note, part 13/13: Localization credit */
9243 /* PLEASE NOTE: do *NOT* simply translate the english original */
9244 /* but write something similar to the following sentence: */
9245 /* "Localization for <language name> (<language code>): <name>" */
9246 _("Original texts in English (en): SIPE developers")
9248 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9249 g_free(tmp);
9252 static void sipe_republish_calendar(PurplePluginAction *action)
9254 PurpleConnection *gc = (PurpleConnection *) action->context;
9255 struct sipe_account_data *sip = gc->proto_data;
9257 sipe_update_calendar(sip);
9260 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9261 gpointer value,
9262 GString* str)
9264 struct sipe_publication *publication = value;
9266 g_string_append_printf( str,
9267 SIPE_PUB_XML_PUBLICATION_CLEAR,
9268 publication->category,
9269 publication->instance,
9270 publication->container,
9271 publication->version,
9272 "static");
9275 static void sipe_reset_status(PurplePluginAction *action)
9277 PurpleConnection *gc = (PurpleConnection *) action->context;
9278 struct sipe_account_data *sip = gc->proto_data;
9280 if (sip->ocs2007) /* 2007+ */
9282 GString* str = g_string_new(NULL);
9283 gchar *publications;
9285 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9286 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
9287 return;
9290 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9291 publications = g_string_free(str, FALSE);
9293 send_presence_publish(sip, publications);
9294 g_free(publications);
9296 else /* 2005 */
9298 send_presence_soap0(sip, FALSE, TRUE);
9302 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9303 gpointer context)
9305 PurpleConnection *gc = (PurpleConnection *)context;
9306 struct sipe_account_data *sip = gc->proto_data;
9307 GList *menu = NULL;
9308 PurplePluginAction *act;
9309 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9311 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9312 menu = g_list_prepend(menu, act);
9314 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9315 menu = g_list_prepend(menu, act);
9317 if (sipe_strequal(calendar, "EXCH")) {
9318 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9319 menu = g_list_prepend(menu, act);
9322 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9323 menu = g_list_prepend(menu, act);
9325 menu = g_list_reverse(menu);
9327 return menu;
9330 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9334 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9336 return TRUE;
9340 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9342 return TRUE;
9346 static char *sipe_status_text(PurpleBuddy *buddy)
9348 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9349 const PurpleStatus *status = purple_presence_get_active_status(presence);
9350 const char *status_id = purple_status_get_id(status);
9351 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9352 struct sipe_buddy *sbuddy;
9353 char *text = NULL;
9355 if (!sip) return NULL; /* happens on pidgin exit */
9357 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9358 if (sbuddy) {
9359 const char *activity_str = sbuddy->activity ?
9360 sbuddy->activity :
9361 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9362 purple_status_get_name(status) : NULL;
9364 if (activity_str && sbuddy->note)
9366 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9368 else if (activity_str)
9370 text = g_strdup(activity_str);
9372 else if (sbuddy->note)
9374 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9378 return text;
9381 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9383 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9384 const PurpleStatus *status = purple_presence_get_active_status(presence);
9385 struct sipe_account_data *sip;
9386 struct sipe_buddy *sbuddy;
9387 char *note = NULL;
9388 gboolean is_oof_note = FALSE;
9389 char *activity = NULL;
9390 char *calendar = NULL;
9391 char *meeting_subject = NULL;
9392 char *meeting_location = NULL;
9394 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9395 if (sip) //happens on pidgin exit
9397 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9398 if (sbuddy)
9400 note = sbuddy->note;
9401 is_oof_note = sbuddy->is_oof_note;
9402 activity = sbuddy->activity;
9403 calendar = sipe_cal_get_description(sbuddy);
9404 meeting_subject = sbuddy->meeting_subject;
9405 meeting_location = sbuddy->meeting_location;
9409 //Layout
9410 if (purple_presence_is_online(presence))
9412 const char *status_str = activity ? activity : purple_status_get_name(status);
9414 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9416 if (purple_presence_is_online(presence) &&
9417 !is_empty(calendar))
9419 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9421 g_free(calendar);
9422 if (!is_empty(meeting_location))
9424 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9426 if (!is_empty(meeting_subject))
9428 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9431 if (note)
9433 char *tmp = g_strdup_printf("<i>%s</i>", note);
9434 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", buddy->name, note);
9436 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9437 g_free(tmp);
9440 if (sip && sip->ocs2007) {
9441 const int container_id = sipe_find_access_level(sip, "user", sipe_get_no_sip_uri(buddy->name));
9442 const char *access_level = sipe_get_access_level_name(container_id);
9444 purple_notify_user_info_add_pair(user_info, _("Access level"), access_level);
9448 #if PURPLE_VERSION_CHECK(2,5,0)
9449 static GHashTable *
9450 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9452 GHashTable *table;
9453 table = g_hash_table_new(g_str_hash, g_str_equal);
9454 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9455 return table;
9457 #endif
9459 static PurpleBuddy *
9460 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9462 PurpleBuddy *clone;
9463 const gchar *server_alias, *email;
9464 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9466 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9468 purple_blist_add_buddy(clone, NULL, group, NULL);
9470 server_alias = purple_buddy_get_server_alias(buddy);
9471 if (server_alias) {
9472 purple_blist_server_alias_buddy(clone, server_alias);
9475 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9476 if (email) {
9477 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9480 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9481 //for UI to update;
9482 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9483 return clone;
9486 static void
9487 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9489 PurpleBuddy *buddy, *b;
9490 PurpleConnection *gc;
9491 PurpleGroup * group = purple_find_group(group_name);
9493 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9495 buddy = (PurpleBuddy *)node;
9497 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
9498 gc = purple_account_get_connection(buddy->account);
9500 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9501 if (!b){
9502 purple_blist_add_buddy_clone(group, buddy);
9505 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9508 static void
9509 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9511 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9513 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
9515 /* 2007+ conference */
9516 if (sip->ocs2007)
9518 sipe_conf_add(sip, buddy->name);
9520 else /* 2005- multiparty chat */
9522 gchar *self = sip_uri_self(sip);
9523 struct sip_session *session;
9525 session = sipe_session_add_chat(sip);
9526 session->chat_title = sipe_chat_get_name(session->callid);
9527 session->roster_manager = g_strdup(self);
9529 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9530 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9531 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9532 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9534 g_free(self);
9538 static gboolean
9539 sipe_is_election_finished(struct sip_session *session)
9541 gboolean res = TRUE;
9543 SIPE_DIALOG_FOREACH {
9544 if (dialog->election_vote == 0) {
9545 res = FALSE;
9546 break;
9548 } SIPE_DIALOG_FOREACH_END;
9550 if (res) {
9551 session->is_voting_in_progress = FALSE;
9553 return res;
9556 static void
9557 sipe_election_start(struct sipe_account_data *sip,
9558 struct sip_session *session)
9560 int election_timeout;
9562 if (session->is_voting_in_progress) {
9563 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
9564 return;
9565 } else {
9566 session->is_voting_in_progress = TRUE;
9568 session->bid = rand();
9570 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
9572 SIPE_DIALOG_FOREACH {
9573 /* reset election_vote for each chat participant */
9574 dialog->election_vote = 0;
9576 /* send RequestRM to each chat participant*/
9577 sipe_send_election_request_rm(sip, dialog, session->bid);
9578 } SIPE_DIALOG_FOREACH_END;
9580 election_timeout = 15; /* sec */
9581 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9585 * @param who a URI to whom to invite to chat
9587 void
9588 sipe_invite_to_chat(struct sipe_account_data *sip,
9589 struct sip_session *session,
9590 const gchar *who)
9592 /* a conference */
9593 if (session->focus_uri)
9595 sipe_invite_conf(sip, session, who);
9597 else /* a multi-party chat */
9599 gchar *self = sip_uri_self(sip);
9600 if (session->roster_manager) {
9601 if (sipe_strcase_equal(session->roster_manager, self)) {
9602 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9603 } else {
9604 sipe_refer(sip, session, who);
9606 } else {
9607 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
9609 session->pending_invite_queue = slist_insert_unique_sorted(
9610 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9612 sipe_election_start(sip, session);
9614 g_free(self);
9618 void
9619 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9620 struct sip_session *session)
9622 gchar *invitee;
9623 GSList *entry = session->pending_invite_queue;
9625 while (entry) {
9626 invitee = entry->data;
9627 sipe_invite_to_chat(sip, session, invitee);
9628 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9629 g_free(invitee);
9633 static void
9634 sipe_election_result(struct sipe_account_data *sip,
9635 void *sess)
9637 struct sip_session *session = (struct sip_session *)sess;
9638 gchar *rival;
9639 gboolean has_won = TRUE;
9641 if (session->roster_manager) {
9642 SIPE_DEBUG_INFO(
9643 "sipe_election_result: RM has already been elected in the meantime. It is %s",
9644 session->roster_manager);
9645 return;
9648 session->is_voting_in_progress = FALSE;
9650 SIPE_DIALOG_FOREACH {
9651 if (dialog->election_vote < 0) {
9652 has_won = FALSE;
9653 rival = dialog->with;
9654 break;
9656 } SIPE_DIALOG_FOREACH_END;
9658 if (has_won) {
9659 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
9661 session->roster_manager = sip_uri_self(sip);
9663 SIPE_DIALOG_FOREACH {
9664 /* send SetRM to each chat participant*/
9665 sipe_send_election_set_rm(sip, dialog);
9666 } SIPE_DIALOG_FOREACH_END;
9667 } else {
9668 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
9670 session->bid = 0;
9672 sipe_process_pending_invite_queue(sip, session);
9676 * For 2007+ conference only.
9678 static void
9679 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9681 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9682 struct sip_session *session;
9684 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
9685 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
9687 session = sipe_session_find_chat_by_title(sip, chat_title);
9689 sipe_conf_modify_user_role(sip, session, buddy->name);
9693 * For 2007+ conference only.
9695 static void
9696 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9698 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9699 struct sip_session *session;
9701 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
9702 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
9704 session = sipe_session_find_chat_by_title(sip, chat_title);
9706 sipe_conf_delete_user(sip, session, buddy->name);
9709 static void
9710 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9712 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9713 struct sip_session *session;
9715 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
9716 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
9718 session = sipe_session_find_chat_by_title(sip, chat_title);
9720 sipe_invite_to_chat(sip, session, buddy->name);
9723 static void
9724 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9726 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9728 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
9729 if (phone) {
9730 char *tel_uri = sip_to_tel_uri(phone);
9732 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
9733 sip_csta_make_call(sip, tel_uri);
9735 g_free(tel_uri);
9739 static void
9740 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9742 const gchar *email;
9743 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
9745 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9746 if (email)
9748 char *mailto = g_strdup_printf("mailto:%s", email);
9749 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s", email);
9750 #ifndef _WIN32
9752 pid_t pid;
9753 char *const parmList[] = {"xdg-email", mailto, NULL};
9754 if ((pid = fork()) == -1)
9756 SIPE_DEBUG_INFO_NOFORMAT("fork() error");
9758 else if (pid == 0)
9760 execvp(parmList[0], parmList);
9761 SIPE_DEBUG_INFO_NOFORMAT("Return not expected. Must be an execvp() error.");
9764 #else
9766 BOOL ret;
9767 _flushall();
9768 errno = 0;
9769 //@TODO resolve env variable %WINDIR% first
9770 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9771 if (errno)
9773 SIPE_DEBUG_INFO("spawnl returned (%s)!", strerror(errno));
9776 #endif
9778 g_free(mailto);
9780 else
9782 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
9786 static void
9787 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy, const int *container_id)
9789 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9791 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: buddy->name=%s, container_id=%d",
9792 buddy->name, container_id ? *container_id : -1);
9793 if (container_id) {
9794 sipe_change_access_level(sip, *container_id, "user", sipe_get_no_sip_uri(buddy->name));
9799 * A menu which appear when right-clicking on buddy in contact list.
9801 static GList *
9802 sipe_buddy_menu(PurpleBuddy *buddy)
9804 PurpleBlistNode *g_node;
9805 PurpleGroup *group, *gr_parent;
9806 PurpleMenuAction *act;
9807 GList *menu = NULL;
9808 GList *menu_groups = NULL;
9809 GList *menu_access_levels = NULL;
9810 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9811 const char *email;
9812 const char *phone;
9813 const char *phone_disp_str;
9814 gchar *self = sip_uri_self(sip);
9815 unsigned int i;
9817 SIPE_SESSION_FOREACH {
9818 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9820 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9822 PurpleConvChatBuddyFlags flags;
9823 PurpleConvChatBuddyFlags flags_us;
9825 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9826 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9827 if (session->focus_uri
9828 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9829 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9831 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9832 act = purple_menu_action_new(label,
9833 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9834 session->chat_title, NULL);
9835 g_free(label);
9836 menu = g_list_prepend(menu, act);
9839 if (session->focus_uri
9840 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9842 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9843 act = purple_menu_action_new(label,
9844 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9845 session->chat_title, NULL);
9846 g_free(label);
9847 menu = g_list_prepend(menu, act);
9850 else
9852 if (!session->focus_uri
9853 || (session->focus_uri && !session->locked))
9855 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9856 act = purple_menu_action_new(label,
9857 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9858 session->chat_title, NULL);
9859 g_free(label);
9860 menu = g_list_prepend(menu, act);
9864 } SIPE_SESSION_FOREACH_END;
9866 act = purple_menu_action_new(_("New chat"),
9867 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9868 NULL, NULL);
9869 menu = g_list_prepend(menu, act);
9871 if (sip->csta && !sip->csta->line_status) {
9872 gchar *tmp = NULL;
9873 /* work phone */
9874 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9875 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9876 if (phone) {
9877 gchar *label = g_strdup_printf(_("Work %s"),
9878 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9879 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9880 g_free(tmp);
9881 tmp = NULL;
9882 g_free(label);
9883 menu = g_list_prepend(menu, act);
9886 /* mobile phone */
9887 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9888 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9889 if (phone) {
9890 gchar *label = g_strdup_printf(_("Mobile %s"),
9891 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9892 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9893 g_free(tmp);
9894 tmp = NULL;
9895 g_free(label);
9896 menu = g_list_prepend(menu, act);
9899 /* home phone */
9900 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9901 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9902 if (phone) {
9903 gchar *label = g_strdup_printf(_("Home %s"),
9904 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9905 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9906 g_free(tmp);
9907 tmp = NULL;
9908 g_free(label);
9909 menu = g_list_prepend(menu, act);
9912 /* other phone */
9913 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9914 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9915 if (phone) {
9916 gchar *label = g_strdup_printf(_("Other %s"),
9917 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9918 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9919 g_free(tmp);
9920 tmp = NULL;
9921 g_free(label);
9922 menu = g_list_prepend(menu, act);
9925 /* custom1 phone */
9926 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9927 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9928 if (phone) {
9929 gchar *label = g_strdup_printf(_("Custom1 %s"),
9930 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9931 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9932 g_free(tmp);
9933 tmp = NULL;
9934 g_free(label);
9935 menu = g_list_prepend(menu, act);
9939 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9940 if (email) {
9941 act = purple_menu_action_new(_("Send email..."),
9942 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9943 NULL, NULL);
9944 menu = g_list_prepend(menu, act);
9947 /* Access Level */
9948 /* get current access level */
9949 for (i = 1; i <= CONTAINERS_LEN; i++) {
9950 /* to put Blocked level last in menu list.
9951 * Blocked should remaim in the first place in the containers[] array.
9953 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
9955 act = purple_menu_action_new(sipe_get_access_level_name(containers[j]),
9956 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
9957 (gpointer)&(containers[j]), NULL);
9958 menu_access_levels = g_list_prepend(menu_access_levels, act);
9960 menu_access_levels = g_list_reverse(menu_access_levels);
9962 act = purple_menu_action_new(_("Access level"),
9963 NULL,
9964 NULL, menu_access_levels);
9965 menu = g_list_prepend(menu, act);
9967 /* Copy to */
9968 gr_parent = purple_buddy_get_group(buddy);
9969 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9970 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9971 continue;
9973 group = (PurpleGroup *)g_node;
9974 if (group == gr_parent)
9975 continue;
9977 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9978 continue;
9980 act = purple_menu_action_new(purple_group_get_name(group),
9981 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9982 group->name, NULL);
9983 menu_groups = g_list_prepend(menu_groups, act);
9985 menu_groups = g_list_reverse(menu_groups);
9987 act = purple_menu_action_new(_("Copy to"),
9988 NULL,
9989 NULL, menu_groups);
9990 menu = g_list_prepend(menu, act);
9992 menu = g_list_reverse(menu);
9994 g_free(self);
9995 return menu;
9998 static void
9999 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
10001 struct sipe_account_data *sip = chat->account->gc->proto_data;
10002 struct sip_session *session;
10004 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10005 sipe_conf_modify_conference_lock(sip, session, locked);
10008 static void
10009 sipe_chat_menu_unlock_cb(PurpleChat *chat)
10011 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
10012 sipe_conf_modify_lock(chat, FALSE);
10015 static void
10016 sipe_chat_menu_lock_cb(PurpleChat *chat)
10018 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
10019 sipe_conf_modify_lock(chat, TRUE);
10022 static GList *
10023 sipe_chat_menu(PurpleChat *chat)
10025 PurpleMenuAction *act;
10026 PurpleConvChatBuddyFlags flags_us;
10027 GList *menu = NULL;
10028 struct sipe_account_data *sip = chat->account->gc->proto_data;
10029 struct sip_session *session;
10030 gchar *self;
10032 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10033 if (!session) return NULL;
10035 self = sip_uri_self(sip);
10036 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
10038 if (session->focus_uri
10039 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
10041 if (session->locked) {
10042 act = purple_menu_action_new(_("Unlock"),
10043 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
10044 NULL, NULL);
10045 menu = g_list_prepend(menu, act);
10046 } else {
10047 act = purple_menu_action_new(_("Lock"),
10048 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
10049 NULL, NULL);
10050 menu = g_list_prepend(menu, act);
10054 menu = g_list_reverse(menu);
10056 g_free(self);
10057 return menu;
10060 static GList *
10061 sipe_blist_node_menu(PurpleBlistNode *node)
10063 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
10064 return sipe_buddy_menu((PurpleBuddy *) node);
10065 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
10066 return sipe_chat_menu((PurpleChat *)node);
10067 } else {
10068 return NULL;
10072 static gboolean
10073 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
10075 char *uri = trans->payload->data;
10077 PurpleNotifyUserInfo *info;
10078 PurpleBuddy *pbuddy = NULL;
10079 struct sipe_buddy *sbuddy;
10080 const char *alias = NULL;
10081 char *device_name = NULL;
10082 char *server_alias = NULL;
10083 char *phone_number = NULL;
10084 char *email = NULL;
10085 const char *site;
10086 char *first_name = NULL;
10087 char *last_name = NULL;
10089 if (!sip) return FALSE;
10091 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sip->username);
10093 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
10094 alias = purple_buddy_get_local_alias(pbuddy);
10096 //will query buddy UA's capabilities and send answer to log
10097 sipe_options_request(sip, uri);
10099 sbuddy = g_hash_table_lookup(sip->buddies, uri);
10100 if (sbuddy) {
10101 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
10104 info = purple_notify_user_info_new();
10106 if (msg->response != 200) {
10107 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg->response);
10108 } else {
10109 xmlnode *searchResults;
10110 xmlnode *mrow;
10112 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
10113 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
10114 if (!searchResults) {
10115 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
10116 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
10117 const char *value;
10118 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
10119 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
10120 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
10122 /* For 2007 system we will take this from ContactCard -
10123 * it has cleaner tel: URIs at least
10125 if (!sip->ocs2007) {
10126 char *tel_uri = sip_to_tel_uri(phone_number);
10127 /* trims its parameters, so call first */
10128 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
10129 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
10130 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
10131 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
10132 g_free(tel_uri);
10135 if (server_alias && strlen(server_alias) > 0) {
10136 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10138 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
10139 purple_notify_user_info_add_pair(info, _("Job title"), value);
10141 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
10142 purple_notify_user_info_add_pair(info, _("Office"), value);
10144 if (phone_number && strlen(phone_number) > 0) {
10145 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
10147 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
10148 purple_notify_user_info_add_pair(info, _("Company"), value);
10150 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
10151 purple_notify_user_info_add_pair(info, _("City"), value);
10153 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
10154 purple_notify_user_info_add_pair(info, _("State"), value);
10156 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
10157 purple_notify_user_info_add_pair(info, _("Country"), value);
10159 if (email && strlen(email) > 0) {
10160 purple_notify_user_info_add_pair(info, _("Email address"), email);
10164 xmlnode_free(searchResults);
10167 purple_notify_user_info_add_section_break(info);
10169 if (is_empty(server_alias)) {
10170 g_free(server_alias);
10171 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
10172 if (server_alias) {
10173 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10177 /* present alias if it differs from server alias */
10178 if (alias && !sipe_strequal(alias, server_alias))
10180 purple_notify_user_info_add_pair(info, _("Alias"), alias);
10183 if (is_empty(email)) {
10184 g_free(email);
10185 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
10186 if (email) {
10187 purple_notify_user_info_add_pair(info, _("Email address"), email);
10191 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
10192 if (site) {
10193 purple_notify_user_info_add_pair(info, _("Site"), site);
10196 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
10197 if (first_name && last_name) {
10198 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
10200 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
10201 g_free(link);
10203 g_free(first_name);
10204 g_free(last_name);
10206 if (device_name) {
10207 purple_notify_user_info_add_pair(info, _("Device"), device_name);
10210 /* show a buddy's user info in a nice dialog box */
10211 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
10212 uri, /* buddy's URI */
10213 info, /* body */
10214 NULL, /* callback called when dialog closed */
10215 NULL); /* userdata for callback */
10217 g_free(phone_number);
10218 g_free(server_alias);
10219 g_free(email);
10220 g_free(device_name);
10222 return TRUE;
10226 * AD search first, LDAP based
10228 static void sipe_get_info(PurpleConnection *gc, const char *username)
10230 struct sipe_account_data *sip = gc->proto_data;
10231 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
10232 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
10233 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
10234 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
10236 payload->destroy = g_free;
10237 payload->data = g_strdup(username);
10239 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
10240 send_soap_request_with_cb(sip, domain_uri, body,
10241 (TransCallback) process_get_info_response, payload);
10242 g_free(domain_uri);
10243 g_free(body);
10244 g_free(row);
10247 PurplePluginProtocolInfo prpl_info =
10249 OPT_PROTO_CHAT_TOPIC,
10250 NULL, /* user_splits */
10251 NULL, /* protocol_options */
10252 NO_BUDDY_ICONS, /* icon_spec */
10253 sipe_list_icon, /* list_icon */
10254 NULL, /* list_emblems */
10255 sipe_status_text, /* status_text */
10256 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10257 sipe_status_types, /* away_states */
10258 sipe_blist_node_menu, /* blist_node_menu */
10259 NULL, /* chat_info */
10260 NULL, /* chat_info_defaults */
10261 sipe_login, /* login */
10262 sipe_close, /* close */
10263 sipe_im_send, /* send_im */
10264 NULL, /* set_info */ // TODO maybe
10265 sipe_send_typing, /* send_typing */
10266 sipe_get_info, /* get_info */
10267 sipe_set_status, /* set_status */
10268 sipe_set_idle, /* set_idle */
10269 NULL, /* change_passwd */
10270 sipe_add_buddy, /* add_buddy */
10271 NULL, /* add_buddies */
10272 sipe_remove_buddy, /* remove_buddy */
10273 NULL, /* remove_buddies */
10274 sipe_add_permit, /* add_permit */
10275 sipe_add_deny, /* add_deny */
10276 sipe_add_deny, /* rem_permit */
10277 sipe_add_permit, /* rem_deny */
10278 dummy_permit_deny, /* set_permit_deny */
10279 NULL, /* join_chat */
10280 NULL, /* reject_chat */
10281 NULL, /* get_chat_name */
10282 sipe_chat_invite, /* chat_invite */
10283 sipe_chat_leave, /* chat_leave */
10284 NULL, /* chat_whisper */
10285 sipe_chat_send, /* chat_send */
10286 sipe_keep_alive, /* keepalive */
10287 NULL, /* register_user */
10288 NULL, /* get_cb_info */ // deprecated
10289 NULL, /* get_cb_away */ // deprecated
10290 sipe_alias_buddy, /* alias_buddy */
10291 sipe_group_buddy, /* group_buddy */
10292 sipe_rename_group, /* rename_group */
10293 NULL, /* buddy_free */
10294 sipe_convo_closed, /* convo_closed */
10295 purple_normalize_nocase, /* normalize */
10296 NULL, /* set_buddy_icon */
10297 sipe_remove_group, /* remove_group */
10298 NULL, /* get_cb_real_name */ // TODO?
10299 NULL, /* set_chat_topic */
10300 NULL, /* find_blist_chat */
10301 NULL, /* roomlist_get_list */
10302 NULL, /* roomlist_cancel */
10303 NULL, /* roomlist_expand_category */
10304 NULL, /* can_receive_file */
10305 sipe_ft_send_file, /* send_file */
10306 sipe_ft_new_xfer, /* new_xfer */
10307 NULL, /* offline_message */
10308 NULL, /* whiteboard_prpl_ops */
10309 sipe_send_raw, /* send_raw */
10310 NULL, /* roomlist_room_serialize */
10311 NULL, /* unregister_user */
10312 NULL, /* send_attention */
10313 NULL, /* get_attention_types */
10314 #if !PURPLE_VERSION_CHECK(2,5,0)
10315 /* Backward compatibility when compiling against 2.4.x API */
10316 (void (*)(void)) /* _purple_reserved4 */
10317 #endif
10318 sizeof(PurplePluginProtocolInfo), /* struct_size */
10319 #if PURPLE_VERSION_CHECK(2,5,0)
10320 sipe_get_account_text_table, /* get_account_text_table */
10321 #if PURPLE_VERSION_CHECK(2,6,0)
10322 NULL, /* initiate_media */
10323 NULL, /* get_media_caps */
10324 #endif
10325 #endif
10329 PurplePluginInfo info = {
10330 PURPLE_PLUGIN_MAGIC,
10331 PURPLE_MAJOR_VERSION,
10332 PURPLE_MINOR_VERSION,
10333 PURPLE_PLUGIN_PROTOCOL, /**< type */
10334 NULL, /**< ui_requirement */
10335 0, /**< flags */
10336 NULL, /**< dependencies */
10337 PURPLE_PRIORITY_DEFAULT, /**< priority */
10338 "prpl-sipe", /**< id */
10339 "Office Communicator", /**< name */
10340 PACKAGE_VERSION, /**< version */
10341 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10342 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10343 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10344 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10345 "Gabriel Burt <gburt@novell.com>, " /**< author */
10346 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10347 "pier11 <pier11@operamail.com>", /**< author */
10348 PACKAGE_URL, /**< homepage */
10349 sipe_plugin_load, /**< load */
10350 sipe_plugin_unload, /**< unload */
10351 sipe_plugin_destroy, /**< destroy */
10352 NULL, /**< ui_info */
10353 &prpl_info, /**< extra_info */
10354 NULL,
10355 sipe_actions,
10356 NULL,
10357 NULL,
10358 NULL,
10359 NULL
10362 void sipe_core_init(void)
10364 srand(time(NULL));
10365 sip_sec_init();
10367 #ifdef ENABLE_NLS
10368 SIPE_DEBUG_INFO("bindtextdomain = %s",
10369 bindtextdomain(PACKAGE_NAME, LOCALEDIR));
10370 SIPE_DEBUG_INFO("bind_textdomain_codeset = %s",
10371 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"));
10372 textdomain(PACKAGE_NAME);
10373 #endif
10374 #ifdef HAVE_GMIME
10375 g_mime_init(0);
10376 #endif
10379 void sipe_core_destroy(void)
10381 #ifdef HAVE_GMIME
10382 g_mime_shutdown();
10383 #endif
10384 sip_sec_destroy();
10388 Local Variables:
10389 mode: c
10390 c-file-style: "bsd"
10391 indent-tabs-mode: t
10392 tab-width: 8
10393 End: