i18n: URL to replace with localized page
[siplcs.git] / src / core / sipe.c
blob5e778f939a12209f79424df480924223df1cf197
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"
88 #include "core-depurple.h" /* Temporary for the core de-purple transition */
90 #include "sipmsg.h"
91 #include "sip-csta.h"
92 #include "sip-sec.h"
93 #include "sipe-backend.h"
94 #include "sipe-cal.h"
95 #include "sipe-chat.h"
96 #include "sipe-conf.h"
97 #include "sipe-core.h"
98 #include "sipe-dialog.h"
99 #include "sipe-ews.h"
100 #include "sipe-ft.h"
101 #include "sipe-mime.h"
102 #include "sipe-nls.h"
103 #include "sipe-session.h"
104 #include "sipe-sign.h"
105 #include "sipe-utils.h"
106 #include "sipe-xml.h"
107 #include "http-conn.h"
108 #include "uuid.h"
109 #include "sipe.h"
111 /* Backward compatibility when compiling against 2.4.x API */
112 #if !PURPLE_VERSION_CHECK(2,5,0)
113 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
114 #endif
116 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
118 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
119 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
121 /* Keep in sync with sipe_transport_type! */
122 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
123 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
125 /* Status identifiers (see also: sipe_status_types()) */
126 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
127 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
128 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
129 /* PURPLE_STATUS_UNAVAILABLE: */
130 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
131 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
132 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
133 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
134 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
135 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
136 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
137 /* PURPLE_STATUS_AWAY: */
138 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
139 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
140 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
141 /** Reuters status (user settable) */
142 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
143 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
144 /* ??? PURPLE_STATUS_MOBILE */
145 /* ??? PURPLE_STATUS_TUNE */
147 /* Status attributes (see also sipe_status_types() */
148 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
150 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
152 static struct sipe_activity_map_struct
154 sipe_activity type;
155 const char *token;
156 const char *desc;
157 const char *status_id;
159 } const sipe_activity_map[] =
161 /* This has nothing to do with Availability numbers, like 3500 (online).
162 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
164 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
165 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
166 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
167 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
168 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
169 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
170 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
171 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
172 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
173 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
174 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
175 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
176 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
177 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
178 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
180 /** @param x is sipe_activity */
181 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
184 /* Action name templates */
185 #define ACTION_NAME_PRESENCE "<presence><%s>"
187 static sipe_activity
188 sipe_get_activity_by_token(const char *token)
190 int i;
192 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
194 if (sipe_strequal(token, sipe_activity_map[i].token))
195 return sipe_activity_map[i].type;
198 return sipe_activity_map[0].type;
201 static const char *
202 sipe_get_activity_desc_by_token(const char *token)
204 if (!token) return NULL;
206 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
209 /** Allows to send typed messages from chat window again after account reinstantiation. */
210 static void
211 sipe_rejoin_chat(PurpleConversation *conv)
213 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
214 PURPLE_CONV_CHAT(conv)->left)
216 PURPLE_CONV_CHAT(conv)->left = FALSE;
217 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
221 static char *genbranch()
223 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
224 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
225 rand() & 0xFFFF, rand() & 0xFFFF);
229 static char *default_ua = NULL;
230 static const char*
231 sipe_get_useragent(struct sipe_account_data *sip)
233 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
234 if (is_empty(useragent)) {
235 if (!default_ua) {
236 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
237 /* ref: lzodefs.h */
238 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
239 #define SIPE_TARGET_PLATFORM "linux"
240 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
241 #define SIPE_TARGET_PLATFORM "bsd"
242 #elif defined(__APPLE__) || defined(__MACOS__)
243 #define SIPE_TARGET_PLATFORM "macosx"
244 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
245 #define SIPE_TARGET_PLATFORM "aix"
246 #elif defined(__solaris__) || defined(__sun)
247 #define SIPE_TARGET_PLATFORM "sun"
248 #elif defined(_WIN32)
249 #define SIPE_TARGET_PLATFORM "win"
250 #elif defined(__CYGWIN__)
251 #define SIPE_TARGET_PLATFORM "cygwin"
252 #elif defined(__hpux__)
253 #define SIPE_TARGET_PLATFORM "hpux"
254 #elif defined(__sgi__)
255 #define SIPE_TARGET_PLATFORM "irix"
256 #else
257 #define SIPE_TARGET_PLATFORM "unknown"
258 #endif
260 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
261 #define SIPE_TARGET_ARCH "x86_64"
262 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
263 #define SIPE_TARGET_ARCH "i386"
264 #elif defined(__ppc64__)
265 #define SIPE_TARGET_ARCH "ppc64"
266 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
267 #define SIPE_TARGET_ARCH "ppc"
268 #elif defined(__hppa__) || defined(__hppa)
269 #define SIPE_TARGET_ARCH "hppa"
270 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
271 #define SIPE_TARGET_ARCH "mips"
272 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
273 #define SIPE_TARGET_ARCH "s390"
274 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
275 #define SIPE_TARGET_ARCH "sparc"
276 #elif defined(__arm__)
277 #define SIPE_TARGET_ARCH "arm"
278 #else
279 #define SIPE_TARGET_ARCH "other"
280 #endif
282 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
283 purple_core_get_version(),
284 sip->server_version ? sip->server_version : "");
286 useragent = default_ua;
288 return useragent;
291 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
292 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
294 return "sipe";
297 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
299 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
300 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
301 gpointer data);
303 static void sipe_close(PurpleConnection *gc);
305 static void send_presence_status(struct sipe_account_data *sip);
307 static void sendout_pkt(PurpleConnection *gc, const char *buf);
309 static void sipe_keep_alive(PurpleConnection *gc)
311 struct sipe_account_data *sip = gc->proto_data;
312 if (sip->transport == SIPE_TRANSPORT_UDP) {
313 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
314 gchar buf[2] = {0, 0};
315 SIPE_DEBUG_INFO_NOFORMAT("sending keep alive");
316 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
317 } else {
318 time_t now = time(NULL);
319 if ((sip->keepalive_timeout > 0) &&
320 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
321 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
323 SIPE_DEBUG_INFO("sending keep alive %d", sip->keepalive_timeout);
324 sendout_pkt(gc, "\r\n\r\n");
325 sip->last_keepalive = now;
330 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
332 struct sip_connection *ret = NULL;
333 GSList *entry = sip->openconns;
334 while (entry) {
335 ret = entry->data;
336 if (ret->fd == fd) return ret;
337 entry = entry->next;
339 return NULL;
342 static void sipe_auth_free(struct sip_auth *auth)
344 g_free(auth->opaque);
345 auth->opaque = NULL;
346 g_free(auth->realm);
347 auth->realm = NULL;
348 g_free(auth->target);
349 auth->target = NULL;
350 auth->version = 0;
351 auth->type = AUTH_TYPE_UNSET;
352 auth->retries = 0;
353 auth->expires = 0;
354 g_free(auth->gssapi_data);
355 auth->gssapi_data = NULL;
356 sip_sec_destroy_context(auth->gssapi_context);
357 auth->gssapi_context = NULL;
360 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
362 struct sip_connection *ret = g_new0(struct sip_connection, 1);
363 ret->fd = fd;
364 sip->openconns = g_slist_append(sip->openconns, ret);
365 return ret;
368 static void connection_remove(struct sipe_account_data *sip, int fd)
370 struct sip_connection *conn = connection_find(sip, fd);
371 if (conn) {
372 sip->openconns = g_slist_remove(sip->openconns, conn);
373 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
374 g_free(conn->inbuf);
375 g_free(conn);
379 static void connection_free_all(struct sipe_account_data *sip)
381 struct sip_connection *ret = NULL;
382 GSList *entry = sip->openconns;
383 while (entry) {
384 ret = entry->data;
385 connection_remove(sip, ret->fd);
386 entry = sip->openconns;
390 static void
391 sipe_make_signature(struct sipe_account_data *sip,
392 struct sipmsg *msg);
394 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
396 gchar noncecount[9];
397 const char *authuser = sip->authuser;
398 gchar *response;
399 gchar *ret;
401 if (!authuser || strlen(authuser) < 1) {
402 authuser = sip->username;
405 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
406 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
407 gchar *version_str;
409 // If we have a signature for the message, include that
410 if (msg->signature) {
411 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);
414 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
415 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
416 gchar *gssapi_data;
417 gchar *opaque;
418 gchar *sign_str = NULL;
420 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
421 &(auth->expires),
422 auth->type,
423 purple_account_get_bool(sip->account, "sso", TRUE),
424 sip->authdomain ? sip->authdomain : "",
425 authuser,
426 sip->password,
427 auth->target,
428 auth->gssapi_data);
429 if (!gssapi_data || !auth->gssapi_context) {
430 sip->gc->wants_to_die = TRUE;
431 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
432 return NULL;
435 if (auth->version > 3) {
436 sipe_make_signature(sip, msg);
437 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
438 msg->rand, msg->num, msg->signature);
439 } else {
440 sign_str = g_strdup("");
443 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
444 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
445 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);
446 g_free(opaque);
447 g_free(gssapi_data);
448 g_free(version_str);
449 g_free(sign_str);
450 return ret;
453 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
454 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
455 g_free(version_str);
456 return ret;
458 } else { /* Digest */
460 /* Calculate new session key */
461 if (!auth->opaque) {
462 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth->gssapi_data, auth->realm);
463 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
464 authuser, auth->realm, sip->password,
465 auth->gssapi_data, NULL);
468 sprintf(noncecount, "%08d", auth->nc++);
469 response = purple_cipher_http_digest_calculate_response("md5",
470 msg->method, msg->target, NULL, NULL,
471 auth->gssapi_data, noncecount, NULL,
472 auth->opaque);
473 SIPE_DEBUG_INFO("Digest response %s", response);
475 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);
476 g_free(response);
477 return ret;
481 static char *parse_attribute(const char *attrname, const char *source)
483 const char *tmp, *tmp2;
484 char *retval = NULL;
485 int len = strlen(attrname);
487 if (g_str_has_prefix(source, attrname)) {
488 tmp = source + len;
489 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
490 if (tmp2)
491 retval = g_strndup(tmp, tmp2 - tmp);
492 else
493 retval = g_strdup(tmp);
496 return retval;
499 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
501 int i;
502 gchar **parts;
504 if (!hdr) {
505 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
506 return;
509 if (!g_strncasecmp(hdr, "NTLM", 4)) {
510 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
511 auth->type = AUTH_TYPE_NTLM;
512 hdr += 5;
513 auth->nc = 1;
514 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
515 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
516 auth->type = AUTH_TYPE_KERBEROS;
517 hdr += 9;
518 auth->nc = 3;
519 } else {
520 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
521 auth->type = AUTH_TYPE_DIGEST;
522 hdr += 7;
525 parts = g_strsplit(hdr, "\", ", 0);
526 for (i = 0; parts[i]; i++) {
527 char *tmp;
529 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
531 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
532 g_free(auth->gssapi_data);
533 auth->gssapi_data = tmp;
535 if (auth->type == AUTH_TYPE_NTLM) {
536 /* NTLM module extracts nonce from gssapi-data */
537 auth->nc = 3;
540 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
541 /* Only used with AUTH_TYPE_DIGEST */
542 g_free(auth->gssapi_data);
543 auth->gssapi_data = tmp;
544 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
545 g_free(auth->opaque);
546 auth->opaque = tmp;
547 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
548 g_free(auth->realm);
549 auth->realm = tmp;
551 if (auth->type == AUTH_TYPE_DIGEST) {
552 /* Throw away old session key */
553 g_free(auth->opaque);
554 auth->opaque = NULL;
555 auth->nc = 1;
557 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
558 g_free(auth->target);
559 auth->target = tmp;
560 } else if ((tmp = parse_attribute("version=", parts[i]))) {
561 auth->version = atoi(tmp);
562 g_free(tmp);
564 // uncomment to revert to previous functionality if version 3+ does not work.
565 // auth->version = 2;
567 g_strfreev(parts);
569 return;
572 static void sipe_canwrite_cb(gpointer data,
573 SIPE_UNUSED_PARAMETER gint source,
574 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
576 PurpleConnection *gc = data;
577 struct sipe_account_data *sip = gc->proto_data;
578 gsize max_write;
579 gssize written;
581 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
583 if (max_write == 0) {
584 if (sip->tx_handler != 0){
585 purple_input_remove(sip->tx_handler);
586 sip->tx_handler = 0;
588 return;
591 written = write(sip->fd, sip->txbuf->outptr, max_write);
593 if (written < 0 && errno == EAGAIN)
594 written = 0;
595 else if (written <= 0) {
596 /*TODO: do we really want to disconnect on a failure to write?*/
597 purple_connection_error(gc, _("Could not write"));
598 return;
601 purple_circ_buffer_mark_read(sip->txbuf, written);
604 static void sipe_canwrite_cb_ssl(gpointer data,
605 SIPE_UNUSED_PARAMETER gint src,
606 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
608 PurpleConnection *gc = data;
609 struct sipe_account_data *sip = gc->proto_data;
610 gsize max_write;
611 gssize written;
613 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
615 if (max_write == 0) {
616 if (sip->tx_handler != 0) {
617 purple_input_remove(sip->tx_handler);
618 sip->tx_handler = 0;
619 return;
623 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
625 if (written < 0 && errno == EAGAIN)
626 written = 0;
627 else if (written <= 0) {
628 /*TODO: do we really want to disconnect on a failure to write?*/
629 purple_connection_error(gc, _("Could not write"));
630 return;
633 purple_circ_buffer_mark_read(sip->txbuf, written);
636 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
638 static void send_later_cb(gpointer data, gint source,
639 SIPE_UNUSED_PARAMETER const gchar *error)
641 PurpleConnection *gc = data;
642 struct sipe_account_data *sip;
643 struct sip_connection *conn;
645 if (!PURPLE_CONNECTION_IS_VALID(gc))
647 if (source >= 0)
648 close(source);
649 return;
652 if (source < 0) {
653 purple_connection_error(gc, _("Could not connect"));
654 return;
657 sip = gc->proto_data;
658 sip->fd = source;
659 sip->connecting = FALSE;
660 sip->last_keepalive = time(NULL);
662 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
664 /* If there is more to write now, we need to register a handler */
665 if (sip->txbuf->bufused > 0)
666 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
668 conn = connection_create(sip, source);
669 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
672 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
674 struct sipe_account_data *sip;
676 if (!PURPLE_CONNECTION_IS_VALID(gc))
678 if (gsc) purple_ssl_close(gsc);
679 return NULL;
682 sip = gc->proto_data;
683 sip->fd = gsc->fd;
684 sip->gsc = gsc;
685 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
686 sip->connecting = FALSE;
687 sip->last_keepalive = time(NULL);
689 connection_create(sip, gsc->fd);
691 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
693 return sip;
696 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
697 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
699 PurpleConnection *gc = data;
700 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
701 if (sip == NULL) return;
703 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
705 /* If there is more to write now */
706 if (sip->txbuf->bufused > 0) {
707 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
712 static void sendlater(PurpleConnection *gc, const char *buf)
714 struct sipe_account_data *sip = gc->proto_data;
716 if (!sip->connecting) {
717 SIPE_DEBUG_INFO("connecting to %s port %d", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
718 if (sip->transport == SIPE_TRANSPORT_TLS){
719 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
720 } else {
721 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
722 purple_connection_error(gc, _("Could not create socket"));
725 sip->connecting = TRUE;
728 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
729 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
731 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
734 static void sendout_pkt(PurpleConnection *gc, const char *buf)
736 struct sipe_account_data *sip = gc->proto_data;
737 time_t currtime = time(NULL);
738 int writelen = strlen(buf);
739 char *tmp;
741 SIPE_DEBUG_INFO("sending - %s######\n%s######", ctime(&currtime), tmp = fix_newlines(buf));
742 g_free(tmp);
743 if (sip->transport == SIPE_TRANSPORT_UDP) {
744 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
745 SIPE_DEBUG_INFO_NOFORMAT("could not send packet");
747 } else {
748 int ret;
749 if (sip->fd < 0) {
750 sendlater(gc, buf);
751 return;
754 if (sip->tx_handler) {
755 ret = -1;
756 errno = EAGAIN;
757 } else{
758 if (sip->gsc){
759 ret = purple_ssl_write(sip->gsc, buf, writelen);
760 }else{
761 ret = write(sip->fd, buf, writelen);
765 if (ret < 0 && errno == EAGAIN)
766 ret = 0;
767 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
768 sendlater(gc, buf);
769 return;
772 if (ret < writelen) {
773 if (!sip->tx_handler){
774 if (sip->gsc){
775 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
777 else{
778 sip->tx_handler = purple_input_add(sip->fd,
779 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
780 gc);
784 /* XXX: is it OK to do this? You might get part of a request sent
785 with part of another. */
786 if (sip->txbuf->bufused > 0)
787 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
789 purple_circ_buffer_append(sip->txbuf, buf + ret,
790 writelen - ret);
795 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
797 sendout_pkt(gc, buf);
798 return len;
801 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
803 GSList *tmp = msg->headers;
804 gchar *name;
805 gchar *value;
806 GString *outstr = g_string_new("");
807 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
808 while (tmp) {
809 name = ((struct sipnameval*) (tmp->data))->name;
810 value = ((struct sipnameval*) (tmp->data))->value;
811 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
812 tmp = g_slist_next(tmp);
814 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
815 sendout_pkt(sip->gc, outstr->str);
816 g_string_free(outstr, TRUE);
819 static void
820 sipe_make_signature(struct sipe_account_data *sip,
821 struct sipmsg *msg)
823 if (sip->registrar.gssapi_context) {
824 struct sipmsg_breakdown msgbd;
825 gchar *signature_input_str;
826 msgbd.msg = msg;
827 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
828 msgbd.rand = g_strdup_printf("%08x", g_random_int());
829 sip->registrar.ntlm_num++;
830 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
831 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
832 if (signature_input_str != NULL) {
833 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
834 msg->signature = signature_hex;
835 msg->rand = g_strdup(msgbd.rand);
836 msg->num = g_strdup(msgbd.num);
837 g_free(signature_input_str);
839 sipmsg_breakdown_free(&msgbd);
843 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
845 gchar * buf;
847 if (sip->registrar.type == AUTH_TYPE_UNSET) {
848 return;
851 sipe_make_signature(sip, msg);
853 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
854 buf = auth_header(sip, &sip->registrar, msg);
855 if (buf) {
856 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
858 g_free(buf);
859 } 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")) {
860 sip->registrar.nc = 3;
861 sip->registrar.type = AUTH_TYPE_NTLM;
862 #ifdef HAVE_LIBKRB5
863 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
864 sip->registrar.type = AUTH_TYPE_KERBEROS;
866 #endif
869 buf = auth_header(sip, &sip->registrar, msg);
870 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
871 g_free(buf);
872 } else {
873 SIPE_DEBUG_INFO("not adding auth header to msg w/ method %s", method);
877 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
878 const char *text, const char *body)
880 gchar *name;
881 gchar *value;
882 GString *outstr = g_string_new("");
883 struct sipe_account_data *sip = gc->proto_data;
884 gchar *contact;
885 GSList *tmp;
886 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
888 /* Can return NULL! */
889 contact = get_contact(sip);
890 if (contact) {
891 sipmsg_add_header(msg, "Contact", contact);
892 g_free(contact);
895 if (body) {
896 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
897 sipmsg_add_header(msg, "Content-Length", len);
898 g_free(len);
899 } else {
900 sipmsg_add_header(msg, "Content-Length", "0");
903 msg->response = code;
905 sipmsg_strip_headers(msg, keepers);
906 sipmsg_merge_new_headers(msg);
907 sign_outgoing_message(msg, sip, msg->method);
909 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
910 tmp = msg->headers;
911 while (tmp) {
912 name = ((struct sipnameval*) (tmp->data))->name;
913 value = ((struct sipnameval*) (tmp->data))->value;
915 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
916 tmp = g_slist_next(tmp);
918 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
919 sendout_pkt(gc, outstr->str);
920 g_string_free(outstr, TRUE);
923 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
925 if (sip->transactions) {
926 sip->transactions = g_slist_remove(sip->transactions, trans);
927 SIPE_DEBUG_INFO("sip->transactions count:%d after removal", g_slist_length(sip->transactions));
929 if (trans->msg) sipmsg_free(trans->msg);
930 if (trans->payload) {
931 (*trans->payload->destroy)(trans->payload->data);
932 g_free(trans->payload);
934 g_free(trans->key);
935 g_free(trans);
939 static struct transaction *
940 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
942 const gchar *call_id;
943 const gchar *cseq;
944 struct transaction *trans = g_new0(struct transaction, 1);
946 trans->time = time(NULL);
947 trans->msg = (struct sipmsg *)msg;
948 call_id = sipmsg_find_header(trans->msg, "Call-ID");
949 cseq = sipmsg_find_header(trans->msg, "CSeq");
950 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
951 trans->callback = callback;
952 sip->transactions = g_slist_append(sip->transactions, trans);
953 SIPE_DEBUG_INFO("sip->transactions count:%d after addition", g_slist_length(sip->transactions));
954 return trans;
957 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
959 struct transaction *trans;
960 GSList *transactions = sip->transactions;
961 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
962 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
963 gchar *key;
965 if (!call_id || !cseq) {
966 SIPE_DEBUG_ERROR_NOFORMAT("transaction_find: no Call-ID or CSeq!");
967 return NULL;
970 key = g_strdup_printf("<%s><%s>", call_id, cseq);
971 while (transactions) {
972 trans = transactions->data;
973 if (!g_strcasecmp(trans->key, key)) {
974 g_free(key);
975 return trans;
977 transactions = transactions->next;
980 g_free(key);
981 return NULL;
984 struct transaction *
985 send_sip_request(PurpleConnection *gc, const gchar *method,
986 const gchar *url, const gchar *to, const gchar *addheaders,
987 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
989 struct sipe_account_data *sip = gc->proto_data;
990 const char *addh = "";
991 char *buf;
992 struct sipmsg *msg;
993 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
994 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
995 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
996 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
997 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
998 gchar *route = g_strdup("");
999 gchar *epid = get_epid(sip);
1000 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
1001 struct transaction *trans = NULL;
1003 if (dialog && dialog->routes)
1005 GSList *iter = dialog->routes;
1007 while(iter)
1009 char *tmp = route;
1010 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1011 g_free(tmp);
1012 iter = g_slist_next(iter);
1016 if (!ourtag && !dialog) {
1017 ourtag = gentag();
1020 if (sipe_strequal(method, "REGISTER")) {
1021 if (sip->regcallid) {
1022 g_free(callid);
1023 callid = g_strdup(sip->regcallid);
1024 } else {
1025 sip->regcallid = g_strdup(callid);
1027 cseq = ++sip->cseq;
1030 if (addheaders) addh = addheaders;
1032 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1033 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1034 "From: <sip:%s>%s%s;epid=%s\r\n"
1035 "To: <%s>%s%s%s%s\r\n"
1036 "Max-Forwards: 70\r\n"
1037 "CSeq: %d %s\r\n"
1038 "User-Agent: %s\r\n"
1039 "Call-ID: %s\r\n"
1040 "%s%s"
1041 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1042 method,
1043 dialog && dialog->request ? dialog->request : url,
1044 TRANSPORT_DESCRIPTOR,
1045 sipe_backend_network_ip_address(),
1046 sip->listenport,
1047 branch ? ";branch=" : "",
1048 branch ? branch : "",
1049 sip->username,
1050 ourtag ? ";tag=" : "",
1051 ourtag ? ourtag : "",
1052 epid,
1054 theirtag ? ";tag=" : "",
1055 theirtag ? theirtag : "",
1056 theirepid ? ";epid=" : "",
1057 theirepid ? theirepid : "",
1058 cseq,
1059 method,
1060 sipe_get_useragent(sip),
1061 callid,
1062 route,
1063 addh,
1064 body ? (gsize) strlen(body) : 0,
1065 body ? body : "");
1068 //printf ("parsing msg buf:\n%s\n\n", buf);
1069 msg = sipmsg_parse_msg(buf);
1071 g_free(buf);
1072 g_free(ourtag);
1073 g_free(theirtag);
1074 g_free(theirepid);
1075 g_free(branch);
1076 g_free(callid);
1077 g_free(route);
1078 g_free(epid);
1080 sign_outgoing_message (msg, sip, method);
1082 buf = sipmsg_to_string (msg);
1084 /* add to ongoing transactions */
1085 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1086 if (!sipe_strequal(method, "ACK")) {
1087 trans = transactions_add_buf(sip, msg, tc);
1088 } else {
1089 sipmsg_free(msg);
1091 sendout_pkt(gc, buf);
1092 g_free(buf);
1094 return trans;
1098 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1100 static void
1101 send_soap_request_with_cb(struct sipe_account_data *sip,
1102 gchar *from0,
1103 gchar *body,
1104 TransCallback callback,
1105 struct transaction_payload *payload)
1107 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1108 gchar *contact = get_contact(sip);
1109 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1110 "Content-Type: application/SOAP+xml\r\n",contact);
1112 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1113 trans->payload = payload;
1115 g_free(from);
1116 g_free(contact);
1117 g_free(hdr);
1120 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1122 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1125 static char *get_contact_register(struct sipe_account_data *sip)
1127 char *epid = get_epid(sip);
1128 char *uuid = generateUUIDfromEPID(epid);
1129 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", sipe_backend_network_ip_address(), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1130 g_free(uuid);
1131 g_free(epid);
1132 return(buf);
1135 static void do_register_exp(struct sipe_account_data *sip, int expire)
1137 char *uri;
1138 char *expires;
1139 char *to;
1140 char *contact;
1141 char *hdr;
1143 if (!sip->sipdomain) return;
1145 uri = sip_uri_from_name(sip->sipdomain);
1146 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1147 to = sip_uri_self(sip);
1148 contact = get_contact_register(sip);
1149 hdr = g_strdup_printf("Contact: %s\r\n"
1150 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1151 "Event: registration\r\n"
1152 "Allow-Events: presence\r\n"
1153 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1154 "%s", contact, expires);
1155 g_free(contact);
1156 g_free(expires);
1158 sip->registerstatus = 1;
1160 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1161 process_register_response);
1163 g_free(hdr);
1164 g_free(uri);
1165 g_free(to);
1168 static void do_register_cb(struct sipe_account_data *sip,
1169 SIPE_UNUSED_PARAMETER void *unused)
1171 do_register_exp(sip, -1);
1172 sip->reregister_set = FALSE;
1175 static void do_register(struct sipe_account_data *sip)
1177 do_register_exp(sip, -1);
1180 /**
1181 * Returns pointer to URI without sip: prefix if any
1183 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
1184 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
1186 * Doesn't allocate memory
1188 static const char *
1189 sipe_get_no_sip_uri(const char *sip_uri)
1191 const char *prefix = "sip:";
1192 if (!sip_uri) return NULL;
1194 if (g_str_has_prefix(sip_uri, prefix)) {
1195 return (sip_uri+strlen(prefix));
1196 } else {
1197 return sip_uri;
1201 static void
1202 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1204 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1205 send_soap_request(sip, body);
1206 g_free(body);
1209 static void
1210 sipe_change_access_level(struct sipe_account_data *sip,
1211 const int container_id,
1212 const gchar *type,
1213 const gchar *value);
1215 static void
1216 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1218 if (allow) {
1219 SIPE_DEBUG_INFO("Authorizing contact %s", who);
1220 } else {
1221 SIPE_DEBUG_INFO("Blocking contact %s", who);
1224 if (sip->ocs2007) {
1225 sipe_change_access_level(sip, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
1226 } else {
1227 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1231 static
1232 void sipe_auth_user_cb(void * data)
1234 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1235 if (!job) return;
1237 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1238 g_free(job);
1241 static
1242 void sipe_deny_user_cb(void * data)
1244 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1245 if (!job) return;
1247 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1248 g_free(job);
1251 static void
1252 sipe_add_permit(PurpleConnection *gc, const char *name)
1254 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1256 sipe_contact_allow_deny(sip, name, TRUE);
1259 static void
1260 sipe_add_deny(PurpleConnection *gc, const char *name)
1262 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1263 sipe_contact_allow_deny(sip, name, FALSE);
1266 /*static void
1267 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1269 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1270 sipe_contact_set_acl(sip, name, "");
1273 /** @applicable: 2005-
1275 static void
1276 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1278 sipe_xml *watchers;
1279 const sipe_xml *watcher;
1280 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1281 if (msg->response != 0 && msg->response != 200) return;
1283 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1285 watchers = sipe_xml_parse(msg->body, msg->bodylen);
1286 if (!watchers) return;
1288 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
1289 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
1290 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
1291 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1293 // TODO pull out optional displayName to pass as alias
1294 if (remote_user) {
1295 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1296 job->who = remote_user;
1297 job->sip = sip;
1298 purple_account_request_authorization(
1299 sip->account,
1300 remote_user,
1301 _("you"), /* id */
1302 alias,
1303 NULL, /* message */
1304 on_list,
1305 sipe_auth_user_cb,
1306 sipe_deny_user_cb,
1307 (void *) job);
1312 sipe_xml_free(watchers);
1313 return;
1316 static void
1317 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1319 PurpleGroup * purple_group = purple_find_group(group->name);
1320 if (!purple_group) {
1321 purple_group = purple_group_new(group->name);
1322 purple_blist_add_group(purple_group, NULL);
1325 if (purple_group) {
1326 group->purple_group = purple_group;
1327 sip->groups = g_slist_append(sip->groups, group);
1328 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
1329 } else {
1330 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
1334 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1336 struct sipe_group *group;
1337 GSList *entry;
1338 if (sip == NULL) {
1339 return NULL;
1342 entry = sip->groups;
1343 while (entry) {
1344 group = entry->data;
1345 if (group->id == id) {
1346 return group;
1348 entry = entry->next;
1350 return NULL;
1353 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1355 struct sipe_group *group;
1356 GSList *entry;
1357 if (!sip || !name) {
1358 return NULL;
1361 entry = sip->groups;
1362 while (entry) {
1363 group = entry->data;
1364 if (sipe_strequal(group->name, name)) {
1365 return group;
1367 entry = entry->next;
1369 return NULL;
1372 static void
1373 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1375 gchar *body;
1376 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
1377 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1378 send_soap_request(sip, body);
1379 g_free(body);
1380 g_free(group->name);
1381 group->name = g_strdup(name);
1385 * Only appends if no such value already stored.
1386 * Like Set in Java.
1388 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1389 GSList * res = list;
1390 if (!g_slist_find_custom(list, data, func)) {
1391 res = g_slist_insert_sorted(list, data, func);
1393 return res;
1396 static int
1397 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1398 return group1->id - group2->id;
1402 * Returns string like "2 4 7 8" - group ids buddy belong to.
1404 static gchar *
1405 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1406 int i = 0;
1407 gchar *res;
1408 //creating array from GList, converting int to gchar*
1409 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1410 GSList *entry = buddy->groups;
1412 if (!ids_arr) return NULL;
1414 while (entry) {
1415 struct sipe_group * group = entry->data;
1416 ids_arr[i] = g_strdup_printf("%d", group->id);
1417 entry = entry->next;
1418 i++;
1420 ids_arr[i] = NULL;
1421 res = g_strjoinv(" ", ids_arr);
1422 g_strfreev(ids_arr);
1423 return res;
1427 * Sends buddy update to server
1429 static void
1430 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1432 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1433 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1435 if (buddy && purple_buddy) {
1436 const char *alias = purple_buddy_get_alias(purple_buddy);
1437 gchar *groups = sipe_get_buddy_groups_string(buddy);
1438 if (groups) {
1439 gchar *body;
1440 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
1442 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1443 alias, groups, "true", buddy->name, sip->contacts_delta++
1445 send_soap_request(sip, body);
1446 g_free(groups);
1447 g_free(body);
1452 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1454 if (msg->response == 200) {
1455 struct sipe_group *group;
1456 struct group_user_context *ctx = trans->payload->data;
1457 sipe_xml *xml;
1458 const sipe_xml *node;
1459 char *group_id;
1460 struct sipe_buddy *buddy;
1462 xml = sipe_xml_parse(msg->body, msg->bodylen);
1463 if (!xml) {
1464 return FALSE;
1467 node = sipe_xml_child(xml, "Body/addGroup/groupID");
1468 if (!node) {
1469 sipe_xml_free(xml);
1470 return FALSE;
1473 group_id = sipe_xml_data(node);
1474 if (!group_id) {
1475 sipe_xml_free(xml);
1476 return FALSE;
1479 group = g_new0(struct sipe_group, 1);
1480 group->id = (int)g_ascii_strtod(group_id, NULL);
1481 g_free(group_id);
1482 group->name = g_strdup(ctx->group_name);
1484 sipe_group_add(sip, group);
1486 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1487 if (buddy) {
1488 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1491 sipe_group_set_user(sip, ctx->user_name);
1493 sipe_xml_free(xml);
1494 return TRUE;
1496 return FALSE;
1499 static void sipe_group_context_destroy(gpointer data)
1501 struct group_user_context *ctx = data;
1502 g_free(ctx->group_name);
1503 g_free(ctx->user_name);
1504 g_free(ctx);
1507 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1509 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1510 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1511 gchar *body;
1512 ctx->group_name = g_strdup(name);
1513 ctx->user_name = g_strdup(who);
1514 payload->destroy = sipe_group_context_destroy;
1515 payload->data = ctx;
1517 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1518 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1519 g_free(body);
1523 * Data structure for scheduled actions
1526 struct scheduled_action {
1528 * Name of action.
1529 * Format is <Event>[<Data>...]
1530 * Example: <presence><sip:user@domain.com> or <registration>
1532 gchar *name;
1533 guint timeout_handler;
1534 gboolean repetitive;
1535 Action action;
1536 GDestroyNotify destroy;
1537 struct sipe_account_data *sip;
1538 void *payload;
1542 * A timer callback
1543 * Should return FALSE if repetitive action is not needed
1545 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1547 gboolean ret;
1548 SIPE_DEBUG_INFO_NOFORMAT("sipe_scheduled_exec: executing");
1549 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1550 SIPE_DEBUG_INFO("sip->timeouts count:%d after removal", g_slist_length(sched_action->sip->timeouts));
1551 (sched_action->action)(sched_action->sip, sched_action->payload);
1552 ret = sched_action->repetitive;
1553 if (sched_action->destroy) {
1554 (*sched_action->destroy)(sched_action->payload);
1556 g_free(sched_action->name);
1557 g_free(sched_action);
1558 return ret;
1562 * Kills action timer effectively cancelling
1563 * scheduled action
1565 * @param name of action
1567 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1569 GSList *entry;
1571 if (!sip->timeouts || !name) return;
1573 entry = sip->timeouts;
1574 while (entry) {
1575 struct scheduled_action *sched_action = entry->data;
1576 if(sipe_strequal(sched_action->name, name)) {
1577 GSList *to_delete = entry;
1578 entry = entry->next;
1579 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1580 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
1581 purple_timeout_remove(sched_action->timeout_handler);
1582 if (sched_action->destroy) {
1583 (*sched_action->destroy)(sched_action->payload);
1585 g_free(sched_action->name);
1586 g_free(sched_action);
1587 } else {
1588 entry = entry->next;
1593 static void
1594 sipe_schedule_action0(const gchar *name,
1595 int timeout,
1596 gboolean isSeconds,
1597 Action action,
1598 GDestroyNotify destroy,
1599 struct sipe_account_data *sip,
1600 void *payload)
1602 struct scheduled_action *sched_action;
1604 /* Make sure each action only exists once */
1605 sipe_cancel_scheduled_action(sip, name);
1607 SIPE_DEBUG_INFO("scheduling action %s timeout:%d(%s)", name, timeout, isSeconds ? "sec" : "msec");
1608 sched_action = g_new0(struct scheduled_action, 1);
1609 sched_action->repetitive = FALSE;
1610 sched_action->name = g_strdup(name);
1611 sched_action->action = action;
1612 sched_action->destroy = destroy;
1613 sched_action->sip = sip;
1614 sched_action->payload = payload;
1615 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1616 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1617 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1618 SIPE_DEBUG_INFO("sip->timeouts count:%d after addition", g_slist_length(sip->timeouts));
1621 void
1622 sipe_schedule_action(const gchar *name,
1623 int timeout,
1624 Action action,
1625 GDestroyNotify destroy,
1626 struct sipe_account_data *sip,
1627 void *payload)
1629 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1633 * Same as sipe_schedule_action() but timeout is in milliseconds.
1635 static void
1636 sipe_schedule_action_msec(const gchar *name,
1637 int timeout,
1638 Action action,
1639 GDestroyNotify destroy,
1640 struct sipe_account_data *sip,
1641 void *payload)
1643 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1646 static void
1647 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1648 time_t calculate_from);
1650 static int
1651 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1653 static const char*
1654 sipe_get_status_by_availability(int avail,
1655 char** activity);
1657 static void
1658 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1659 const char *status_id,
1660 const char *message,
1661 time_t do_not_publish[]);
1663 static void
1664 sipe_apply_calendar_status(struct sipe_account_data *sip,
1665 struct sipe_buddy *sbuddy,
1666 const char *status_id)
1668 time_t cal_avail_since;
1669 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1670 int avail;
1671 gchar *self_uri;
1673 if (!sbuddy) return;
1675 if (cal_status < SIPE_CAL_NO_DATA) {
1676 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
1677 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1680 /* scheduled Cal update call */
1681 if (!status_id) {
1682 status_id = sbuddy->last_non_cal_status_id;
1683 g_free(sbuddy->activity);
1684 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1687 if (!status_id) {
1688 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
1689 sbuddy->name ? sbuddy->name : "" );
1690 return;
1693 /* adjust to calendar status */
1694 if (cal_status != SIPE_CAL_NO_DATA) {
1695 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1697 if (cal_status == SIPE_CAL_BUSY
1698 && cal_avail_since > sbuddy->user_avail_since
1699 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1701 status_id = SIPE_STATUS_ID_BUSY;
1702 g_free(sbuddy->activity);
1703 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1705 avail = sipe_get_availability_by_status(status_id, NULL);
1707 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1708 if (cal_avail_since > sbuddy->activity_since) {
1709 if (cal_status == SIPE_CAL_OOF
1710 && avail >= 15000) /* 12000 in 2007 */
1712 g_free(sbuddy->activity);
1713 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1718 /* then set status_id actually */
1719 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
1720 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1722 /* set our account state to the one in roaming (including calendar info) */
1723 self_uri = sip_uri_self(sip);
1724 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1725 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1726 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1729 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
1730 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1732 g_free(self_uri);
1735 static void
1736 sipe_got_user_status(struct sipe_account_data *sip,
1737 const char* uri,
1738 const char *status_id)
1740 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1742 if (!sbuddy) return;
1744 /* Check if on 2005 system contact's calendar,
1745 * then set/preserve it.
1747 if (!sip->ocs2007) {
1748 sipe_apply_calendar_status(sip, sbuddy, status_id);
1749 } else {
1750 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1754 static void
1755 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1756 struct sipe_buddy *sbuddy,
1757 struct sipe_account_data *sip)
1759 sipe_apply_calendar_status(sip, sbuddy, NULL);
1763 * Updates contact's status
1764 * based on their calendar information.
1766 * Applicability: 2005 systems
1768 static void
1769 update_calendar_status(struct sipe_account_data *sip)
1771 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
1772 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1774 /* repeat scheduling */
1775 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1779 * Schedules process of contacts' status update
1780 * based on their calendar information.
1781 * Should be scheduled to the beginning of every
1782 * 15 min interval, like:
1783 * 13:00, 13:15, 13:30, 13:45, etc.
1785 * Applicability: 2005 systems
1787 static void
1788 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1789 time_t calculate_from)
1791 int interval = 15*60;
1792 /** start of the beginning of closest 15 min interval. */
1793 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1795 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
1796 asctime(localtime(&calculate_from)));
1797 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
1798 asctime(localtime(&next_start)));
1800 sipe_schedule_action("<+2005-cal-status>",
1801 (int)(next_start - time(NULL)),
1802 (Action)update_calendar_status,
1803 NULL,
1804 sip,
1805 NULL);
1809 * Schedules process of self status publish
1810 * based on own calendar information.
1811 * Should be scheduled to the beginning of every
1812 * 15 min interval, like:
1813 * 13:00, 13:15, 13:30, 13:45, etc.
1815 * Applicability: 2007+ systems
1817 static void
1818 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1819 time_t calculate_from)
1821 int interval = 5*60;
1822 /** start of the beginning of closest 5 min interval. */
1823 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1825 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1826 asctime(localtime(&calculate_from)));
1827 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1828 asctime(localtime(&next_start)));
1830 sipe_schedule_action("<+2007-cal-status>",
1831 (int)(next_start - time(NULL)),
1832 (Action)publish_calendar_status_self,
1833 NULL,
1834 sip,
1835 NULL);
1838 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1840 /** Should be g_free()'d
1842 static gchar *
1843 sipe_get_subscription_key(const gchar *event,
1844 const gchar *with)
1846 gchar *key = NULL;
1848 if (is_empty(event)) return NULL;
1850 if (event && sipe_strcase_equal(event, "presence")) {
1851 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1852 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1854 /* @TODO drop participated buddies' just_added flag */
1855 } else if (event) {
1856 /* Subscription is identified by <event> key */
1857 key = g_strdup_printf("<%s>", event);
1860 return key;
1863 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1864 SIPE_UNUSED_PARAMETER struct transaction *trans)
1866 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1867 const gchar *event = sipmsg_find_header(msg, "Event");
1868 gchar *key;
1870 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1871 if (!event) {
1872 struct sipmsg *request_msg = trans->msg;
1873 event = sipmsg_find_header(request_msg, "Event");
1876 key = sipe_get_subscription_key(event, with);
1878 /* 200 OK; 481 Call Leg Does Not Exist */
1879 if (key && (msg->response == 200 || msg->response == 481)) {
1880 if (g_hash_table_lookup(sip->subscriptions, key)) {
1881 g_hash_table_remove(sip->subscriptions, key);
1882 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
1886 /* create/store subscription dialog if not yet */
1887 if (msg->response == 200) {
1888 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1889 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1891 if (key) {
1892 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1893 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1895 subscription->dialog.callid = g_strdup(callid);
1896 subscription->dialog.cseq = atoi(cseq);
1897 subscription->dialog.with = g_strdup(with);
1898 subscription->event = g_strdup(event);
1899 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1901 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key);
1904 g_free(cseq);
1907 g_free(key);
1908 g_free(with);
1910 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1912 process_incoming_notify(sip, msg, FALSE, FALSE);
1914 return TRUE;
1917 static void sipe_subscribe_resource_uri(const char *name,
1918 SIPE_UNUSED_PARAMETER gpointer value,
1919 gchar **resources_uri)
1921 gchar *tmp = *resources_uri;
1922 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1923 g_free(tmp);
1926 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1928 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1929 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1930 gchar *tmp = *resources_uri;
1932 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1934 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1935 g_free(tmp);
1939 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1940 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1941 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1942 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1943 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1946 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1948 gchar *key;
1949 gchar *contact = get_contact(sip);
1950 gchar *request;
1951 gchar *content;
1952 gchar *require = "";
1953 gchar *accept = "";
1954 gchar *autoextend = "";
1955 gchar *content_type;
1956 struct sip_dialog *dialog;
1958 if (sip->ocs2007) {
1959 require = ", categoryList";
1960 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1961 content_type = "application/msrtc-adrl-categorylist+xml";
1962 content = g_strdup_printf(
1963 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1964 "<action name=\"subscribe\" id=\"63792024\">\n"
1965 "<adhocList>\n%s</adhocList>\n"
1966 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1967 "<category name=\"calendarData\"/>\n"
1968 "<category name=\"contactCard\"/>\n"
1969 "<category name=\"note\"/>\n"
1970 "<category name=\"state\"/>\n"
1971 "</categoryList>\n"
1972 "</action>\n"
1973 "</batchSub>", sip->username, resources_uri);
1974 } else {
1975 autoextend = "Supported: com.microsoft.autoextend\r\n";
1976 content_type = "application/adrl+xml";
1977 content = g_strdup_printf(
1978 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1979 "<create xmlns=\"\">\n%s</create>\n"
1980 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1982 g_free(resources_uri);
1984 request = g_strdup_printf(
1985 "Require: adhoclist%s\r\n"
1986 "Supported: eventlist\r\n"
1987 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1988 "Supported: ms-piggyback-first-notify\r\n"
1989 "%sSupported: ms-benotify\r\n"
1990 "Proxy-Require: ms-benotify\r\n"
1991 "Event: presence\r\n"
1992 "Content-Type: %s\r\n"
1993 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1994 g_free(contact);
1996 /* subscribe to buddy presence */
1997 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1998 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1999 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2000 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2002 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2004 g_free(content);
2005 g_free(to);
2006 g_free(request);
2007 g_free(key);
2010 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
2011 SIPE_UNUSED_PARAMETER void *unused)
2013 gchar *to = sip_uri_self(sip);
2014 gchar *resources_uri = g_strdup("");
2015 if (sip->ocs2007) {
2016 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
2017 } else {
2018 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
2021 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
2024 struct presence_batched_routed {
2025 gchar *host;
2026 GSList *buddies;
2029 static void sipe_subscribe_presence_batched_routed_free(void *payload)
2031 struct presence_batched_routed *data = payload;
2032 GSList *buddies = data->buddies;
2033 while (buddies) {
2034 g_free(buddies->data);
2035 buddies = buddies->next;
2037 g_slist_free(data->buddies);
2038 g_free(data->host);
2039 g_free(payload);
2042 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
2044 struct presence_batched_routed *data = payload;
2045 GSList *buddies = data->buddies;
2046 gchar *resources_uri = g_strdup("");
2047 while (buddies) {
2048 gchar *tmp = resources_uri;
2049 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2050 g_free(tmp);
2051 buddies = buddies->next;
2053 sipe_subscribe_presence_batched_to(sip, resources_uri,
2054 g_strdup(data->host));
2058 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2059 * The user sends a single SUBSCRIBE request to the subscribed contact.
2060 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2064 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2067 gchar *key;
2068 gchar *to = sip_uri((char *)buddy_name);
2069 gchar *tmp = get_contact(sip);
2070 gchar *request;
2071 gchar *content = NULL;
2072 gchar *autoextend = "";
2073 gchar *content_type = "";
2074 struct sip_dialog *dialog;
2075 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2076 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2078 if (sbuddy) sbuddy->just_added = FALSE;
2080 if (sip->ocs2007) {
2081 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2082 } else {
2083 autoextend = "Supported: com.microsoft.autoextend\r\n";
2086 request = g_strdup_printf(
2087 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2088 "Supported: ms-piggyback-first-notify\r\n"
2089 "%s%sSupported: ms-benotify\r\n"
2090 "Proxy-Require: ms-benotify\r\n"
2091 "Event: presence\r\n"
2092 "Contact: %s\r\n", autoextend, content_type, tmp);
2094 if (sip->ocs2007) {
2095 content = g_strdup_printf(
2096 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2097 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2098 "<resource uri=\"%s\"%s\n"
2099 "</adhocList>\n"
2100 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2101 "<category name=\"calendarData\"/>\n"
2102 "<category name=\"contactCard\"/>\n"
2103 "<category name=\"note\"/>\n"
2104 "<category name=\"state\"/>\n"
2105 "</categoryList>\n"
2106 "</action>\n"
2107 "</batchSub>", sip->username, to, context);
2110 g_free(tmp);
2112 /* subscribe to buddy presence */
2113 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2114 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2115 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2116 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2118 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2120 g_free(content);
2121 g_free(to);
2122 g_free(request);
2123 g_free(key);
2126 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2128 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
2130 if (!purple_status_is_active(status))
2131 return;
2133 if (account->gc) {
2134 struct sipe_account_data *sip = account->gc->proto_data;
2136 if (sip) {
2137 gchar *action_name;
2138 gchar *tmp;
2139 time_t now = time(NULL);
2140 const char *status_id = purple_status_get_id(status);
2141 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2142 sipe_activity activity = sipe_get_activity_by_token(status_id);
2143 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2145 /* when other point of presence clears note, but we are keeping
2146 * state if OOF note.
2148 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2149 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
2150 do_not_publish = FALSE;
2153 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
2154 status_id, (int)sip->do_not_publish[activity], (int)now);
2156 sip->do_not_publish[activity] = 0;
2157 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
2158 status_id, (int)sip->do_not_publish[activity]);
2160 if (do_not_publish)
2162 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
2163 return;
2166 g_free(sip->status);
2167 sip->status = g_strdup(status_id);
2169 /* hack to escape apostrof before comparison */
2170 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
2172 /* this will preserve OOF flag as well */
2173 if (!sipe_strequal(tmp, sip->note)) {
2174 sip->is_oof_note = FALSE;
2175 g_free(sip->note);
2176 sip->note = g_strdup(note);
2177 sip->note_since = time(NULL);
2179 g_free(tmp);
2181 /* schedule 2 sec to capture idle flag */
2182 action_name = g_strdup_printf("<%s>", "+set-status");
2183 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2184 g_free(action_name);
2188 static void
2189 sipe_set_idle(PurpleConnection * gc,
2190 int interval)
2192 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
2194 if (gc) {
2195 struct sipe_account_data *sip = gc->proto_data;
2197 if (sip) {
2198 sip->idle_switch = time(NULL);
2199 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2204 static void
2205 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2206 SIPE_UNUSED_PARAMETER const char *alias)
2208 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2209 sipe_group_set_user(sip, name);
2212 static void
2213 sipe_group_buddy(PurpleConnection *gc,
2214 const char *who,
2215 const char *old_group_name,
2216 const char *new_group_name)
2218 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2219 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2220 struct sipe_group * old_group = NULL;
2221 struct sipe_group * new_group;
2223 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
2224 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2226 if(!buddy) { // buddy not in roaming list
2227 return;
2230 if (old_group_name) {
2231 old_group = sipe_group_find_by_name(sip, old_group_name);
2233 new_group = sipe_group_find_by_name(sip, new_group_name);
2235 if (old_group) {
2236 buddy->groups = g_slist_remove(buddy->groups, old_group);
2237 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
2240 if (!new_group) {
2241 sipe_group_create(sip, new_group_name, who);
2242 } else {
2243 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2244 sipe_group_set_user(sip, who);
2248 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2250 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2252 /* libpurple can call us with undefined buddy or group */
2253 if (buddy && group) {
2254 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2256 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2257 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2258 purple_blist_rename_buddy(buddy, buddy_name);
2259 g_free(buddy_name);
2261 /* Prepend sip: if needed */
2262 if (!g_str_has_prefix(buddy->name, "sip:")) {
2263 gchar *buf = sip_uri_from_name(buddy->name);
2264 purple_blist_rename_buddy(buddy, buf);
2265 g_free(buf);
2268 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2269 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2270 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
2271 b->name = g_strdup(buddy->name);
2272 b->just_added = TRUE;
2273 g_hash_table_insert(sip->buddies, b->name, b);
2274 sipe_group_buddy(gc, b->name, NULL, group->name);
2275 /* @TODO should go to callback */
2276 sipe_subscribe_presence_single(sip, b->name);
2277 } else {
2278 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
2283 static void sipe_free_buddy(struct sipe_buddy *buddy)
2285 #ifndef _WIN32
2287 * We are calling g_hash_table_foreach_steal(). That means that no
2288 * key/value deallocation functions are called. Therefore the glib
2289 * hash code does not touch the key (buddy->name) or value (buddy)
2290 * of the to-be-deleted hash node at all. It follows that we
2292 * - MUST free the memory for the key ourselves and
2293 * - ARE allowed to do it in this function
2295 * Conclusion: glib must be broken on the Windows platform if sipe
2296 * crashes with SIGTRAP when closing. You'll have to live
2297 * with the memory leak until this is fixed.
2299 g_free(buddy->name);
2300 #endif
2301 g_free(buddy->activity);
2302 g_free(buddy->meeting_subject);
2303 g_free(buddy->meeting_location);
2304 g_free(buddy->note);
2306 g_free(buddy->cal_start_time);
2307 g_free(buddy->cal_free_busy_base64);
2308 g_free(buddy->cal_free_busy);
2309 g_free(buddy->last_non_cal_activity);
2311 sipe_cal_free_working_hours(buddy->cal_working_hours);
2313 g_free(buddy->device_name);
2314 g_slist_free(buddy->groups);
2315 g_free(buddy);
2319 * Unassociates buddy from group first.
2320 * Then see if no groups left, removes buddy completely.
2321 * Otherwise updates buddy groups on server.
2323 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2325 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2326 struct sipe_buddy *b;
2327 struct sipe_group *g = NULL;
2329 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2330 if (!buddy) return;
2332 b = g_hash_table_lookup(sip->buddies, buddy->name);
2333 if (!b) return;
2335 if (group) {
2336 g = sipe_group_find_by_name(sip, group->name);
2339 if (g) {
2340 b->groups = g_slist_remove(b->groups, g);
2341 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
2344 if (g_slist_length(b->groups) < 1) {
2345 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2346 sipe_cancel_scheduled_action(sip, action_name);
2347 g_free(action_name);
2349 g_hash_table_remove(sip->buddies, buddy->name);
2351 if (b->name) {
2352 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2353 send_soap_request(sip, body);
2354 g_free(body);
2357 sipe_free_buddy(b);
2358 } else {
2359 //updates groups on server
2360 sipe_group_set_user(sip, b->name);
2365 static void
2366 sipe_rename_group(PurpleConnection *gc,
2367 const char *old_name,
2368 PurpleGroup *group,
2369 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2371 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2372 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2373 if (s_group) {
2374 sipe_group_rename(sip, s_group, group->name);
2375 } else {
2376 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
2380 static void
2381 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2383 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2384 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2385 if (s_group) {
2386 gchar *body;
2387 SIPE_DEBUG_INFO("Deleting group %s", group->name);
2388 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2389 send_soap_request(sip, body);
2390 g_free(body);
2392 sip->groups = g_slist_remove(sip->groups, s_group);
2393 g_free(s_group->name);
2394 g_free(s_group);
2395 } else {
2396 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
2400 /** All statuses need message attribute to pass Note */
2401 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2403 PurpleStatusType *type;
2404 GList *types = NULL;
2406 /* Macros to reduce code repetition.
2407 Translators: noun */
2408 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2409 prim, id, name, \
2410 TRUE, user, FALSE, \
2411 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2412 NULL); \
2413 types = g_list_append(types, type);
2415 /* Online */
2416 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2417 NULL,
2418 NULL,
2419 TRUE);
2421 /* Busy */
2422 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2423 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2424 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2425 TRUE);
2427 /* Do Not Disturb */
2428 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2429 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2430 NULL,
2431 TRUE);
2433 /* Away */
2434 /* Goes first in the list as
2435 * purple picks the first status with the AWAY type
2436 * for idle.
2438 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2439 NULL,
2440 NULL,
2441 TRUE);
2443 /* Be Right Back */
2444 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2445 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2446 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2447 TRUE);
2449 /* Appear Offline */
2450 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2451 NULL,
2452 NULL,
2453 TRUE);
2455 /* Offline */
2456 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2457 NULL,
2458 NULL,
2459 TRUE);
2460 types = g_list_append(types, type);
2462 return types;
2466 * A callback for g_hash_table_foreach
2468 static void
2469 sipe_buddy_subscribe_cb(char *buddy_name,
2470 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2471 struct sipe_account_data *sip)
2473 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2474 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2475 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2476 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2478 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2479 g_free(action_name);
2483 * Removes entries from purple buddy list
2484 * that does not correspond ones in the roaming contact list.
2486 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2487 GSList *buddies = purple_find_buddies(sip->account, NULL);
2488 GSList *entry = buddies;
2489 struct sipe_buddy *buddy;
2490 PurpleBuddy *b;
2491 PurpleGroup *g;
2493 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
2494 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sip->buddies));
2495 while (entry) {
2496 b = entry->data;
2497 g = purple_buddy_get_group(b);
2498 buddy = g_hash_table_lookup(sip->buddies, b->name);
2499 if(buddy) {
2500 gboolean in_sipe_groups = FALSE;
2501 GSList *entry2 = buddy->groups;
2502 while (entry2) {
2503 struct sipe_group *group = entry2->data;
2504 if (sipe_strequal(group->name, g->name)) {
2505 in_sipe_groups = TRUE;
2506 break;
2508 entry2 = entry2->next;
2510 if(!in_sipe_groups) {
2511 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
2512 purple_blist_remove_buddy(b);
2514 } else {
2515 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
2516 purple_blist_remove_buddy(b);
2518 entry = entry->next;
2520 g_slist_free(buddies);
2523 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2525 int len = msg->bodylen;
2527 const gchar *tmp = sipmsg_find_header(msg, "Event");
2528 const sipe_xml *item;
2529 sipe_xml *isc;
2530 const gchar *contacts_delta;
2531 const sipe_xml *group_node;
2532 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2533 return FALSE;
2536 /* Convert the contact from XML to Purple Buddies */
2537 isc = sipe_xml_parse(msg->body, len);
2538 if (!isc) {
2539 return FALSE;
2542 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
2543 if (contacts_delta) {
2544 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2547 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
2549 /* Parse groups */
2550 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
2551 struct sipe_group * group = g_new0(struct sipe_group, 1);
2552 const char *name = sipe_xml_attribute(group_node, "name");
2554 if (g_str_has_prefix(name, "~")) {
2555 name = _("Other Contacts");
2557 group->name = g_strdup(name);
2558 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
2560 sipe_group_add(sip, group);
2563 // Make sure we have at least one group
2564 if (g_slist_length(sip->groups) == 0) {
2565 struct sipe_group * group = g_new0(struct sipe_group, 1);
2566 PurpleGroup *purple_group;
2567 group->name = g_strdup(_("Other Contacts"));
2568 group->id = 1;
2569 purple_group = purple_group_new(group->name);
2570 purple_blist_add_group(purple_group, NULL);
2571 sip->groups = g_slist_append(sip->groups, group);
2574 /* Parse contacts */
2575 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
2576 const gchar *uri = sipe_xml_attribute(item, "uri");
2577 const gchar *name = sipe_xml_attribute(item, "name");
2578 gchar *buddy_name;
2579 struct sipe_buddy *buddy = NULL;
2580 gchar *tmp;
2581 gchar **item_groups;
2582 int i = 0;
2584 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2585 tmp = sip_uri_from_name(uri);
2586 buddy_name = g_ascii_strdown(tmp, -1);
2587 g_free(tmp);
2589 /* assign to group Other Contacts if nothing else received */
2590 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
2591 if(is_empty(tmp)) {
2592 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2593 g_free(tmp);
2594 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2596 item_groups = g_strsplit(tmp, " ", 0);
2597 g_free(tmp);
2599 while (item_groups[i]) {
2600 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2602 // If couldn't find the right group for this contact, just put them in the first group we have
2603 if (group == NULL && g_slist_length(sip->groups) > 0) {
2604 group = sip->groups->data;
2607 if (group != NULL) {
2608 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2609 if (!b){
2610 b = purple_buddy_new(sip->account, buddy_name, uri);
2611 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2613 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
2616 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2617 if (name != NULL && strlen(name) != 0) {
2618 purple_blist_alias_buddy(b, name);
2620 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
2624 if (!buddy) {
2625 buddy = g_new0(struct sipe_buddy, 1);
2626 buddy->name = g_strdup(b->name);
2627 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2630 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2632 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
2633 } else {
2634 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2635 name);
2638 i++;
2639 } // while, contact groups
2640 g_strfreev(item_groups);
2641 g_free(buddy_name);
2643 } // for, contacts
2645 sipe_cleanup_local_blist(sip);
2647 /* Add self-contact if not there yet. 2005 systems. */
2648 /* This will resemble subscription to roaming_self in 2007 systems */
2649 if (!sip->ocs2007) {
2650 gchar *self_uri = sip_uri_self(sip);
2651 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2653 if (!buddy) {
2654 buddy = g_new0(struct sipe_buddy, 1);
2655 buddy->name = g_strdup(self_uri);
2656 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2658 g_free(self_uri);
2661 sipe_xml_free(isc);
2663 /* subscribe to buddies */
2664 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2665 if (sip->batched_support) {
2666 sipe_subscribe_presence_batched(sip, NULL);
2667 } else {
2668 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2670 sip->subscribed_buddies = TRUE;
2672 /* for 2005 systems schedule contacts' status update
2673 * based on their calendar information
2675 if (!sip->ocs2007) {
2676 sipe_sched_calendar_status_update(sip, time(NULL));
2679 return 0;
2683 * Subscribe roaming contacts
2685 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2687 gchar *to = sip_uri_self(sip);
2688 gchar *tmp = get_contact(sip);
2689 gchar *hdr = g_strdup_printf(
2690 "Event: vnd-microsoft-roaming-contacts\r\n"
2691 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2692 "Supported: com.microsoft.autoextend\r\n"
2693 "Supported: ms-benotify\r\n"
2694 "Proxy-Require: ms-benotify\r\n"
2695 "Supported: ms-piggyback-first-notify\r\n"
2696 "Contact: %s\r\n", tmp);
2697 g_free(tmp);
2699 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2700 g_free(to);
2701 g_free(hdr);
2704 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2705 SIPE_UNUSED_PARAMETER void *unused)
2707 gchar *key;
2708 struct sip_dialog *dialog;
2709 gchar *to = sip_uri_self(sip);
2710 gchar *tmp = get_contact(sip);
2711 gchar *hdr = g_strdup_printf(
2712 "Event: presence.wpending\r\n"
2713 "Accept: text/xml+msrtc.wpending\r\n"
2714 "Supported: com.microsoft.autoextend\r\n"
2715 "Supported: ms-benotify\r\n"
2716 "Proxy-Require: ms-benotify\r\n"
2717 "Supported: ms-piggyback-first-notify\r\n"
2718 "Contact: %s\r\n", tmp);
2719 g_free(tmp);
2721 /* Subscription is identified by <event> key */
2722 key = g_strdup_printf("<%s>", "presence.wpending");
2723 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2724 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2726 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2728 g_free(to);
2729 g_free(hdr);
2730 g_free(key);
2734 * Fires on deregistration event initiated by server.
2735 * [MS-SIPREGE] SIP extension.
2738 // 2007 Example
2740 // Content-Type: text/registration-event
2741 // subscription-state: terminated;expires=0
2742 // ms-diagnostics-public: 4141;reason="User disabled"
2744 // deregistered;event=rejected
2746 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2748 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2749 gchar *event = NULL;
2750 gchar *reason = NULL;
2751 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2752 gchar *warning;
2754 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2755 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2757 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2758 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2759 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2760 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2761 } else {
2762 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2763 return;
2766 if (diagnostics != NULL) {
2767 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2768 } else { // for LCS2005
2769 int error_id = 0;
2770 if (event && sipe_strcase_equal(event, "unregistered")) {
2771 error_id = 4140; // [MS-SIPREGE]
2772 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2773 reason = g_strdup(_("you are already signed in at another location"));
2774 } else if (event && sipe_strcase_equal(event, "rejected")) {
2775 error_id = 4141;
2776 reason = g_strdup(_("user disabled")); // [MS-OCER]
2777 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2778 error_id = 4142;
2779 reason = g_strdup(_("user moved")); // [MS-OCER]
2782 g_free(event);
2783 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2784 g_free(reason);
2786 sip->gc->wants_to_die = TRUE;
2787 purple_connection_error(sip->gc, warning);
2788 g_free(warning);
2792 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2794 sipe_xml *xn_provision_group_list;
2795 const sipe_xml *node;
2797 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
2799 /* provisionGroup */
2800 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
2801 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
2802 g_free(sip->focus_factory_uri);
2803 sip->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
2804 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2805 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2806 break;
2809 sipe_xml_free(xn_provision_group_list);
2812 /** for 2005 system */
2813 static void
2814 sipe_process_provisioning(struct sipe_account_data *sip,
2815 struct sipmsg *msg)
2817 sipe_xml *xn_provision;
2818 const sipe_xml *node;
2820 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
2821 if ((node = sipe_xml_child(xn_provision, "user"))) {
2822 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
2823 if ((node = sipe_xml_child(node, "line"))) {
2824 const gchar *line_uri = sipe_xml_attribute(node, "uri");
2825 const gchar *server = sipe_xml_attribute(node, "server");
2826 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2827 sip_csta_open(sip, line_uri, server);
2830 sipe_xml_free(xn_provision);
2833 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2835 const gchar *contacts_delta;
2836 sipe_xml *xml;
2838 xml = sipe_xml_parse(msg->body, msg->bodylen);
2839 if (!xml)
2841 return;
2844 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
2845 if (contacts_delta)
2847 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2850 sipe_xml_free(xml);
2853 static void
2854 free_container_member(struct sipe_container_member *member)
2856 if (!member) return;
2858 g_free(member->type);
2859 g_free(member->value);
2860 g_free(member);
2863 static void
2864 free_container(struct sipe_container *container)
2866 GSList *entry;
2868 if (!container) return;
2870 entry = container->members;
2871 while (entry) {
2872 void *data = entry->data;
2873 entry = g_slist_remove(entry, data);
2874 free_container_member((struct sipe_container_member *)data);
2876 g_free(container);
2879 static void
2880 sipe_send_container_members_prepare(struct sipe_account_data *sip,
2881 const guint container_id,
2882 const guint container_version,
2883 const gchar *action,
2884 const gchar *type,
2885 const gchar *value,
2886 char **container_xmls)
2888 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2889 gchar *body;
2891 if (!container_xmls) return;
2893 body = g_strdup_printf(
2894 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
2895 container_id,
2896 container_version,
2897 action,
2898 type,
2899 value_str);
2900 g_free(value_str);
2902 if ((*container_xmls) == NULL) {
2903 *container_xmls = body;
2904 } else {
2905 char *tmp = *container_xmls;
2907 *container_xmls = g_strconcat(*container_xmls, body, NULL);
2908 g_free(tmp);
2909 g_free(body);
2913 static void
2914 sipe_send_set_container_members(struct sipe_account_data *sip,
2915 char *container_xmls)
2917 gchar *self;
2918 gchar *contact;
2919 gchar *hdr;
2920 gchar *body;
2922 if (!container_xmls) return;
2924 self = sip_uri_self(sip);
2925 body = g_strdup_printf(
2926 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2927 "%s"
2928 "</setContainerMembers>",
2929 container_xmls);
2931 contact = get_contact(sip);
2932 hdr = g_strdup_printf("Contact: %s\r\n"
2933 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2934 g_free(contact);
2936 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2938 g_free(hdr);
2939 g_free(body);
2940 g_free(self);
2944 * Finds locally stored MS-PRES container member
2946 static struct sipe_container_member *
2947 sipe_find_container_member(struct sipe_container *container,
2948 const gchar *type,
2949 const gchar *value)
2951 struct sipe_container_member *member;
2952 GSList *entry;
2954 if (container == NULL || type == NULL) {
2955 return NULL;
2958 entry = container->members;
2959 while (entry) {
2960 member = entry->data;
2961 if (sipe_strcase_equal(member->type, type) &&
2962 sipe_strcase_equal(member->value, value))
2964 return member;
2966 entry = entry->next;
2968 return NULL;
2972 * Finds locally stored MS-PRES container by id
2974 static struct sipe_container *
2975 sipe_find_container(struct sipe_account_data *sip,
2976 guint id)
2978 struct sipe_container *container;
2979 GSList *entry;
2981 if (sip == NULL) {
2982 return NULL;
2985 entry = sip->containers;
2986 while (entry) {
2987 container = entry->data;
2988 if (id == container->id) {
2989 return container;
2991 entry = entry->next;
2993 return NULL;
2996 /**
2997 * Returns pointer to domain part in provided Email URL
2999 * @param email an email URL. Example: first.last@hq.company.com
3000 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
3002 * Doesn't allocate memory
3004 static const char *
3005 sipe_get_domain(const char *email)
3007 char *tmp;
3009 if (!email) return NULL;
3011 tmp = strstr(email, "@");
3013 if (tmp && ((tmp+1) < (email + strlen(email)))) {
3014 return tmp+1;
3015 } else {
3016 return NULL;
3021 /* @TODO: replace with binary search for faster access? */
3022 /** source: http://support.microsoft.com/kb/897567 */
3023 static const char * const public_domains [] = {
3024 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
3025 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
3026 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
3027 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
3028 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
3029 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
3030 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
3031 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
3032 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
3033 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
3034 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
3035 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
3036 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
3037 "yahoo.com",
3038 NULL};
3040 static gboolean
3041 sipe_is_public_domain(const char *domain)
3043 int i = 0;
3044 while (public_domains[i]) {
3045 if (sipe_strcase_equal(public_domains[i], domain)) {
3046 return TRUE;
3048 i++;
3050 return FALSE;
3054 * Access Levels
3055 * 32000 - Blocked
3056 * 400 - Personal
3057 * 300 - Team
3058 * 200 - Company
3059 * 100 - Public
3061 static const char *
3062 sipe_get_access_level_name(int container_id)
3064 switch(container_id) {
3065 case 32000: return _("Blocked");
3066 case 400: return _("Personal");
3067 case 300: return _("Team");
3068 case 200: return _("Company");
3069 case 100: return _("Public");
3071 return _("Unknown");
3074 static const guint containers[] = {32000, 400, 300, 200, 100};
3075 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
3078 static int
3079 sipe_find_member_access_level(struct sipe_account_data *sip,
3080 const gchar *type,
3081 const gchar *value)
3083 unsigned int i = 0;
3084 const gchar *value_mod = value;
3086 if (!type) return -1;
3088 if (sipe_strequal("user", type)) {
3089 value_mod = sipe_get_no_sip_uri(value);
3092 for (i = 0; i < CONTAINERS_LEN; i++) {
3093 struct sipe_container_member *member;
3094 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3095 if (!container) continue;
3097 member = sipe_find_container_member(container, type, value_mod);
3098 if (member) return containers[i];
3101 return -1;
3104 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
3105 static int
3106 sipe_find_access_level(struct sipe_account_data *sip,
3107 const gchar *type,
3108 const gchar *value)
3110 int container_id = -1;
3112 if (sipe_strequal("user", type)) {
3113 const char *domain;
3114 const char *no_sip_uri = sipe_get_no_sip_uri(value);
3116 container_id = sipe_find_member_access_level(sip, "user", no_sip_uri);
3117 if (container_id >= 0) return container_id;
3119 domain = sipe_get_domain(no_sip_uri);
3120 container_id = sipe_find_member_access_level(sip, "domain", domain);
3121 if (container_id >= 0) return container_id;
3123 container_id = sipe_find_member_access_level(sip, "sameEnterprise", NULL);
3124 if ((container_id >= 0) && sipe_strcase_equal(sip->sipdomain, domain)) {
3125 return container_id;
3128 container_id = sipe_find_member_access_level(sip, "publicCloud", NULL);
3129 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
3130 return container_id;
3133 container_id = sipe_find_member_access_level(sip, "everyone", NULL);
3134 if ((container_id >= 0)) {
3135 return container_id;
3137 } else {
3138 container_id = sipe_find_member_access_level(sip, type, value);
3141 return container_id;
3145 * @param container_id a new access level. If -1 then current access level
3146 * is just removed (I.e. the member is removed from all containers).
3147 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
3148 * @param value a value for member. E.g. SIP URI for "user" member type.
3150 static void
3151 sipe_change_access_level(struct sipe_account_data *sip,
3152 const int container_id,
3153 const gchar *type,
3154 const gchar *value)
3156 unsigned int i;
3157 int current_container_id = -1;
3158 char *container_xmls = NULL;
3160 /* for each container: find/delete */
3161 for (i = 0; i < CONTAINERS_LEN; i++) {
3162 struct sipe_container_member *member;
3163 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3165 if (!container) continue;
3167 member = sipe_find_container_member(container, type, value);
3168 if (member) {
3169 current_container_id = containers[i];
3170 /* delete/publish current access level */
3171 if (container_id < 0 || container_id != current_container_id) {
3172 sipe_send_container_members_prepare(
3173 sip, current_container_id, container->version, "remove", type, value, &container_xmls);
3174 /* remove member from our cache, to be able to recalculate AL below */
3175 container->members = g_slist_remove(container->members, member);
3176 current_container_id = -1;
3181 /* recalculate AL below */
3182 current_container_id = sipe_find_access_level(sip, type, value);
3184 /* assign/publish new access level */
3185 if (container_id != current_container_id && container_id >= 0) {
3186 struct sipe_container *container = sipe_find_container(sip, container_id);
3187 guint version = container ? container->version : 0;
3189 sipe_send_container_members_prepare(sip, container_id, version, "add", type, value, &container_xmls);
3192 if (container_xmls) {
3193 sipe_send_set_container_members(sip, container_xmls);
3195 g_free(container_xmls);
3198 static void
3199 free_publication(struct sipe_publication *publication)
3201 g_free(publication->category);
3202 g_free(publication->cal_event_hash);
3203 g_free(publication->note);
3205 g_free(publication->working_hours_xml_str);
3206 g_free(publication->fb_start_str);
3207 g_free(publication->free_busy_base64);
3209 g_free(publication);
3212 /* key is <category><instance><container> */
3213 static gboolean
3214 sipe_is_our_publication(struct sipe_account_data *sip,
3215 const gchar *key)
3217 GSList *entry;
3219 /* filling keys for our publications if not yet cached */
3220 if (!sip->our_publication_keys) {
3221 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
3222 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
3223 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
3224 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
3225 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
3226 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
3227 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
3229 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
3230 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
3231 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
3232 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
3233 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
3234 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
3235 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
3236 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
3237 SIPE_DEBUG_INFO("\tNote : %u", 0);
3238 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
3240 /* device */
3241 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3242 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
3244 /* state:machineState */
3245 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3246 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
3247 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3248 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3250 /* state:userState */
3251 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3252 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3253 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3254 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3256 /* state:calendarState */
3257 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3258 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3259 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3260 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3262 /* state:calendarState OOF */
3263 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3264 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3265 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3266 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3268 /* note */
3269 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3270 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3271 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3272 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3273 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3274 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3276 /* note OOF */
3277 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3278 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3279 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3280 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3281 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3282 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3284 /* calendarData:WorkingHours */
3285 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3286 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3287 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3288 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3289 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3290 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3291 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3292 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3293 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3294 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3295 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3296 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3298 /* calendarData:FreeBusy */
3299 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3300 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3301 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3302 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3303 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3304 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3305 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3306 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3307 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3308 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3309 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3310 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3312 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3313 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3316 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3318 entry = sip->our_publication_keys;
3319 while (entry) {
3320 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3321 if (sipe_strequal(entry->data, key)) {
3322 return TRUE;
3324 entry = entry->next;
3326 return FALSE;
3329 /** Property names to store in blist.xml */
3330 #define ALIAS_PROP "alias"
3331 #define EMAIL_PROP "email"
3332 #define PHONE_PROP "phone"
3333 #define PHONE_DISPLAY_PROP "phone-display"
3334 #define PHONE_MOBILE_PROP "phone-mobile"
3335 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3336 #define PHONE_HOME_PROP "phone-home"
3337 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3338 #define PHONE_OTHER_PROP "phone-other"
3339 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3340 #define PHONE_CUSTOM1_PROP "phone-custom1"
3341 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3342 #define SITE_PROP "site"
3343 #define COMPANY_PROP "company"
3344 #define DEPARTMENT_PROP "department"
3345 #define TITLE_PROP "title"
3346 #define OFFICE_PROP "office"
3347 /** implies work address */
3348 #define ADDRESS_STREET_PROP "address-street"
3349 #define ADDRESS_CITY_PROP "address-city"
3350 #define ADDRESS_STATE_PROP "address-state"
3351 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3352 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3355 * Tries to figure out user first and last name
3356 * based on Display Name and email properties.
3358 * Allocates memory - must be g_free()'d
3360 * Examples to parse:
3361 * First Last
3362 * First Last - Company Name
3363 * Last, First
3364 * Last, First M.
3365 * Last, First (C)(STP) (Company)
3366 * first.last@company.com (preprocessed as "first last")
3367 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3369 * Unusable examples:
3370 * user@company.com (preprocessed as "user")
3371 * first.m.last@company.com (preprocessed as "first m last")
3372 * user.company.com@reuters.net (preprocessed as "user company com")
3374 static void
3375 sipe_get_first_last_names(struct sipe_account_data *sip,
3376 const char *uri,
3377 char **first_name,
3378 char **last_name)
3380 PurpleBuddy *p_buddy;
3381 char *display_name;
3382 const char *email;
3383 const char *first, *last;
3384 char *tmp;
3385 char **parts;
3386 gboolean has_comma = FALSE;
3388 if (!sip || !uri) return;
3390 p_buddy = purple_find_buddy(sip->account, uri);
3392 if (!p_buddy) return;
3394 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3395 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3397 if (!display_name && !email) return;
3399 /* if no display name, make "first last anything_else" out of email */
3400 if (email && !display_name) {
3401 display_name = g_strndup(email, strstr(email, "@") - email);
3402 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
3403 g_free(tmp);
3406 if (display_name) {
3407 has_comma = (strstr(display_name, ",") != NULL);
3408 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
3409 g_free(tmp);
3410 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
3411 g_free(tmp);
3414 parts = g_strsplit(display_name, " ", 0);
3416 if (!parts[0] || !parts[1]) {
3417 g_free(display_name);
3418 g_strfreev(parts);
3419 return;
3422 if (has_comma) {
3423 last = parts[0];
3424 first = parts[1];
3425 } else {
3426 first = parts[0];
3427 last = parts[1];
3430 if (first_name) {
3431 *first_name = g_strstrip(g_strdup(first));
3434 if (last_name) {
3435 *last_name = g_strstrip(g_strdup(last));
3438 g_free(display_name);
3439 g_strfreev(parts);
3443 * Update user information
3445 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3446 * @param property_name
3447 * @param property_value may be modified to strip white space
3449 static void
3450 sipe_update_user_info(struct sipe_account_data *sip,
3451 const char *uri,
3452 const char *property_name,
3453 char *property_value)
3455 GSList *buddies, *entry;
3457 if (!property_name || strlen(property_name) == 0) return;
3459 if (property_value)
3460 property_value = g_strstrip(property_value);
3462 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3463 while (entry) {
3464 const char *prop_str;
3465 const char *server_alias;
3466 PurpleBuddy *p_buddy = entry->data;
3468 /* for Display Name */
3469 if (sipe_strequal(property_name, ALIAS_PROP)) {
3470 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3471 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
3472 purple_blist_alias_buddy(p_buddy, property_value);
3475 server_alias = purple_buddy_get_server_alias(p_buddy);
3476 if (!is_empty(property_value) &&
3477 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3479 purple_blist_server_alias_buddy(p_buddy, property_value);
3482 /* for other properties */
3483 else {
3484 if (!is_empty(property_value)) {
3485 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3486 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3487 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3492 entry = entry->next;
3494 g_slist_free(buddies);
3498 * Update user phone
3499 * Suitable for both 2005 and 2007 systems.
3501 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3502 * @param phone_type
3503 * @param phone may be modified to strip white space
3504 * @param phone_display_string may be modified to strip white space
3506 static void
3507 sipe_update_user_phone(struct sipe_account_data *sip,
3508 const char *uri,
3509 const gchar *phone_type,
3510 gchar *phone,
3511 gchar *phone_display_string)
3513 const char *phone_node = PHONE_PROP; /* work phone by default */
3514 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3516 if(!phone || strlen(phone) == 0) return;
3518 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3519 phone_node = PHONE_MOBILE_PROP;
3520 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3521 } else if (sipe_strequal(phone_type, "home")) {
3522 phone_node = PHONE_HOME_PROP;
3523 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3524 } else if (sipe_strequal(phone_type, "other")) {
3525 phone_node = PHONE_OTHER_PROP;
3526 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3527 } else if (sipe_strequal(phone_type, "custom1")) {
3528 phone_node = PHONE_CUSTOM1_PROP;
3529 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3532 sipe_update_user_info(sip, uri, phone_node, phone);
3533 if (phone_display_string) {
3534 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3538 static void
3539 sipe_update_calendar(struct sipe_account_data *sip)
3541 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3543 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: started.");
3545 if (sipe_strequal(calendar, "EXCH")) {
3546 sipe_ews_update_calendar(sip);
3549 /* schedule repeat */
3550 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3552 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: finished.");
3556 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3557 * by using standard Purple's means of signals and saved statuses.
3559 * Thus all UI elements get updated: Status Button with Note, docklet.
3560 * This is ablolutely important as both our status and note can come
3561 * inbound (roaming) or be updated programmatically (e.g. based on our
3562 * calendar data).
3564 static void
3565 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3566 const char *status_id,
3567 const char *message,
3568 time_t do_not_publish[])
3570 PurpleStatus *status = purple_account_get_active_status(account);
3571 gboolean changed = TRUE;
3573 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3574 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3576 changed = FALSE;
3579 if (purple_savedstatus_is_idleaway()) {
3580 changed = FALSE;
3583 if (changed) {
3584 PurpleSavedStatus *saved_status;
3585 const PurpleStatusType *acct_status_type =
3586 purple_status_type_find_with_id(account->status_types, status_id);
3587 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3588 sipe_activity activity = sipe_get_activity_by_token(status_id);
3590 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3591 if (saved_status) {
3592 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3595 /* If this type+message is unique then create a new transient saved status
3596 * Ref: gtkstatusbox.c
3598 if (!saved_status) {
3599 GList *tmp;
3600 GList *active_accts = purple_accounts_get_all_active();
3602 saved_status = purple_savedstatus_new(NULL, primitive);
3603 purple_savedstatus_set_message(saved_status, message);
3605 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3606 purple_savedstatus_set_substatus(saved_status,
3607 (PurpleAccount *)tmp->data, acct_status_type, message);
3609 g_list_free(active_accts);
3612 do_not_publish[activity] = time(NULL);
3613 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3614 status_id, (int)do_not_publish[activity]);
3616 /* Set the status for each account */
3617 purple_savedstatus_activate(saved_status);
3621 struct hash_table_delete_payload {
3622 GHashTable *hash_table;
3623 guint container;
3626 static void
3627 sipe_remove_category_container_publications_cb(const char *name,
3628 struct sipe_publication *publication,
3629 struct hash_table_delete_payload *payload)
3631 if (publication->container == payload->container) {
3632 g_hash_table_remove(payload->hash_table, name);
3635 static void
3636 sipe_remove_category_container_publications(GHashTable *our_publications,
3637 const char *category,
3638 guint container)
3640 struct hash_table_delete_payload payload;
3641 payload.hash_table = g_hash_table_lookup(our_publications, category);
3643 if (!payload.hash_table) return;
3645 payload.container = container;
3646 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3649 static void
3650 send_publish_category_initial(struct sipe_account_data *sip);
3653 * When we receive some self (BE) NOTIFY with a new subscriber
3654 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3657 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3659 gchar *contact;
3660 gchar *to;
3661 sipe_xml *xml;
3662 const sipe_xml *node;
3663 const sipe_xml *node2;
3664 char *display_name = NULL;
3665 char *uri;
3666 GSList *category_names = NULL;
3667 int aggreg_avail = 0;
3668 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3669 gboolean do_update_status = FALSE;
3670 gboolean has_note_cleaned = FALSE;
3672 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3674 xml = sipe_xml_parse(msg->body, msg->bodylen);
3675 if (!xml) return;
3677 contact = get_contact(sip);
3678 to = sip_uri_self(sip);
3681 /* categories */
3682 /* set list of categories participating in this XML */
3683 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3684 const gchar *name = sipe_xml_attribute(node, "name");
3685 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3687 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3688 category_names ? (int) g_slist_length(category_names) : -1);
3689 /* drop category information */
3690 if (category_names) {
3691 GSList *entry = category_names;
3692 while (entry) {
3693 GHashTable *cat_publications;
3694 const gchar *category = entry->data;
3695 entry = entry->next;
3696 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
3697 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3698 if (cat_publications) {
3699 g_hash_table_remove(sip->our_publications, category);
3700 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
3704 g_slist_free(category_names);
3705 /* filling our categories reflected in roaming data */
3706 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3707 const char *tmp;
3708 const gchar *name = sipe_xml_attribute(node, "name");
3709 guint container = sipe_xml_int_attribute(node, "container", -1);
3710 guint instance = sipe_xml_int_attribute(node, "instance", -1);
3711 guint version = sipe_xml_int_attribute(node, "version", 0);
3712 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
3713 sipe_utils_str_to_time(tmp) : 0;
3714 gchar *key;
3715 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3717 /* Ex. clear note: <category name="note"/> */
3718 if (container == (guint)-1) {
3719 g_free(sip->note);
3720 sip->note = NULL;
3721 do_update_status = TRUE;
3722 continue;
3725 /* Ex. clear note: <category name="note" container="200"/> */
3726 if (instance == (guint)-1) {
3727 if (container == 200) {
3728 g_free(sip->note);
3729 sip->note = NULL;
3730 do_update_status = TRUE;
3732 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
3733 sipe_remove_category_container_publications(
3734 sip->our_publications, name, container);
3735 continue;
3738 /* key is <category><instance><container> */
3739 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3740 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
3742 /* capture all userState publication for later clean up if required */
3743 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3744 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3746 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
3747 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3748 publication->category = g_strdup(name);
3749 publication->instance = instance;
3750 publication->container = container;
3751 publication->version = version;
3753 if (!sip->user_state_publications) {
3754 sip->user_state_publications = g_hash_table_new_full(
3755 g_str_hash, g_str_equal,
3756 g_free, (GDestroyNotify)free_publication);
3758 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3759 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3760 key, version);
3764 if (sipe_is_our_publication(sip, key)) {
3765 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3767 publication->category = g_strdup(name);
3768 publication->instance = instance;
3769 publication->container = container;
3770 publication->version = version;
3772 /* filling publication->availability */
3773 if (sipe_strequal(name, "state")) {
3774 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3775 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3777 if (xn_avail) {
3778 gchar *avail_str = sipe_xml_data(xn_avail);
3779 if (avail_str) {
3780 publication->availability = atoi(avail_str);
3782 g_free(avail_str);
3784 /* for calendarState */
3785 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
3786 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3787 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3789 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
3790 if (xn_activity) {
3791 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
3792 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3794 event->is_meeting = TRUE;
3797 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
3798 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
3800 publication->cal_event_hash = sipe_cal_event_hash(event);
3801 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3802 publication->cal_event_hash);
3803 sipe_cal_event_free(event);
3806 /* filling publication->note */
3807 if (sipe_strequal(name, "note")) {
3808 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
3810 if (!has_note_cleaned) {
3811 has_note_cleaned = TRUE;
3813 g_free(sip->note);
3814 sip->note = NULL;
3815 sip->note_since = publish_time;
3817 do_update_status = TRUE;
3820 g_free(publication->note);
3821 publication->note = NULL;
3822 if (xn_body) {
3823 char *tmp;
3825 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
3826 g_free(tmp);
3827 if (publish_time >= sip->note_since) {
3828 g_free(sip->note);
3829 sip->note = g_strdup(publication->note);
3830 sip->note_since = publish_time;
3831 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
3833 do_update_status = TRUE;
3838 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3839 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3840 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
3841 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
3842 if (xn_free_busy) {
3843 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3844 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
3846 if (xn_working_hours) {
3847 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
3851 if (!cat_publications) {
3852 cat_publications = g_hash_table_new_full(
3853 g_str_hash, g_str_equal,
3854 g_free, (GDestroyNotify)free_publication);
3855 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3856 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3858 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3859 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3861 g_free(key);
3863 /* aggregateState (not an our publication) from 2-nd container */
3864 if (sipe_strequal(name, "state") && container == 2) {
3865 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3867 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
3868 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3869 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3871 if (xn_avail) {
3872 gchar *avail_str = sipe_xml_data(xn_avail);
3873 if (avail_str) {
3874 aggreg_avail = atoi(avail_str);
3876 g_free(avail_str);
3879 if (xn_activity) {
3880 const char *activity_token = sipe_xml_attribute(xn_activity, "token");
3882 aggreg_activity = sipe_get_activity_by_token(activity_token);
3885 do_update_status = TRUE;
3889 /* userProperties published by server from AD */
3890 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3891 const sipe_xml *line;
3892 /* line, for Remote Call Control (RCC) */
3893 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
3894 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
3895 const gchar *line_type = sipe_xml_attribute(line, "lineType");
3896 gchar *line_uri;
3898 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3900 line_uri = sipe_xml_data(line);
3901 if (line_uri) {
3902 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
3903 sip_csta_open(sip, line_uri, line_server);
3905 g_free(line_uri);
3907 break;
3911 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
3912 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3914 /* containers */
3915 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
3916 guint id = sipe_xml_int_attribute(node, "id", 0);
3917 struct sipe_container *container = sipe_find_container(sip, id);
3919 if (container) {
3920 sip->containers = g_slist_remove(sip->containers, container);
3921 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
3922 free_container(container);
3924 container = g_new0(struct sipe_container, 1);
3925 container->id = id;
3926 container->version = sipe_xml_int_attribute(node, "version", 0);
3927 sip->containers = g_slist_append(sip->containers, container);
3928 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
3930 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
3931 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3932 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
3933 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
3934 container->members = g_slist_append(container->members, member);
3935 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
3936 member->type, member->value ? member->value : "");
3940 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
3941 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
3942 char *container_xmls = NULL;
3943 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3944 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3946 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
3947 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
3948 /* initial set-up to let counterparties see your status */
3949 if (sameEnterpriseAL < 0) {
3950 struct sipe_container *container = sipe_find_container(sip, 200);
3951 guint version = container ? container->version : 0;
3952 sipe_send_container_members_prepare(sip, 200, version, "add", "sameEnterprise", NULL, &container_xmls);
3954 if (federatedAL < 0) {
3955 struct sipe_container *container = sipe_find_container(sip, 100);
3956 guint version = container ? container->version : 0;
3957 sipe_send_container_members_prepare(sip, 100, version, "add", "federated", NULL, &container_xmls);
3959 sip->access_level_set = TRUE;
3961 if (container_xmls) {
3962 sipe_send_set_container_members(sip, container_xmls);
3964 g_free(container_xmls);
3967 /* subscribers */
3968 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
3969 const char *user;
3970 const char *acknowledged;
3971 gchar *hdr;
3972 gchar *body;
3974 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
3975 if (!user) continue;
3976 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
3977 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
3978 uri = sip_uri_from_name(user);
3980 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3982 acknowledged= sipe_xml_attribute(node, "acknowledged");
3983 if(sipe_strcase_equal(acknowledged,"false")){
3984 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
3985 if (!purple_find_buddy(sip->account, uri)) {
3986 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3989 hdr = g_strdup_printf(
3990 "Contact: %s\r\n"
3991 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3993 body = g_strdup_printf(
3994 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3995 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3996 "</setSubscribers>", user);
3998 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3999 g_free(body);
4000 g_free(hdr);
4002 g_free(display_name);
4003 g_free(uri);
4006 g_free(contact);
4007 sipe_xml_free(xml);
4009 /* Publish initial state if not yet.
4010 * Assuming this happens on initial responce to subscription to roaming-self
4011 * so we've already updated our roaming data in full.
4012 * Only for 2007+
4014 if (!sip->initial_state_published) {
4015 send_publish_category_initial(sip);
4016 sip->initial_state_published = TRUE;
4017 /* dalayed run */
4018 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
4019 do_update_status = FALSE;
4020 } else if (aggreg_avail) {
4022 g_free(sip->status);
4023 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
4024 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
4025 } else {
4026 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
4030 if (do_update_status) {
4031 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
4032 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
4035 g_free(to);
4038 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
4040 gchar *to = sip_uri_self(sip);
4041 gchar *tmp = get_contact(sip);
4042 gchar *hdr = g_strdup_printf(
4043 "Event: vnd-microsoft-roaming-ACL\r\n"
4044 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
4045 "Supported: com.microsoft.autoextend\r\n"
4046 "Supported: ms-benotify\r\n"
4047 "Proxy-Require: ms-benotify\r\n"
4048 "Supported: ms-piggyback-first-notify\r\n"
4049 "Contact: %s\r\n", tmp);
4050 g_free(tmp);
4052 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
4053 g_free(to);
4054 g_free(hdr);
4058 * To request for presence information about the user, access level settings that have already been configured by the user
4059 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
4060 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
4063 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
4065 gchar *to = sip_uri_self(sip);
4066 gchar *tmp = get_contact(sip);
4067 gchar *hdr = g_strdup_printf(
4068 "Event: vnd-microsoft-roaming-self\r\n"
4069 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
4070 "Supported: ms-benotify\r\n"
4071 "Proxy-Require: ms-benotify\r\n"
4072 "Supported: ms-piggyback-first-notify\r\n"
4073 "Contact: %s\r\n"
4074 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
4076 gchar *body=g_strdup(
4077 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
4078 "<roaming type=\"categories\"/>"
4079 "<roaming type=\"containers\"/>"
4080 "<roaming type=\"subscribers\"/></roamingList>");
4082 g_free(tmp);
4083 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4084 g_free(body);
4085 g_free(to);
4086 g_free(hdr);
4090 * For 2005 version
4092 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
4094 gchar *to = sip_uri_self(sip);
4095 gchar *tmp = get_contact(sip);
4096 gchar *hdr = g_strdup_printf(
4097 "Event: vnd-microsoft-provisioning\r\n"
4098 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
4099 "Supported: com.microsoft.autoextend\r\n"
4100 "Supported: ms-benotify\r\n"
4101 "Proxy-Require: ms-benotify\r\n"
4102 "Supported: ms-piggyback-first-notify\r\n"
4103 "Expires: 0\r\n"
4104 "Contact: %s\r\n", tmp);
4106 g_free(tmp);
4107 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
4108 g_free(to);
4109 g_free(hdr);
4112 /** Subscription for provisioning information to help with initial
4113 * configuration. This subscription is a one-time query (denoted by the Expires header,
4114 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
4115 * configuration, meeting policies, and policy settings that Communicator must enforce.
4116 * TODO: for what we need this information.
4119 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
4121 gchar *to = sip_uri_self(sip);
4122 gchar *tmp = get_contact(sip);
4123 gchar *hdr = g_strdup_printf(
4124 "Event: vnd-microsoft-provisioning-v2\r\n"
4125 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
4126 "Supported: com.microsoft.autoextend\r\n"
4127 "Supported: ms-benotify\r\n"
4128 "Proxy-Require: ms-benotify\r\n"
4129 "Supported: ms-piggyback-first-notify\r\n"
4130 "Expires: 0\r\n"
4131 "Contact: %s\r\n"
4132 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
4133 gchar *body = g_strdup(
4134 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
4135 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
4136 "<provisioningGroup name=\"ucPolicy\"/>"
4137 "</provisioningGroupList>");
4139 g_free(tmp);
4140 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4141 g_free(body);
4142 g_free(to);
4143 g_free(hdr);
4146 static void
4147 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
4148 gpointer value, gpointer user_data)
4150 struct sip_subscription *subscription = value;
4151 struct sip_dialog *dialog = &subscription->dialog;
4152 struct sipe_account_data *sip = user_data;
4153 gchar *tmp = get_contact(sip);
4154 gchar *hdr = g_strdup_printf(
4155 "Event: %s\r\n"
4156 "Expires: 0\r\n"
4157 "Contact: %s\r\n", subscription->event, tmp);
4158 g_free(tmp);
4160 /* Rate limit to max. 25 requests per seconds */
4161 g_usleep(1000000 / 25);
4163 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
4164 g_free(hdr);
4167 /* IM Session (INVITE and MESSAGE methods) */
4169 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
4170 static gchar *
4171 get_end_points (struct sipe_account_data *sip,
4172 struct sip_session *session)
4174 gchar *res;
4176 if (session == NULL) {
4177 return NULL;
4180 res = g_strdup_printf("<sip:%s>", sip->username);
4182 SIPE_DIALOG_FOREACH {
4183 gchar *tmp = res;
4184 res = g_strdup_printf("%s, <%s>", res, dialog->with);
4185 g_free(tmp);
4187 if (dialog->theirepid) {
4188 tmp = res;
4189 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
4190 g_free(tmp);
4192 } SIPE_DIALOG_FOREACH_END;
4194 return res;
4197 static gboolean
4198 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
4199 struct sipmsg *msg,
4200 SIPE_UNUSED_PARAMETER struct transaction *trans)
4202 gboolean ret = TRUE;
4204 if (msg->response != 200) {
4205 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
4206 return FALSE;
4209 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
4211 return ret;
4215 * Asks UA/proxy about its capabilities.
4217 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
4219 gchar *to = sip_uri(who);
4220 gchar *contact = get_contact(sip);
4221 gchar *request = g_strdup_printf(
4222 "Accept: application/sdp\r\n"
4223 "Contact: %s\r\n", contact);
4224 g_free(contact);
4226 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
4228 g_free(to);
4229 g_free(request);
4232 static void
4233 sipe_notify_user(struct sipe_account_data *sip,
4234 struct sip_session *session,
4235 PurpleMessageFlags flags,
4236 const gchar *message)
4238 PurpleConversation *conv;
4240 if (!session->conv) {
4241 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
4242 } else {
4243 conv = session->conv;
4245 purple_conversation_write(conv, NULL, message, flags, time(NULL));
4248 void
4249 sipe_present_info(struct sipe_account_data *sip,
4250 struct sip_session *session,
4251 const gchar *message)
4253 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4256 static void
4257 sipe_present_err(struct sipe_account_data *sip,
4258 struct sip_session *session,
4259 const gchar *message)
4261 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4264 void
4265 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4266 struct sip_session *session,
4267 int sip_error,
4268 int sip_warning,
4269 const gchar *who,
4270 const gchar *message)
4272 char *msg, *msg_tmp, *msg_tmp2;
4273 const char *label;
4275 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
4276 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4277 g_free(msg_tmp);
4278 /* Service unavailable; Server Internal Error; Server Time-out */
4279 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4280 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4281 g_free(msg);
4282 msg = NULL;
4283 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4284 label = _("This message was not delivered to %s because the service is not available");
4285 } else if (sip_error == 486) { /* Busy Here */
4286 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4287 } else if (sip_error == 415) { /* Unsupported media type */
4288 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4289 } else {
4290 label = _("This message was not delivered to %s because one or more recipients are offline");
4293 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4294 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4295 msg ? ":" : "",
4296 msg ? msg : "");
4297 sipe_present_err(sip, session, msg_tmp);
4298 g_free(msg_tmp2);
4299 g_free(msg_tmp);
4300 g_free(msg);
4304 static gboolean
4305 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4306 SIPE_UNUSED_PARAMETER struct transaction *trans)
4308 gboolean ret = TRUE;
4309 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4310 struct sip_session *session = sipe_session_find_im(sip, with);
4311 struct sip_dialog *dialog;
4312 gchar *cseq;
4313 char *key;
4314 struct queued_message *message;
4316 if (!session) {
4317 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4318 g_free(with);
4319 return FALSE;
4322 dialog = sipe_dialog_find(session, with);
4323 if (!dialog) {
4324 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4325 g_free(with);
4326 return FALSE;
4329 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4330 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4331 g_free(cseq);
4332 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4334 if (msg->response >= 400) {
4335 PurpleBuddy *pbuddy;
4336 const char *alias = with;
4337 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4338 int warning = -1;
4340 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4342 if (warn_hdr) {
4343 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4344 if (parts[0]) {
4345 warning = atoi(parts[0]);
4347 g_strfreev(parts);
4350 /* cancel file transfer as rejected by server */
4351 if (msg->response == 606 && /* Not acceptable all. */
4352 warning == 309 && /* Message contents not allowed by policy */
4353 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4355 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4356 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4357 sipe_utils_nameval_free(parsed_body);
4360 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4361 alias = purple_buddy_get_alias(pbuddy);
4364 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4366 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4367 if (msg->response == 408 || /* Request timeout */
4368 msg->response == 480 || /* Temporarily Unavailable */
4369 msg->response == 481) { /* Call/Transaction Does Not Exist */
4370 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4371 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4374 ret = FALSE;
4375 } else {
4376 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4377 if (message_id) {
4378 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4379 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4380 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4383 g_hash_table_remove(session->unconfirmed_messages, key);
4384 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4385 key, g_hash_table_size(session->unconfirmed_messages));
4388 g_free(key);
4389 g_free(with);
4391 if (ret) sipe_im_process_queue(sip, session);
4392 return ret;
4395 static gboolean
4396 sipe_is_election_finished(struct sip_session *session);
4398 static void
4399 sipe_election_result(struct sipe_account_data *sip,
4400 void *sess);
4402 static gboolean
4403 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4404 SIPE_UNUSED_PARAMETER struct transaction *trans)
4406 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4407 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4408 struct sip_dialog *dialog;
4409 struct sip_session *session;
4411 session = sipe_session_find_chat_by_callid(sip, callid);
4412 if (!session) {
4413 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
4414 return FALSE;
4417 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4418 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
4419 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
4420 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
4422 if (xn_request_rm_response) {
4423 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
4424 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
4426 dialog = sipe_dialog_find(session, with);
4427 if (!dialog) {
4428 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
4429 sipe_xml_free(xn_action);
4430 return FALSE;
4433 if (allow && !g_strcasecmp(allow, "true")) {
4434 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
4435 dialog->election_vote = 1;
4436 } else if (allow && !g_strcasecmp(allow, "false")) {
4437 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
4438 dialog->election_vote = -1;
4441 if (sipe_is_election_finished(session)) {
4442 sipe_election_result(sip, session);
4445 } else if (xn_set_rm_response) {
4448 sipe_xml_free(xn_action);
4452 return TRUE;
4455 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4457 gchar *hdr;
4458 gchar *tmp;
4459 char *msgtext = NULL;
4460 const gchar *msgr = "";
4461 gchar *tmp2 = NULL;
4463 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4464 char *msgformat;
4465 gchar *msgr_value;
4467 sipe_parse_html(msg, &msgformat, &msgtext);
4468 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
4470 msgr_value = sipmsg_get_msgr_string(msgformat);
4471 g_free(msgformat);
4472 if (msgr_value) {
4473 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4474 g_free(msgr_value);
4476 } else {
4477 msgtext = g_strdup(msg);
4480 tmp = get_contact(sip);
4481 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4482 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4483 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4484 if (content_type == NULL)
4485 content_type = "text/plain";
4487 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4488 g_free(tmp);
4489 g_free(tmp2);
4491 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4492 g_free(msgtext);
4493 g_free(hdr);
4497 void
4498 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4500 GSList *entry2 = session->outgoing_message_queue;
4501 while (entry2) {
4502 struct queued_message *msg = entry2->data;
4504 /* for multiparty chat or conference */
4505 if (session->is_multiparty || session->focus_uri) {
4506 gchar *who = sip_uri_self(sip);
4507 serv_got_chat_in(sip->gc, session->chat_id, who,
4508 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4509 g_free(who);
4512 SIPE_DIALOG_FOREACH {
4513 char *key;
4514 struct queued_message *message;
4516 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4518 message = g_new0(struct queued_message,1);
4519 message->body = g_strdup(msg->body);
4520 if (msg->content_type != NULL)
4521 message->content_type = g_strdup(msg->content_type);
4523 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4524 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4525 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4526 key, g_hash_table_size(session->unconfirmed_messages));
4527 g_free(key);
4529 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4530 } SIPE_DIALOG_FOREACH_END;
4532 entry2 = sipe_session_dequeue_message(session);
4536 static void
4537 sipe_refer_notify(struct sipe_account_data *sip,
4538 struct sip_session *session,
4539 const gchar *who,
4540 int status,
4541 const gchar *desc)
4543 gchar *hdr;
4544 gchar *body;
4545 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4547 hdr = g_strdup_printf(
4548 "Event: refer\r\n"
4549 "Subscription-State: %s\r\n"
4550 "Content-Type: message/sipfrag\r\n",
4551 status >= 200 ? "terminated" : "active");
4553 body = g_strdup_printf(
4554 "SIP/2.0 %d %s\r\n",
4555 status, desc);
4557 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4559 g_free(hdr);
4560 g_free(body);
4563 static gboolean
4564 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4566 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4567 struct sip_session *session;
4568 struct sip_dialog *dialog;
4569 char *cseq;
4570 char *key;
4571 struct queued_message *message;
4572 struct sipmsg *request_msg = trans->msg;
4574 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4575 gchar *referred_by;
4577 session = sipe_session_find_chat_by_callid(sip, callid);
4578 if (!session) {
4579 session = sipe_session_find_im(sip, with);
4581 if (!session) {
4582 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4583 g_free(with);
4584 return FALSE;
4587 dialog = sipe_dialog_find(session, with);
4588 if (!dialog) {
4589 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4590 g_free(with);
4591 return FALSE;
4594 sipe_dialog_parse(dialog, msg, TRUE);
4596 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4597 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4598 g_free(cseq);
4599 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4601 if (msg->response != 200) {
4602 PurpleBuddy *pbuddy;
4603 const char *alias = with;
4604 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4605 int warning = -1;
4607 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4609 if (warn_hdr) {
4610 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4611 if (parts[0]) {
4612 warning = atoi(parts[0]);
4614 g_strfreev(parts);
4617 /* cancel file transfer as rejected by server */
4618 if (msg->response == 606 && /* Not acceptable all. */
4619 warning == 309 && /* Message contents not allowed by policy */
4620 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4622 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4623 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4624 sipe_utils_nameval_free(parsed_body);
4627 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4628 alias = purple_buddy_get_alias(pbuddy);
4631 if (message) {
4632 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4633 } else {
4634 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4635 sipe_present_err(sip, session, tmp_msg);
4636 g_free(tmp_msg);
4639 sipe_dialog_remove(session, with);
4641 g_free(key);
4642 g_free(with);
4643 return FALSE;
4646 dialog->cseq = 0;
4647 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4648 dialog->outgoing_invite = NULL;
4649 dialog->is_established = TRUE;
4651 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4652 if (referred_by) {
4653 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4654 g_free(referred_by);
4657 /* add user to chat if it is a multiparty session */
4658 if (session->is_multiparty) {
4659 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4660 with, NULL,
4661 PURPLE_CBFLAGS_NONE, TRUE);
4664 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4665 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4666 sipe_session_dequeue_message(session);
4669 sipe_im_process_queue(sip, session);
4671 g_hash_table_remove(session->unconfirmed_messages, key);
4672 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4673 key, g_hash_table_size(session->unconfirmed_messages));
4675 g_free(key);
4676 g_free(with);
4677 return TRUE;
4681 void
4682 sipe_invite(struct sipe_account_data *sip,
4683 struct sip_session *session,
4684 const gchar *who,
4685 const gchar *msg_body,
4686 const gchar *msg_content_type,
4687 const gchar *referred_by,
4688 const gboolean is_triggered)
4690 gchar *hdr;
4691 gchar *to;
4692 gchar *contact;
4693 gchar *body;
4694 gchar *self;
4695 char *ms_text_format = NULL;
4696 gchar *roster_manager;
4697 gchar *end_points;
4698 gchar *referred_by_str;
4699 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4701 if (dialog && dialog->is_established) {
4702 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
4703 return;
4706 if (!dialog) {
4707 dialog = sipe_dialog_add(session);
4708 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4709 dialog->with = g_strdup(who);
4712 if (!(dialog->ourtag)) {
4713 dialog->ourtag = gentag();
4716 to = sip_uri(who);
4718 if (msg_body) {
4719 char *msgtext = NULL;
4720 char *base64_msg;
4721 const gchar *msgr = "";
4722 char *key;
4723 struct queued_message *message;
4724 gchar *tmp = NULL;
4726 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4727 char *msgformat;
4728 gchar *msgr_value;
4730 sipe_parse_html(msg_body, &msgformat, &msgtext);
4731 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
4733 msgr_value = sipmsg_get_msgr_string(msgformat);
4734 g_free(msgformat);
4735 if (msgr_value) {
4736 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4737 g_free(msgr_value);
4739 } else {
4740 msgtext = g_strdup(msg_body);
4743 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4744 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4745 msg_content_type ? msg_content_type : "text/plain",
4746 msgr,
4747 base64_msg);
4748 g_free(msgtext);
4749 g_free(tmp);
4750 g_free(base64_msg);
4752 message = g_new0(struct queued_message,1);
4753 message->body = g_strdup(msg_body);
4754 if (msg_content_type != NULL)
4755 message->content_type = g_strdup(msg_content_type);
4757 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4758 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4759 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4760 key, g_hash_table_size(session->unconfirmed_messages));
4761 g_free(key);
4764 contact = get_contact(sip);
4765 end_points = get_end_points(sip, session);
4766 self = sip_uri_self(sip);
4767 roster_manager = g_strdup_printf(
4768 "Roster-Manager: %s\r\n"
4769 "EndPoints: %s\r\n",
4770 self,
4771 end_points);
4772 referred_by_str = referred_by ?
4773 g_strdup_printf(
4774 "Referred-By: %s\r\n",
4775 referred_by)
4776 : g_strdup("");
4777 hdr = g_strdup_printf(
4778 "Supported: ms-sender\r\n"
4779 "%s"
4780 "%s"
4781 "%s"
4782 "%s"
4783 "Contact: %s\r\n%s"
4784 "Content-Type: application/sdp\r\n",
4785 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4786 referred_by_str,
4787 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4788 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4789 contact,
4790 ms_text_format ? ms_text_format : "");
4791 g_free(ms_text_format);
4792 g_free(self);
4794 body = g_strdup_printf(
4795 "v=0\r\n"
4796 "o=- 0 0 IN IP4 %s\r\n"
4797 "s=session\r\n"
4798 "c=IN IP4 %s\r\n"
4799 "t=0 0\r\n"
4800 "m=%s %d sip null\r\n"
4801 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4802 sipe_backend_network_ip_address(),
4803 sipe_backend_network_ip_address(),
4804 sip->ocs2007 ? "message" : "x-ms-message",
4805 sip->realport);
4807 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4808 to, to, hdr, body, dialog, process_invite_response);
4810 g_free(to);
4811 g_free(roster_manager);
4812 g_free(end_points);
4813 g_free(referred_by_str);
4814 g_free(body);
4815 g_free(hdr);
4816 g_free(contact);
4819 static void
4820 sipe_refer(struct sipe_account_data *sip,
4821 struct sip_session *session,
4822 const gchar *who)
4824 gchar *hdr;
4825 gchar *contact;
4826 gchar *epid = get_epid(sip);
4827 struct sip_dialog *dialog = sipe_dialog_find(session,
4828 session->roster_manager);
4829 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4831 contact = get_contact(sip);
4832 hdr = g_strdup_printf(
4833 "Contact: %s\r\n"
4834 "Refer-to: <%s>\r\n"
4835 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4836 "Require: com.microsoft.rtc-multiparty\r\n",
4837 contact,
4838 who,
4839 sip->username,
4840 ourtag ? ";tag=" : "",
4841 ourtag ? ourtag : "",
4842 epid);
4843 g_free(epid);
4845 send_sip_request(sip->gc, "REFER",
4846 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4848 g_free(hdr);
4849 g_free(contact);
4852 static void
4853 sipe_send_election_request_rm(struct sipe_account_data *sip,
4854 struct sip_dialog *dialog,
4855 int bid)
4857 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4859 gchar *body = g_strdup_printf(
4860 "<?xml version=\"1.0\"?>\r\n"
4861 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4862 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4863 sip->username, bid);
4865 send_sip_request(sip->gc, "INFO",
4866 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4868 g_free(body);
4871 static void
4872 sipe_send_election_set_rm(struct sipe_account_data *sip,
4873 struct sip_dialog *dialog)
4875 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4877 gchar *body = g_strdup_printf(
4878 "<?xml version=\"1.0\"?>\r\n"
4879 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4880 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4881 sip->username);
4883 send_sip_request(sip->gc, "INFO",
4884 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4886 g_free(body);
4889 static void
4890 sipe_session_close(struct sipe_account_data *sip,
4891 struct sip_session * session)
4893 if (session && session->focus_uri) {
4894 sipe_conf_immcu_closed(sip, session);
4895 conf_session_close(sip, session);
4898 if (session) {
4899 SIPE_DIALOG_FOREACH {
4900 /* @TODO slow down BYE message sending rate */
4901 /* @see single subscription code */
4902 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4903 } SIPE_DIALOG_FOREACH_END;
4905 sipe_session_remove(sip, session);
4909 static void
4910 sipe_session_close_all(struct sipe_account_data *sip)
4912 GSList *entry;
4913 while ((entry = sip->sessions) != NULL) {
4914 sipe_session_close(sip, entry->data);
4918 static void
4919 sipe_convo_closed(PurpleConnection * gc, const char *who)
4921 struct sipe_account_data *sip = gc->proto_data;
4923 SIPE_DEBUG_INFO("conversation with %s closed", who);
4924 sipe_session_close(sip, sipe_session_find_im(sip, who));
4927 static void
4928 sipe_chat_invite(PurpleConnection *gc, int id,
4929 SIPE_UNUSED_PARAMETER const char *message,
4930 const char *name)
4932 sipe_chat_create(gc->proto_data, id, name);
4935 static void
4936 sipe_chat_leave (PurpleConnection *gc, int id)
4938 struct sipe_account_data *sip = gc->proto_data;
4939 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4941 sipe_session_close(sip, session);
4944 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4945 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4947 struct sipe_account_data *sip = gc->proto_data;
4948 struct sip_session *session;
4949 struct sip_dialog *dialog;
4950 gchar *uri = sip_uri(who);
4952 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
4954 session = sipe_session_find_or_add_im(sip, uri);
4955 dialog = sipe_dialog_find(session, uri);
4957 // Queue the message
4958 sipe_session_enqueue_message(session, what, NULL);
4960 if (dialog && !dialog->outgoing_invite) {
4961 sipe_im_process_queue(sip, session);
4962 } else if (!dialog || !dialog->outgoing_invite) {
4963 // Need to send the INVITE to get the outgoing dialog setup
4964 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4967 g_free(uri);
4968 return 1;
4971 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4972 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4974 struct sipe_account_data *sip = gc->proto_data;
4975 struct sip_session *session;
4977 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
4979 session = sipe_session_find_chat_by_id(sip, id);
4981 // Queue the message
4982 if (session && session->dialogs) {
4983 sipe_session_enqueue_message(session,what,NULL);
4984 sipe_im_process_queue(sip, session);
4985 } else if (sip) {
4986 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4987 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4989 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
4990 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
4992 if (sip->ocs2007) {
4993 struct sip_session *session = sipe_session_add_chat(sip);
4995 session->is_multiparty = FALSE;
4996 session->focus_uri = g_strdup(proto_chat_id);
4997 sipe_session_enqueue_message(session, what, NULL);
4998 sipe_invite_conf_focus(sip, session);
5002 return 1;
5005 /* End IM Session (INVITE and MESSAGE methods) */
5007 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
5009 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
5010 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5011 gchar *from;
5012 struct sip_session *session;
5014 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
5016 /* Call Control protocol */
5017 if (g_str_has_prefix(contenttype, "application/csta+xml"))
5019 process_incoming_info_csta(sip, msg);
5020 return;
5023 from = parse_from(sipmsg_find_header(msg, "From"));
5024 session = sipe_session_find_chat_by_callid(sip, callid);
5025 if (!session) {
5026 session = sipe_session_find_im(sip, from);
5028 if (!session) {
5029 g_free(from);
5030 return;
5033 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
5035 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
5036 const sipe_xml *xn_request_rm = sipe_xml_child(xn_action, "RequestRM");
5037 const sipe_xml *xn_set_rm = sipe_xml_child(xn_action, "SetRM");
5039 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
5041 if (xn_request_rm) {
5042 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
5043 int bid = sipe_xml_int_attribute(xn_request_rm, "bid", 0);
5044 gchar *body = g_strdup_printf(
5045 "<?xml version=\"1.0\"?>\r\n"
5046 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5047 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
5048 sip->username,
5049 session->bid < bid ? "true" : "false");
5050 send_sip_response(sip->gc, msg, 200, "OK", body);
5051 g_free(body);
5052 } else if (xn_set_rm) {
5053 gchar *body;
5054 const char *rm = sipe_xml_attribute(xn_set_rm, "uri");
5055 g_free(session->roster_manager);
5056 session->roster_manager = g_strdup(rm);
5058 body = g_strdup_printf(
5059 "<?xml version=\"1.0\"?>\r\n"
5060 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5061 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
5062 sip->username);
5063 send_sip_response(sip->gc, msg, 200, "OK", body);
5064 g_free(body);
5066 sipe_xml_free(xn_action);
5069 else
5071 /* looks like purple lacks typing notification for chat */
5072 if (!session->is_multiparty && !session->focus_uri) {
5073 sipe_xml *xn_keyboard_activity = sipe_xml_parse(msg->body, msg->bodylen);
5074 const char *status = sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity, "status"),
5075 "status");
5076 if (sipe_strequal(status, "type")) {
5077 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
5078 } else if (sipe_strequal(status, "idle")) {
5079 serv_got_typing_stopped(sip->gc, from);
5081 sipe_xml_free(xn_keyboard_activity);
5084 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5086 g_free(from);
5089 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
5091 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5092 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5093 struct sip_session *session;
5094 struct sip_dialog *dialog;
5096 /* collect dialog identification
5097 * we need callid, ourtag and theirtag to unambiguously identify dialog
5099 /* take data before 'msg' will be modified by send_sip_response */
5100 dialog = g_new0(struct sip_dialog, 1);
5101 dialog->callid = g_strdup(callid);
5102 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
5103 dialog->with = g_strdup(from);
5104 sipe_dialog_parse(dialog, msg, FALSE);
5106 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5108 session = sipe_session_find_chat_by_callid(sip, callid);
5109 if (!session) {
5110 session = sipe_session_find_im(sip, from);
5112 if (!session) {
5113 sipe_dialog_free(dialog);
5114 g_free(from);
5115 return;
5118 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
5119 g_free(session->roster_manager);
5120 session->roster_manager = NULL;
5123 /* This what BYE is essentially for - terminating dialog */
5124 sipe_dialog_remove_3(session, dialog);
5125 sipe_dialog_free(dialog);
5126 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
5127 sipe_conf_immcu_closed(sip, session);
5128 } else if (session->is_multiparty) {
5129 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
5132 g_free(from);
5135 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
5137 gchar *self = sip_uri_self(sip);
5138 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5139 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5140 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
5141 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
5142 struct sip_session *session;
5143 struct sip_dialog *dialog;
5145 session = sipe_session_find_chat_by_callid(sip, callid);
5146 dialog = sipe_dialog_find(session, from);
5148 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
5149 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
5150 } else {
5151 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
5153 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
5156 g_free(self);
5157 g_free(from);
5158 g_free(refer_to);
5159 g_free(referred_by);
5162 static unsigned int
5163 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
5165 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
5166 struct sip_session *session;
5167 struct sip_dialog *dialog;
5169 if (state == PURPLE_NOT_TYPING)
5170 return 0;
5172 session = sipe_session_find_im(sip, who);
5173 dialog = sipe_dialog_find(session, who);
5175 if (session && dialog && dialog->is_established) {
5176 send_sip_request(gc, "INFO", who, who,
5177 "Content-Type: application/xml\r\n",
5178 SIPE_SEND_TYPING, dialog, NULL);
5180 return SIPE_TYPING_SEND_TIMEOUT;
5183 static gboolean resend_timeout(struct sipe_account_data *sip)
5185 GSList *tmp = sip->transactions;
5186 time_t currtime = time(NULL);
5187 while (tmp) {
5188 struct transaction *trans = tmp->data;
5189 tmp = tmp->next;
5190 SIPE_DEBUG_INFO("have open transaction age: %ld", (long int)currtime-trans->time);
5191 if ((currtime - trans->time > 5) && trans->retries >= 1) {
5192 /* TODO 408 */
5193 } else {
5194 if ((currtime - trans->time > 2) && trans->retries == 0) {
5195 trans->retries++;
5196 sendout_sipmsg(sip, trans->msg);
5200 return TRUE;
5203 static void do_reauthenticate_cb(struct sipe_account_data *sip,
5204 SIPE_UNUSED_PARAMETER void *unused)
5206 /* register again when security token expires */
5207 /* we have to start a new authentication as the security token
5208 * is almost expired by sending a not signed REGISTER message */
5209 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
5210 sipe_auth_free(&sip->registrar);
5211 sipe_auth_free(&sip->proxy);
5212 sip->registerstatus = 0;
5213 do_register(sip);
5214 sip->reauthenticate_set = FALSE;
5217 static gboolean
5218 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
5219 struct sipmsg *msg,
5220 GSList *parsed_body)
5222 gboolean found = FALSE;
5224 if (parsed_body) {
5225 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
5227 if (sipe_strequal(invitation_command, "INVITE")) {
5228 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
5229 found = TRUE;
5230 } else if (sipe_strequal(invitation_command, "CANCEL")) {
5231 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
5232 found = TRUE;
5233 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
5234 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
5235 found = TRUE;
5238 return found;
5241 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
5243 gchar *from;
5244 const gchar *contenttype;
5245 gboolean found = FALSE;
5247 from = parse_from(sipmsg_find_header(msg, "From"));
5249 if (!from) return;
5251 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
5253 contenttype = sipmsg_find_header(msg, "Content-Type");
5254 if (g_str_has_prefix(contenttype, "text/plain")
5255 || g_str_has_prefix(contenttype, "text/html")
5256 || g_str_has_prefix(contenttype, "multipart/related")
5257 || g_str_has_prefix(contenttype, "multipart/alternative"))
5259 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5260 gchar *html = get_html_message(contenttype, msg->body);
5262 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5263 if (!session) {
5264 session = sipe_session_find_im(sip, from);
5267 if (session && session->focus_uri) { /* a conference */
5268 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5269 gchar *sender = parse_from(tmp);
5270 g_free(tmp);
5271 serv_got_chat_in(sip->gc, session->chat_id, sender,
5272 PURPLE_MESSAGE_RECV, html, time(NULL));
5273 g_free(sender);
5274 } else if (session && session->is_multiparty) { /* a multiparty chat */
5275 serv_got_chat_in(sip->gc, session->chat_id, from,
5276 PURPLE_MESSAGE_RECV, html, time(NULL));
5277 } else {
5278 serv_got_im(sip->gc, from, html, 0, time(NULL));
5280 g_free(html);
5281 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5282 found = TRUE;
5284 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5285 sipe_xml *isc = sipe_xml_parse(msg->body, msg->bodylen);
5286 const sipe_xml *state;
5287 gchar *statedata;
5289 if (!isc) {
5290 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
5291 g_free(from);
5292 return;
5295 state = sipe_xml_child(isc, "state");
5297 if (!state) {
5298 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
5299 sipe_xml_free(isc);
5300 g_free(from);
5301 return;
5304 statedata = sipe_xml_data(state);
5305 if (statedata) {
5306 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5307 else serv_got_typing_stopped(sip->gc, from);
5309 g_free(statedata);
5311 sipe_xml_free(isc);
5312 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5313 found = TRUE;
5314 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5315 GSList *body = sipe_ft_parse_msg_body(msg->body);
5316 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5317 sipe_utils_nameval_free(body);
5318 if (found) {
5319 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5322 if (!found) {
5323 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5324 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5325 if (!session) {
5326 session = sipe_session_find_im(sip, from);
5328 if (session) {
5329 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5330 from);
5331 sipe_present_err(sip, session, errmsg);
5332 g_free(errmsg);
5335 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
5336 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5338 g_free(from);
5341 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5343 gchar *body;
5344 gchar *newTag;
5345 const gchar *oldHeader;
5346 gchar *newHeader;
5347 gboolean is_multiparty = FALSE;
5348 gboolean is_triggered = FALSE;
5349 gboolean was_multiparty = TRUE;
5350 gboolean just_joined = FALSE;
5351 gchar *from;
5352 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5353 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5354 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5355 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5356 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5357 GSList *end_points = NULL;
5358 char *tmp = NULL;
5359 struct sip_session *session;
5360 const gchar *ms_text_format;
5362 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
5363 g_free(tmp);
5365 /* Invitation to join conference */
5366 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5367 process_incoming_invite_conf(sip, msg);
5368 return;
5371 /* Only accept text invitations */
5372 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5373 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5374 return;
5377 // TODO There *must* be a better way to clean up the To header to add a tag...
5378 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5379 oldHeader = sipmsg_find_header(msg, "To");
5380 newTag = gentag();
5381 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5382 sipmsg_remove_header_now(msg, "To");
5383 sipmsg_add_header_now(msg, "To", newHeader);
5384 g_free(newHeader);
5386 if (end_points_hdr) {
5387 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5389 if (g_slist_length(end_points) > 2) {
5390 is_multiparty = TRUE;
5393 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5394 is_triggered = TRUE;
5395 is_multiparty = TRUE;
5398 session = sipe_session_find_chat_by_callid(sip, callid);
5399 /* Convert to multiparty */
5400 if (session && is_multiparty && !session->is_multiparty) {
5401 g_free(session->with);
5402 session->with = NULL;
5403 was_multiparty = FALSE;
5404 session->is_multiparty = TRUE;
5405 session->chat_id = rand();
5408 if (!session && is_multiparty) {
5409 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5411 /* IM session */
5412 from = parse_from(sipmsg_find_header(msg, "From"));
5413 if (!session) {
5414 session = sipe_session_find_or_add_im(sip, from);
5417 if (session) {
5418 g_free(session->callid);
5419 session->callid = g_strdup(callid);
5421 session->is_multiparty = is_multiparty;
5422 if (roster_manager) {
5423 session->roster_manager = g_strdup(roster_manager);
5427 if (is_multiparty && end_points) {
5428 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5429 GSList *entry = end_points;
5430 while (entry) {
5431 struct sip_dialog *dialog;
5432 struct sipendpoint *end_point = entry->data;
5433 entry = entry->next;
5435 if (!g_strcasecmp(from, end_point->contact) ||
5436 !g_strcasecmp(to, end_point->contact))
5437 continue;
5439 dialog = sipe_dialog_find(session, end_point->contact);
5440 if (dialog) {
5441 g_free(dialog->theirepid);
5442 dialog->theirepid = end_point->epid;
5443 end_point->epid = NULL;
5444 } else {
5445 dialog = sipe_dialog_add(session);
5447 dialog->callid = g_strdup(session->callid);
5448 dialog->with = end_point->contact;
5449 end_point->contact = NULL;
5450 dialog->theirepid = end_point->epid;
5451 end_point->epid = NULL;
5453 just_joined = TRUE;
5455 /* send triggered INVITE */
5456 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5459 g_free(to);
5462 if (end_points) {
5463 GSList *entry = end_points;
5464 while (entry) {
5465 struct sipendpoint *end_point = entry->data;
5466 entry = entry->next;
5467 g_free(end_point->contact);
5468 g_free(end_point->epid);
5469 g_free(end_point);
5471 g_slist_free(end_points);
5474 if (session) {
5475 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5476 if (dialog) {
5477 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5478 sipe_dialog_parse_routes(dialog, msg, FALSE);
5479 } else {
5480 dialog = sipe_dialog_add(session);
5482 dialog->callid = g_strdup(session->callid);
5483 dialog->with = g_strdup(from);
5484 sipe_dialog_parse(dialog, msg, FALSE);
5486 if (!dialog->ourtag) {
5487 dialog->ourtag = newTag;
5488 newTag = NULL;
5491 just_joined = TRUE;
5493 } else {
5494 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5496 g_free(newTag);
5498 if (is_multiparty && !session->conv) {
5499 gchar *chat_title = sipe_chat_get_name(callid);
5500 gchar *self = sip_uri_self(sip);
5501 /* create prpl chat */
5502 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5503 session->chat_title = g_strdup(chat_title);
5504 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5505 /* add self */
5506 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5507 self, NULL,
5508 PURPLE_CBFLAGS_NONE, FALSE);
5509 g_free(chat_title);
5510 g_free(self);
5513 if (is_multiparty && !was_multiparty) {
5514 /* add current IM counterparty to chat */
5515 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5516 sipe_dialog_first(session)->with, NULL,
5517 PURPLE_CBFLAGS_NONE, FALSE);
5520 /* add inviting party to chat */
5521 if (just_joined && session->conv) {
5522 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5523 from, NULL,
5524 PURPLE_CBFLAGS_NONE, TRUE);
5527 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5529 /* This used only in 2005 official client, not 2007 or Reuters.
5530 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5531 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5533 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5534 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5535 if (is_multiparty ||
5536 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5538 if (ms_text_format) {
5539 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5541 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5542 if (tmp) {
5543 gsize len;
5544 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5546 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5548 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5549 sipe_utils_nameval_free(parsed_body);
5550 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5552 g_free(tmp);
5554 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5556 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5557 gchar *html = get_html_message(ms_text_format, NULL);
5558 if (html) {
5559 if (is_multiparty) {
5560 serv_got_chat_in(sip->gc, session->chat_id, from,
5561 PURPLE_MESSAGE_RECV, html, time(NULL));
5562 } else {
5563 serv_got_im(sip->gc, from, html, 0, time(NULL));
5565 g_free(html);
5566 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5572 g_free(from);
5574 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5575 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5576 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5578 body = g_strdup_printf(
5579 "v=0\r\n"
5580 "o=- 0 0 IN IP4 %s\r\n"
5581 "s=session\r\n"
5582 "c=IN IP4 %s\r\n"
5583 "t=0 0\r\n"
5584 "m=%s %d sip sip:%s\r\n"
5585 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5586 sipe_backend_network_ip_address(),
5587 sipe_backend_network_ip_address(),
5588 sip->ocs2007 ? "message" : "x-ms-message",
5589 sip->realport,
5590 sip->username);
5591 send_sip_response(sip->gc, msg, 200, "OK", body);
5592 g_free(body);
5595 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5597 gchar *body;
5599 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5600 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5601 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5603 body = g_strdup_printf(
5604 "v=0\r\n"
5605 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5606 "s=session\r\n"
5607 "c=IN IP4 0.0.0.0\r\n"
5608 "t=0 0\r\n"
5609 "m=%s %d sip sip:%s\r\n"
5610 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5611 sip->ocs2007 ? "message" : "x-ms-message",
5612 sip->realport,
5613 sip->username);
5614 send_sip_response(sip->gc, msg, 200, "OK", body);
5615 g_free(body);
5618 static const char*
5619 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5621 const char *res = "NTLM";
5622 #ifdef HAVE_LIBKRB5
5623 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5624 res = "Kerberos";
5626 #else
5627 (void) sip; /* make compiler happy */
5628 #endif
5629 return res;
5632 static void sipe_connection_cleanup(struct sipe_account_data *);
5633 static void create_connection(struct sipe_account_data *, gchar *, int);
5635 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5636 SIPE_UNUSED_PARAMETER struct transaction *trans)
5638 gchar *tmp;
5639 const gchar *expires_header;
5640 int expires, i;
5641 GSList *hdr = msg->headers;
5642 struct sipnameval *elem;
5644 expires_header = sipmsg_find_header(msg, "Expires");
5645 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5646 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
5648 switch (msg->response) {
5649 case 200:
5650 if (expires == 0) {
5651 sip->registerstatus = 0;
5652 } else {
5653 const gchar *contact_hdr;
5654 gchar *gruu = NULL;
5655 gchar *epid;
5656 gchar *uuid;
5657 gchar *timeout;
5658 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5659 const char *auth_scheme;
5661 if (!sip->reregister_set) {
5662 gchar *action_name = g_strdup_printf("<%s>", "registration");
5663 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5664 g_free(action_name);
5665 sip->reregister_set = TRUE;
5668 sip->registerstatus = 3;
5670 if (server_hdr && !sip->server_version) {
5671 sip->server_version = g_strdup(server_hdr);
5672 g_free(default_ua);
5673 default_ua = NULL;
5676 auth_scheme = sipe_get_auth_scheme_name(sip);
5677 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5679 if (tmp) {
5680 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
5681 fill_auth(tmp, &sip->registrar);
5684 if (!sip->reauthenticate_set) {
5685 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5686 guint reauth_timeout;
5687 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5688 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5689 reauth_timeout = sip->registrar.expires - 300;
5690 } else {
5691 /* NTLM: we have to reauthenticate as our security token expires
5692 after eight hours (be five minutes early) */
5693 reauth_timeout = (8 * 3600) - 300;
5695 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5696 g_free(action_name);
5697 sip->reauthenticate_set = TRUE;
5700 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5702 epid = get_epid(sip);
5703 uuid = generateUUIDfromEPID(epid);
5704 g_free(epid);
5706 // There can be multiple Contact headers (one per location where the user is logged in) so
5707 // make sure to only get the one for this uuid
5708 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5709 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5710 if (valid_contact) {
5711 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5712 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5713 g_free(valid_contact);
5714 break;
5715 } else {
5716 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5719 g_free(uuid);
5721 g_free(sip->contact);
5722 if(gruu) {
5723 sip->contact = g_strdup_printf("<%s>", gruu);
5724 g_free(gruu);
5725 } else {
5726 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5727 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, sipe_backend_network_ip_address(), TRANSPORT_DESCRIPTOR);
5729 sip->ocs2007 = FALSE;
5730 sip->batched_support = FALSE;
5732 while(hdr)
5734 elem = hdr->data;
5735 if (sipe_strcase_equal(elem->name, "Supported")) {
5736 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5737 /* We interpret this as OCS2007+ indicator */
5738 sip->ocs2007 = TRUE;
5739 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5741 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5742 sip->batched_support = TRUE;
5743 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5746 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5747 gchar **caps = g_strsplit(elem->value,",",0);
5748 i = 0;
5749 while (caps[i]) {
5750 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5751 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5752 i++;
5754 g_strfreev(caps);
5756 hdr = g_slist_next(hdr);
5759 /* rejoin open chats to be able to use them by continue to send messages */
5760 purple_conversation_foreach(sipe_rejoin_chat);
5762 /* subscriptions */
5763 if (!sip->subscribed) { //do it just once, not every re-register
5765 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5766 (GCompareFunc)g_ascii_strcasecmp)) {
5767 sipe_subscribe_roaming_contacts(sip);
5770 /* For 2007+ it does not make sence to subscribe to:
5771 * vnd-microsoft-roaming-ACL
5772 * vnd-microsoft-provisioning (not v2)
5773 * presence.wpending
5774 * These are for backward compatibility.
5776 if (sip->ocs2007)
5778 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5779 (GCompareFunc)g_ascii_strcasecmp)) {
5780 sipe_subscribe_roaming_self(sip);
5782 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5783 (GCompareFunc)g_ascii_strcasecmp)) {
5784 sipe_subscribe_roaming_provisioning_v2(sip);
5787 /* For 2005- servers */
5788 else
5790 //sipe_options_request(sip, sip->sipdomain);
5792 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5793 (GCompareFunc)g_ascii_strcasecmp)) {
5794 sipe_subscribe_roaming_acl(sip);
5796 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5797 (GCompareFunc)g_ascii_strcasecmp)) {
5798 sipe_subscribe_roaming_provisioning(sip);
5800 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5801 (GCompareFunc)g_ascii_strcasecmp)) {
5802 sipe_subscribe_presence_wpending(sip, msg);
5805 /* For 2007+ we publish our initial statuses and calendar data only after
5806 * received our existing publications in sipe_process_roaming_self()
5807 * Only in this case we know versions of current publications made
5808 * on our behalf.
5810 /* For 2005- we publish our initial statuses only after
5811 * received our existing UserInfo data in response to
5812 * self subscription.
5813 * Only in this case we won't override existing UserInfo data
5814 * set earlier or by other client on our behalf.
5818 sip->subscribed = TRUE;
5821 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5822 "timeout=", ";", NULL);
5823 if (timeout != NULL) {
5824 sscanf(timeout, "%u", &sip->keepalive_timeout);
5825 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5826 sip->keepalive_timeout);
5827 g_free(timeout);
5830 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5832 break;
5833 case 301:
5835 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5837 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5838 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5839 gchar **tmp;
5840 gchar *hostname;
5841 int port = 0;
5842 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5843 int i = 1;
5845 tmp = g_strsplit(parts[0], ":", 0);
5846 hostname = g_strdup(tmp[0]);
5847 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5848 g_strfreev(tmp);
5850 while (parts[i]) {
5851 tmp = g_strsplit(parts[i], "=", 0);
5852 if (tmp[1]) {
5853 if (g_strcasecmp("transport", tmp[0]) == 0) {
5854 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5855 transport = SIPE_TRANSPORT_TCP;
5856 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5857 transport = SIPE_TRANSPORT_UDP;
5861 g_strfreev(tmp);
5862 i++;
5864 g_strfreev(parts);
5866 /* Close old connection */
5867 sipe_connection_cleanup(sip);
5869 /* Create new connection */
5870 sip->transport = transport;
5871 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5872 hostname, port, TRANSPORT_DESCRIPTOR);
5873 create_connection(sip, hostname, port);
5875 g_free(redirect);
5877 break;
5878 case 401:
5879 if (sip->registerstatus != 2) {
5880 const char *auth_scheme;
5881 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5882 if (sip->registrar.retries > 3) {
5883 sip->gc->wants_to_die = TRUE;
5884 purple_connection_error(sip->gc, _("Authentication failed"));
5885 return TRUE;
5888 auth_scheme = sipe_get_auth_scheme_name(sip);
5889 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5891 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
5892 if (!tmp) {
5893 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5894 sip->gc->wants_to_die = TRUE;
5895 purple_connection_error(sip->gc, tmp2);
5896 g_free(tmp2);
5897 return TRUE;
5899 fill_auth(tmp, &sip->registrar);
5900 sip->registerstatus = 2;
5901 if (sip->account->disconnecting) {
5902 do_register_exp(sip, 0);
5903 } else {
5904 do_register(sip);
5907 break;
5908 case 403:
5910 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5911 gchar **reason = NULL;
5912 gchar *warning;
5913 if (diagnostics != NULL) {
5914 /* Example header:
5915 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5917 reason = g_strsplit(diagnostics, "\"", 0);
5919 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5920 (reason && reason[1]) ? reason[1] : _("no reason given"));
5921 g_strfreev(reason);
5923 sip->gc->wants_to_die = TRUE;
5924 purple_connection_error(sip->gc, warning);
5925 g_free(warning);
5926 return TRUE;
5928 break;
5929 case 404:
5931 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5932 gchar *reason = NULL;
5933 gchar *warning;
5934 if (diagnostics != NULL) {
5935 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5937 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5938 diagnostics ? (reason ? reason : _("no reason given")) :
5939 _("SIP is either not enabled for the destination URI or it does not exist"));
5940 g_free(reason);
5942 sip->gc->wants_to_die = TRUE;
5943 purple_connection_error(sip->gc, warning);
5944 g_free(warning);
5945 return TRUE;
5947 break;
5948 case 503:
5949 case 504: /* Server time-out */
5951 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5952 gchar *reason = NULL;
5953 gchar *warning;
5954 if (diagnostics != NULL) {
5955 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5957 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5958 g_free(reason);
5960 sip->gc->wants_to_die = TRUE;
5961 purple_connection_error(sip->gc, warning);
5962 g_free(warning);
5963 return TRUE;
5965 break;
5967 return TRUE;
5971 * Returns 2005-style activity and Availability.
5973 * @param status Sipe statis id.
5975 static void
5976 sipe_get_act_avail_by_status_2005(const char *status,
5977 int *activity,
5978 int *availability)
5980 int avail = 300; /* online */
5981 int act = 400; /* Available */
5983 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5984 act = 100;
5985 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5986 // act = 150;
5987 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5988 act = 300;
5989 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5990 act = 400;
5991 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5992 // act = 500;
5993 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5994 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5995 act = 600;
5996 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5997 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5998 avail = 0; /* offline */
5999 act = 100;
6000 } else {
6001 act = 400; /* Available */
6004 if (activity) *activity = act;
6005 if (availability) *availability = avail;
6009 * [MS-SIP] 2.2.1
6011 * @param activity 2005 aggregated activity. Ex.: 600
6012 * @param availablity 2005 aggregated availablity. Ex.: 300
6014 static const char *
6015 sipe_get_status_by_act_avail_2005(const int activity,
6016 const int availablity,
6017 char **activity_desc)
6019 const char *status_id = NULL;
6020 const char *act = NULL;
6022 if (activity < 150) {
6023 status_id = SIPE_STATUS_ID_AWAY;
6024 } else if (activity < 200) {
6025 //status_id = SIPE_STATUS_ID_LUNCH;
6026 status_id = SIPE_STATUS_ID_AWAY;
6027 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
6028 } else if (activity < 300) {
6029 //status_id = SIPE_STATUS_ID_IDLE;
6030 status_id = SIPE_STATUS_ID_AWAY;
6031 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6032 } else if (activity < 400) {
6033 status_id = SIPE_STATUS_ID_BRB;
6034 } else if (activity < 500) {
6035 status_id = SIPE_STATUS_ID_AVAILABLE;
6036 } else if (activity < 600) {
6037 //status_id = SIPE_STATUS_ID_ON_PHONE;
6038 status_id = SIPE_STATUS_ID_BUSY;
6039 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
6040 } else if (activity < 700) {
6041 status_id = SIPE_STATUS_ID_BUSY;
6042 } else if (activity < 800) {
6043 status_id = SIPE_STATUS_ID_AWAY;
6044 } else {
6045 status_id = SIPE_STATUS_ID_AVAILABLE;
6048 if (availablity < 100)
6049 status_id = SIPE_STATUS_ID_OFFLINE;
6051 if (activity_desc && act) {
6052 g_free(*activity_desc);
6053 *activity_desc = g_strdup(act);
6056 return status_id;
6060 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
6062 static const char*
6063 sipe_get_status_by_availability(int avail,
6064 char** activity_desc)
6066 const char *status;
6067 const char *act = NULL;
6069 if (avail < 3000) {
6070 status = SIPE_STATUS_ID_OFFLINE;
6071 } else if (avail < 4500) {
6072 status = SIPE_STATUS_ID_AVAILABLE;
6073 } else if (avail < 6000) {
6074 //status = SIPE_STATUS_ID_IDLE;
6075 status = SIPE_STATUS_ID_AVAILABLE;
6076 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6077 } else if (avail < 7500) {
6078 status = SIPE_STATUS_ID_BUSY;
6079 } else if (avail < 9000) {
6080 //status = SIPE_STATUS_ID_BUSYIDLE;
6081 status = SIPE_STATUS_ID_BUSY;
6082 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
6083 } else if (avail < 12000) {
6084 status = SIPE_STATUS_ID_DND;
6085 } else if (avail < 15000) {
6086 status = SIPE_STATUS_ID_BRB;
6087 } else if (avail < 18000) {
6088 status = SIPE_STATUS_ID_AWAY;
6089 } else {
6090 status = SIPE_STATUS_ID_OFFLINE;
6093 if (activity_desc && act) {
6094 g_free(*activity_desc);
6095 *activity_desc = g_strdup(act);
6098 return status;
6102 * Returns 2007-style availability value
6104 * @param sipe_status_id (in)
6105 * @param activity_token (out) Must be g_free()'d after use if consumed.
6107 static int
6108 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
6110 int availability;
6111 sipe_activity activity = SIPE_ACTIVITY_UNSET;
6113 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
6114 availability = 15500;
6115 if (!activity_token || !(*activity_token)) {
6116 activity = SIPE_ACTIVITY_AWAY;
6118 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
6119 availability = 12500;
6120 activity = SIPE_ACTIVITY_BRB;
6121 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
6122 availability = 9500;
6123 activity = SIPE_ACTIVITY_DND;
6124 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
6125 availability = 6500;
6126 if (!activity_token || !(*activity_token)) {
6127 activity = SIPE_ACTIVITY_BUSY;
6129 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
6130 availability = 3500;
6131 activity = SIPE_ACTIVITY_ONLINE;
6132 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
6133 availability = 0;
6134 } else {
6135 // Offline or invisible
6136 availability = 18500;
6137 activity = SIPE_ACTIVITY_OFFLINE;
6140 if (activity_token) {
6141 *activity_token = g_strdup(sipe_activity_map[activity].token);
6143 return availability;
6146 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
6148 const char *uri;
6149 sipe_xml *xn_categories;
6150 const sipe_xml *xn_category;
6151 const char *status = NULL;
6152 gboolean do_update_status = FALSE;
6153 gboolean has_note_cleaned = FALSE;
6154 gboolean has_free_busy_cleaned = FALSE;
6156 xn_categories = sipe_xml_parse(data, len);
6157 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
6159 for (xn_category = sipe_xml_child(xn_categories, "category");
6160 xn_category ;
6161 xn_category = sipe_xml_twin(xn_category) )
6163 const sipe_xml *xn_node;
6164 const char *tmp;
6165 const char *attrVar = sipe_xml_attribute(xn_category, "name");
6166 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
6167 sipe_utils_str_to_time(tmp) : 0;
6169 /* contactCard */
6170 if (sipe_strequal(attrVar, "contactCard"))
6172 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
6174 if (card) {
6175 const sipe_xml *node;
6176 /* identity - Display Name and email */
6177 node = sipe_xml_child(card, "identity");
6178 if (node) {
6179 char* display_name = sipe_xml_data(
6180 sipe_xml_child(node, "name/displayName"));
6181 char* email = sipe_xml_data(
6182 sipe_xml_child(node, "email"));
6184 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6185 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6187 g_free(display_name);
6188 g_free(email);
6190 /* company */
6191 node = sipe_xml_child(card, "company");
6192 if (node) {
6193 char* company = sipe_xml_data(node);
6194 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
6195 g_free(company);
6197 /* department */
6198 node = sipe_xml_child(card, "department");
6199 if (node) {
6200 char* department = sipe_xml_data(node);
6201 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
6202 g_free(department);
6204 /* title */
6205 node = sipe_xml_child(card, "title");
6206 if (node) {
6207 char* title = sipe_xml_data(node);
6208 sipe_update_user_info(sip, uri, TITLE_PROP, title);
6209 g_free(title);
6211 /* office */
6212 node = sipe_xml_child(card, "office");
6213 if (node) {
6214 char* office = sipe_xml_data(node);
6215 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
6216 g_free(office);
6218 /* site (url) */
6219 node = sipe_xml_child(card, "url");
6220 if (node) {
6221 char* site = sipe_xml_data(node);
6222 sipe_update_user_info(sip, uri, SITE_PROP, site);
6223 g_free(site);
6225 /* phone */
6226 for (node = sipe_xml_child(card, "phone");
6227 node;
6228 node = sipe_xml_twin(node))
6230 const char *phone_type = sipe_xml_attribute(node, "type");
6231 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
6232 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
6234 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
6236 g_free(phone);
6237 g_free(phone_display_string);
6239 /* address */
6240 for (node = sipe_xml_child(card, "address");
6241 node;
6242 node = sipe_xml_twin(node))
6244 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
6245 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
6246 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
6247 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
6248 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
6249 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
6251 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
6252 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
6253 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
6254 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
6255 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
6257 g_free(street);
6258 g_free(city);
6259 g_free(state);
6260 g_free(zipcode);
6261 g_free(country_code);
6263 break;
6268 /* note */
6269 else if (sipe_strequal(attrVar, "note"))
6271 if (uri) {
6272 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6274 if (!has_note_cleaned) {
6275 has_note_cleaned = TRUE;
6277 g_free(sbuddy->note);
6278 sbuddy->note = NULL;
6279 sbuddy->is_oof_note = FALSE;
6280 sbuddy->note_since = publish_time;
6282 do_update_status = TRUE;
6284 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6285 /* clean up in case no 'note' element is supplied
6286 * which indicate note removal in client
6288 g_free(sbuddy->note);
6289 sbuddy->note = NULL;
6290 sbuddy->is_oof_note = FALSE;
6291 sbuddy->note_since = publish_time;
6293 xn_node = sipe_xml_child(xn_category, "note/body");
6294 if (xn_node) {
6295 char *tmp;
6296 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6297 g_free(tmp);
6298 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6299 sbuddy->note_since = publish_time;
6301 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6302 uri, sbuddy->note ? sbuddy->note : "");
6304 /* to trigger UI refresh in case no status info is supplied in this update */
6305 do_update_status = TRUE;
6309 /* state */
6310 else if(sipe_strequal(attrVar, "state"))
6312 char *tmp;
6313 int availability;
6314 const sipe_xml *xn_availability;
6315 const sipe_xml *xn_activity;
6316 const sipe_xml *xn_meeting_subject;
6317 const sipe_xml *xn_meeting_location;
6318 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6320 xn_node = sipe_xml_child(xn_category, "state");
6321 if (!xn_node) continue;
6322 xn_availability = sipe_xml_child(xn_node, "availability");
6323 if (!xn_availability) continue;
6324 xn_activity = sipe_xml_child(xn_node, "activity");
6325 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6326 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6328 tmp = sipe_xml_data(xn_availability);
6329 availability = atoi(tmp);
6330 g_free(tmp);
6332 /* activity, meeting_subject, meeting_location */
6333 if (sbuddy) {
6334 char *tmp = NULL;
6336 /* activity */
6337 g_free(sbuddy->activity);
6338 sbuddy->activity = NULL;
6339 if (xn_activity) {
6340 const char *token = sipe_xml_attribute(xn_activity, "token");
6341 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6343 /* from token */
6344 if (!is_empty(token)) {
6345 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6347 /* from custom element */
6348 if (xn_custom) {
6349 char *custom = sipe_xml_data(xn_custom);
6351 if (!is_empty(custom)) {
6352 sbuddy->activity = custom;
6353 custom = NULL;
6355 g_free(custom);
6358 /* meeting_subject */
6359 g_free(sbuddy->meeting_subject);
6360 sbuddy->meeting_subject = NULL;
6361 if (xn_meeting_subject) {
6362 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6364 if (!is_empty(meeting_subject)) {
6365 sbuddy->meeting_subject = meeting_subject;
6366 meeting_subject = NULL;
6368 g_free(meeting_subject);
6370 /* meeting_location */
6371 g_free(sbuddy->meeting_location);
6372 sbuddy->meeting_location = NULL;
6373 if (xn_meeting_location) {
6374 char *meeting_location = sipe_xml_data(xn_meeting_location);
6376 if (!is_empty(meeting_location)) {
6377 sbuddy->meeting_location = meeting_location;
6378 meeting_location = NULL;
6380 g_free(meeting_location);
6383 status = sipe_get_status_by_availability(availability, &tmp);
6384 if (sbuddy->activity && tmp) {
6385 char *tmp2 = sbuddy->activity;
6387 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6388 g_free(tmp);
6389 g_free(tmp2);
6390 } else if (tmp) {
6391 sbuddy->activity = tmp;
6395 do_update_status = TRUE;
6397 /* calendarData */
6398 else if(sipe_strequal(attrVar, "calendarData"))
6400 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6401 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6402 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6404 if (sbuddy && xn_free_busy) {
6405 if (!has_free_busy_cleaned) {
6406 has_free_busy_cleaned = TRUE;
6408 g_free(sbuddy->cal_start_time);
6409 sbuddy->cal_start_time = NULL;
6411 g_free(sbuddy->cal_free_busy_base64);
6412 sbuddy->cal_free_busy_base64 = NULL;
6414 g_free(sbuddy->cal_free_busy);
6415 sbuddy->cal_free_busy = NULL;
6417 sbuddy->cal_free_busy_published = publish_time;
6420 if (publish_time >= sbuddy->cal_free_busy_published) {
6421 g_free(sbuddy->cal_start_time);
6422 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6424 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6425 15 : 0;
6427 g_free(sbuddy->cal_free_busy_base64);
6428 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6430 g_free(sbuddy->cal_free_busy);
6431 sbuddy->cal_free_busy = NULL;
6433 sbuddy->cal_free_busy_published = publish_time;
6435 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);
6439 if (sbuddy && xn_working_hours) {
6440 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6445 if (do_update_status) {
6446 if (!status) { /* no status category in this update, using contact's current status */
6447 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6448 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6449 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6450 status = purple_status_get_id(pstatus);
6453 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
6454 sipe_got_user_status(sip, uri, status);
6457 sipe_xml_free(xn_categories);
6460 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6462 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6463 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
6464 payload->host = g_strdup(host);
6465 payload->buddies = server;
6466 sipe_subscribe_presence_batched_routed(sip, payload);
6467 sipe_subscribe_presence_batched_routed_free(payload);
6470 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6472 sipe_xml *xn_list;
6473 const sipe_xml *xn_resource;
6474 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6475 g_free, NULL);
6476 GSList *server;
6477 gchar *host;
6479 xn_list = sipe_xml_parse(data, len);
6481 for (xn_resource = sipe_xml_child(xn_list, "resource");
6482 xn_resource;
6483 xn_resource = sipe_xml_twin(xn_resource) )
6485 const char *uri, *state;
6486 const sipe_xml *xn_instance;
6488 xn_instance = sipe_xml_child(xn_resource, "instance");
6489 if (!xn_instance) continue;
6491 uri = sipe_xml_attribute(xn_resource, "uri");
6492 state = sipe_xml_attribute(xn_instance, "state");
6493 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
6495 if (strstr(state, "resubscribe")) {
6496 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
6498 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6499 gchar *user = g_strdup(uri);
6500 host = g_strdup(poolFqdn);
6501 server = g_hash_table_lookup(servers, host);
6502 server = g_slist_append(server, user);
6503 g_hash_table_insert(servers, host, server);
6504 } else {
6505 sipe_subscribe_presence_single(sip, (void *) uri);
6510 /* Send out any deferred poolFqdn subscriptions */
6511 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6512 g_hash_table_destroy(servers);
6514 sipe_xml_free(xn_list);
6517 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6519 gchar *uri;
6520 gchar *getbasic;
6521 gchar *activity = NULL;
6522 sipe_xml *pidf;
6523 const sipe_xml *basicstatus = NULL, *tuple, *status;
6524 gboolean isonline = FALSE;
6525 const sipe_xml *display_name_node;
6527 pidf = sipe_xml_parse(data, len);
6528 if (!pidf) {
6529 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
6530 return;
6533 if ((tuple = sipe_xml_child(pidf, "tuple")))
6535 if ((status = sipe_xml_child(tuple, "status"))) {
6536 basicstatus = sipe_xml_child(status, "basic");
6540 if (!basicstatus) {
6541 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6542 sipe_xml_free(pidf);
6543 return;
6546 getbasic = sipe_xml_data(basicstatus);
6547 if (!getbasic) {
6548 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6549 sipe_xml_free(pidf);
6550 return;
6553 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
6554 if (strstr(getbasic, "open")) {
6555 isonline = TRUE;
6557 g_free(getbasic);
6559 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6561 display_name_node = sipe_xml_child(pidf, "display-name");
6562 if (display_name_node) {
6563 char * display_name = sipe_xml_data(display_name_node);
6565 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6566 g_free(display_name);
6569 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
6570 if ((status = sipe_xml_child(tuple, "status"))) {
6571 if ((basicstatus = sipe_xml_child(status, "activities"))) {
6572 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
6573 activity = sipe_xml_data(basicstatus);
6574 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
6580 if (isonline) {
6581 const gchar * status_id = NULL;
6582 if (activity) {
6583 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6584 status_id = SIPE_STATUS_ID_BUSY;
6585 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6586 status_id = SIPE_STATUS_ID_AWAY;
6590 if (!status_id) {
6591 status_id = SIPE_STATUS_ID_AVAILABLE;
6594 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
6595 sipe_got_user_status(sip, uri, status_id);
6596 } else {
6597 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6600 g_free(activity);
6601 g_free(uri);
6602 sipe_xml_free(pidf);
6605 /** 2005 */
6606 static void
6607 sipe_user_info_has_updated(struct sipe_account_data *sip,
6608 const sipe_xml *xn_userinfo)
6610 const sipe_xml *xn_states;
6612 g_free(sip->user_states);
6613 sip->user_states = NULL;
6614 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
6615 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
6617 /* this is a hack-around to remove added newline after inner element,
6618 * state in this case, where it shouldn't be.
6619 * After several use of sipe_xml_stringify, amount of added newlines
6620 * grows significantly.
6622 if (orig) {
6623 gchar c, *stripped = orig;
6624 while ((c = *orig++)) {
6625 if ((c != '\n') /* && (c != '\r') */) {
6626 *stripped++ = c;
6629 *stripped = '\0';
6633 /* Publish initial state if not yet.
6634 * Assuming this happens on initial responce to self subscription
6635 * so we've already updated our UserInfo.
6637 if (!sip->initial_state_published) {
6638 send_presence_soap(sip, FALSE);
6639 /* dalayed run */
6640 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6644 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6646 char *activity = NULL;
6647 const char *epid;
6648 const char *status_id = NULL;
6649 const char *name;
6650 char *uri;
6651 char *self_uri = sip_uri_self(sip);
6652 int avl;
6653 int act;
6654 const char *device_name = NULL;
6655 const char *cal_start_time = NULL;
6656 const char *cal_granularity = NULL;
6657 char *cal_free_busy_base64 = NULL;
6658 struct sipe_buddy *sbuddy;
6659 const sipe_xml *node;
6660 sipe_xml *xn_presentity;
6661 const sipe_xml *xn_availability;
6662 const sipe_xml *xn_activity;
6663 const sipe_xml *xn_display_name;
6664 const sipe_xml *xn_email;
6665 const sipe_xml *xn_phone_number;
6666 const sipe_xml *xn_userinfo;
6667 const sipe_xml *xn_note;
6668 const sipe_xml *xn_oof;
6669 const sipe_xml *xn_state;
6670 const sipe_xml *xn_contact;
6671 char *note;
6672 char *free_activity;
6673 int user_avail;
6674 const char *user_avail_nil;
6675 int res_avail;
6676 time_t user_avail_since = 0;
6677 time_t activity_since = 0;
6679 /* fix for Reuters environment on Linux */
6680 if (data && strstr(data, "encoding=\"utf-16\"")) {
6681 char *tmp_data;
6682 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6683 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
6684 g_free(tmp_data);
6685 } else {
6686 xn_presentity = sipe_xml_parse(data, len);
6689 xn_availability = sipe_xml_child(xn_presentity, "availability");
6690 xn_activity = sipe_xml_child(xn_presentity, "activity");
6691 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
6692 xn_email = sipe_xml_child(xn_presentity, "email");
6693 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
6694 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
6695 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
6696 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
6697 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
6698 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
6699 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
6700 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
6701 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
6702 note = xn_note ? sipe_xml_data(xn_note) : NULL;
6704 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6705 user_avail = 0;
6706 user_avail_since = 0;
6709 free_activity = NULL;
6711 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
6712 uri = sip_uri_from_name(name);
6713 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
6714 epid = sipe_xml_attribute(xn_availability, "epid");
6715 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
6717 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6718 res_avail = sipe_get_availability_by_status(status_id, NULL);
6719 if (user_avail > res_avail) {
6720 res_avail = user_avail;
6721 status_id = sipe_get_status_by_availability(user_avail, NULL);
6724 if (xn_display_name) {
6725 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
6726 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
6727 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
6728 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
6729 char *tel_uri = sip_to_tel_uri(phone_number);
6731 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6732 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6733 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6734 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6736 g_free(tel_uri);
6737 g_free(phone_label);
6738 g_free(phone_number);
6739 g_free(email);
6740 g_free(display_name);
6743 if (xn_contact) {
6744 /* tel */
6745 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
6747 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6748 const char *phone_type = sipe_xml_attribute(node, "type");
6749 char* phone = sipe_xml_data(node);
6751 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6753 g_free(phone);
6757 /* devicePresence */
6758 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
6759 const sipe_xml *xn_device_name;
6760 const sipe_xml *xn_calendar_info;
6761 const sipe_xml *xn_state;
6762 char *state;
6764 /* deviceName */
6765 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
6766 xn_device_name = sipe_xml_child(node, "deviceName");
6767 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
6770 /* calendarInfo */
6771 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
6772 if (xn_calendar_info) {
6773 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
6775 if (cal_start_time) {
6776 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6777 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6779 if (cal_start_time_t_tmp > cal_start_time_t) {
6780 cal_start_time = cal_start_time_tmp;
6781 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6782 g_free(cal_free_busy_base64);
6783 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6785 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);
6787 } else {
6788 cal_start_time = cal_start_time_tmp;
6789 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6790 g_free(cal_free_busy_base64);
6791 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6793 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);
6797 /* state */
6798 xn_state = sipe_xml_child(node, "states/state");
6799 if (xn_state) {
6800 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
6801 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
6803 state = sipe_xml_data(xn_state);
6804 if (dev_avail_since > user_avail_since &&
6805 dev_avail >= res_avail)
6807 res_avail = dev_avail;
6808 if (!is_empty(state))
6810 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6811 g_free(activity);
6812 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6813 } else if (sipe_strequal(state, "presenting")) {
6814 g_free(activity);
6815 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6816 } else {
6817 activity = state;
6818 state = NULL;
6820 activity_since = dev_avail_since;
6822 status_id = sipe_get_status_by_availability(res_avail, &activity);
6824 g_free(state);
6828 /* oof */
6829 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6830 g_free(activity);
6831 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6832 activity_since = 0;
6835 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6836 if (sbuddy)
6838 g_free(sbuddy->activity);
6839 sbuddy->activity = activity;
6840 activity = NULL;
6842 sbuddy->activity_since = activity_since;
6844 sbuddy->user_avail = user_avail;
6845 sbuddy->user_avail_since = user_avail_since;
6847 g_free(sbuddy->note);
6848 sbuddy->note = NULL;
6849 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6851 sbuddy->is_oof_note = (xn_oof != NULL);
6853 g_free(sbuddy->device_name);
6854 sbuddy->device_name = NULL;
6855 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6857 if (!is_empty(cal_free_busy_base64)) {
6858 g_free(sbuddy->cal_start_time);
6859 sbuddy->cal_start_time = g_strdup(cal_start_time);
6861 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6863 g_free(sbuddy->cal_free_busy_base64);
6864 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6865 cal_free_busy_base64 = NULL;
6867 g_free(sbuddy->cal_free_busy);
6868 sbuddy->cal_free_busy = NULL;
6871 sbuddy->last_non_cal_status_id = status_id;
6872 g_free(sbuddy->last_non_cal_activity);
6873 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6875 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6876 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6878 sip->is_oof_note = sbuddy->is_oof_note;
6880 g_free(sip->note);
6881 sip->note = g_strdup(sbuddy->note);
6883 sip->note_since = time(NULL);
6886 g_free(sip->status);
6887 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6890 g_free(cal_free_busy_base64);
6891 g_free(activity);
6893 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
6894 sipe_got_user_status(sip, uri, status_id);
6896 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6897 sipe_user_info_has_updated(sip, xn_userinfo);
6900 g_free(note);
6901 sipe_xml_free(xn_presentity);
6902 g_free(uri);
6903 g_free(self_uri);
6906 static void sipe_presence_mime_cb(gpointer user_data,
6907 const gchar *type,
6908 const gchar *body,
6909 gsize length)
6911 if (strstr(type,"application/rlmi+xml")) {
6912 process_incoming_notify_rlmi_resub(user_data, body, length);
6913 } else if (strstr(type, "text/xml+msrtc.pidf")) {
6914 process_incoming_notify_msrtc(user_data, body, length);
6915 } else {
6916 process_incoming_notify_rlmi(user_data, body, length);
6920 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6922 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6924 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
6926 if (ctype &&
6927 (strstr(ctype, "application/rlmi+xml") ||
6928 strstr(ctype, "application/msrtc-event-categories+xml")))
6930 if (strstr(ctype, "multipart"))
6932 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sip);
6934 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6936 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6938 else if(strstr(ctype, "application/rlmi+xml"))
6940 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6943 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6945 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6947 else
6949 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6953 static void sipe_presence_timeout_mime_cb(gpointer user_data,
6954 SIPE_UNUSED_PARAMETER const gchar *type,
6955 const gchar *body,
6956 gsize length)
6958 GSList **buddies = user_data;
6959 sipe_xml *xml = sipe_xml_parse(body, length);
6961 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
6962 const gchar *uri = sipe_xml_attribute(xml, "uri");
6963 const sipe_xml *xn_category;
6966 * automaton: presence is never expected to change
6968 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
6970 for (xn_category = sipe_xml_child(xml, "category");
6971 xn_category;
6972 xn_category = sipe_xml_twin(xn_category)) {
6973 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
6974 "contactCard")) {
6975 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
6976 if (node) {
6977 char *boolean = sipe_xml_data(node);
6978 if (sipe_strequal(boolean, "true")) {
6979 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
6980 uri);
6981 uri = NULL;
6983 g_free(boolean);
6985 break;
6989 if (uri) {
6990 *buddies = g_slist_append(*buddies, sip_uri(uri));
6994 sipe_xml_free(xml);
6997 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6999 const char *ctype = sipmsg_find_header(msg, "Content-Type");
7000 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7002 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
7004 if (ctype &&
7005 strstr(ctype, "multipart") &&
7006 (strstr(ctype, "application/rlmi+xml") ||
7007 strstr(ctype, "application/msrtc-event-categories+xml"))) {
7008 GSList *buddies = NULL;
7010 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
7012 if (buddies) {
7013 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
7014 payload->host = g_strdup(who);
7015 payload->buddies = buddies;
7016 sipe_schedule_action(action_name, timeout,
7017 sipe_subscribe_presence_batched_routed,
7018 sipe_subscribe_presence_batched_routed_free,
7019 sip, payload);
7020 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", 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 with batched support(%s) in %d", who, timeout);
7027 g_free(action_name);
7031 * Dispatcher for all incoming subscription information
7032 * whether it comes from NOTIFY, BENOTIFY requests or
7033 * piggy-backed to subscription's OK responce.
7035 * @param request whether initiated from BE/NOTIFY request or OK-response message.
7036 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
7038 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
7040 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
7041 const gchar *event = sipmsg_find_header(msg, "Event");
7042 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
7043 char *tmp;
7045 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
7046 event ? event : "",
7047 tmp = fix_newlines(msg->body));
7048 g_free(tmp);
7049 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
7051 /* implicit subscriptions */
7052 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
7053 sipe_process_imdn(sip, msg);
7056 if (event) {
7057 /* for one off subscriptions (send with Expire: 0) */
7058 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
7060 sipe_process_provisioning_v2(sip, msg);
7062 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
7064 sipe_process_provisioning(sip, msg);
7066 else if (sipe_strcase_equal(event, "presence"))
7068 sipe_process_presence(sip, msg);
7070 else if (sipe_strcase_equal(event, "registration-notify"))
7072 sipe_process_registration_notify(sip, msg);
7075 if (!subscription_state || strstr(subscription_state, "active"))
7077 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
7079 sipe_process_roaming_contacts(sip, msg);
7081 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
7083 sipe_process_roaming_self(sip, msg);
7085 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
7087 sipe_process_roaming_acl(sip, msg);
7089 else if (sipe_strcase_equal(event, "presence.wpending"))
7091 sipe_process_presence_wpending(sip, msg);
7093 else if (sipe_strcase_equal(event, "conference"))
7095 sipe_process_conference(sip, msg);
7100 /* The server sends status 'terminated' */
7101 if (subscription_state && strstr(subscription_state, "terminated") ) {
7102 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
7103 gchar *key = sipe_get_subscription_key(event, who);
7105 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
7106 g_free(who);
7108 if (g_hash_table_lookup(sip->subscriptions, key)) {
7109 g_hash_table_remove(sip->subscriptions, key);
7110 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
7113 g_free(key);
7116 if (!request && event) {
7117 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
7118 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
7119 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
7121 if (timeout) {
7122 /* 2 min ahead of expiration */
7123 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
7125 if (sipe_strcase_equal(event, "presence.wpending") &&
7126 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
7128 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
7129 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
7130 g_free(action_name);
7132 else if (sipe_strcase_equal(event, "presence") &&
7133 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
7135 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
7136 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7138 if (sip->batched_support) {
7139 sipe_process_presence_timeout(sip, msg, who, timeout);
7141 else {
7142 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7143 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
7145 g_free(action_name);
7146 g_free(who);
7151 /* The client responses on received a NOTIFY message */
7152 if (request && !benotify)
7154 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7159 * Whether user manually changed status or
7160 * it was changed automatically due to user
7161 * became inactive/active again
7163 static gboolean
7164 sipe_is_user_state(struct sipe_account_data *sip)
7166 gboolean res;
7167 time_t now = time(NULL);
7169 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
7170 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
7172 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
7174 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
7175 return res;
7178 static void
7179 send_presence_soap0(struct sipe_account_data *sip,
7180 gboolean do_publish_calendar,
7181 gboolean do_reset_status)
7183 struct sipe_ews* ews = sip->ews;
7184 int availability = 0;
7185 int activity = 0;
7186 gchar *body;
7187 gchar *tmp;
7188 gchar *tmp2 = NULL;
7189 gchar *res_note = NULL;
7190 gchar *res_oof = NULL;
7191 const gchar *note_pub = NULL;
7192 gchar *states = NULL;
7193 gchar *calendar_data = NULL;
7194 gchar *epid = get_epid(sip);
7195 time_t now = time(NULL);
7196 gchar *since_time_str = sipe_utils_time_to_str(now);
7197 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
7198 const char *user_input;
7199 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
7201 if (oof_note && sip->note) {
7202 SIPE_DEBUG_INFO("ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
7203 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
7206 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
7208 if (!sip->initial_state_published ||
7209 do_reset_status)
7211 g_free(sip->status);
7212 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
7215 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
7217 /* Note */
7218 if (pub_oof) {
7219 note_pub = oof_note;
7220 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
7221 ews->published = TRUE;
7222 } else if (sip->note) {
7223 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
7224 g_free(sip->note);
7225 sip->note = NULL;
7226 sip->is_oof_note = FALSE;
7227 sip->note_since = 0;
7228 } else {
7229 note_pub = sip->note;
7230 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
7234 if (note_pub)
7236 /* to protocol internal plain text format */
7237 tmp = sipe_backend_markup_strip_html(note_pub);
7238 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
7239 g_free(tmp);
7242 /* User State */
7243 if (!do_reset_status) {
7244 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
7246 gchar *activity_token = NULL;
7247 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
7249 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
7250 avail_2007,
7251 since_time_str,
7252 epid,
7253 activity_token);
7254 g_free(activity_token);
7256 else /* preserve existing publication */
7258 if (sip->user_states) {
7259 states = g_strdup(sip->user_states);
7262 } else {
7263 /* do nothing - then User state will be erased */
7265 sip->initial_state_published = TRUE;
7267 /* CalendarInfo */
7268 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
7270 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7271 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7272 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
7273 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
7274 fb_start_str,
7275 free_busy_base64);
7276 g_free(fb_start_str);
7277 g_free(free_busy_base64);
7280 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
7282 /* forming resulting XML */
7283 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7284 sip->username,
7285 availability,
7286 activity,
7287 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7288 res_note ? res_note : "",
7289 res_oof ? res_oof : "",
7290 states ? states : "",
7291 calendar_data ? calendar_data : "",
7292 epid,
7293 since_time_str,
7294 since_time_str,
7295 user_input);
7296 g_free(tmp);
7297 g_free(tmp2);
7298 g_free(res_note);
7299 g_free(states);
7300 g_free(calendar_data);
7302 send_soap_request(sip, body);
7304 g_free(body);
7305 g_free(since_time_str);
7306 g_free(epid);
7309 void
7310 send_presence_soap(struct sipe_account_data *sip,
7311 gboolean do_publish_calendar)
7313 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7317 static gboolean
7318 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7319 struct sipmsg *msg,
7320 struct transaction *trans)
7322 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7324 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7325 sipe_xml *xml;
7326 const sipe_xml *node;
7327 gchar *fault_code;
7328 GHashTable *faults;
7329 int index_our;
7330 gboolean has_device_publication = FALSE;
7332 xml = sipe_xml_parse(msg->body, msg->bodylen);
7334 /* test if version mismatch fault */
7335 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
7336 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7337 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
7338 g_free(fault_code);
7339 sipe_xml_free(xml);
7340 return TRUE;
7342 g_free(fault_code);
7344 /* accumulating information about faulty versions */
7345 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7346 for (node = sipe_xml_child(xml, "details/operation");
7347 node;
7348 node = sipe_xml_twin(node))
7350 const gchar *index = sipe_xml_attribute(node, "index");
7351 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
7353 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7354 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
7356 sipe_xml_free(xml);
7358 /* here we are parsing own request to figure out what publication
7359 * referensed here only by index went wrong
7361 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
7363 /* publication */
7364 for (node = sipe_xml_child(xml, "publications/publication"),
7365 index_our = 1; /* starts with 1 - our first publication */
7366 node;
7367 node = sipe_xml_twin(node), index_our++)
7369 gchar *idx = g_strdup_printf("%d", index_our);
7370 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7371 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
7372 g_free(idx);
7374 if (sipe_strequal("device", categoryName)) {
7375 has_device_publication = TRUE;
7378 if (curVersion) { /* fault exist on this index */
7379 const gchar *container = sipe_xml_attribute(node, "container");
7380 const gchar *instance = sipe_xml_attribute(node, "instance");
7381 /* key is <category><instance><container> */
7382 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7383 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
7385 if (category) {
7386 struct sipe_publication *publication =
7387 g_hash_table_lookup(category, key);
7389 SIPE_DEBUG_INFO("key is %s", key);
7391 if (publication) {
7392 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7393 key, curVersion, publication->version);
7394 /* updating publication's version to the correct one */
7395 publication->version = atoi(curVersion);
7397 } else {
7398 /* We somehow lost this category from our publications... */
7399 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
7400 publication->category = g_strdup(categoryName);
7401 publication->instance = atoi(instance);
7402 publication->container = atoi(container);
7403 publication->version = atoi(curVersion);
7404 category = g_hash_table_new_full(g_str_hash, g_str_equal,
7405 g_free, (GDestroyNotify)free_publication);
7406 g_hash_table_insert(category, g_strdup(key), publication);
7407 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
7408 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
7410 g_free(key);
7413 sipe_xml_free(xml);
7414 g_hash_table_destroy(faults);
7416 /* rebublishing with right versions */
7417 if (has_device_publication) {
7418 send_publish_category_initial(sip);
7419 } else {
7420 send_presence_status(sip);
7423 return TRUE;
7427 * Returns 'device' XML part for publication.
7428 * Must be g_free'd after use.
7430 static gchar *
7431 sipe_publish_get_category_device(struct sipe_account_data *sip)
7433 gchar *uri;
7434 gchar *doc;
7435 gchar *epid = get_epid(sip);
7436 gchar *uuid = generateUUIDfromEPID(epid);
7437 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7438 /* key is <category><instance><container> */
7439 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7440 struct sipe_publication *publication =
7441 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7443 g_free(key);
7444 g_free(epid);
7446 uri = sip_uri_self(sip);
7447 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7448 device_instance,
7449 publication ? publication->version : 0,
7450 uuid,
7451 uri,
7452 "00:00:00+01:00", /* @TODO make timezone real*/
7453 g_get_host_name()
7456 g_free(uri);
7457 g_free(uuid);
7459 return doc;
7463 * A service method - use
7464 * - send_publish_get_category_state_machine and
7465 * - send_publish_get_category_state_user instead.
7466 * Must be g_free'd after use.
7468 static gchar *
7469 sipe_publish_get_category_state(struct sipe_account_data *sip,
7470 gboolean is_user_state)
7472 int availability = sipe_get_availability_by_status(sip->status, NULL);
7473 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7474 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7475 /* key is <category><instance><container> */
7476 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7477 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7478 struct sipe_publication *publication_2 =
7479 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7480 struct sipe_publication *publication_3 =
7481 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7483 g_free(key_2);
7484 g_free(key_3);
7486 if (publication_2 && (publication_2->availability == availability))
7488 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7489 return NULL; /* nothing to update */
7492 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7493 instance,
7494 publication_2 ? publication_2->version : 0,
7495 availability,
7496 instance,
7497 publication_3 ? publication_3->version : 0,
7498 availability);
7502 * Only Busy and OOF calendar event are published.
7503 * Different instances are used for that.
7505 * Must be g_free'd after use.
7507 static gchar *
7508 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7509 struct sipe_cal_event *event,
7510 const char *uri,
7511 int cal_satus)
7513 gchar *start_time_str;
7514 int availability = 0;
7515 gchar *res;
7516 gchar *tmp = NULL;
7517 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7518 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7519 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7521 /* key is <category><instance><container> */
7522 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7523 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7524 struct sipe_publication *publication_2 =
7525 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7526 struct sipe_publication *publication_3 =
7527 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7529 g_free(key_2);
7530 g_free(key_3);
7532 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7533 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7534 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
7535 return NULL;
7538 if (event &&
7539 publication_3 &&
7540 (publication_3->availability == availability) &&
7541 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7543 g_free(tmp);
7544 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7545 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
7546 return NULL; /* nothing to update */
7548 g_free(tmp);
7550 if (event &&
7551 (event->cal_status == SIPE_CAL_BUSY ||
7552 event->cal_status == SIPE_CAL_OOF))
7554 gchar *availability_xml_str = NULL;
7555 gchar *activity_xml_str = NULL;
7557 if (event->cal_status == SIPE_CAL_BUSY) {
7558 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7561 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7562 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7563 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7564 "minAvailability=\"6500\"",
7565 "maxAvailability=\"8999\"");
7566 } else if (event->cal_status == SIPE_CAL_OOF) {
7567 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7568 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7569 "minAvailability=\"12000\"",
7570 "");
7572 start_time_str = sipe_utils_time_to_str(event->start_time);
7574 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7575 instance,
7576 publication_2 ? publication_2->version : 0,
7577 uri,
7578 start_time_str,
7579 availability_xml_str ? availability_xml_str : "",
7580 activity_xml_str ? activity_xml_str : "",
7581 event->subject ? event->subject : "",
7582 event->location ? event->location : "",
7584 instance,
7585 publication_3 ? publication_3->version : 0,
7586 uri,
7587 start_time_str,
7588 availability_xml_str ? availability_xml_str : "",
7589 activity_xml_str ? activity_xml_str : "",
7590 event->subject ? event->subject : "",
7591 event->location ? event->location : ""
7593 g_free(start_time_str);
7594 g_free(availability_xml_str);
7595 g_free(activity_xml_str);
7598 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7600 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7601 instance,
7602 publication_2 ? publication_2->version : 0,
7604 instance,
7605 publication_3 ? publication_3->version : 0
7609 return res;
7613 * Returns 'machineState' XML part for publication.
7614 * Must be g_free'd after use.
7616 static gchar *
7617 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7619 return sipe_publish_get_category_state(sip, FALSE);
7623 * Returns 'userState' XML part for publication.
7624 * Must be g_free'd after use.
7626 static gchar *
7627 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7629 return sipe_publish_get_category_state(sip, TRUE);
7633 * Returns 'note' XML part for publication.
7634 * Must be g_free'd after use.
7636 * Protocol format for Note is plain text.
7638 * @param note a note in Sipe internal HTML format
7639 * @param note_type either personal or OOF
7641 static gchar *
7642 sipe_publish_get_category_note(struct sipe_account_data *sip,
7643 const char *note, /* html */
7644 const char *note_type,
7645 time_t note_start,
7646 time_t note_end)
7648 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7649 /* key is <category><instance><container> */
7650 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7651 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7652 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7654 struct sipe_publication *publication_note_200 =
7655 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7656 struct sipe_publication *publication_note_300 =
7657 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7658 struct sipe_publication *publication_note_400 =
7659 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7661 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7662 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7663 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7664 char *res, *tmp1, *tmp2, *tmp3;
7665 char *start_time_attr;
7666 char *end_time_attr;
7668 g_free(tmp);
7669 tmp = NULL;
7670 g_free(key_note_200);
7671 g_free(key_note_300);
7672 g_free(key_note_400);
7674 /* we even need to republish empty note */
7675 if (sipe_strequal(n1, n2))
7677 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7678 g_free(n1);
7679 return NULL; /* nothing to update */
7682 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7683 g_free(tmp);
7684 tmp = NULL;
7685 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7686 g_free(tmp);
7688 if (n1) {
7689 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7690 instance,
7691 200,
7692 publication_note_200 ? publication_note_200->version : 0,
7693 note_type,
7694 start_time_attr ? start_time_attr : "",
7695 end_time_attr ? end_time_attr : "",
7696 n1);
7698 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7699 instance,
7700 300,
7701 publication_note_300 ? publication_note_300->version : 0,
7702 note_type,
7703 start_time_attr ? start_time_attr : "",
7704 end_time_attr ? end_time_attr : "",
7705 n1);
7707 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7708 instance,
7709 400,
7710 publication_note_400 ? publication_note_400->version : 0,
7711 note_type,
7712 start_time_attr ? start_time_attr : "",
7713 end_time_attr ? end_time_attr : "",
7714 n1);
7715 } else {
7716 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7717 "note",
7718 instance,
7719 200,
7720 publication_note_200 ? publication_note_200->version : 0,
7721 "static");
7722 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7723 "note",
7724 instance,
7725 300,
7726 publication_note_200 ? publication_note_200->version : 0,
7727 "static");
7728 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7729 "note",
7730 instance,
7731 400,
7732 publication_note_200 ? publication_note_200->version : 0,
7733 "static");
7735 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7737 g_free(start_time_attr);
7738 g_free(end_time_attr);
7739 g_free(tmp1);
7740 g_free(tmp2);
7741 g_free(tmp3);
7742 g_free(n1);
7744 return res;
7748 * Returns 'calendarData' XML part with WorkingHours for publication.
7749 * Must be g_free'd after use.
7751 static gchar *
7752 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7754 struct sipe_ews* ews = sip->ews;
7756 /* key is <category><instance><container> */
7757 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7758 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7759 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7760 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7761 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7762 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7764 struct sipe_publication *publication_cal_1 =
7765 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7766 struct sipe_publication *publication_cal_100 =
7767 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7768 struct sipe_publication *publication_cal_200 =
7769 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7770 struct sipe_publication *publication_cal_300 =
7771 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7772 struct sipe_publication *publication_cal_400 =
7773 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7774 struct sipe_publication *publication_cal_32000 =
7775 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7777 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7778 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7780 g_free(key_cal_1);
7781 g_free(key_cal_100);
7782 g_free(key_cal_200);
7783 g_free(key_cal_300);
7784 g_free(key_cal_400);
7785 g_free(key_cal_32000);
7787 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7788 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7789 return NULL;
7792 if (sipe_strequal(n1, n2))
7794 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7795 return NULL; /* nothing to update */
7798 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7799 /* 1 */
7800 publication_cal_1 ? publication_cal_1->version : 0,
7801 ews->email,
7802 ews->working_hours_xml_str,
7803 /* 100 - Public */
7804 publication_cal_100 ? publication_cal_100->version : 0,
7805 /* 200 - Company */
7806 publication_cal_200 ? publication_cal_200->version : 0,
7807 ews->email,
7808 ews->working_hours_xml_str,
7809 /* 300 - Team */
7810 publication_cal_300 ? publication_cal_300->version : 0,
7811 ews->email,
7812 ews->working_hours_xml_str,
7813 /* 400 - Personal */
7814 publication_cal_400 ? publication_cal_400->version : 0,
7815 ews->email,
7816 ews->working_hours_xml_str,
7817 /* 32000 - Blocked */
7818 publication_cal_32000 ? publication_cal_32000->version : 0
7823 * Returns 'calendarData' XML part with FreeBusy for publication.
7824 * Must be g_free'd after use.
7826 static gchar *
7827 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7829 struct sipe_ews* ews = sip->ews;
7830 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7831 char *fb_start_str;
7832 char *free_busy_base64;
7833 const char *st;
7834 const char *fb;
7835 char *res;
7837 /* key is <category><instance><container> */
7838 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7839 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7840 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7841 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7842 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7843 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7845 struct sipe_publication *publication_cal_1 =
7846 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7847 struct sipe_publication *publication_cal_100 =
7848 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7849 struct sipe_publication *publication_cal_200 =
7850 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7851 struct sipe_publication *publication_cal_300 =
7852 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7853 struct sipe_publication *publication_cal_400 =
7854 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7855 struct sipe_publication *publication_cal_32000 =
7856 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7858 g_free(key_cal_1);
7859 g_free(key_cal_100);
7860 g_free(key_cal_200);
7861 g_free(key_cal_300);
7862 g_free(key_cal_400);
7863 g_free(key_cal_32000);
7865 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7866 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7867 return NULL;
7870 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7871 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7873 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7874 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7876 /* we will rebuplish the same data to refresh publication time,
7877 * so if data from multiple sources, most recent will be choosen
7879 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7881 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7882 // g_free(fb_start_str);
7883 // g_free(free_busy_base64);
7884 // return NULL; /* nothing to update */
7887 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7888 /* 1 */
7889 cal_data_instance,
7890 publication_cal_1 ? publication_cal_1->version : 0,
7891 /* 100 - Public */
7892 cal_data_instance,
7893 publication_cal_100 ? publication_cal_100->version : 0,
7894 /* 200 - Company */
7895 cal_data_instance,
7896 publication_cal_200 ? publication_cal_200->version : 0,
7897 ews->email,
7898 fb_start_str,
7899 free_busy_base64,
7900 /* 300 - Team */
7901 cal_data_instance,
7902 publication_cal_300 ? publication_cal_300->version : 0,
7903 ews->email,
7904 fb_start_str,
7905 free_busy_base64,
7906 /* 400 - Personal */
7907 cal_data_instance,
7908 publication_cal_400 ? publication_cal_400->version : 0,
7909 ews->email,
7910 fb_start_str,
7911 free_busy_base64,
7912 /* 32000 - Blocked */
7913 cal_data_instance,
7914 publication_cal_32000 ? publication_cal_32000->version : 0
7917 g_free(fb_start_str);
7918 g_free(free_busy_base64);
7919 return res;
7922 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7924 gchar *uri;
7925 gchar *doc;
7926 gchar *tmp;
7927 gchar *hdr;
7929 uri = sip_uri_self(sip);
7930 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7931 uri,
7932 publications);
7934 tmp = get_contact(sip);
7935 hdr = g_strdup_printf("Contact: %s\r\n"
7936 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7938 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7940 g_free(tmp);
7941 g_free(hdr);
7942 g_free(uri);
7943 g_free(doc);
7946 static void
7947 send_publish_category_initial(struct sipe_account_data *sip)
7949 gchar *pub_device = sipe_publish_get_category_device(sip);
7950 gchar *pub_machine;
7951 gchar *publications;
7953 g_free(sip->status);
7954 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7956 pub_machine = sipe_publish_get_category_state_machine(sip);
7957 publications = g_strdup_printf("%s%s",
7958 pub_device,
7959 pub_machine ? pub_machine : "");
7960 g_free(pub_device);
7961 g_free(pub_machine);
7963 send_presence_publish(sip, publications);
7964 g_free(publications);
7967 static void
7968 send_presence_category_publish(struct sipe_account_data *sip)
7970 gchar *pub_state = sipe_is_user_state(sip) ?
7971 sipe_publish_get_category_state_user(sip) :
7972 sipe_publish_get_category_state_machine(sip);
7973 gchar *pub_note = sipe_publish_get_category_note(sip,
7974 sip->note,
7975 sip->is_oof_note ? "OOF" : "personal",
7978 gchar *publications;
7980 if (!pub_state && !pub_note) {
7981 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
7982 return;
7985 publications = g_strdup_printf("%s%s",
7986 pub_state ? pub_state : "",
7987 pub_note ? pub_note : "");
7989 g_free(pub_state);
7990 g_free(pub_note);
7992 send_presence_publish(sip, publications);
7993 g_free(publications);
7997 * Publishes self status
7998 * based on own calendar information.
8000 * For 2007+
8002 void
8003 publish_calendar_status_self(struct sipe_account_data *sip)
8005 struct sipe_cal_event* event = NULL;
8006 gchar *pub_cal_working_hours = NULL;
8007 gchar *pub_cal_free_busy = NULL;
8008 gchar *pub_calendar = NULL;
8009 gchar *pub_calendar2 = NULL;
8010 gchar *pub_oof_note = NULL;
8011 const gchar *oof_note;
8012 time_t oof_start = 0;
8013 time_t oof_end = 0;
8015 if (!sip->ews) {
8016 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
8017 return;
8020 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
8021 if (sip->ews->cal_events) {
8022 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
8025 if (!event) {
8026 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
8027 } else {
8028 char *desc = sipe_cal_event_describe(event);
8029 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
8030 g_free(desc);
8033 /* Logic
8034 if OOF
8035 OOF publish, Busy clean
8036 ilse if Busy
8037 OOF clean, Busy publish
8038 else
8039 OOF clean, Busy clean
8041 if (event && event->cal_status == SIPE_CAL_OOF) {
8042 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
8043 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8044 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
8045 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8046 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
8047 } else {
8048 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8049 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8052 oof_note = sipe_ews_get_oof_note(sip->ews);
8053 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
8054 oof_start = sip->ews->oof_start;
8055 oof_end = sip->ews->oof_end;
8057 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
8059 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
8060 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
8062 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
8063 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
8064 } else {
8065 gchar *publications = g_strdup_printf("%s%s%s%s%s",
8066 pub_cal_working_hours ? pub_cal_working_hours : "",
8067 pub_cal_free_busy ? pub_cal_free_busy : "",
8068 pub_calendar ? pub_calendar : "",
8069 pub_calendar2 ? pub_calendar2 : "",
8070 pub_oof_note ? pub_oof_note : "");
8072 send_presence_publish(sip, publications);
8073 g_free(publications);
8076 g_free(pub_cal_working_hours);
8077 g_free(pub_cal_free_busy);
8078 g_free(pub_calendar);
8079 g_free(pub_calendar2);
8080 g_free(pub_oof_note);
8082 /* repeat scheduling */
8083 sipe_sched_calendar_status_self_publish(sip, time(NULL));
8086 static void send_presence_status(struct sipe_account_data *sip)
8088 PurpleStatus * status = purple_account_get_active_status(sip->account);
8090 if (!status) return;
8092 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
8093 purple_status_get_id(status) ? purple_status_get_id(status) : "",
8094 sipe_is_user_state(sip) ? "USER" : "MACHINE");
8096 if (sip->ocs2007) {
8097 send_presence_category_publish(sip);
8098 } else {
8099 send_presence_soap(sip, FALSE);
8103 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
8105 gboolean found = FALSE;
8106 const char *method = msg->method ? msg->method : "NOT FOUND";
8107 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
8108 if (msg->response == 0) { /* request */
8109 if (sipe_strequal(method, "MESSAGE")) {
8110 process_incoming_message(sip, msg);
8111 found = TRUE;
8112 } else if (sipe_strequal(method, "NOTIFY")) {
8113 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
8114 process_incoming_notify(sip, msg, TRUE, FALSE);
8115 found = TRUE;
8116 } else if (sipe_strequal(method, "BENOTIFY")) {
8117 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
8118 process_incoming_notify(sip, msg, TRUE, TRUE);
8119 found = TRUE;
8120 } else if (sipe_strequal(method, "INVITE")) {
8121 process_incoming_invite(sip, msg);
8122 found = TRUE;
8123 } else if (sipe_strequal(method, "REFER")) {
8124 process_incoming_refer(sip, msg);
8125 found = TRUE;
8126 } else if (sipe_strequal(method, "OPTIONS")) {
8127 process_incoming_options(sip, msg);
8128 found = TRUE;
8129 } else if (sipe_strequal(method, "INFO")) {
8130 process_incoming_info(sip, msg);
8131 found = TRUE;
8132 } else if (sipe_strequal(method, "ACK")) {
8133 // ACK's don't need any response
8134 found = TRUE;
8135 } else if (sipe_strequal(method, "SUBSCRIBE")) {
8136 // LCS 2005 sends us these - just respond 200 OK
8137 found = TRUE;
8138 send_sip_response(sip->gc, msg, 200, "OK", NULL);
8139 } else if (sipe_strequal(method, "BYE")) {
8140 process_incoming_bye(sip, msg);
8141 found = TRUE;
8142 } else {
8143 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
8145 } else { /* response */
8146 struct transaction *trans = transactions_find(sip, msg);
8147 if (trans) {
8148 if (msg->response == 407) {
8149 gchar *resend, *auth;
8150 const gchar *ptmp;
8152 if (sip->proxy.retries > 30) return;
8153 sip->proxy.retries++;
8154 /* do proxy authentication */
8156 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
8158 fill_auth(ptmp, &sip->proxy);
8159 auth = auth_header(sip, &sip->proxy, trans->msg);
8160 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
8161 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
8162 g_free(auth);
8163 resend = sipmsg_to_string(trans->msg);
8164 /* resend request */
8165 sendout_pkt(sip->gc, resend);
8166 g_free(resend);
8167 } else {
8168 if (msg->response < 200) {
8169 /* ignore provisional response */
8170 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
8171 } else {
8172 sip->proxy.retries = 0;
8173 if (sipe_strequal(trans->msg->method, "REGISTER")) {
8174 if (msg->response == 401)
8176 sip->registrar.retries++;
8178 else
8180 sip->registrar.retries = 0;
8182 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
8183 } else {
8184 if (msg->response == 401) {
8185 gchar *resend, *auth, *ptmp;
8186 const char* auth_scheme;
8188 if (sip->registrar.retries > 4) return;
8189 sip->registrar.retries++;
8191 auth_scheme = sipe_get_auth_scheme_name(sip);
8192 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
8194 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
8195 if (!ptmp) {
8196 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
8197 sip->gc->wants_to_die = TRUE;
8198 purple_connection_error(sip->gc, tmp2);
8199 g_free(tmp2);
8200 return;
8203 fill_auth(ptmp, &sip->registrar);
8204 auth = auth_header(sip, &sip->registrar, trans->msg);
8205 sipmsg_remove_header_now(trans->msg, "Authorization");
8206 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
8207 g_free(auth);
8208 resend = sipmsg_to_string(trans->msg);
8209 /* resend request */
8210 sendout_pkt(sip->gc, resend);
8211 g_free(resend);
8215 if (trans->callback) {
8216 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
8217 /* call the callback to process response*/
8218 (trans->callback)(sip, msg, trans);
8221 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
8222 transactions_remove(sip, trans);
8226 found = TRUE;
8227 } else {
8228 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
8231 if (!found) {
8232 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
8236 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
8238 char *cur;
8239 char *dummy;
8240 char *tmp;
8241 struct sipmsg *msg;
8242 int restlen;
8243 cur = conn->inbuf;
8245 /* according to the RFC remove CRLF at the beginning */
8246 while (*cur == '\r' || *cur == '\n') {
8247 cur++;
8249 if (cur != conn->inbuf) {
8250 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
8251 conn->inbufused = strlen(conn->inbuf);
8254 /* Received a full Header? */
8255 sip->processing_input = TRUE;
8256 while (sip->processing_input &&
8257 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
8258 time_t currtime = time(NULL);
8259 cur += 2;
8260 cur[0] = '\0';
8261 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
8262 g_free(tmp);
8263 msg = sipmsg_parse_header(conn->inbuf);
8264 cur[0] = '\r';
8265 cur += 2;
8266 restlen = conn->inbufused - (cur - conn->inbuf);
8267 if (msg && restlen >= msg->bodylen) {
8268 dummy = g_malloc(msg->bodylen + 1);
8269 memcpy(dummy, cur, msg->bodylen);
8270 dummy[msg->bodylen] = '\0';
8271 msg->body = dummy;
8272 cur += msg->bodylen;
8273 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
8274 conn->inbufused = strlen(conn->inbuf);
8275 } else {
8276 if (msg){
8277 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen, msg->bodylen, (int)strlen(conn->inbuf));
8278 sipmsg_free(msg);
8280 return;
8283 /*if (msg->body) {
8284 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8287 // Verify the signature before processing it
8288 if (sip->registrar.gssapi_context) {
8289 struct sipmsg_breakdown msgbd;
8290 gchar *signature_input_str;
8291 gchar *rspauth;
8292 msgbd.msg = msg;
8293 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8294 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8296 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8298 if (rspauth != NULL) {
8299 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8300 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8301 process_input_message(sip, msg);
8302 } else {
8303 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8304 purple_connection_error(sip->gc, _("Invalid message signature received"));
8305 sip->gc->wants_to_die = TRUE;
8307 } else if (msg->response == 401) {
8308 purple_connection_error(sip->gc, _("Authentication failed"));
8309 sip->gc->wants_to_die = TRUE;
8311 g_free(signature_input_str);
8313 g_free(rspauth);
8314 sipmsg_breakdown_free(&msgbd);
8315 } else {
8316 process_input_message(sip, msg);
8319 sipmsg_free(msg);
8323 static void sipe_udp_process(gpointer data, gint source,
8324 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8326 PurpleConnection *gc = data;
8327 struct sipe_account_data *sip = gc->proto_data;
8328 int len;
8330 static char buffer[65536];
8331 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8332 time_t currtime = time(NULL);
8333 struct sipmsg *msg;
8334 buffer[len] = '\0';
8335 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), buffer);
8336 msg = sipmsg_parse_msg(buffer);
8337 if (msg) process_input_message(sip, msg);
8341 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8343 struct sipe_account_data *sip = gc->proto_data;
8344 PurpleSslConnection *gsc = sip->gsc;
8346 SIPE_DEBUG_ERROR("%s", debug);
8347 purple_connection_error(gc, msg);
8349 /* Invalidate this connection. Next send will open a new one */
8350 if (gsc) {
8351 connection_remove(sip, gsc->fd);
8352 purple_ssl_close(gsc);
8354 sip->gsc = NULL;
8355 sip->fd = -1;
8358 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8359 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8361 PurpleConnection *gc = data;
8362 struct sipe_account_data *sip;
8363 struct sip_connection *conn;
8364 int readlen, len;
8365 gboolean firstread = TRUE;
8367 /* NOTE: This check *IS* necessary */
8368 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8369 purple_ssl_close(gsc);
8370 return;
8373 sip = gc->proto_data;
8374 conn = connection_find(sip, gsc->fd);
8375 if (conn == NULL) {
8376 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
8377 gc->wants_to_die = TRUE;
8378 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8379 return;
8382 /* Read all available data from the SSL connection */
8383 do {
8384 /* Increase input buffer size as needed */
8385 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8386 conn->inbuflen += SIMPLE_BUF_INC;
8387 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8388 SIPE_DEBUG_INFO("sipe_input_cb_ssl: new input buffer length %d", conn->inbuflen);
8391 /* Try to read as much as there is space left in the buffer */
8392 readlen = conn->inbuflen - conn->inbufused - 1;
8393 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8395 if (len < 0 && errno == EAGAIN) {
8396 /* Try again later */
8397 return;
8398 } else if (len < 0) {
8399 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8400 return;
8401 } else if (firstread && (len == 0)) {
8402 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8403 return;
8406 conn->inbufused += len;
8407 firstread = FALSE;
8409 /* Equivalence indicates that there is possibly more data to read */
8410 } while (len == readlen);
8412 conn->inbuf[conn->inbufused] = '\0';
8413 process_input(sip, conn);
8417 static void sipe_input_cb(gpointer data, gint source,
8418 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8420 PurpleConnection *gc = data;
8421 struct sipe_account_data *sip = gc->proto_data;
8422 int len;
8423 struct sip_connection *conn = connection_find(sip, source);
8424 if (!conn) {
8425 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found!");
8426 return;
8429 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8430 conn->inbuflen += SIMPLE_BUF_INC;
8431 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8434 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8436 if (len < 0 && errno == EAGAIN)
8437 return;
8438 else if (len <= 0) {
8439 SIPE_DEBUG_INFO_NOFORMAT("sipe_input_cb: read error");
8440 connection_remove(sip, source);
8441 if (sip->fd == source) sip->fd = -1;
8442 return;
8445 conn->inbufused += len;
8446 conn->inbuf[conn->inbufused] = '\0';
8448 process_input(sip, conn);
8451 /* Callback for new connections on incoming TCP port */
8452 static void sipe_newconn_cb(gpointer data, gint source,
8453 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8455 PurpleConnection *gc = data;
8456 struct sipe_account_data *sip = gc->proto_data;
8457 struct sip_connection *conn;
8459 int newfd = accept(source, NULL, NULL);
8461 conn = connection_create(sip, newfd);
8463 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8466 static void login_cb(gpointer data, gint source,
8467 SIPE_UNUSED_PARAMETER const gchar *error_message)
8469 PurpleConnection *gc = data;
8470 struct sipe_account_data *sip;
8471 struct sip_connection *conn;
8473 if (!PURPLE_CONNECTION_IS_VALID(gc))
8475 if (source >= 0)
8476 close(source);
8477 return;
8480 if (source < 0) {
8481 purple_connection_error(gc, _("Could not connect"));
8482 return;
8485 sip = gc->proto_data;
8486 sip->fd = source;
8487 sip->last_keepalive = time(NULL);
8489 conn = connection_create(sip, source);
8491 do_register(sip);
8493 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8496 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8497 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8499 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8500 if (sip == NULL) return;
8502 do_register(sip);
8505 static guint sipe_ht_hash_nick(const char *nick)
8507 char *lc = g_utf8_strdown(nick, -1);
8508 guint bucket = g_str_hash(lc);
8509 g_free(lc);
8511 return bucket;
8514 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8516 char *nick1_norm = NULL;
8517 char *nick2_norm = NULL;
8518 gboolean equal;
8520 if (nick1 == NULL && nick2 == NULL) return TRUE;
8521 if (nick1 == NULL || nick2 == NULL ||
8522 !g_utf8_validate(nick1, -1, NULL) ||
8523 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
8525 nick1_norm = g_utf8_casefold(nick1, -1);
8526 nick2_norm = g_utf8_casefold(nick2, -1);
8527 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
8528 g_free(nick2_norm);
8529 g_free(nick1_norm);
8531 return equal;
8534 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8536 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8538 sip->listen_data = NULL;
8540 if (listenfd == -1) {
8541 purple_connection_error(sip->gc, _("Could not create listen socket"));
8542 return;
8545 sip->fd = listenfd;
8547 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8548 sip->listenfd = sip->fd;
8550 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8552 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8553 do_register(sip);
8556 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8557 SIPE_UNUSED_PARAMETER const char *error_message)
8559 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8561 sip->query_data = NULL;
8563 if (!hosts || !hosts->data) {
8564 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8565 return;
8568 hosts = g_slist_remove(hosts, hosts->data);
8569 g_free(sip->serveraddr);
8570 sip->serveraddr = hosts->data;
8571 hosts = g_slist_remove(hosts, hosts->data);
8572 while (hosts) {
8573 void *tmp = hosts->data;
8574 hosts = g_slist_remove(hosts, tmp);
8575 hosts = g_slist_remove(hosts, tmp);
8576 g_free(tmp);
8579 /* create socket for incoming connections */
8580 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8581 sipe_udp_host_resolved_listen_cb, sip);
8582 if (sip->listen_data == NULL) {
8583 purple_connection_error(sip->gc, _("Could not create listen socket"));
8584 return;
8588 static const struct sipe_service_data *current_service = NULL;
8590 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8591 PurpleSslErrorType error,
8592 gpointer data)
8594 PurpleConnection *gc = data;
8595 struct sipe_account_data *sip;
8597 /* If the connection is already disconnected, we don't need to do anything else */
8598 if (!PURPLE_CONNECTION_IS_VALID(gc))
8599 return;
8601 sip = gc->proto_data;
8602 current_service = sip->service_data;
8603 if (current_service) {
8604 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8605 current_service->transport ? current_service->transport : "NULL",
8606 current_service->service ? current_service->service : "NULL");
8609 sip->fd = -1;
8610 sip->gsc = NULL;
8612 switch(error) {
8613 case PURPLE_SSL_CONNECT_FAILED:
8614 purple_connection_error(gc, _("Connection failed"));
8615 break;
8616 case PURPLE_SSL_HANDSHAKE_FAILED:
8617 purple_connection_error(gc, _("SSL handshake failed"));
8618 break;
8619 case PURPLE_SSL_CERTIFICATE_INVALID:
8620 purple_connection_error(gc, _("SSL certificate invalid"));
8621 break;
8625 static void
8626 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8628 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8629 PurpleProxyConnectData *connect_data;
8631 sip->listen_data = NULL;
8633 sip->listenfd = listenfd;
8634 if (sip->listenfd == -1) {
8635 purple_connection_error(sip->gc, _("Could not create listen socket"));
8636 return;
8639 SIPE_DEBUG_INFO("listenfd: %d", sip->listenfd);
8640 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8641 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8642 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8643 sipe_newconn_cb, sip->gc);
8644 SIPE_DEBUG_INFO("connecting to %s port %d",
8645 sip->realhostname, sip->realport);
8646 /* open tcp connection to the server */
8647 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8648 sip->realport, login_cb, sip->gc);
8650 if (connect_data == NULL) {
8651 purple_connection_error(sip->gc, _("Could not create socket"));
8655 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8657 PurpleAccount *account = sip->account;
8658 PurpleConnection *gc = sip->gc;
8660 if (port == 0) {
8661 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8664 sip->realhostname = hostname;
8665 sip->realport = port;
8667 SIPE_DEBUG_INFO("create_connection - hostname: %s port: %d",
8668 hostname, port);
8670 /* TODO: is there a good default grow size? */
8671 if (sip->transport != SIPE_TRANSPORT_UDP)
8672 sip->txbuf = purple_circ_buffer_new(0);
8674 if (sip->transport == SIPE_TRANSPORT_TLS) {
8675 /* SSL case */
8676 if (!purple_ssl_is_supported()) {
8677 gc->wants_to_die = TRUE;
8678 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8679 return;
8682 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
8684 sip->gsc = purple_ssl_connect(account, hostname, port,
8685 login_cb_ssl, sipe_ssl_connect_failure, gc);
8686 if (sip->gsc == NULL) {
8687 purple_connection_error(gc, _("Could not create SSL context"));
8688 return;
8690 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8691 /* UDP case */
8692 SIPE_DEBUG_INFO_NOFORMAT("using UDP");
8694 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8695 if (sip->query_data == NULL) {
8696 purple_connection_error(gc, _("Could not resolve hostname"));
8698 } else {
8699 /* TCP case */
8700 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
8701 /* create socket for incoming connections */
8702 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8703 sipe_tcp_connect_listen_cb, sip);
8704 if (sip->listen_data == NULL) {
8705 purple_connection_error(gc, _("Could not create listen socket"));
8706 return;
8711 /* Service list for autodection */
8712 static const struct sipe_service_data service_autodetect[] = {
8713 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8714 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8715 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8716 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8717 { NULL, NULL, 0 }
8720 /* Service list for SSL/TLS */
8721 static const struct sipe_service_data service_tls[] = {
8722 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8723 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8724 { NULL, NULL, 0 }
8727 /* Service list for TCP */
8728 static const struct sipe_service_data service_tcp[] = {
8729 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8730 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8731 { NULL, NULL, 0 }
8734 /* Service list for UDP */
8735 static const struct sipe_service_data service_udp[] = {
8736 { "sip", "udp", SIPE_TRANSPORT_UDP },
8737 { NULL, NULL, 0 }
8740 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8741 static void resolve_next_service(struct sipe_account_data *sip,
8742 const struct sipe_service_data *start)
8744 if (start) {
8745 sip->service_data = start;
8746 } else {
8747 sip->service_data++;
8748 if (sip->service_data->service == NULL) {
8749 gchar *hostname;
8750 /* Try connecting to the SIP hostname directly */
8751 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8752 if (sip->auto_transport) {
8753 // If SSL is supported, default to using it; OCS servers aren't configured
8754 // by default to accept TCP
8755 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8756 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8757 SIPE_DEBUG_INFO_NOFORMAT("set transport type..");
8760 hostname = g_strdup(sip->sipdomain);
8761 create_connection(sip, hostname, 0);
8762 return;
8766 /* Try to resolve next service */
8767 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8768 sip->service_data->transport,
8769 sip->sipdomain,
8770 srvresolved, sip);
8773 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8775 struct sipe_account_data *sip = data;
8777 sip->srv_query_data = NULL;
8779 /* find the host to connect to */
8780 if (results) {
8781 gchar *hostname = g_strdup(resp->hostname);
8782 int port = resp->port;
8783 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8784 hostname, port);
8785 g_free(resp);
8787 sip->transport = sip->service_data->type;
8789 create_connection(sip, hostname, port);
8790 } else {
8791 resolve_next_service(sip, NULL);
8795 static void sipe_login(PurpleAccount *account)
8797 PurpleConnection *gc;
8798 struct sipe_account_data *sip;
8799 gchar **signinname_login, **userserver;
8800 const char *transport;
8801 const char *email;
8803 const char *username = purple_account_get_username(account);
8804 gc = purple_account_get_connection(account);
8806 SIPE_DEBUG_INFO("sipe_login: username '%s'", username);
8808 if (strpbrk(username, "\t\v\r\n") != NULL) {
8809 gc->wants_to_die = TRUE;
8810 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8811 return;
8814 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8815 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8816 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8817 sip->gc = gc;
8818 sip->account = account;
8819 sip->reregister_set = FALSE;
8820 sip->reauthenticate_set = FALSE;
8821 sip->subscribed = FALSE;
8822 sip->subscribed_buddies = FALSE;
8823 sip->initial_state_published = FALSE;
8825 /* username format: <username>,[<optional login>] */
8826 signinname_login = g_strsplit(username, ",", 2);
8827 SIPE_DEBUG_INFO("sipe_login: signinname[0] '%s'", signinname_login[0]);
8829 /* ensure that username format is name@domain */
8830 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8831 g_strfreev(signinname_login);
8832 gc->wants_to_die = TRUE;
8833 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8834 return;
8836 sip->username = g_strdup(signinname_login[0]);
8838 /* ensure that email format is name@domain if provided */
8839 email = purple_account_get_string(sip->account, "email", NULL);
8840 if (!is_empty(email) &&
8841 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8843 gc->wants_to_die = TRUE;
8844 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8845 return;
8847 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8849 /* login name specified? */
8850 if (signinname_login[1] && strlen(signinname_login[1])) {
8851 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8852 gboolean has_domain = domain_user[1] != NULL;
8853 SIPE_DEBUG_INFO("sipe_login: signinname[1] '%s'", signinname_login[1]);
8854 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8855 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8856 SIPE_DEBUG_INFO("sipe_login: auth domain '%s' user '%s'",
8857 sip->authdomain ? sip->authdomain : "", sip->authuser);
8858 g_strfreev(domain_user);
8861 userserver = g_strsplit(signinname_login[0], "@", 2);
8862 SIPE_DEBUG_INFO("sipe_login: user '%s' server '%s'", userserver[0], userserver[1]);
8863 purple_connection_set_display_name(gc, userserver[0]);
8864 sip->sipdomain = g_strdup(userserver[1]);
8865 g_strfreev(userserver);
8866 g_strfreev(signinname_login);
8868 if (strchr(sip->username, ' ') != NULL) {
8869 gc->wants_to_die = TRUE;
8870 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8871 return;
8874 sip->password = g_strdup(purple_connection_get_password(gc));
8876 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8877 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8878 g_free, (GDestroyNotify)g_hash_table_destroy);
8879 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8880 g_free, (GDestroyNotify)sipe_subscription_free);
8882 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8884 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8886 g_free(sip->status);
8887 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8889 sip->auto_transport = FALSE;
8890 transport = purple_account_get_string(account, "transport", "auto");
8891 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8892 if (userserver[0]) {
8893 /* Use user specified server[:port] */
8894 int port = 0;
8896 if (userserver[1])
8897 port = atoi(userserver[1]);
8899 SIPE_DEBUG_INFO("sipe_login: user specified SIP server %s:%d",
8900 userserver[0], port);
8902 if (sipe_strequal(transport, "auto")) {
8903 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8904 } else if (sipe_strequal(transport, "tls")) {
8905 sip->transport = SIPE_TRANSPORT_TLS;
8906 } else if (sipe_strequal(transport, "tcp")) {
8907 sip->transport = SIPE_TRANSPORT_TCP;
8908 } else {
8909 sip->transport = SIPE_TRANSPORT_UDP;
8912 create_connection(sip, g_strdup(userserver[0]), port);
8913 } else {
8914 /* Server auto-discovery */
8915 if (sipe_strequal(transport, "auto")) {
8916 sip->auto_transport = TRUE;
8917 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8918 current_service++;
8919 resolve_next_service(sip, current_service);
8920 } else {
8921 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8923 } else if (sipe_strequal(transport, "tls")) {
8924 resolve_next_service(sip, service_tls);
8925 } else if (sipe_strequal(transport, "tcp")) {
8926 resolve_next_service(sip, service_tcp);
8927 } else {
8928 resolve_next_service(sip, service_udp);
8931 g_strfreev(userserver);
8934 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8936 connection_free_all(sip);
8938 g_free(sip->epid);
8939 sip->epid = NULL;
8941 if (sip->query_data != NULL)
8942 purple_dnsquery_destroy(sip->query_data);
8943 sip->query_data = NULL;
8945 if (sip->srv_query_data != NULL)
8946 purple_srv_cancel(sip->srv_query_data);
8947 sip->srv_query_data = NULL;
8949 if (sip->listen_data != NULL)
8950 purple_network_listen_cancel(sip->listen_data);
8951 sip->listen_data = NULL;
8953 if (sip->gsc != NULL)
8954 purple_ssl_close(sip->gsc);
8955 sip->gsc = NULL;
8957 sipe_auth_free(&sip->registrar);
8958 sipe_auth_free(&sip->proxy);
8960 if (sip->txbuf)
8961 purple_circ_buffer_destroy(sip->txbuf);
8962 sip->txbuf = NULL;
8964 g_free(sip->realhostname);
8965 sip->realhostname = NULL;
8967 g_free(sip->server_version);
8968 sip->server_version = NULL;
8970 if (sip->listenpa)
8971 purple_input_remove(sip->listenpa);
8972 sip->listenpa = 0;
8973 if (sip->tx_handler)
8974 purple_input_remove(sip->tx_handler);
8975 sip->tx_handler = 0;
8976 if (sip->resendtimeout)
8977 purple_timeout_remove(sip->resendtimeout);
8978 sip->resendtimeout = 0;
8979 if (sip->timeouts) {
8980 GSList *entry = sip->timeouts;
8981 while (entry) {
8982 struct scheduled_action *sched_action = entry->data;
8983 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
8984 purple_timeout_remove(sched_action->timeout_handler);
8985 if (sched_action->destroy) {
8986 (*sched_action->destroy)(sched_action->payload);
8988 g_free(sched_action->name);
8989 g_free(sched_action);
8990 entry = entry->next;
8993 g_slist_free(sip->timeouts);
8995 if (sip->allow_events) {
8996 GSList *entry = sip->allow_events;
8997 while (entry) {
8998 g_free(entry->data);
8999 entry = entry->next;
9002 g_slist_free(sip->allow_events);
9004 if (sip->containers) {
9005 GSList *entry = sip->containers;
9006 while (entry) {
9007 free_container((struct sipe_container *)entry->data);
9008 entry = entry->next;
9011 g_slist_free(sip->containers);
9013 if (sip->contact)
9014 g_free(sip->contact);
9015 sip->contact = NULL;
9016 if (sip->regcallid)
9017 g_free(sip->regcallid);
9018 sip->regcallid = NULL;
9020 if (sip->serveraddr)
9021 g_free(sip->serveraddr);
9022 sip->serveraddr = NULL;
9024 if (sip->focus_factory_uri)
9025 g_free(sip->focus_factory_uri);
9026 sip->focus_factory_uri = NULL;
9028 sip->fd = -1;
9029 sip->processing_input = FALSE;
9031 if (sip->ews) {
9032 sipe_ews_free(sip->ews);
9034 sip->ews = NULL;
9038 * A callback for g_hash_table_foreach_remove
9040 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
9041 SIPE_UNUSED_PARAMETER gpointer user_data)
9043 sipe_free_buddy((struct sipe_buddy *) buddy);
9045 /* We must return TRUE as the key/value have already been deleted */
9046 return(TRUE);
9049 static void sipe_close(PurpleConnection *gc)
9051 struct sipe_account_data *sip = gc->proto_data;
9053 if (sip) {
9054 /* leave all conversations */
9055 sipe_session_close_all(sip);
9056 sipe_session_remove_all(sip);
9058 if (sip->csta) {
9059 sip_csta_close(sip);
9062 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
9063 /* unsubscribe all */
9064 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
9066 /* unregister */
9067 do_register_exp(sip, 0);
9070 sipe_connection_cleanup(sip);
9071 g_free(sip->sipdomain);
9072 g_free(sip->username);
9073 g_free(sip->email);
9074 g_free(sip->password);
9075 g_free(sip->authdomain);
9076 g_free(sip->authuser);
9077 g_free(sip->status);
9078 g_free(sip->note);
9079 g_free(sip->user_states);
9081 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
9082 g_hash_table_destroy(sip->buddies);
9083 g_hash_table_destroy(sip->our_publications);
9084 g_hash_table_destroy(sip->user_state_publications);
9085 g_hash_table_destroy(sip->subscriptions);
9086 g_hash_table_destroy(sip->filetransfers);
9088 if (sip->groups) {
9089 GSList *entry = sip->groups;
9090 while (entry) {
9091 struct sipe_group *group = entry->data;
9092 g_free(group->name);
9093 g_free(group);
9094 entry = entry->next;
9097 g_slist_free(sip->groups);
9099 if (sip->our_publication_keys) {
9100 GSList *entry = sip->our_publication_keys;
9101 while (entry) {
9102 g_free(entry->data);
9103 entry = entry->next;
9106 g_slist_free(sip->our_publication_keys);
9108 while (sip->transactions)
9109 transactions_remove(sip, sip->transactions->data);
9111 g_free(gc->proto_data);
9112 gc->proto_data = NULL;
9115 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
9116 SIPE_UNUSED_PARAMETER void *user_data)
9118 PurpleAccount *acct = purple_connection_get_account(gc);
9119 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
9120 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
9121 if (conv == NULL)
9122 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
9123 purple_conversation_present(conv);
9124 g_free(id);
9127 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
9128 SIPE_UNUSED_PARAMETER void *user_data)
9131 purple_blist_request_add_buddy(purple_connection_get_account(gc),
9132 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
9135 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
9136 SIPE_UNUSED_PARAMETER struct transaction *trans)
9138 PurpleNotifySearchResults *results;
9139 PurpleNotifySearchColumn *column;
9140 sipe_xml *searchResults;
9141 const sipe_xml *mrow;
9142 int match_count = 0;
9143 gboolean more = FALSE;
9144 gchar *secondary;
9146 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
9148 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
9149 if (!searchResults) {
9150 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
9151 return FALSE;
9154 results = purple_notify_searchresults_new();
9156 if (results == NULL) {
9157 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
9158 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
9160 sipe_xml_free(searchResults);
9161 return FALSE;
9164 column = purple_notify_searchresults_column_new(_("User name"));
9165 purple_notify_searchresults_column_add(results, column);
9167 column = purple_notify_searchresults_column_new(_("Name"));
9168 purple_notify_searchresults_column_add(results, column);
9170 column = purple_notify_searchresults_column_new(_("Company"));
9171 purple_notify_searchresults_column_add(results, column);
9173 column = purple_notify_searchresults_column_new(_("Country"));
9174 purple_notify_searchresults_column_add(results, column);
9176 column = purple_notify_searchresults_column_new(_("Email"));
9177 purple_notify_searchresults_column_add(results, column);
9179 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
9180 GList *row = NULL;
9182 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
9183 row = g_list_append(row, g_strdup(uri_parts[1]));
9184 g_strfreev(uri_parts);
9186 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
9187 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
9188 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
9189 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
9191 purple_notify_searchresults_row_add(results, row);
9192 match_count++;
9195 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
9196 char *data = sipe_xml_data(mrow);
9197 more = (g_strcasecmp(data, "true") == 0);
9198 g_free(data);
9201 secondary = g_strdup_printf(
9202 dngettext(PACKAGE_NAME,
9203 "Found %d contact%s:",
9204 "Found %d contacts%s:", match_count),
9205 match_count, more ? _(" (more matched your query)") : "");
9207 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
9208 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
9209 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
9211 g_free(secondary);
9212 sipe_xml_free(searchResults);
9213 return TRUE;
9216 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
9218 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
9219 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
9220 unsigned i = 0;
9222 if (!attrs) return;
9224 do {
9225 PurpleRequestField *field = entries->data;
9226 const char *id = purple_request_field_get_id(field);
9227 const char *value = purple_request_field_string_get_value(field);
9229 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
9231 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
9232 } while ((entries = g_list_next(entries)) != NULL);
9233 attrs[i] = NULL;
9235 if (i > 0) {
9236 struct sipe_account_data *sip = gc->proto_data;
9237 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9238 gchar *query = g_strjoinv(NULL, attrs);
9239 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
9240 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
9241 send_soap_request_with_cb(sip, domain_uri, body,
9242 (TransCallback) process_search_contact_response, NULL);
9243 g_free(domain_uri);
9244 g_free(body);
9245 g_free(query);
9248 g_strfreev(attrs);
9251 static void sipe_show_find_contact(PurplePluginAction *action)
9253 PurpleConnection *gc = (PurpleConnection *) action->context;
9254 PurpleRequestFields *fields;
9255 PurpleRequestFieldGroup *group;
9256 PurpleRequestField *field;
9258 fields = purple_request_fields_new();
9259 group = purple_request_field_group_new(NULL);
9260 purple_request_fields_add_group(fields, group);
9262 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
9263 purple_request_field_group_add_field(group, field);
9264 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
9265 purple_request_field_group_add_field(group, field);
9266 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
9267 purple_request_field_group_add_field(group, field);
9268 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
9269 purple_request_field_group_add_field(group, field);
9271 purple_request_fields(gc,
9272 _("Search"),
9273 _("Search for a contact"),
9274 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
9275 fields,
9276 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
9277 _("_Cancel"), NULL,
9278 purple_connection_get_account(gc), NULL, NULL, gc);
9281 static void sipe_show_about_plugin(PurplePluginAction *action)
9283 PurpleConnection *gc = (PurpleConnection *) action->context;
9284 char *tmp = g_strdup_printf(
9286 * Non-translatable parts, like markup, are hard-coded
9287 * into the format string. This requires more translatable
9288 * texts but it makes the translations less error prone.
9290 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
9291 "<br/>"
9292 /* 1 */ "%s:<br/>"
9293 "<li> - MS Office Communications Server 2007 R2</li><br/>"
9294 "<li> - MS Office Communications Server 2007</li><br/>"
9295 "<li> - MS Live Communications Server 2005</li><br/>"
9296 "<li> - MS Live Communications Server 2003</li><br/>"
9297 "<li> - Reuters Messaging</li><br/>"
9298 "<br/>"
9299 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
9300 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
9301 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
9302 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
9303 /* 8 */ "%s: GPLv2+<br/>"
9304 "<br/>"
9305 /* 9 */ "%s:<br/>"
9306 " - CERN<br/>"
9307 " - Reuters Messaging network<br/>"
9308 " - Deutsche Bank<br/>"
9309 " - Merrill Lynch<br/>"
9310 " - Wachovia<br/>"
9311 " - Intel<br/>"
9312 " - Nokia<br/>"
9313 " - HP<br/>"
9314 " - Symantec<br/>"
9315 " - Accenture<br/>"
9316 " - Capgemini<br/>"
9317 " - Siemens<br/>"
9318 " - Alcatel-Lucent<br/>"
9319 " - BT<br/>"
9320 "<br/>"
9321 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9322 "<br/>"
9323 /* 12 */ "<b>%s:</b><br/>"
9324 " - Anibal Avelar<br/>"
9325 " - Gabriel Burt<br/>"
9326 " - Stefan Becker<br/>"
9327 " - pier11<br/>"
9328 " - Jakub Adam<br/>"
9329 " - Tomáš Hrabčík<br/>"
9330 "<br/>"
9331 /* 13 */ "%s<br/>"
9333 /* The next 13 texts make up the SIPE about note text */
9334 /* About note, part 1/13: introduction */
9335 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9336 /* About note, part 2/13: home page URL (label) */
9337 _("Home"),
9338 /* About note, part 3/13: support forum URL (label) */
9339 _("Support"),
9340 /* About note, part 4/13: support forum name (hyperlink text) */
9341 _("Help Forum"),
9342 /* About note, part 5/13: bug tracker URL (label) */
9343 _("Report Problems"),
9344 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9345 _("Bug Tracker"),
9346 /* About note, part 7/13: translation service URL (label) */
9347 _("Translations"),
9348 /* About note, part 8/13: license type (label) */
9349 _("License"),
9350 /* About note, part 9/13: known users */
9351 _("We support users in such organizations as"),
9352 /* About note, part 10/13: translation request, text before Transifex.net URL */
9353 /* append a space if text is not empty */
9354 _("Please help us to translate SIPE to your native language here at "),
9355 /* About note, part 11/13: translation request, text after Transifex.net URL */
9356 /* start with a space if text is not empty */
9357 _(" using convenient web interface"),
9358 /* About note, part 12/13: author list (header) */
9359 _("Authors"),
9360 /* About note, part 13/13: Localization credit */
9361 /* PLEASE NOTE: do *NOT* simply translate the english original */
9362 /* but write something similar to the following sentence: */
9363 /* "Localization for <language name> (<language code>): <name>" */
9364 _("Original texts in English (en): SIPE developers")
9366 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9367 g_free(tmp);
9370 static void sipe_republish_calendar(PurplePluginAction *action)
9372 PurpleConnection *gc = (PurpleConnection *) action->context;
9373 struct sipe_account_data *sip = gc->proto_data;
9375 sipe_update_calendar(sip);
9378 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9379 gpointer value,
9380 GString* str)
9382 struct sipe_publication *publication = value;
9384 g_string_append_printf( str,
9385 SIPE_PUB_XML_PUBLICATION_CLEAR,
9386 publication->category,
9387 publication->instance,
9388 publication->container,
9389 publication->version,
9390 "static");
9393 static void sipe_reset_status(PurplePluginAction *action)
9395 PurpleConnection *gc = (PurpleConnection *) action->context;
9396 struct sipe_account_data *sip = gc->proto_data;
9398 if (sip->ocs2007) /* 2007+ */
9400 GString* str = g_string_new(NULL);
9401 gchar *publications;
9403 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9404 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
9405 return;
9408 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9409 publications = g_string_free(str, FALSE);
9411 send_presence_publish(sip, publications);
9412 g_free(publications);
9414 else /* 2005 */
9416 send_presence_soap0(sip, FALSE, TRUE);
9420 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9421 gpointer context)
9423 PurpleConnection *gc = (PurpleConnection *)context;
9424 struct sipe_account_data *sip = gc->proto_data;
9425 GList *menu = NULL;
9426 PurplePluginAction *act;
9427 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9429 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9430 menu = g_list_prepend(menu, act);
9432 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9433 menu = g_list_prepend(menu, act);
9435 if (sipe_strequal(calendar, "EXCH")) {
9436 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9437 menu = g_list_prepend(menu, act);
9440 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9441 menu = g_list_prepend(menu, act);
9443 menu = g_list_reverse(menu);
9445 return menu;
9448 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9452 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9454 return TRUE;
9458 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9460 return TRUE;
9464 static char *sipe_status_text(PurpleBuddy *buddy)
9466 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9467 const PurpleStatus *status = purple_presence_get_active_status(presence);
9468 const char *status_id = purple_status_get_id(status);
9469 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9470 struct sipe_buddy *sbuddy;
9471 char *text = NULL;
9473 if (!sip) return NULL; /* happens on pidgin exit */
9475 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9476 if (sbuddy) {
9477 const char *activity_str = sbuddy->activity ?
9478 sbuddy->activity :
9479 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9480 purple_status_get_name(status) : NULL;
9482 if (activity_str && sbuddy->note)
9484 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9486 else if (activity_str)
9488 text = g_strdup(activity_str);
9490 else if (sbuddy->note)
9492 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9496 return text;
9499 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9501 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9502 const PurpleStatus *status = purple_presence_get_active_status(presence);
9503 struct sipe_account_data *sip;
9504 struct sipe_buddy *sbuddy;
9505 char *note = NULL;
9506 gboolean is_oof_note = FALSE;
9507 char *activity = NULL;
9508 char *calendar = NULL;
9509 char *meeting_subject = NULL;
9510 char *meeting_location = NULL;
9512 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9513 if (sip) //happens on pidgin exit
9515 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9516 if (sbuddy)
9518 note = sbuddy->note;
9519 is_oof_note = sbuddy->is_oof_note;
9520 activity = sbuddy->activity;
9521 calendar = sipe_cal_get_description(sbuddy);
9522 meeting_subject = sbuddy->meeting_subject;
9523 meeting_location = sbuddy->meeting_location;
9527 //Layout
9528 if (purple_presence_is_online(presence))
9530 const char *status_str = activity ? activity : purple_status_get_name(status);
9532 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9534 if (purple_presence_is_online(presence) &&
9535 !is_empty(calendar))
9537 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9539 g_free(calendar);
9540 if (!is_empty(meeting_location))
9542 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9544 if (!is_empty(meeting_subject))
9546 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9549 if (note)
9551 char *tmp = g_strdup_printf("<i>%s</i>", note);
9552 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", buddy->name, note);
9554 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9555 g_free(tmp);
9558 if (sip && sip->ocs2007) {
9559 const int container_id = sipe_find_access_level(sip, "user", sipe_get_no_sip_uri(buddy->name));
9560 const char *access_level = sipe_get_access_level_name(container_id);
9562 purple_notify_user_info_add_pair(user_info, _("Access level"), access_level);
9566 #if PURPLE_VERSION_CHECK(2,5,0)
9567 static GHashTable *
9568 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9570 GHashTable *table;
9571 table = g_hash_table_new(g_str_hash, g_str_equal);
9572 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9573 return table;
9575 #endif
9577 static PurpleBuddy *
9578 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9580 PurpleBuddy *clone;
9581 const gchar *server_alias, *email;
9582 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9584 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9586 purple_blist_add_buddy(clone, NULL, group, NULL);
9588 server_alias = purple_buddy_get_server_alias(buddy);
9589 if (server_alias) {
9590 purple_blist_server_alias_buddy(clone, server_alias);
9593 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9594 if (email) {
9595 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9598 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9599 //for UI to update;
9600 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9601 return clone;
9604 static void
9605 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9607 PurpleBuddy *buddy, *b;
9608 PurpleConnection *gc;
9609 PurpleGroup * group = purple_find_group(group_name);
9611 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9613 buddy = (PurpleBuddy *)node;
9615 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
9616 gc = purple_account_get_connection(buddy->account);
9618 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9619 if (!b){
9620 purple_blist_add_buddy_clone(group, buddy);
9623 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9626 static void
9627 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9629 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9631 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
9633 /* 2007+ conference */
9634 if (sip->ocs2007)
9636 sipe_conf_add(sip, buddy->name);
9638 else /* 2005- multiparty chat */
9640 gchar *self = sip_uri_self(sip);
9641 struct sip_session *session;
9643 session = sipe_session_add_chat(sip);
9644 session->chat_title = sipe_chat_get_name(session->callid);
9645 session->roster_manager = g_strdup(self);
9647 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9648 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9649 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9650 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9652 g_free(self);
9656 static gboolean
9657 sipe_is_election_finished(struct sip_session *session)
9659 gboolean res = TRUE;
9661 SIPE_DIALOG_FOREACH {
9662 if (dialog->election_vote == 0) {
9663 res = FALSE;
9664 break;
9666 } SIPE_DIALOG_FOREACH_END;
9668 if (res) {
9669 session->is_voting_in_progress = FALSE;
9671 return res;
9674 static void
9675 sipe_election_start(struct sipe_account_data *sip,
9676 struct sip_session *session)
9678 int election_timeout;
9680 if (session->is_voting_in_progress) {
9681 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
9682 return;
9683 } else {
9684 session->is_voting_in_progress = TRUE;
9686 session->bid = rand();
9688 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
9690 SIPE_DIALOG_FOREACH {
9691 /* reset election_vote for each chat participant */
9692 dialog->election_vote = 0;
9694 /* send RequestRM to each chat participant*/
9695 sipe_send_election_request_rm(sip, dialog, session->bid);
9696 } SIPE_DIALOG_FOREACH_END;
9698 election_timeout = 15; /* sec */
9699 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9703 * @param who a URI to whom to invite to chat
9705 void
9706 sipe_invite_to_chat(struct sipe_account_data *sip,
9707 struct sip_session *session,
9708 const gchar *who)
9710 /* a conference */
9711 if (session->focus_uri)
9713 sipe_invite_conf(sip, session, who);
9715 else /* a multi-party chat */
9717 gchar *self = sip_uri_self(sip);
9718 if (session->roster_manager) {
9719 if (sipe_strcase_equal(session->roster_manager, self)) {
9720 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9721 } else {
9722 sipe_refer(sip, session, who);
9724 } else {
9725 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
9727 session->pending_invite_queue = slist_insert_unique_sorted(
9728 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9730 sipe_election_start(sip, session);
9732 g_free(self);
9736 void
9737 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9738 struct sip_session *session)
9740 gchar *invitee;
9741 GSList *entry = session->pending_invite_queue;
9743 while (entry) {
9744 invitee = entry->data;
9745 sipe_invite_to_chat(sip, session, invitee);
9746 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9747 g_free(invitee);
9751 static void
9752 sipe_election_result(struct sipe_account_data *sip,
9753 void *sess)
9755 struct sip_session *session = (struct sip_session *)sess;
9756 gchar *rival;
9757 gboolean has_won = TRUE;
9759 if (session->roster_manager) {
9760 SIPE_DEBUG_INFO(
9761 "sipe_election_result: RM has already been elected in the meantime. It is %s",
9762 session->roster_manager);
9763 return;
9766 session->is_voting_in_progress = FALSE;
9768 SIPE_DIALOG_FOREACH {
9769 if (dialog->election_vote < 0) {
9770 has_won = FALSE;
9771 rival = dialog->with;
9772 break;
9774 } SIPE_DIALOG_FOREACH_END;
9776 if (has_won) {
9777 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
9779 session->roster_manager = sip_uri_self(sip);
9781 SIPE_DIALOG_FOREACH {
9782 /* send SetRM to each chat participant*/
9783 sipe_send_election_set_rm(sip, dialog);
9784 } SIPE_DIALOG_FOREACH_END;
9785 } else {
9786 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
9788 session->bid = 0;
9790 sipe_process_pending_invite_queue(sip, session);
9794 * For 2007+ conference only.
9796 static void
9797 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9799 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9800 struct sip_session *session;
9802 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
9803 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
9805 session = sipe_session_find_chat_by_title(sip, chat_title);
9807 sipe_conf_modify_user_role(sip, session, buddy->name);
9811 * For 2007+ conference only.
9813 static void
9814 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9816 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9817 struct sip_session *session;
9819 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
9820 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
9822 session = sipe_session_find_chat_by_title(sip, chat_title);
9824 sipe_conf_delete_user(sip, session, buddy->name);
9827 static void
9828 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9830 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9831 struct sip_session *session;
9833 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
9834 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
9836 session = sipe_session_find_chat_by_title(sip, chat_title);
9838 sipe_invite_to_chat(sip, session, buddy->name);
9841 static void
9842 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9844 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9846 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
9847 if (phone) {
9848 char *tel_uri = sip_to_tel_uri(phone);
9850 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
9851 sip_csta_make_call(sip, tel_uri);
9853 g_free(tel_uri);
9857 static void
9858 sipe_open_url(const char *url)
9860 const char *util;
9861 char *command_line;
9863 if (!url) return;
9865 #ifdef _WIN32
9866 util = "cmd /c start";
9867 #else
9868 util = g_has_prefix(url, "mailto:") ? "xdg-email" : "xdg-open";
9869 #endif
9871 command_line = g_strdup_printf("%s %s", util, url);
9872 g_spawn_command_line_async(command_line, NULL);
9873 g_free(command_line);
9876 static void
9877 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
9879 /** Translators: replace with URL to localized page */
9880 sipe_open_url(_("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
9883 static void
9884 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9886 const gchar *email;
9887 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
9889 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9890 if (email)
9892 char *mailto = g_strdup_printf("mailto:%s", email);
9893 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s", email);
9894 sipe_open_url(mailto);
9895 g_free(mailto);
9897 else
9899 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
9903 static void
9904 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy, const int *container_id)
9906 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9908 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: buddy->name=%s, container_id=%d",
9909 buddy->name, container_id ? *container_id : -1);
9910 if (container_id) {
9911 sipe_change_access_level(sip, *container_id, "user", sipe_get_no_sip_uri(buddy->name));
9916 * A menu which appear when right-clicking on buddy in contact list.
9918 static GList *
9919 sipe_buddy_menu(PurpleBuddy *buddy)
9921 PurpleBlistNode *g_node;
9922 PurpleGroup *group, *gr_parent;
9923 PurpleMenuAction *act;
9924 GList *menu = NULL;
9925 GList *menu_groups = NULL;
9926 GList *menu_access_levels = NULL;
9927 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9928 const char *email;
9929 const char *phone;
9930 const char *phone_disp_str;
9931 gchar *self = sip_uri_self(sip);
9933 SIPE_SESSION_FOREACH {
9934 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9936 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9938 PurpleConvChatBuddyFlags flags;
9939 PurpleConvChatBuddyFlags flags_us;
9941 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9942 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9943 if (session->focus_uri
9944 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9945 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9947 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9948 act = purple_menu_action_new(label,
9949 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9950 session->chat_title, NULL);
9951 g_free(label);
9952 menu = g_list_prepend(menu, act);
9955 if (session->focus_uri
9956 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9958 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9959 act = purple_menu_action_new(label,
9960 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9961 session->chat_title, NULL);
9962 g_free(label);
9963 menu = g_list_prepend(menu, act);
9966 else
9968 if (!session->focus_uri
9969 || (session->focus_uri && !session->locked))
9971 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9972 act = purple_menu_action_new(label,
9973 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9974 session->chat_title, NULL);
9975 g_free(label);
9976 menu = g_list_prepend(menu, act);
9980 } SIPE_SESSION_FOREACH_END;
9982 act = purple_menu_action_new(_("New chat"),
9983 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9984 NULL, NULL);
9985 menu = g_list_prepend(menu, act);
9987 if (sip->csta && !sip->csta->line_status) {
9988 gchar *tmp = NULL;
9989 /* work phone */
9990 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9991 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9992 if (phone) {
9993 gchar *label = g_strdup_printf(_("Work %s"),
9994 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9995 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9996 g_free(tmp);
9997 tmp = NULL;
9998 g_free(label);
9999 menu = g_list_prepend(menu, act);
10002 /* mobile phone */
10003 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
10004 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
10005 if (phone) {
10006 gchar *label = g_strdup_printf(_("Mobile %s"),
10007 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10008 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10009 g_free(tmp);
10010 tmp = NULL;
10011 g_free(label);
10012 menu = g_list_prepend(menu, act);
10015 /* home phone */
10016 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
10017 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
10018 if (phone) {
10019 gchar *label = g_strdup_printf(_("Home %s"),
10020 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10021 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10022 g_free(tmp);
10023 tmp = NULL;
10024 g_free(label);
10025 menu = g_list_prepend(menu, act);
10028 /* other phone */
10029 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
10030 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
10031 if (phone) {
10032 gchar *label = g_strdup_printf(_("Other %s"),
10033 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10034 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10035 g_free(tmp);
10036 tmp = NULL;
10037 g_free(label);
10038 menu = g_list_prepend(menu, act);
10041 /* custom1 phone */
10042 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
10043 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
10044 if (phone) {
10045 gchar *label = g_strdup_printf(_("Custom1 %s"),
10046 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10047 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10048 g_free(tmp);
10049 tmp = NULL;
10050 g_free(label);
10051 menu = g_list_prepend(menu, act);
10055 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
10056 if (email) {
10057 act = purple_menu_action_new(_("Send email..."),
10058 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
10059 NULL, NULL);
10060 menu = g_list_prepend(menu, act);
10063 /* Access Level */
10064 if (sip->ocs2007) {
10065 char *menu_name;
10066 unsigned int i;
10067 int container_id = sipe_find_access_level(sip, "user", sipe_get_no_sip_uri(buddy->name));
10069 for (i = 1; i <= CONTAINERS_LEN; i++) {
10070 /* to put Blocked level last in menu list.
10071 * Blocked should remaim in the first place in the containers[] array.
10073 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
10074 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
10076 /* current container/access level */
10077 if (((int)containers[j]) == container_id) {
10078 menu_name = g_strdup_printf("* %s", acc_level_name);
10079 } else {
10080 menu_name = g_strdup_printf(" %s", acc_level_name);
10083 act = purple_menu_action_new(menu_name,
10084 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
10085 (gpointer)&(containers[j]), NULL);
10086 g_free(menu_name);
10087 menu_access_levels = g_list_prepend(menu_access_levels, act);
10089 /* a separator :) */
10090 /* Online help... */
10091 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
10092 menu_access_levels = g_list_prepend(menu_access_levels, act);
10094 menu_name = g_strdup_printf(" %s", _("Online help..."));
10095 act = purple_menu_action_new(menu_name,
10096 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
10097 NULL, NULL);
10098 g_free(menu_name);
10099 menu_access_levels = g_list_prepend(menu_access_levels, act);
10101 menu_access_levels = g_list_reverse(menu_access_levels);
10103 act = purple_menu_action_new(_("Access level"),
10104 NULL,
10105 NULL, menu_access_levels);
10106 menu = g_list_prepend(menu, act);
10109 /* Copy to */
10110 gr_parent = purple_buddy_get_group(buddy);
10111 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
10112 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
10113 continue;
10115 group = (PurpleGroup *)g_node;
10116 if (group == gr_parent)
10117 continue;
10119 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
10120 continue;
10122 act = purple_menu_action_new(purple_group_get_name(group),
10123 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
10124 group->name, NULL);
10125 menu_groups = g_list_prepend(menu_groups, act);
10127 menu_groups = g_list_reverse(menu_groups);
10129 act = purple_menu_action_new(_("Copy to"),
10130 NULL,
10131 NULL, menu_groups);
10132 menu = g_list_prepend(menu, act);
10134 menu = g_list_reverse(menu);
10136 g_free(self);
10137 return menu;
10140 static void
10141 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
10143 struct sipe_account_data *sip = chat->account->gc->proto_data;
10144 struct sip_session *session;
10146 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10147 sipe_conf_modify_conference_lock(sip, session, locked);
10150 static void
10151 sipe_chat_menu_unlock_cb(PurpleChat *chat)
10153 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
10154 sipe_conf_modify_lock(chat, FALSE);
10157 static void
10158 sipe_chat_menu_lock_cb(PurpleChat *chat)
10160 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
10161 sipe_conf_modify_lock(chat, TRUE);
10164 static GList *
10165 sipe_chat_menu(PurpleChat *chat)
10167 PurpleMenuAction *act;
10168 PurpleConvChatBuddyFlags flags_us;
10169 GList *menu = NULL;
10170 struct sipe_account_data *sip = chat->account->gc->proto_data;
10171 struct sip_session *session;
10172 gchar *self;
10174 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10175 if (!session) return NULL;
10177 self = sip_uri_self(sip);
10178 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
10180 if (session->focus_uri
10181 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
10183 if (session->locked) {
10184 act = purple_menu_action_new(_("Unlock"),
10185 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
10186 NULL, NULL);
10187 menu = g_list_prepend(menu, act);
10188 } else {
10189 act = purple_menu_action_new(_("Lock"),
10190 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
10191 NULL, NULL);
10192 menu = g_list_prepend(menu, act);
10196 menu = g_list_reverse(menu);
10198 g_free(self);
10199 return menu;
10202 static GList *
10203 sipe_blist_node_menu(PurpleBlistNode *node)
10205 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
10206 return sipe_buddy_menu((PurpleBuddy *) node);
10207 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
10208 return sipe_chat_menu((PurpleChat *)node);
10209 } else {
10210 return NULL;
10214 static gboolean
10215 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
10217 char *uri = trans->payload->data;
10219 PurpleNotifyUserInfo *info;
10220 PurpleBuddy *pbuddy = NULL;
10221 struct sipe_buddy *sbuddy;
10222 const char *alias = NULL;
10223 char *device_name = NULL;
10224 char *server_alias = NULL;
10225 char *phone_number = NULL;
10226 char *email = NULL;
10227 const char *site;
10228 char *first_name = NULL;
10229 char *last_name = NULL;
10231 if (!sip) return FALSE;
10233 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sip->username);
10235 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
10236 alias = purple_buddy_get_local_alias(pbuddy);
10238 //will query buddy UA's capabilities and send answer to log
10239 sipe_options_request(sip, uri);
10241 sbuddy = g_hash_table_lookup(sip->buddies, uri);
10242 if (sbuddy) {
10243 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
10246 info = purple_notify_user_info_new();
10248 if (msg->response != 200) {
10249 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg->response);
10250 } else {
10251 sipe_xml *searchResults;
10252 const sipe_xml *mrow;
10254 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
10255 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
10256 if (!searchResults) {
10257 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
10258 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
10259 const char *value;
10260 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
10261 email = g_strdup(sipe_xml_attribute(mrow, "email"));
10262 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
10264 /* For 2007 system we will take this from ContactCard -
10265 * it has cleaner tel: URIs at least
10267 if (!sip->ocs2007) {
10268 char *tel_uri = sip_to_tel_uri(phone_number);
10269 /* trims its parameters, so call first */
10270 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
10271 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
10272 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
10273 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
10274 g_free(tel_uri);
10277 if (server_alias && strlen(server_alias) > 0) {
10278 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10280 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
10281 purple_notify_user_info_add_pair(info, _("Job title"), value);
10283 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
10284 purple_notify_user_info_add_pair(info, _("Office"), value);
10286 if (phone_number && strlen(phone_number) > 0) {
10287 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
10289 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
10290 purple_notify_user_info_add_pair(info, _("Company"), value);
10292 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
10293 purple_notify_user_info_add_pair(info, _("City"), value);
10295 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
10296 purple_notify_user_info_add_pair(info, _("State"), value);
10298 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
10299 purple_notify_user_info_add_pair(info, _("Country"), value);
10301 if (email && strlen(email) > 0) {
10302 purple_notify_user_info_add_pair(info, _("Email address"), email);
10306 sipe_xml_free(searchResults);
10309 purple_notify_user_info_add_section_break(info);
10311 if (is_empty(server_alias)) {
10312 g_free(server_alias);
10313 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
10314 if (server_alias) {
10315 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10319 /* present alias if it differs from server alias */
10320 if (alias && !sipe_strequal(alias, server_alias))
10322 purple_notify_user_info_add_pair(info, _("Alias"), alias);
10325 if (is_empty(email)) {
10326 g_free(email);
10327 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
10328 if (email) {
10329 purple_notify_user_info_add_pair(info, _("Email address"), email);
10333 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
10334 if (site) {
10335 purple_notify_user_info_add_pair(info, _("Site"), site);
10338 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
10339 if (first_name && last_name) {
10340 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
10342 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
10343 g_free(link);
10345 g_free(first_name);
10346 g_free(last_name);
10348 if (device_name) {
10349 purple_notify_user_info_add_pair(info, _("Device"), device_name);
10352 /* show a buddy's user info in a nice dialog box */
10353 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
10354 uri, /* buddy's URI */
10355 info, /* body */
10356 NULL, /* callback called when dialog closed */
10357 NULL); /* userdata for callback */
10359 g_free(phone_number);
10360 g_free(server_alias);
10361 g_free(email);
10362 g_free(device_name);
10364 return TRUE;
10368 * AD search first, LDAP based
10370 static void sipe_get_info(PurpleConnection *gc, const char *username)
10372 struct sipe_account_data *sip = gc->proto_data;
10373 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
10374 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
10375 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
10376 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
10378 payload->destroy = g_free;
10379 payload->data = g_strdup(username);
10381 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
10382 send_soap_request_with_cb(sip, domain_uri, body,
10383 (TransCallback) process_get_info_response, payload);
10384 g_free(domain_uri);
10385 g_free(body);
10386 g_free(row);
10389 PurplePluginProtocolInfo prpl_info =
10391 OPT_PROTO_CHAT_TOPIC,
10392 NULL, /* user_splits */
10393 NULL, /* protocol_options */
10394 NO_BUDDY_ICONS, /* icon_spec */
10395 sipe_list_icon, /* list_icon */
10396 NULL, /* list_emblems */
10397 sipe_status_text, /* status_text */
10398 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10399 sipe_status_types, /* away_states */
10400 sipe_blist_node_menu, /* blist_node_menu */
10401 NULL, /* chat_info */
10402 NULL, /* chat_info_defaults */
10403 sipe_login, /* login */
10404 sipe_close, /* close */
10405 sipe_im_send, /* send_im */
10406 NULL, /* set_info */ // TODO maybe
10407 sipe_send_typing, /* send_typing */
10408 sipe_get_info, /* get_info */
10409 sipe_set_status, /* set_status */
10410 sipe_set_idle, /* set_idle */
10411 NULL, /* change_passwd */
10412 sipe_add_buddy, /* add_buddy */
10413 NULL, /* add_buddies */
10414 sipe_remove_buddy, /* remove_buddy */
10415 NULL, /* remove_buddies */
10416 sipe_add_permit, /* add_permit */
10417 sipe_add_deny, /* add_deny */
10418 sipe_add_deny, /* rem_permit */
10419 sipe_add_permit, /* rem_deny */
10420 dummy_permit_deny, /* set_permit_deny */
10421 NULL, /* join_chat */
10422 NULL, /* reject_chat */
10423 NULL, /* get_chat_name */
10424 sipe_chat_invite, /* chat_invite */
10425 sipe_chat_leave, /* chat_leave */
10426 NULL, /* chat_whisper */
10427 sipe_chat_send, /* chat_send */
10428 sipe_keep_alive, /* keepalive */
10429 NULL, /* register_user */
10430 NULL, /* get_cb_info */ // deprecated
10431 NULL, /* get_cb_away */ // deprecated
10432 sipe_alias_buddy, /* alias_buddy */
10433 sipe_group_buddy, /* group_buddy */
10434 sipe_rename_group, /* rename_group */
10435 NULL, /* buddy_free */
10436 sipe_convo_closed, /* convo_closed */
10437 purple_normalize_nocase, /* normalize */
10438 NULL, /* set_buddy_icon */
10439 sipe_remove_group, /* remove_group */
10440 NULL, /* get_cb_real_name */ // TODO?
10441 NULL, /* set_chat_topic */
10442 NULL, /* find_blist_chat */
10443 NULL, /* roomlist_get_list */
10444 NULL, /* roomlist_cancel */
10445 NULL, /* roomlist_expand_category */
10446 NULL, /* can_receive_file */
10447 sipe_ft_send_file, /* send_file */
10448 sipe_ft_new_xfer, /* new_xfer */
10449 NULL, /* offline_message */
10450 NULL, /* whiteboard_prpl_ops */
10451 sipe_send_raw, /* send_raw */
10452 NULL, /* roomlist_room_serialize */
10453 NULL, /* unregister_user */
10454 NULL, /* send_attention */
10455 NULL, /* get_attention_types */
10456 #if !PURPLE_VERSION_CHECK(2,5,0)
10457 /* Backward compatibility when compiling against 2.4.x API */
10458 (void (*)(void)) /* _purple_reserved4 */
10459 #endif
10460 sizeof(PurplePluginProtocolInfo), /* struct_size */
10461 #if PURPLE_VERSION_CHECK(2,5,0)
10462 sipe_get_account_text_table, /* get_account_text_table */
10463 #if PURPLE_VERSION_CHECK(2,6,0)
10464 NULL, /* initiate_media */
10465 NULL, /* get_media_caps */
10466 #if PURPLE_VERSION_CHECK(2,7,0)
10467 NULL, /* get_moods */
10468 #endif
10469 #endif
10470 #endif
10474 PurplePluginInfo info = {
10475 PURPLE_PLUGIN_MAGIC,
10476 PURPLE_MAJOR_VERSION,
10477 PURPLE_MINOR_VERSION,
10478 PURPLE_PLUGIN_PROTOCOL, /**< type */
10479 NULL, /**< ui_requirement */
10480 0, /**< flags */
10481 NULL, /**< dependencies */
10482 PURPLE_PRIORITY_DEFAULT, /**< priority */
10483 "prpl-sipe", /**< id */
10484 "Office Communicator", /**< name */
10485 PACKAGE_VERSION, /**< version */
10486 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10487 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10488 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10489 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10490 "Gabriel Burt <gburt@novell.com>, " /**< author */
10491 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10492 "pier11 <pier11@operamail.com>", /**< author */
10493 PACKAGE_URL, /**< homepage */
10494 sipe_plugin_load, /**< load */
10495 sipe_plugin_unload, /**< unload */
10496 sipe_plugin_destroy, /**< destroy */
10497 NULL, /**< ui_info */
10498 &prpl_info, /**< extra_info */
10499 NULL,
10500 sipe_actions,
10501 NULL,
10502 NULL,
10503 NULL,
10504 NULL
10507 void sipe_core_init(void)
10509 srand(time(NULL));
10510 sip_sec_init();
10512 #ifdef ENABLE_NLS
10513 SIPE_DEBUG_INFO("bindtextdomain = %s",
10514 bindtextdomain(PACKAGE_NAME, LOCALEDIR));
10515 SIPE_DEBUG_INFO("bind_textdomain_codeset = %s",
10516 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"));
10517 textdomain(PACKAGE_NAME);
10518 #endif
10519 #ifdef HAVE_GMIME
10520 g_mime_init(0);
10521 #endif
10524 void sipe_core_destroy(void)
10526 #ifdef HAVE_GMIME
10527 g_mime_shutdown();
10528 #endif
10529 sip_sec_destroy();
10533 Local Variables:
10534 mode: c
10535 c-file-style: "bsd"
10536 indent-tabs-mode: t
10537 tab-width: 8
10538 End: