Send BYE when response to IM message is 408/480/481
[siplcs.git] / src / core / sipe.c
blob563170f7f40576279990d17557e23edcf003708d
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 pier11 <pier11@operamail.com>
7 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
10 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
13 * ***
14 * Thanks to Google's Summer of Code Program and the helpful mentors
15 * ***
17 * Session-based SIP MESSAGE documentation:
18 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 #ifdef HAVE_CONFIG_H
36 #include "config.h"
37 #endif
39 #ifdef _WIN32
40 #ifdef _DLL
41 #define _WS2TCPIP_H_
42 #define _WINSOCK2API_
43 #define _LIBC_INTERNAL_
44 #endif /* _DLL */
45 #include "internal.h"
46 #else
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <netinet/in.h>
50 #endif /* _WIN32 */
52 #include <time.h>
53 #include <stdio.h>
54 #include <errno.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include <glib.h>
60 #include "accountopt.h"
61 #include "blist.h"
62 #include "conversation.h"
63 #include "dnsquery.h"
64 #include "debug.h"
65 #include "notify.h"
66 #include "savedstatuses.h"
67 #include "privacy.h"
68 #include "prpl.h"
69 #include "plugin.h"
70 #include "util.h"
71 #include "version.h"
72 #include "network.h"
73 #include "xmlnode.h"
74 #include "mime.h"
75 #include "core.h"
77 #include "sipe.h"
78 #include "sipe-cal.h"
79 #include "sipe-ews.h"
80 #include "sipe-chat.h"
81 #include "sipe-conf.h"
82 #include "sip-csta.h"
83 #include "sipe-dialog.h"
84 #include "sipe-nls.h"
85 #include "sipe-session.h"
86 #include "sipe-utils.h"
87 #include "sipe-ft.h"
88 #include "sipmsg.h"
89 #include "sipe-sign.h"
90 #include "dnssrv.h"
91 #include "request.h"
93 /* Backward compatibility when compiling against 2.4.x API */
94 #if !PURPLE_VERSION_CHECK(2,5,0)
95 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
96 #endif
98 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
100 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
101 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
103 /* Keep in sync with sipe_transport_type! */
104 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
105 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
107 /* Status identifiers (see also: sipe_status_types()) */
108 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
109 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
110 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
111 /* PURPLE_STATUS_UNAVAILABLE: */
112 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
113 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
114 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
115 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
116 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
117 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
118 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
119 /* PURPLE_STATUS_AWAY: */
120 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
121 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
122 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
123 /** Reuters status (user settable) */
124 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
125 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
126 /* ??? PURPLE_STATUS_MOBILE */
127 /* ??? PURPLE_STATUS_TUNE */
129 /* Status attributes (see also sipe_status_types() */
130 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
132 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
134 static struct sipe_activity_map_struct
136 sipe_activity type;
137 const char *token;
138 const char *desc;
139 const char *status_id;
141 } const sipe_activity_map[] =
143 /* This has nothing to do with Availability numbers, like 3500 (online).
144 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
146 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
147 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
148 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
149 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
150 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
151 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
152 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
153 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
154 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
155 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
156 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
157 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
158 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
159 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
160 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
162 /** @param x is sipe_activity */
163 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
166 /* Action name templates */
167 #define ACTION_NAME_PRESENCE "<presence><%s>"
169 static sipe_activity
170 sipe_get_activity_by_token(const char *token)
172 int i;
174 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
176 if (sipe_strequal(token, sipe_activity_map[i].token))
177 return sipe_activity_map[i].type;
180 return sipe_activity_map[0].type;
183 static const char *
184 sipe_get_activity_desc_by_token(const char *token)
186 if (!token) return NULL;
188 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
191 /** Allows to send typed messages from chat window again after account reinstantiation. */
192 static void
193 sipe_rejoin_chat(PurpleConversation *conv)
195 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
196 PURPLE_CONV_CHAT(conv)->left)
198 PURPLE_CONV_CHAT(conv)->left = FALSE;
199 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
203 static char *genbranch()
205 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
206 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
207 rand() & 0xFFFF, rand() & 0xFFFF);
211 static char *default_ua = NULL;
212 static const char*
213 sipe_get_useragent(struct sipe_account_data *sip)
215 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
216 if (is_empty(useragent)) {
217 if (!default_ua) {
218 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
219 /* ref: lzodefs.h */
220 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
221 #define SIPE_TARGET_PLATFORM "linux"
222 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
223 #define SIPE_TARGET_PLATFORM "bsd"
224 #elif defined(__APPLE__) || defined(__MACOS__)
225 #define SIPE_TARGET_PLATFORM "macosx"
226 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
227 #define SIPE_TARGET_PLATFORM "aix"
228 #elif defined(__solaris__) || defined(__sun)
229 #define SIPE_TARGET_PLATFORM "sun"
230 #elif defined(_WIN32)
231 #define SIPE_TARGET_PLATFORM "win"
232 #elif defined(__CYGWIN__)
233 #define SIPE_TARGET_PLATFORM "cygwin"
234 #elif defined(__hpux__)
235 #define SIPE_TARGET_PLATFORM "hpux"
236 #elif defined(__sgi__)
237 #define SIPE_TARGET_PLATFORM "irix"
238 #else
239 #define SIPE_TARGET_PLATFORM "unknown"
240 #endif
242 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
243 #define SIPE_TARGET_ARCH "x86_64"
244 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
245 #define SIPE_TARGET_ARCH "i386"
246 #elif defined(__ppc64__)
247 #define SIPE_TARGET_ARCH "ppc64"
248 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
249 #define SIPE_TARGET_ARCH "ppc"
250 #elif defined(__hppa__) || defined(__hppa)
251 #define SIPE_TARGET_ARCH "hppa"
252 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
253 #define SIPE_TARGET_ARCH "mips"
254 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
255 #define SIPE_TARGET_ARCH "s390"
256 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
257 #define SIPE_TARGET_ARCH "sparc"
258 #elif defined(__arm__)
259 #define SIPE_TARGET_ARCH "arm"
260 #else
261 #define SIPE_TARGET_ARCH "other"
262 #endif
264 default_ua = g_strdup_printf("Purple/%s Sipe/" SIPE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
265 purple_core_get_version(),
266 sip->server_version ? sip->server_version : "");
268 useragent = default_ua;
270 return useragent;
273 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
274 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
276 return "sipe";
279 static void sipe_plugin_destroy(PurplePlugin *plugin);
281 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
283 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
284 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
285 gpointer data);
287 static void sipe_close(PurpleConnection *gc);
289 static void send_presence_status(struct sipe_account_data *sip);
291 static void sendout_pkt(PurpleConnection *gc, const char *buf);
293 static void sipe_keep_alive(PurpleConnection *gc)
295 struct sipe_account_data *sip = gc->proto_data;
296 if (sip->transport == SIPE_TRANSPORT_UDP) {
297 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
298 gchar buf[2] = {0, 0};
299 purple_debug_info("sipe", "sending keep alive\n");
300 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
301 } else {
302 time_t now = time(NULL);
303 if ((sip->keepalive_timeout > 0) &&
304 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
305 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
307 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
308 sendout_pkt(gc, "\r\n\r\n");
309 sip->last_keepalive = now;
314 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
316 struct sip_connection *ret = NULL;
317 GSList *entry = sip->openconns;
318 while (entry) {
319 ret = entry->data;
320 if (ret->fd == fd) return ret;
321 entry = entry->next;
323 return NULL;
326 static void sipe_auth_free(struct sip_auth *auth)
328 g_free(auth->opaque);
329 auth->opaque = NULL;
330 g_free(auth->realm);
331 auth->realm = NULL;
332 g_free(auth->target);
333 auth->target = NULL;
334 auth->type = AUTH_TYPE_UNSET;
335 auth->retries = 0;
336 auth->expires = 0;
337 g_free(auth->gssapi_data);
338 auth->gssapi_data = NULL;
339 sip_sec_destroy_context(auth->gssapi_context);
340 auth->gssapi_context = NULL;
343 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
345 struct sip_connection *ret = g_new0(struct sip_connection, 1);
346 ret->fd = fd;
347 sip->openconns = g_slist_append(sip->openconns, ret);
348 return ret;
351 static void connection_remove(struct sipe_account_data *sip, int fd)
353 struct sip_connection *conn = connection_find(sip, fd);
354 if (conn) {
355 sip->openconns = g_slist_remove(sip->openconns, conn);
356 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
357 g_free(conn->inbuf);
358 g_free(conn);
362 static void connection_free_all(struct sipe_account_data *sip)
364 struct sip_connection *ret = NULL;
365 GSList *entry = sip->openconns;
366 while (entry) {
367 ret = entry->data;
368 connection_remove(sip, ret->fd);
369 entry = sip->openconns;
373 static void
374 sipe_make_signature(struct sipe_account_data *sip,
375 struct sipmsg *msg);
377 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
379 gchar noncecount[9];
380 const char *authuser = sip->authuser;
381 gchar *response;
382 gchar *ret;
384 if (!authuser || strlen(authuser) < 1) {
385 authuser = sip->username;
388 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
389 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
390 gchar *version_str;
392 // If we have a signature for the message, include that
393 if (msg->signature) {
394 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);
397 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
398 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
399 gchar *gssapi_data;
400 gchar *opaque;
401 gchar *sign_str = NULL;
403 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
404 &(auth->expires),
405 auth->type,
406 purple_account_get_bool(sip->account, "sso", TRUE),
407 sip->authdomain ? sip->authdomain : "",
408 authuser,
409 sip->password,
410 auth->target,
411 auth->gssapi_data);
412 if (!gssapi_data || !auth->gssapi_context) {
413 sip->gc->wants_to_die = TRUE;
414 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
415 return NULL;
418 if (auth->version > 3) {
419 sipe_make_signature(sip, msg);
420 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
421 msg->rand, msg->num, msg->signature);
422 } else {
423 sign_str = g_strdup("");
426 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
427 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
428 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);
429 g_free(opaque);
430 g_free(gssapi_data);
431 g_free(version_str);
432 g_free(sign_str);
433 return ret;
436 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
437 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
438 g_free(version_str);
439 return ret;
441 } else { /* Digest */
443 /* Calculate new session key */
444 if (!auth->opaque) {
445 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
446 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
447 authuser, auth->realm, sip->password,
448 auth->gssapi_data, NULL);
451 sprintf(noncecount, "%08d", auth->nc++);
452 response = purple_cipher_http_digest_calculate_response("md5",
453 msg->method, msg->target, NULL, NULL,
454 auth->gssapi_data, noncecount, NULL,
455 auth->opaque);
456 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
458 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);
459 g_free(response);
460 return ret;
464 static char *parse_attribute(const char *attrname, const char *source)
466 const char *tmp, *tmp2;
467 char *retval = NULL;
468 int len = strlen(attrname);
470 if (g_str_has_prefix(source, attrname)) {
471 tmp = source + len;
472 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
473 if (tmp2)
474 retval = g_strndup(tmp, tmp2 - tmp);
475 else
476 retval = g_strdup(tmp);
479 return retval;
482 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
484 int i;
485 gchar **parts;
487 if (!hdr) {
488 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
489 return;
492 if (!g_strncasecmp(hdr, "NTLM", 4)) {
493 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
494 auth->type = AUTH_TYPE_NTLM;
495 hdr += 5;
496 auth->nc = 1;
497 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
498 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
499 auth->type = AUTH_TYPE_KERBEROS;
500 hdr += 9;
501 auth->nc = 3;
502 } else {
503 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
504 auth->type = AUTH_TYPE_DIGEST;
505 hdr += 7;
508 parts = g_strsplit(hdr, "\", ", 0);
509 for (i = 0; parts[i]; i++) {
510 char *tmp;
512 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
514 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
515 g_free(auth->gssapi_data);
516 auth->gssapi_data = tmp;
518 if (auth->type == AUTH_TYPE_NTLM) {
519 /* NTLM module extracts nonce from gssapi-data */
520 auth->nc = 3;
523 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
524 /* Only used with AUTH_TYPE_DIGEST */
525 g_free(auth->gssapi_data);
526 auth->gssapi_data = tmp;
527 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
528 g_free(auth->opaque);
529 auth->opaque = tmp;
530 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
531 g_free(auth->realm);
532 auth->realm = tmp;
534 if (auth->type == AUTH_TYPE_DIGEST) {
535 /* Throw away old session key */
536 g_free(auth->opaque);
537 auth->opaque = NULL;
538 auth->nc = 1;
540 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
541 g_free(auth->target);
542 auth->target = tmp;
543 } else if ((tmp = parse_attribute("version=", parts[i]))) {
544 auth->version = atoi(tmp);
545 g_free(tmp);
547 // uncomment to revert to previous functionality if version 3+ does not work.
548 // auth->version = 2;
550 g_strfreev(parts);
552 return;
555 static void sipe_canwrite_cb(gpointer data,
556 SIPE_UNUSED_PARAMETER gint source,
557 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
559 PurpleConnection *gc = data;
560 struct sipe_account_data *sip = gc->proto_data;
561 gsize max_write;
562 gssize written;
564 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
566 if (max_write == 0) {
567 if (sip->tx_handler != 0){
568 purple_input_remove(sip->tx_handler);
569 sip->tx_handler = 0;
571 return;
574 written = write(sip->fd, sip->txbuf->outptr, max_write);
576 if (written < 0 && errno == EAGAIN)
577 written = 0;
578 else if (written <= 0) {
579 /*TODO: do we really want to disconnect on a failure to write?*/
580 purple_connection_error(gc, _("Could not write"));
581 return;
584 purple_circ_buffer_mark_read(sip->txbuf, written);
587 static void sipe_canwrite_cb_ssl(gpointer data,
588 SIPE_UNUSED_PARAMETER gint src,
589 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
591 PurpleConnection *gc = data;
592 struct sipe_account_data *sip = gc->proto_data;
593 gsize max_write;
594 gssize written;
596 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
598 if (max_write == 0) {
599 if (sip->tx_handler != 0) {
600 purple_input_remove(sip->tx_handler);
601 sip->tx_handler = 0;
602 return;
606 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
608 if (written < 0 && errno == EAGAIN)
609 written = 0;
610 else if (written <= 0) {
611 /*TODO: do we really want to disconnect on a failure to write?*/
612 purple_connection_error(gc, _("Could not write"));
613 return;
616 purple_circ_buffer_mark_read(sip->txbuf, written);
619 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
621 static void send_later_cb(gpointer data, gint source,
622 SIPE_UNUSED_PARAMETER const gchar *error)
624 PurpleConnection *gc = data;
625 struct sipe_account_data *sip;
626 struct sip_connection *conn;
628 if (!PURPLE_CONNECTION_IS_VALID(gc))
630 if (source >= 0)
631 close(source);
632 return;
635 if (source < 0) {
636 purple_connection_error(gc, _("Could not connect"));
637 return;
640 sip = gc->proto_data;
641 sip->fd = source;
642 sip->connecting = FALSE;
643 sip->last_keepalive = time(NULL);
645 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
647 /* If there is more to write now, we need to register a handler */
648 if (sip->txbuf->bufused > 0)
649 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
651 conn = connection_create(sip, source);
652 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
655 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
657 struct sipe_account_data *sip;
659 if (!PURPLE_CONNECTION_IS_VALID(gc))
661 if (gsc) purple_ssl_close(gsc);
662 return NULL;
665 sip = gc->proto_data;
666 sip->fd = gsc->fd;
667 sip->gsc = gsc;
668 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
669 sip->connecting = FALSE;
670 sip->last_keepalive = time(NULL);
672 connection_create(sip, gsc->fd);
674 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
676 return sip;
679 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
680 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
682 PurpleConnection *gc = data;
683 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
684 if (sip == NULL) return;
686 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
688 /* If there is more to write now */
689 if (sip->txbuf->bufused > 0) {
690 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
695 static void sendlater(PurpleConnection *gc, const char *buf)
697 struct sipe_account_data *sip = gc->proto_data;
699 if (!sip->connecting) {
700 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
701 if (sip->transport == SIPE_TRANSPORT_TLS){
702 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
703 } else {
704 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
705 purple_connection_error(gc, _("Could not create socket"));
708 sip->connecting = TRUE;
711 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
712 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
714 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
717 static void sendout_pkt(PurpleConnection *gc, const char *buf)
719 struct sipe_account_data *sip = gc->proto_data;
720 time_t currtime = time(NULL);
721 int writelen = strlen(buf);
722 char *tmp;
724 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
725 g_free(tmp);
726 if (sip->transport == SIPE_TRANSPORT_UDP) {
727 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
728 purple_debug_info("sipe", "could not send packet\n");
730 } else {
731 int ret;
732 if (sip->fd < 0) {
733 sendlater(gc, buf);
734 return;
737 if (sip->tx_handler) {
738 ret = -1;
739 errno = EAGAIN;
740 } else{
741 if (sip->gsc){
742 ret = purple_ssl_write(sip->gsc, buf, writelen);
743 }else{
744 ret = write(sip->fd, buf, writelen);
748 if (ret < 0 && errno == EAGAIN)
749 ret = 0;
750 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
751 sendlater(gc, buf);
752 return;
755 if (ret < writelen) {
756 if (!sip->tx_handler){
757 if (sip->gsc){
758 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
760 else{
761 sip->tx_handler = purple_input_add(sip->fd,
762 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
763 gc);
767 /* XXX: is it OK to do this? You might get part of a request sent
768 with part of another. */
769 if (sip->txbuf->bufused > 0)
770 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
772 purple_circ_buffer_append(sip->txbuf, buf + ret,
773 writelen - ret);
778 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
780 sendout_pkt(gc, buf);
781 return len;
784 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
786 GSList *tmp = msg->headers;
787 gchar *name;
788 gchar *value;
789 GString *outstr = g_string_new("");
790 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
791 while (tmp) {
792 name = ((struct sipnameval*) (tmp->data))->name;
793 value = ((struct sipnameval*) (tmp->data))->value;
794 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
795 tmp = g_slist_next(tmp);
797 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
798 sendout_pkt(sip->gc, outstr->str);
799 g_string_free(outstr, TRUE);
802 static void
803 sipe_make_signature(struct sipe_account_data *sip,
804 struct sipmsg *msg)
806 if (sip->registrar.gssapi_context) {
807 struct sipmsg_breakdown msgbd;
808 gchar *signature_input_str;
809 msgbd.msg = msg;
810 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
811 msgbd.rand = g_strdup_printf("%08x", g_random_int());
812 sip->registrar.ntlm_num++;
813 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
814 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
815 if (signature_input_str != NULL) {
816 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
817 msg->signature = signature_hex;
818 msg->rand = g_strdup(msgbd.rand);
819 msg->num = g_strdup(msgbd.num);
820 g_free(signature_input_str);
822 sipmsg_breakdown_free(&msgbd);
826 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
828 gchar * buf;
830 if (sip->registrar.type == AUTH_TYPE_UNSET) {
831 return;
834 sipe_make_signature(sip, msg);
836 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
837 buf = auth_header(sip, &sip->registrar, msg);
838 if (buf) {
839 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
841 g_free(buf);
842 } 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")) {
843 sip->registrar.nc = 3;
844 sip->registrar.type = AUTH_TYPE_NTLM;
845 #ifdef USE_KERBEROS
846 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
847 sip->registrar.type = AUTH_TYPE_KERBEROS;
849 #endif
852 buf = auth_header(sip, &sip->registrar, msg);
853 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
854 g_free(buf);
855 } else {
856 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
860 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
861 const char *text, const char *body)
863 gchar *name;
864 gchar *value;
865 GString *outstr = g_string_new("");
866 struct sipe_account_data *sip = gc->proto_data;
867 gchar *contact;
868 GSList *tmp;
869 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
871 /* Can return NULL! */
872 contact = get_contact(sip);
873 if (contact) {
874 sipmsg_add_header(msg, "Contact", contact);
875 g_free(contact);
878 if (body) {
879 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
880 sipmsg_add_header(msg, "Content-Length", len);
881 g_free(len);
882 } else {
883 sipmsg_add_header(msg, "Content-Length", "0");
886 msg->response = code;
888 sipmsg_strip_headers(msg, keepers);
889 sipmsg_merge_new_headers(msg);
890 sign_outgoing_message(msg, sip, msg->method);
892 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
893 tmp = msg->headers;
894 while (tmp) {
895 name = ((struct sipnameval*) (tmp->data))->name;
896 value = ((struct sipnameval*) (tmp->data))->value;
898 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
899 tmp = g_slist_next(tmp);
901 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
902 sendout_pkt(gc, outstr->str);
903 g_string_free(outstr, TRUE);
906 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
908 if (sip->transactions) {
909 sip->transactions = g_slist_remove(sip->transactions, trans);
910 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
912 if (trans->msg) sipmsg_free(trans->msg);
913 if (trans->payload) {
914 (*trans->payload->destroy)(trans->payload->data);
915 g_free(trans->payload);
917 g_free(trans->key);
918 g_free(trans);
922 static struct transaction *
923 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
925 const gchar *call_id;
926 const gchar *cseq;
927 struct transaction *trans = g_new0(struct transaction, 1);
929 trans->time = time(NULL);
930 trans->msg = (struct sipmsg *)msg;
931 call_id = sipmsg_find_header(trans->msg, "Call-ID");
932 cseq = sipmsg_find_header(trans->msg, "CSeq");
933 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
934 trans->callback = callback;
935 sip->transactions = g_slist_append(sip->transactions, trans);
936 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
937 return trans;
940 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
942 struct transaction *trans;
943 GSList *transactions = sip->transactions;
944 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
945 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
946 gchar *key;
948 if (!call_id || !cseq) {
949 purple_debug(PURPLE_DEBUG_ERROR, "sipe", "transaction_find: no Call-ID or CSeq!\n");
950 return NULL;
953 key = g_strdup_printf("<%s><%s>", call_id, cseq);
954 while (transactions) {
955 trans = transactions->data;
956 if (!g_strcasecmp(trans->key, key)) {
957 g_free(key);
958 return trans;
960 transactions = transactions->next;
963 g_free(key);
964 return NULL;
967 struct transaction *
968 send_sip_request(PurpleConnection *gc, const gchar *method,
969 const gchar *url, const gchar *to, const gchar *addheaders,
970 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
972 struct sipe_account_data *sip = gc->proto_data;
973 const char *addh = "";
974 char *buf;
975 struct sipmsg *msg;
976 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
977 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
978 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
979 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
980 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
981 gchar *route = g_strdup("");
982 gchar *epid = get_epid(sip);
983 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
984 struct transaction *trans = NULL;
986 if (dialog && dialog->routes)
988 GSList *iter = dialog->routes;
990 while(iter)
992 char *tmp = route;
993 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
994 g_free(tmp);
995 iter = g_slist_next(iter);
999 if (!ourtag && !dialog) {
1000 ourtag = gentag();
1003 if (sipe_strequal(method, "REGISTER")) {
1004 if (sip->regcallid) {
1005 g_free(callid);
1006 callid = g_strdup(sip->regcallid);
1007 } else {
1008 sip->regcallid = g_strdup(callid);
1010 cseq = ++sip->cseq;
1013 if (addheaders) addh = addheaders;
1015 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1016 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1017 "From: <sip:%s>%s%s;epid=%s\r\n"
1018 "To: <%s>%s%s%s%s\r\n"
1019 "Max-Forwards: 70\r\n"
1020 "CSeq: %d %s\r\n"
1021 "User-Agent: %s\r\n"
1022 "Call-ID: %s\r\n"
1023 "%s%s"
1024 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1025 method,
1026 dialog && dialog->request ? dialog->request : url,
1027 TRANSPORT_DESCRIPTOR,
1028 purple_network_get_my_ip(-1),
1029 sip->listenport,
1030 branch ? ";branch=" : "",
1031 branch ? branch : "",
1032 sip->username,
1033 ourtag ? ";tag=" : "",
1034 ourtag ? ourtag : "",
1035 epid,
1037 theirtag ? ";tag=" : "",
1038 theirtag ? theirtag : "",
1039 theirepid ? ";epid=" : "",
1040 theirepid ? theirepid : "",
1041 cseq,
1042 method,
1043 sipe_get_useragent(sip),
1044 callid,
1045 route,
1046 addh,
1047 body ? (gsize) strlen(body) : 0,
1048 body ? body : "");
1051 //printf ("parsing msg buf:\n%s\n\n", buf);
1052 msg = sipmsg_parse_msg(buf);
1054 g_free(buf);
1055 g_free(ourtag);
1056 g_free(theirtag);
1057 g_free(theirepid);
1058 g_free(branch);
1059 g_free(callid);
1060 g_free(route);
1061 g_free(epid);
1063 sign_outgoing_message (msg, sip, method);
1065 buf = sipmsg_to_string (msg);
1067 /* add to ongoing transactions */
1068 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1069 if (!sipe_strequal(method, "ACK")) {
1070 trans = transactions_add_buf(sip, msg, tc);
1071 } else {
1072 sipmsg_free(msg);
1074 sendout_pkt(gc, buf);
1075 g_free(buf);
1077 return trans;
1081 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1083 static void
1084 send_soap_request_with_cb(struct sipe_account_data *sip,
1085 gchar *from0,
1086 gchar *body,
1087 TransCallback callback,
1088 struct transaction_payload *payload)
1090 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1091 gchar *contact = get_contact(sip);
1092 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1093 "Content-Type: application/SOAP+xml\r\n",contact);
1095 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1096 trans->payload = payload;
1098 g_free(from);
1099 g_free(contact);
1100 g_free(hdr);
1103 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1105 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1108 static char *get_contact_register(struct sipe_account_data *sip)
1110 char *epid = get_epid(sip);
1111 char *uuid = generateUUIDfromEPID(epid);
1112 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1113 g_free(uuid);
1114 g_free(epid);
1115 return(buf);
1118 static void do_register_exp(struct sipe_account_data *sip, int expire)
1120 char *uri;
1121 char *expires;
1122 char *to;
1123 char *contact;
1124 char *hdr;
1126 if (!sip->sipdomain) return;
1128 uri = sip_uri_from_name(sip->sipdomain);
1129 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1130 to = sip_uri_self(sip);
1131 contact = get_contact_register(sip);
1132 hdr = g_strdup_printf("Contact: %s\r\n"
1133 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1134 "Event: registration\r\n"
1135 "Allow-Events: presence\r\n"
1136 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1137 "%s", contact, expires);
1138 g_free(contact);
1139 g_free(expires);
1141 sip->registerstatus = 1;
1143 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1144 process_register_response);
1146 g_free(hdr);
1147 g_free(uri);
1148 g_free(to);
1151 static void do_register_cb(struct sipe_account_data *sip,
1152 SIPE_UNUSED_PARAMETER void *unused)
1154 do_register_exp(sip, -1);
1155 sip->reregister_set = FALSE;
1158 static void do_register(struct sipe_account_data *sip)
1160 do_register_exp(sip, -1);
1163 static void
1164 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1166 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1167 send_soap_request(sip, body);
1168 g_free(body);
1171 static void
1172 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1174 if (allow) {
1175 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1176 } else {
1177 purple_debug_info("sipe", "Blocking contact %s\n", who);
1180 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1183 static
1184 void sipe_auth_user_cb(void * data)
1186 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1187 if (!job) return;
1189 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1190 g_free(job);
1193 static
1194 void sipe_deny_user_cb(void * data)
1196 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1197 if (!job) return;
1199 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1200 g_free(job);
1203 static void
1204 sipe_add_permit(PurpleConnection *gc, const char *name)
1206 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1207 sipe_contact_allow_deny(sip, name, TRUE);
1210 static void
1211 sipe_add_deny(PurpleConnection *gc, const char *name)
1213 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1214 sipe_contact_allow_deny(sip, name, FALSE);
1217 /*static void
1218 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1220 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1221 sipe_contact_set_acl(sip, name, "");
1224 static void
1225 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1227 xmlnode *watchers;
1228 xmlnode *watcher;
1229 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1230 if (msg->response != 0 && msg->response != 200) return;
1232 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1234 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1235 if (!watchers) return;
1237 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1238 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1239 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1240 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1242 // TODO pull out optional displayName to pass as alias
1243 if (remote_user) {
1244 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1245 job->who = remote_user;
1246 job->sip = sip;
1247 purple_account_request_authorization(
1248 sip->account,
1249 remote_user,
1250 _("you"), /* id */
1251 alias,
1252 NULL, /* message */
1253 on_list,
1254 sipe_auth_user_cb,
1255 sipe_deny_user_cb,
1256 (void *) job);
1261 xmlnode_free(watchers);
1262 return;
1265 static void
1266 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1268 PurpleGroup * purple_group = purple_find_group(group->name);
1269 if (!purple_group) {
1270 purple_group = purple_group_new(group->name);
1271 purple_blist_add_group(purple_group, NULL);
1274 if (purple_group) {
1275 group->purple_group = purple_group;
1276 sip->groups = g_slist_append(sip->groups, group);
1277 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1278 } else {
1279 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1283 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1285 struct sipe_group *group;
1286 GSList *entry;
1287 if (sip == NULL) {
1288 return NULL;
1291 entry = sip->groups;
1292 while (entry) {
1293 group = entry->data;
1294 if (group->id == id) {
1295 return group;
1297 entry = entry->next;
1299 return NULL;
1302 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1304 struct sipe_group *group;
1305 GSList *entry;
1306 if (!sip || !name) {
1307 return NULL;
1310 entry = sip->groups;
1311 while (entry) {
1312 group = entry->data;
1313 if (sipe_strequal(group->name, name)) {
1314 return group;
1316 entry = entry->next;
1318 return NULL;
1321 static void
1322 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1324 gchar *body;
1325 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1326 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1327 send_soap_request(sip, body);
1328 g_free(body);
1329 g_free(group->name);
1330 group->name = g_strdup(name);
1334 * Only appends if no such value already stored.
1335 * Like Set in Java.
1337 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1338 GSList * res = list;
1339 if (!g_slist_find_custom(list, data, func)) {
1340 res = g_slist_insert_sorted(list, data, func);
1342 return res;
1345 static int
1346 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1347 return group1->id - group2->id;
1351 * Returns string like "2 4 7 8" - group ids buddy belong to.
1353 static gchar *
1354 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1355 int i = 0;
1356 gchar *res;
1357 //creating array from GList, converting int to gchar*
1358 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1359 GSList *entry = buddy->groups;
1361 if (!ids_arr) return NULL;
1363 while (entry) {
1364 struct sipe_group * group = entry->data;
1365 ids_arr[i] = g_strdup_printf("%d", group->id);
1366 entry = entry->next;
1367 i++;
1369 ids_arr[i] = NULL;
1370 res = g_strjoinv(" ", ids_arr);
1371 g_strfreev(ids_arr);
1372 return res;
1376 * Sends buddy update to server
1378 static void
1379 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1381 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1382 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1384 if (buddy && purple_buddy) {
1385 const char *alias = purple_buddy_get_alias(purple_buddy);
1386 gchar *groups = sipe_get_buddy_groups_string(buddy);
1387 if (groups) {
1388 gchar *body;
1389 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1391 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1392 alias, groups, "true", buddy->name, sip->contacts_delta++
1394 send_soap_request(sip, body);
1395 g_free(groups);
1396 g_free(body);
1401 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1403 if (msg->response == 200) {
1404 struct sipe_group *group;
1405 struct group_user_context *ctx = trans->payload->data;
1406 xmlnode *xml;
1407 xmlnode *node;
1408 char *group_id;
1409 struct sipe_buddy *buddy;
1411 xml = xmlnode_from_str(msg->body, msg->bodylen);
1412 if (!xml) {
1413 return FALSE;
1416 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1417 if (!node) {
1418 xmlnode_free(xml);
1419 return FALSE;
1422 group_id = xmlnode_get_data(node);
1423 if (!group_id) {
1424 xmlnode_free(xml);
1425 return FALSE;
1428 group = g_new0(struct sipe_group, 1);
1429 group->id = (int)g_ascii_strtod(group_id, NULL);
1430 g_free(group_id);
1431 group->name = g_strdup(ctx->group_name);
1433 sipe_group_add(sip, group);
1435 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1436 if (buddy) {
1437 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1440 sipe_group_set_user(sip, ctx->user_name);
1442 xmlnode_free(xml);
1443 return TRUE;
1445 return FALSE;
1448 static void sipe_group_context_destroy(gpointer data)
1450 struct group_user_context *ctx = data;
1451 g_free(ctx->group_name);
1452 g_free(ctx->user_name);
1453 g_free(ctx);
1456 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1458 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1459 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1460 gchar *body;
1461 ctx->group_name = g_strdup(name);
1462 ctx->user_name = g_strdup(who);
1463 payload->destroy = sipe_group_context_destroy;
1464 payload->data = ctx;
1466 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1467 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1468 g_free(body);
1472 * Data structure for scheduled actions
1475 struct scheduled_action {
1477 * Name of action.
1478 * Format is <Event>[<Data>...]
1479 * Example: <presence><sip:user@domain.com> or <registration>
1481 gchar *name;
1482 guint timeout_handler;
1483 gboolean repetitive;
1484 Action action;
1485 GDestroyNotify destroy;
1486 struct sipe_account_data *sip;
1487 void *payload;
1491 * A timer callback
1492 * Should return FALSE if repetitive action is not needed
1494 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1496 gboolean ret;
1497 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1498 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1499 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1500 (sched_action->action)(sched_action->sip, sched_action->payload);
1501 ret = sched_action->repetitive;
1502 if (sched_action->destroy) {
1503 (*sched_action->destroy)(sched_action->payload);
1505 g_free(sched_action->name);
1506 g_free(sched_action);
1507 return ret;
1511 * Kills action timer effectively cancelling
1512 * scheduled action
1514 * @param name of action
1516 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1518 GSList *entry;
1520 if (!sip->timeouts || !name) return;
1522 entry = sip->timeouts;
1523 while (entry) {
1524 struct scheduled_action *sched_action = entry->data;
1525 if(sipe_strequal(sched_action->name, name)) {
1526 GSList *to_delete = entry;
1527 entry = entry->next;
1528 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1529 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1530 purple_timeout_remove(sched_action->timeout_handler);
1531 if (sched_action->destroy) {
1532 (*sched_action->destroy)(sched_action->payload);
1534 g_free(sched_action->name);
1535 g_free(sched_action);
1536 } else {
1537 entry = entry->next;
1542 static void
1543 sipe_schedule_action0(const gchar *name,
1544 int timeout,
1545 gboolean isSeconds,
1546 Action action,
1547 GDestroyNotify destroy,
1548 struct sipe_account_data *sip,
1549 void *payload)
1551 struct scheduled_action *sched_action;
1553 /* Make sure each action only exists once */
1554 sipe_cancel_scheduled_action(sip, name);
1556 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1557 sched_action = g_new0(struct scheduled_action, 1);
1558 sched_action->repetitive = FALSE;
1559 sched_action->name = g_strdup(name);
1560 sched_action->action = action;
1561 sched_action->destroy = destroy;
1562 sched_action->sip = sip;
1563 sched_action->payload = payload;
1564 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1565 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1566 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1567 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1570 void
1571 sipe_schedule_action(const gchar *name,
1572 int timeout,
1573 Action action,
1574 GDestroyNotify destroy,
1575 struct sipe_account_data *sip,
1576 void *payload)
1578 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1582 * Same as sipe_schedule_action() but timeout is in milliseconds.
1584 static void
1585 sipe_schedule_action_msec(const gchar *name,
1586 int timeout,
1587 Action action,
1588 GDestroyNotify destroy,
1589 struct sipe_account_data *sip,
1590 void *payload)
1592 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1595 static void
1596 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1597 time_t calculate_from);
1599 static int
1600 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1602 static const char*
1603 sipe_get_status_by_availability(int avail,
1604 char** activity);
1606 static void
1607 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1608 const char *status_id,
1609 const char *message,
1610 time_t do_not_publish[]);
1612 static void
1613 sipe_apply_calendar_status(struct sipe_account_data *sip,
1614 struct sipe_buddy *sbuddy,
1615 const char *status_id)
1617 time_t cal_avail_since;
1618 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1619 int avail;
1620 gchar *self_uri;
1622 if (!sbuddy) return;
1624 if (cal_status < SIPE_CAL_NO_DATA) {
1625 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_status : %d for %s\n", cal_status, sbuddy->name);
1626 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1629 /* scheduled Cal update call */
1630 if (!status_id) {
1631 status_id = sbuddy->last_non_cal_status_id;
1632 g_free(sbuddy->activity);
1633 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1636 if (!status_id) {
1637 purple_debug_info("sipe", "sipe_apply_calendar_status: status_id is NULL for %s, exiting.\n",
1638 sbuddy->name ? sbuddy->name : "" );
1639 return;
1642 /* adjust to calendar status */
1643 if (cal_status != SIPE_CAL_NO_DATA) {
1644 purple_debug_info("sipe", "sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1646 if (cal_status == SIPE_CAL_BUSY
1647 && cal_avail_since > sbuddy->user_avail_since
1648 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1650 status_id = SIPE_STATUS_ID_BUSY;
1651 g_free(sbuddy->activity);
1652 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1654 avail = sipe_get_availability_by_status(status_id, NULL);
1656 purple_debug_info("sipe", "sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1657 if (cal_avail_since > sbuddy->activity_since) {
1658 if (cal_status == SIPE_CAL_OOF
1659 && avail >= 15000) /* 12000 in 2007 */
1661 g_free(sbuddy->activity);
1662 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1667 /* then set status_id actually */
1668 purple_debug_info("sipe", "sipe_apply_calendar_status: to %s for %s\n", status_id, sbuddy->name ? sbuddy->name : "" );
1669 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1671 /* set our account state to the one in roaming (including calendar info) */
1672 self_uri = sip_uri_self(sip);
1673 if (sip->initial_state_published && sipe_strequal(sbuddy->name, self_uri)) {
1674 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1675 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1678 purple_debug_info("sipe", "sipe_apply_calendar_status: switch to '%s' for the account\n", sip->status);
1679 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1681 g_free(self_uri);
1684 static void
1685 sipe_got_user_status(struct sipe_account_data *sip,
1686 const char* uri,
1687 const char *status_id)
1689 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1691 if (!sbuddy) return;
1693 /* Check if on 2005 system contact's calendar,
1694 * then set/preserve it.
1696 if (!sip->ocs2007) {
1697 sipe_apply_calendar_status(sip, sbuddy, status_id);
1698 } else {
1699 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1703 static void
1704 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1705 struct sipe_buddy *sbuddy,
1706 struct sipe_account_data *sip)
1708 sipe_apply_calendar_status(sip, sbuddy, NULL);
1712 * Updates contact's status
1713 * based on their calendar information.
1715 * Applicability: 2005 systems
1717 static void
1718 update_calendar_status(struct sipe_account_data *sip)
1720 purple_debug_info("sipe", "update_calendar_status() started.\n");
1721 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1723 /* repeat scheduling */
1724 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1728 * Schedules process of contacts' status update
1729 * based on their calendar information.
1730 * Should be scheduled to the beginning of every
1731 * 15 min interval, like:
1732 * 13:00, 13:15, 13:30, 13:45, etc.
1734 * Applicability: 2005 systems
1736 static void
1737 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1738 time_t calculate_from)
1740 int interval = 15*60;
1741 /** start of the beginning of closest 15 min interval. */
1742 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1744 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1745 asctime(localtime(&calculate_from)));
1746 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1747 asctime(localtime(&next_start)));
1749 sipe_schedule_action("<+2005-cal-status>",
1750 (int)(next_start - time(NULL)),
1751 (Action)update_calendar_status,
1752 NULL,
1753 sip,
1754 NULL);
1758 * Schedules process of self status publish
1759 * based on own calendar information.
1760 * Should be scheduled to the beginning of every
1761 * 15 min interval, like:
1762 * 13:00, 13:15, 13:30, 13:45, etc.
1764 * Applicability: 2007+ systems
1766 static void
1767 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1768 time_t calculate_from)
1770 int interval = 5*60;
1771 /** start of the beginning of closest 5 min interval. */
1772 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1774 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1775 asctime(localtime(&calculate_from)));
1776 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1777 asctime(localtime(&next_start)));
1779 sipe_schedule_action("<+2007-cal-status>",
1780 (int)(next_start - time(NULL)),
1781 (Action)publish_calendar_status_self,
1782 NULL,
1783 sip,
1784 NULL);
1787 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1789 /** Should be g_free()'d
1791 static gchar *
1792 sipe_get_subscription_key(const gchar *event,
1793 const gchar *with)
1795 gchar *key = NULL;
1797 if (is_empty(event)) return NULL;
1799 if (event && !g_ascii_strcasecmp(event, "presence")) {
1800 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1801 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1803 /* @TODO drop participated buddies' just_added flag */
1804 } else if (event) {
1805 /* Subscription is identified by <event> key */
1806 key = g_strdup_printf("<%s>", event);
1809 return key;
1812 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1813 SIPE_UNUSED_PARAMETER struct transaction *trans)
1815 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1816 const gchar *event = sipmsg_find_header(msg, "Event");
1817 gchar *key;
1819 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1820 if (!event) {
1821 struct sipmsg *request_msg = trans->msg;
1822 event = sipmsg_find_header(request_msg, "Event");
1825 key = sipe_get_subscription_key(event, with);
1827 /* 200 OK; 481 Call Leg Does Not Exist */
1828 if (key && (msg->response == 200 || msg->response == 481)) {
1829 if (g_hash_table_lookup(sip->subscriptions, key)) {
1830 g_hash_table_remove(sip->subscriptions, key);
1831 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1835 /* create/store subscription dialog if not yet */
1836 if (msg->response == 200) {
1837 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1838 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1840 if (key) {
1841 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1842 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1844 subscription->dialog.callid = g_strdup(callid);
1845 subscription->dialog.cseq = atoi(cseq);
1846 subscription->dialog.with = g_strdup(with);
1847 subscription->event = g_strdup(event);
1848 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1850 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1853 g_free(cseq);
1856 g_free(key);
1857 g_free(with);
1859 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1861 process_incoming_notify(sip, msg, FALSE, FALSE);
1863 return TRUE;
1866 static void sipe_subscribe_resource_uri(const char *name,
1867 SIPE_UNUSED_PARAMETER gpointer value,
1868 gchar **resources_uri)
1870 gchar *tmp = *resources_uri;
1871 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1872 g_free(tmp);
1875 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1877 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1878 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1879 gchar *tmp = *resources_uri;
1881 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1883 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1884 g_free(tmp);
1888 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1889 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1890 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1891 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1892 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1895 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1897 gchar *key;
1898 gchar *contact = get_contact(sip);
1899 gchar *request;
1900 gchar *content;
1901 gchar *require = "";
1902 gchar *accept = "";
1903 gchar *autoextend = "";
1904 gchar *content_type;
1905 struct sip_dialog *dialog;
1907 if (sip->ocs2007) {
1908 require = ", categoryList";
1909 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1910 content_type = "application/msrtc-adrl-categorylist+xml";
1911 content = g_strdup_printf(
1912 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1913 "<action name=\"subscribe\" id=\"63792024\">\n"
1914 "<adhocList>\n%s</adhocList>\n"
1915 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1916 "<category name=\"calendarData\"/>\n"
1917 "<category name=\"contactCard\"/>\n"
1918 "<category name=\"note\"/>\n"
1919 "<category name=\"state\"/>\n"
1920 "</categoryList>\n"
1921 "</action>\n"
1922 "</batchSub>", sip->username, resources_uri);
1923 } else {
1924 autoextend = "Supported: com.microsoft.autoextend\r\n";
1925 content_type = "application/adrl+xml";
1926 content = g_strdup_printf(
1927 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1928 "<create xmlns=\"\">\n%s</create>\n"
1929 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1931 g_free(resources_uri);
1933 request = g_strdup_printf(
1934 "Require: adhoclist%s\r\n"
1935 "Supported: eventlist\r\n"
1936 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1937 "Supported: ms-piggyback-first-notify\r\n"
1938 "%sSupported: ms-benotify\r\n"
1939 "Proxy-Require: ms-benotify\r\n"
1940 "Event: presence\r\n"
1941 "Content-Type: %s\r\n"
1942 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1943 g_free(contact);
1945 /* subscribe to buddy presence */
1946 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1947 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1948 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1949 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1951 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1953 g_free(content);
1954 g_free(to);
1955 g_free(request);
1956 g_free(key);
1959 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1960 SIPE_UNUSED_PARAMETER void *unused)
1962 gchar *to = sip_uri_self(sip);
1963 gchar *resources_uri = g_strdup("");
1964 if (sip->ocs2007) {
1965 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1966 } else {
1967 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1970 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1973 struct presence_batched_routed {
1974 gchar *host;
1975 GSList *buddies;
1978 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1980 struct presence_batched_routed *data = payload;
1981 GSList *buddies = data->buddies;
1982 while (buddies) {
1983 g_free(buddies->data);
1984 buddies = buddies->next;
1986 g_slist_free(data->buddies);
1987 g_free(data->host);
1988 g_free(payload);
1991 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1993 struct presence_batched_routed *data = payload;
1994 GSList *buddies = data->buddies;
1995 gchar *resources_uri = g_strdup("");
1996 while (buddies) {
1997 gchar *tmp = resources_uri;
1998 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1999 g_free(tmp);
2000 buddies = buddies->next;
2002 sipe_subscribe_presence_batched_to(sip, resources_uri,
2003 g_strdup(data->host));
2007 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2008 * The user sends a single SUBSCRIBE request to the subscribed contact.
2009 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2013 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2016 gchar *key;
2017 gchar *to = sip_uri((char *)buddy_name);
2018 gchar *tmp = get_contact(sip);
2019 gchar *request;
2020 gchar *content = NULL;
2021 gchar *autoextend = "";
2022 gchar *content_type = "";
2023 struct sip_dialog *dialog;
2024 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2025 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2027 if (sbuddy) sbuddy->just_added = FALSE;
2029 if (sip->ocs2007) {
2030 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2031 } else {
2032 autoextend = "Supported: com.microsoft.autoextend\r\n";
2035 request = g_strdup_printf(
2036 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2037 "Supported: ms-piggyback-first-notify\r\n"
2038 "%s%sSupported: ms-benotify\r\n"
2039 "Proxy-Require: ms-benotify\r\n"
2040 "Event: presence\r\n"
2041 "Contact: %s\r\n", autoextend, content_type, tmp);
2043 if (sip->ocs2007) {
2044 content = g_strdup_printf(
2045 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2046 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2047 "<resource uri=\"%s\"%s\n"
2048 "</adhocList>\n"
2049 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2050 "<category name=\"calendarData\"/>\n"
2051 "<category name=\"contactCard\"/>\n"
2052 "<category name=\"note\"/>\n"
2053 "<category name=\"state\"/>\n"
2054 "</categoryList>\n"
2055 "</action>\n"
2056 "</batchSub>", sip->username, to, context);
2059 g_free(tmp);
2061 /* subscribe to buddy presence */
2062 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2063 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2064 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2065 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2067 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2069 g_free(content);
2070 g_free(to);
2071 g_free(request);
2072 g_free(key);
2075 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2077 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2079 if (!purple_status_is_active(status))
2080 return;
2082 if (account->gc) {
2083 struct sipe_account_data *sip = account->gc->proto_data;
2085 if (sip) {
2086 gchar *action_name;
2087 gchar *tmp;
2088 time_t now = time(NULL);
2089 const char *status_id = purple_status_get_id(status);
2090 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2091 sipe_activity activity = sipe_get_activity_by_token(status_id);
2092 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2094 /* when other point of presence clears note, but we are keeping
2095 * state if OOF note.
2097 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2098 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2099 do_not_publish = FALSE;
2102 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2103 status_id, (int)sip->do_not_publish[activity], (int)now);
2105 sip->do_not_publish[activity] = 0;
2106 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2107 status_id, (int)sip->do_not_publish[activity]);
2109 if (do_not_publish)
2111 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2112 return;
2115 g_free(sip->status);
2116 sip->status = g_strdup(status_id);
2118 /* hack to escape apostrof before comparison */
2119 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2121 /* this will preserve OOF flag as well */
2122 if (!sipe_strequal(tmp, sip->note)) {
2123 sip->is_oof_note = FALSE;
2124 g_free(sip->note);
2125 sip->note = g_strdup(note);
2126 sip->note_since = time(NULL);
2128 g_free(tmp);
2130 /* schedule 2 sec to capture idle flag */
2131 action_name = g_strdup_printf("<%s>", "+set-status");
2132 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2133 g_free(action_name);
2137 static void
2138 sipe_set_idle(PurpleConnection * gc,
2139 int interval)
2141 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2143 if (gc) {
2144 struct sipe_account_data *sip = gc->proto_data;
2146 if (sip) {
2147 sip->idle_switch = time(NULL);
2148 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2153 static void
2154 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2155 SIPE_UNUSED_PARAMETER const char *alias)
2157 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2158 sipe_group_set_user(sip, name);
2161 static void
2162 sipe_group_buddy(PurpleConnection *gc,
2163 const char *who,
2164 const char *old_group_name,
2165 const char *new_group_name)
2167 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2168 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2169 struct sipe_group * old_group = NULL;
2170 struct sipe_group * new_group;
2172 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2173 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2175 if(!buddy) { // buddy not in roaming list
2176 return;
2179 if (old_group_name) {
2180 old_group = sipe_group_find_by_name(sip, old_group_name);
2182 new_group = sipe_group_find_by_name(sip, new_group_name);
2184 if (old_group) {
2185 buddy->groups = g_slist_remove(buddy->groups, old_group);
2186 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2189 if (!new_group) {
2190 sipe_group_create(sip, new_group_name, who);
2191 } else {
2192 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2193 sipe_group_set_user(sip, who);
2197 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2199 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2201 /* libpurple can call us with undefined buddy or group */
2202 if (buddy && group) {
2203 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2205 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2206 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2207 purple_blist_rename_buddy(buddy, buddy_name);
2208 g_free(buddy_name);
2210 /* Prepend sip: if needed */
2211 if (!g_str_has_prefix(buddy->name, "sip:")) {
2212 gchar *buf = sip_uri_from_name(buddy->name);
2213 purple_blist_rename_buddy(buddy, buf);
2214 g_free(buf);
2217 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2218 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2219 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2220 b->name = g_strdup(buddy->name);
2221 b->just_added = TRUE;
2222 g_hash_table_insert(sip->buddies, b->name, b);
2223 sipe_group_buddy(gc, b->name, NULL, group->name);
2224 /* @TODO should go to callback */
2225 sipe_subscribe_presence_single(sip, b->name);
2226 } else {
2227 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2232 static void sipe_free_buddy(struct sipe_buddy *buddy)
2234 #ifndef _WIN32
2236 * We are calling g_hash_table_foreach_steal(). That means that no
2237 * key/value deallocation functions are called. Therefore the glib
2238 * hash code does not touch the key (buddy->name) or value (buddy)
2239 * of the to-be-deleted hash node at all. It follows that we
2241 * - MUST free the memory for the key ourselves and
2242 * - ARE allowed to do it in this function
2244 * Conclusion: glib must be broken on the Windows platform if sipe
2245 * crashes with SIGTRAP when closing. You'll have to live
2246 * with the memory leak until this is fixed.
2248 g_free(buddy->name);
2249 #endif
2250 g_free(buddy->activity);
2251 g_free(buddy->meeting_subject);
2252 g_free(buddy->meeting_location);
2253 g_free(buddy->note);
2255 g_free(buddy->cal_start_time);
2256 g_free(buddy->cal_free_busy_base64);
2257 g_free(buddy->cal_free_busy);
2258 g_free(buddy->last_non_cal_activity);
2260 sipe_cal_free_working_hours(buddy->cal_working_hours);
2262 g_free(buddy->device_name);
2263 g_slist_free(buddy->groups);
2264 g_free(buddy);
2268 * Unassociates buddy from group first.
2269 * Then see if no groups left, removes buddy completely.
2270 * Otherwise updates buddy groups on server.
2272 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2274 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2275 struct sipe_buddy *b;
2276 struct sipe_group *g = NULL;
2278 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2279 if (!buddy) return;
2281 b = g_hash_table_lookup(sip->buddies, buddy->name);
2282 if (!b) return;
2284 if (group) {
2285 g = sipe_group_find_by_name(sip, group->name);
2288 if (g) {
2289 b->groups = g_slist_remove(b->groups, g);
2290 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2293 if (g_slist_length(b->groups) < 1) {
2294 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2295 sipe_cancel_scheduled_action(sip, action_name);
2296 g_free(action_name);
2298 g_hash_table_remove(sip->buddies, buddy->name);
2300 if (b->name) {
2301 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2302 send_soap_request(sip, body);
2303 g_free(body);
2306 sipe_free_buddy(b);
2307 } else {
2308 //updates groups on server
2309 sipe_group_set_user(sip, b->name);
2314 static void
2315 sipe_rename_group(PurpleConnection *gc,
2316 const char *old_name,
2317 PurpleGroup *group,
2318 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2320 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2321 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2322 if (s_group) {
2323 sipe_group_rename(sip, s_group, group->name);
2324 } else {
2325 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2329 static void
2330 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2332 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2333 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2334 if (s_group) {
2335 gchar *body;
2336 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2337 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2338 send_soap_request(sip, body);
2339 g_free(body);
2341 sip->groups = g_slist_remove(sip->groups, s_group);
2342 g_free(s_group->name);
2343 g_free(s_group);
2344 } else {
2345 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2349 /** All statuses need message attribute to pass Note */
2350 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2352 PurpleStatusType *type;
2353 GList *types = NULL;
2355 /* Macros to reduce code repetition.
2356 Translators: noun */
2357 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2358 prim, id, name, \
2359 TRUE, user, FALSE, \
2360 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2361 NULL); \
2362 types = g_list_append(types, type);
2364 /* Online */
2365 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2366 NULL,
2367 NULL,
2368 TRUE);
2370 /* Busy */
2371 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2372 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2373 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2374 TRUE);
2376 /* Do Not Disturb */
2377 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2378 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2379 NULL,
2380 TRUE);
2382 /* Away */
2383 /* Goes first in the list as
2384 * purple picks the first status with the AWAY type
2385 * for idle.
2387 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2388 NULL,
2389 NULL,
2390 TRUE);
2392 /* Be Right Back */
2393 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2394 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2395 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2396 TRUE);
2398 /* Appear Offline */
2399 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2400 NULL,
2401 NULL,
2402 TRUE);
2404 /* Offline */
2405 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2406 NULL,
2407 NULL,
2408 TRUE);
2409 types = g_list_append(types, type);
2411 return types;
2415 * A callback for g_hash_table_foreach
2417 static void
2418 sipe_buddy_subscribe_cb(char *buddy_name,
2419 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2420 struct sipe_account_data *sip)
2422 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2423 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2424 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2425 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2427 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2428 g_free(action_name);
2432 * Removes entries from purple buddy list
2433 * that does not correspond ones in the roaming contact list.
2435 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2436 GSList *buddies = purple_find_buddies(sip->account, NULL);
2437 GSList *entry = buddies;
2438 struct sipe_buddy *buddy;
2439 PurpleBuddy *b;
2440 PurpleGroup *g;
2442 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2443 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2444 while (entry) {
2445 b = entry->data;
2446 g = purple_buddy_get_group(b);
2447 buddy = g_hash_table_lookup(sip->buddies, b->name);
2448 if(buddy) {
2449 gboolean in_sipe_groups = FALSE;
2450 GSList *entry2 = buddy->groups;
2451 while (entry2) {
2452 struct sipe_group *group = entry2->data;
2453 if (sipe_strequal(group->name, g->name)) {
2454 in_sipe_groups = TRUE;
2455 break;
2457 entry2 = entry2->next;
2459 if(!in_sipe_groups) {
2460 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2461 purple_blist_remove_buddy(b);
2463 } else {
2464 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2465 purple_blist_remove_buddy(b);
2467 entry = entry->next;
2469 g_slist_free(buddies);
2472 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2474 int len = msg->bodylen;
2476 const gchar *tmp = sipmsg_find_header(msg, "Event");
2477 xmlnode *item;
2478 xmlnode *isc;
2479 const gchar *contacts_delta;
2480 xmlnode *group_node;
2481 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2482 return FALSE;
2485 /* Convert the contact from XML to Purple Buddies */
2486 isc = xmlnode_from_str(msg->body, len);
2487 if (!isc) {
2488 return FALSE;
2491 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2492 if (contacts_delta) {
2493 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2496 if (sipe_strequal(isc->name, "contactList")) {
2498 /* Parse groups */
2499 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2500 struct sipe_group * group = g_new0(struct sipe_group, 1);
2501 const char *name = xmlnode_get_attrib(group_node, "name");
2503 if (g_str_has_prefix(name, "~")) {
2504 name = _("Other Contacts");
2506 group->name = g_strdup(name);
2507 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2509 sipe_group_add(sip, group);
2512 // Make sure we have at least one group
2513 if (g_slist_length(sip->groups) == 0) {
2514 struct sipe_group * group = g_new0(struct sipe_group, 1);
2515 PurpleGroup *purple_group;
2516 group->name = g_strdup(_("Other Contacts"));
2517 group->id = 1;
2518 purple_group = purple_group_new(group->name);
2519 purple_blist_add_group(purple_group, NULL);
2520 sip->groups = g_slist_append(sip->groups, group);
2523 /* Parse contacts */
2524 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2525 const gchar *uri = xmlnode_get_attrib(item, "uri");
2526 const gchar *name = xmlnode_get_attrib(item, "name");
2527 gchar *buddy_name;
2528 struct sipe_buddy *buddy = NULL;
2529 gchar *tmp;
2530 gchar **item_groups;
2531 int i = 0;
2533 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2534 tmp = sip_uri_from_name(uri);
2535 buddy_name = g_ascii_strdown(tmp, -1);
2536 g_free(tmp);
2538 /* assign to group Other Contacts if nothing else received */
2539 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2540 if(is_empty(tmp)) {
2541 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2542 g_free(tmp);
2543 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2545 item_groups = g_strsplit(tmp, " ", 0);
2546 g_free(tmp);
2548 while (item_groups[i]) {
2549 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2551 // If couldn't find the right group for this contact, just put them in the first group we have
2552 if (group == NULL && g_slist_length(sip->groups) > 0) {
2553 group = sip->groups->data;
2556 if (group != NULL) {
2557 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2558 if (!b){
2559 b = purple_buddy_new(sip->account, buddy_name, uri);
2560 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2562 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2565 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2566 if (name != NULL && strlen(name) != 0) {
2567 purple_blist_alias_buddy(b, name);
2569 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2573 if (!buddy) {
2574 buddy = g_new0(struct sipe_buddy, 1);
2575 buddy->name = g_strdup(b->name);
2576 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2579 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2581 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2582 } else {
2583 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2584 name);
2587 i++;
2588 } // while, contact groups
2589 g_strfreev(item_groups);
2590 g_free(buddy_name);
2592 } // for, contacts
2594 sipe_cleanup_local_blist(sip);
2596 /* Add self-contact if not there yet. 2005 systems. */
2597 /* This will resemble subscription to roaming_self in 2007 systems */
2598 if (!sip->ocs2007) {
2599 gchar *self_uri = sip_uri_self(sip);
2600 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2602 if (!buddy) {
2603 buddy = g_new0(struct sipe_buddy, 1);
2604 buddy->name = g_strdup(self_uri);
2605 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2607 g_free(self_uri);
2610 xmlnode_free(isc);
2612 /* subscribe to buddies */
2613 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2614 if (sip->batched_support) {
2615 sipe_subscribe_presence_batched(sip, NULL);
2616 } else {
2617 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2619 sip->subscribed_buddies = TRUE;
2621 /* for 2005 systems schedule contacts' status update
2622 * based on their calendar information
2624 if (!sip->ocs2007) {
2625 sipe_sched_calendar_status_update(sip, time(NULL));
2628 return 0;
2632 * Subscribe roaming contacts
2634 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2636 gchar *to = sip_uri_self(sip);
2637 gchar *tmp = get_contact(sip);
2638 gchar *hdr = g_strdup_printf(
2639 "Event: vnd-microsoft-roaming-contacts\r\n"
2640 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2641 "Supported: com.microsoft.autoextend\r\n"
2642 "Supported: ms-benotify\r\n"
2643 "Proxy-Require: ms-benotify\r\n"
2644 "Supported: ms-piggyback-first-notify\r\n"
2645 "Contact: %s\r\n", tmp);
2646 g_free(tmp);
2648 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2649 g_free(to);
2650 g_free(hdr);
2653 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2654 SIPE_UNUSED_PARAMETER void *unused)
2656 gchar *key;
2657 struct sip_dialog *dialog;
2658 gchar *to = sip_uri_self(sip);
2659 gchar *tmp = get_contact(sip);
2660 gchar *hdr = g_strdup_printf(
2661 "Event: presence.wpending\r\n"
2662 "Accept: text/xml+msrtc.wpending\r\n"
2663 "Supported: com.microsoft.autoextend\r\n"
2664 "Supported: ms-benotify\r\n"
2665 "Proxy-Require: ms-benotify\r\n"
2666 "Supported: ms-piggyback-first-notify\r\n"
2667 "Contact: %s\r\n", tmp);
2668 g_free(tmp);
2670 /* Subscription is identified by <event> key */
2671 key = g_strdup_printf("<%s>", "presence.wpending");
2672 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2673 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2675 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2677 g_free(to);
2678 g_free(hdr);
2679 g_free(key);
2683 * Fires on deregistration event initiated by server.
2684 * [MS-SIPREGE] SIP extension.
2687 // 2007 Example
2689 // Content-Type: text/registration-event
2690 // subscription-state: terminated;expires=0
2691 // ms-diagnostics-public: 4141;reason="User disabled"
2693 // deregistered;event=rejected
2695 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2697 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2698 gchar *event = NULL;
2699 gchar *reason = NULL;
2700 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2701 gchar *warning;
2703 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2704 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2706 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2707 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2708 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2709 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2710 } else {
2711 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2712 return;
2715 if (diagnostics != NULL) {
2716 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2717 } else { // for LCS2005
2718 int error_id = 0;
2719 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2720 error_id = 4140; // [MS-SIPREGE]
2721 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2722 reason = g_strdup(_("you are already signed in at another location"));
2723 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2724 error_id = 4141;
2725 reason = g_strdup(_("user disabled")); // [MS-OCER]
2726 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2727 error_id = 4142;
2728 reason = g_strdup(_("user moved")); // [MS-OCER]
2731 g_free(event);
2732 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2733 g_free(reason);
2735 sip->gc->wants_to_die = TRUE;
2736 purple_connection_error(sip->gc, warning);
2737 g_free(warning);
2741 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2743 xmlnode *xn_provision_group_list;
2744 xmlnode *node;
2746 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2748 /* provisionGroup */
2749 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2750 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2751 g_free(sip->focus_factory_uri);
2752 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2753 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2754 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2755 break;
2758 xmlnode_free(xn_provision_group_list);
2761 /** for 2005 system */
2762 static void
2763 sipe_process_provisioning(struct sipe_account_data *sip,
2764 struct sipmsg *msg)
2766 xmlnode *xn_provision;
2767 xmlnode *node;
2769 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2770 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2771 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2772 if ((node = xmlnode_get_child(node, "line"))) {
2773 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2774 const gchar *server = xmlnode_get_attrib(node, "server");
2775 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2776 sip_csta_open(sip, line_uri, server);
2779 xmlnode_free(xn_provision);
2782 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2784 const gchar *contacts_delta;
2785 xmlnode *xml;
2787 xml = xmlnode_from_str(msg->body, msg->bodylen);
2788 if (!xml)
2790 return;
2793 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2794 if (contacts_delta)
2796 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2799 xmlnode_free(xml);
2802 static void
2803 free_container(struct sipe_container *container)
2805 GSList *entry;
2807 if (!container) return;
2809 entry = container->members;
2810 while (entry) {
2811 void *data = entry->data;
2812 entry = g_slist_remove(entry, data);
2813 g_free(data);
2815 g_free(container);
2819 * Finds locally stored MS-PRES container member
2821 static struct sipe_container_member *
2822 sipe_find_container_member(struct sipe_container *container,
2823 const gchar *type,
2824 const gchar *value)
2826 struct sipe_container_member *member;
2827 GSList *entry;
2829 if (container == NULL || type == NULL) {
2830 return NULL;
2833 entry = container->members;
2834 while (entry) {
2835 member = entry->data;
2836 if (!g_strcasecmp(member->type, type)
2837 && ((!member->value && !value)
2838 || (value && member->value && !g_strcasecmp(member->value, value)))
2840 return member;
2842 entry = entry->next;
2844 return NULL;
2848 * Finds locally stored MS-PRES container by id
2850 static struct sipe_container *
2851 sipe_find_container(struct sipe_account_data *sip,
2852 guint id)
2854 struct sipe_container *container;
2855 GSList *entry;
2857 if (sip == NULL) {
2858 return NULL;
2861 entry = sip->containers;
2862 while (entry) {
2863 container = entry->data;
2864 if (id == container->id) {
2865 return container;
2867 entry = entry->next;
2869 return NULL;
2873 * Access Levels
2874 * 32000 - Blocked
2875 * 400 - Personal
2876 * 300 - Team
2877 * 200 - Company
2878 * 100 - Public
2880 static int
2881 sipe_find_access_level(struct sipe_account_data *sip,
2882 const gchar *type,
2883 const gchar *value)
2885 guint containers[] = {32000, 400, 300, 200, 100};
2886 int i = 0;
2888 for (i = 0; i < 5; i++) {
2889 struct sipe_container_member *member;
2890 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2891 if (!container) continue;
2893 member = sipe_find_container_member(container, type, value);
2894 if (member) {
2895 return containers[i];
2899 return -1;
2902 static void
2903 sipe_send_set_container_members(struct sipe_account_data *sip,
2904 guint container_id,
2905 guint container_version,
2906 const gchar* action,
2907 const gchar* type,
2908 const gchar* value)
2910 gchar *self = sip_uri_self(sip);
2911 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2912 gchar *contact;
2913 gchar *hdr;
2914 gchar *body = g_strdup_printf(
2915 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2916 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2917 "</setContainerMembers>",
2918 container_id,
2919 container_version,
2920 action,
2921 type,
2922 value_str);
2923 g_free(value_str);
2925 contact = get_contact(sip);
2926 hdr = g_strdup_printf("Contact: %s\r\n"
2927 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2928 g_free(contact);
2930 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2932 g_free(hdr);
2933 g_free(body);
2934 g_free(self);
2937 static void
2938 free_publication(struct sipe_publication *publication)
2940 g_free(publication->category);
2941 g_free(publication->cal_event_hash);
2942 g_free(publication->note);
2944 g_free(publication->working_hours_xml_str);
2945 g_free(publication->fb_start_str);
2946 g_free(publication->free_busy_base64);
2948 g_free(publication);
2951 /* key is <category><instance><container> */
2952 static gboolean
2953 sipe_is_our_publication(struct sipe_account_data *sip,
2954 const gchar *key)
2956 GSList *entry;
2958 /* filling keys for our publications if not yet cached */
2959 if (!sip->our_publication_keys) {
2960 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2961 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2962 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2963 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2964 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2965 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2966 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2968 purple_debug_info("sipe", "* Our Publication Instances *\n");
2969 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2970 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2971 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2972 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2973 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2974 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2975 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2976 purple_debug_info("sipe", "\tNote : %u\n", 0);
2977 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2979 /* device */
2980 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2981 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2983 /* state:machineState */
2984 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2985 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2986 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2987 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2989 /* state:userState */
2990 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2991 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2992 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2993 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2995 /* state:calendarState */
2996 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2997 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2998 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2999 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3001 /* state:calendarState OOF */
3002 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3003 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3004 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3005 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3007 /* note */
3008 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3009 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3010 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3011 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3012 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3013 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3015 /* note OOF */
3016 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3017 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3018 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3019 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3020 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3021 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3023 /* calendarData:WorkingHours */
3024 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3025 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3026 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3027 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3028 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3029 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3030 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3031 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3032 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3033 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3034 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3035 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3037 /* calendarData:FreeBusy */
3038 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3039 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3040 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3041 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3042 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3043 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3044 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3045 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3046 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3047 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3048 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3049 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3051 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
3052 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3055 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3057 entry = sip->our_publication_keys;
3058 while (entry) {
3059 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3060 if (sipe_strequal(entry->data, key)) {
3061 return TRUE;
3063 entry = entry->next;
3065 return FALSE;
3068 /** Property names to store in blist.xml */
3069 #define ALIAS_PROP "alias"
3070 #define EMAIL_PROP "email"
3071 #define PHONE_PROP "phone"
3072 #define PHONE_DISPLAY_PROP "phone-display"
3073 #define PHONE_MOBILE_PROP "phone-mobile"
3074 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3075 #define PHONE_HOME_PROP "phone-home"
3076 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3077 #define PHONE_OTHER_PROP "phone-other"
3078 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3079 #define PHONE_CUSTOM1_PROP "phone-custom1"
3080 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3081 #define SITE_PROP "site"
3082 #define COMPANY_PROP "company"
3083 #define DEPARTMENT_PROP "department"
3084 #define TITLE_PROP "title"
3085 #define OFFICE_PROP "office"
3086 /** implies work address */
3087 #define ADDRESS_STREET_PROP "address-street"
3088 #define ADDRESS_CITY_PROP "address-city"
3089 #define ADDRESS_STATE_PROP "address-state"
3090 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3091 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3094 * Tries to figure out user first and last name
3095 * based on Display Name and email properties.
3097 * Allocates memory - must be g_free()'d
3099 * Examples to parse:
3100 * First Last
3101 * First Last - Company Name
3102 * Last, First
3103 * Last, First M.
3104 * Last, First (C)(STP) (Company)
3105 * first.last@company.com (preprocessed as "first last")
3106 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3108 * Unusable examples:
3109 * user@company.com (preprocessed as "user")
3110 * first.m.last@company.com (preprocessed as "first m last")
3111 * user.company.com@reuters.net (preprocessed as "user company com")
3113 static void
3114 sipe_get_first_last_names(struct sipe_account_data *sip,
3115 const char *uri,
3116 char **first_name,
3117 char **last_name)
3119 PurpleBuddy *p_buddy;
3120 char *display_name;
3121 const char *email;
3122 const char *first, *last;
3123 char *tmp;
3124 char **parts;
3125 gboolean has_comma = FALSE;
3127 if (!sip || !uri) return;
3129 p_buddy = purple_find_buddy(sip->account, uri);
3131 if (!p_buddy) return;
3133 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3134 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3136 if (!display_name && !email) return;
3138 /* if no display name, make "first last anything_else" out of email */
3139 if (email && !display_name) {
3140 display_name = g_strndup(email, strstr(email, "@") - email);
3141 display_name = purple_strreplace((tmp = display_name), ".", " ");
3142 g_free(tmp);
3145 if (display_name) {
3146 has_comma = (strstr(display_name, ",") != NULL);
3147 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3148 g_free(tmp);
3149 display_name = purple_strreplace((tmp = display_name), ",", " ");
3150 g_free(tmp);
3153 parts = g_strsplit(display_name, " ", 0);
3155 if (!parts[0] || !parts[1]) {
3156 g_free(display_name);
3157 g_strfreev(parts);
3158 return;
3161 if (has_comma) {
3162 last = parts[0];
3163 first = parts[1];
3164 } else {
3165 first = parts[0];
3166 last = parts[1];
3169 if (first_name) {
3170 *first_name = g_strstrip(g_strdup(first));
3173 if (last_name) {
3174 *last_name = g_strstrip(g_strdup(last));
3177 g_free(display_name);
3178 g_strfreev(parts);
3182 * Update user information
3184 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3185 * @param property_name
3186 * @param property_value may be modified to strip white space
3188 static void
3189 sipe_update_user_info(struct sipe_account_data *sip,
3190 const char *uri,
3191 const char *property_name,
3192 char *property_value)
3194 GSList *buddies, *entry;
3196 if (!property_name || strlen(property_name) == 0) return;
3198 if (property_value)
3199 property_value = g_strstrip(property_value);
3201 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3202 while (entry) {
3203 const char *prop_str;
3204 const char *server_alias;
3205 PurpleBuddy *p_buddy = entry->data;
3207 /* for Display Name */
3208 if (sipe_strequal(property_name, ALIAS_PROP)) {
3209 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3210 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3211 purple_blist_alias_buddy(p_buddy, property_value);
3214 server_alias = purple_buddy_get_server_alias(p_buddy);
3215 if (!is_empty(property_value) &&
3216 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3218 purple_blist_server_alias_buddy(p_buddy, property_value);
3221 /* for other properties */
3222 else {
3223 if (!is_empty(property_value)) {
3224 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3225 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3226 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3231 entry = entry->next;
3233 g_slist_free(buddies);
3237 * Update user phone
3238 * Suitable for both 2005 and 2007 systems.
3240 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3241 * @param phone_type
3242 * @param phone may be modified to strip white space
3243 * @param phone_display_string may be modified to strip white space
3245 static void
3246 sipe_update_user_phone(struct sipe_account_data *sip,
3247 const char *uri,
3248 const gchar *phone_type,
3249 gchar *phone,
3250 gchar *phone_display_string)
3252 const char *phone_node = PHONE_PROP; /* work phone by default */
3253 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3255 if(!phone || strlen(phone) == 0) return;
3257 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3258 phone_node = PHONE_MOBILE_PROP;
3259 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3260 } else if (sipe_strequal(phone_type, "home")) {
3261 phone_node = PHONE_HOME_PROP;
3262 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3263 } else if (sipe_strequal(phone_type, "other")) {
3264 phone_node = PHONE_OTHER_PROP;
3265 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3266 } else if (sipe_strequal(phone_type, "custom1")) {
3267 phone_node = PHONE_CUSTOM1_PROP;
3268 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3271 sipe_update_user_info(sip, uri, phone_node, phone);
3272 if (phone_display_string) {
3273 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3277 static void
3278 sipe_update_calendar(struct sipe_account_data *sip)
3280 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3282 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3284 if (sipe_strequal(calendar, "EXCH")) {
3285 sipe_ews_update_calendar(sip);
3288 /* schedule repeat */
3289 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3291 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3295 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3296 * by using standard Purple's means of signals and saved statuses.
3298 * Thus all UI elements get updated: Status Button with Note, docklet.
3299 * This is ablolutely important as both our status and note can come
3300 * inbound (roaming) or be updated programmatically (e.g. based on our
3301 * calendar data).
3303 static void
3304 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3305 const char *status_id,
3306 const char *message,
3307 time_t do_not_publish[])
3309 PurpleStatus *status = purple_account_get_active_status(account);
3310 gboolean changed = TRUE;
3312 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3313 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3315 changed = FALSE;
3318 if (purple_savedstatus_is_idleaway()) {
3319 changed = FALSE;
3322 if (changed) {
3323 PurpleSavedStatus *saved_status;
3324 const PurpleStatusType *acct_status_type =
3325 purple_status_type_find_with_id(account->status_types, status_id);
3326 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3327 sipe_activity activity = sipe_get_activity_by_token(status_id);
3329 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3330 if (saved_status) {
3331 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3334 /* If this type+message is unique then create a new transient saved status
3335 * Ref: gtkstatusbox.c
3337 if (!saved_status) {
3338 GList *tmp;
3339 GList *active_accts = purple_accounts_get_all_active();
3341 saved_status = purple_savedstatus_new(NULL, primitive);
3342 purple_savedstatus_set_message(saved_status, message);
3344 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3345 purple_savedstatus_set_substatus(saved_status,
3346 (PurpleAccount *)tmp->data, acct_status_type, message);
3348 g_list_free(active_accts);
3351 do_not_publish[activity] = time(NULL);
3352 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3353 status_id, (int)do_not_publish[activity]);
3355 /* Set the status for each account */
3356 purple_savedstatus_activate(saved_status);
3360 struct hash_table_delete_payload {
3361 GHashTable *hash_table;
3362 guint container;
3365 static void
3366 sipe_remove_category_container_publications_cb(const char *name,
3367 struct sipe_publication *publication,
3368 struct hash_table_delete_payload *payload)
3370 if (publication->container == payload->container) {
3371 g_hash_table_remove(payload->hash_table, name);
3374 static void
3375 sipe_remove_category_container_publications(GHashTable *our_publications,
3376 const char *category,
3377 guint container)
3379 struct hash_table_delete_payload payload;
3380 payload.hash_table = g_hash_table_lookup(our_publications, category);
3382 if (!payload.hash_table) return;
3384 payload.container = container;
3385 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3388 static void
3389 send_publish_category_initial(struct sipe_account_data *sip);
3392 * When we receive some self (BE) NOTIFY with a new subscriber
3393 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3396 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3398 gchar *contact;
3399 gchar *to;
3400 xmlnode *xml;
3401 xmlnode *node;
3402 xmlnode *node2;
3403 char *display_name = NULL;
3404 char *uri;
3405 GSList *category_names = NULL;
3406 int aggreg_avail = 0;
3407 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3408 gboolean do_update_status = FALSE;
3409 gboolean has_note_cleaned = FALSE;
3411 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3413 xml = xmlnode_from_str(msg->body, msg->bodylen);
3414 if (!xml) return;
3416 contact = get_contact(sip);
3417 to = sip_uri_self(sip);
3420 /* categories */
3421 /* set list of categories participating in this XML */
3422 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3423 const gchar *name = xmlnode_get_attrib(node, "name");
3424 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3426 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3427 category_names ? (int) g_slist_length(category_names) : -1);
3428 /* drop category information */
3429 if (category_names) {
3430 GSList *entry = category_names;
3431 while (entry) {
3432 GHashTable *cat_publications;
3433 const gchar *category = entry->data;
3434 entry = entry->next;
3435 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3436 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3437 if (cat_publications) {
3438 g_hash_table_remove(sip->our_publications, category);
3439 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3443 g_slist_free(category_names);
3444 /* filling our categories reflected in roaming data */
3445 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3446 const char *tmp;
3447 const gchar *name = xmlnode_get_attrib(node, "name");
3448 guint container = xmlnode_get_int_attrib(node, "container", -1);
3449 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3450 guint version = xmlnode_get_int_attrib(node, "version", 0);
3451 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3452 sipe_utils_str_to_time(tmp) : 0;
3453 gchar *key;
3454 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3456 /* Ex. clear note: <category name="note"/> */
3457 if (container == (guint)-1) {
3458 g_free(sip->note);
3459 sip->note = NULL;
3460 do_update_status = TRUE;
3461 continue;
3464 /* Ex. clear note: <category name="note" container="200"/> */
3465 if (instance == (guint)-1) {
3466 if (container == 200) {
3467 g_free(sip->note);
3468 sip->note = NULL;
3469 do_update_status = TRUE;
3471 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3472 sipe_remove_category_container_publications(
3473 sip->our_publications, name, container);
3474 continue;
3477 /* key is <category><instance><container> */
3478 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3479 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3481 /* capture all userState publication for later clean up if required */
3482 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3483 xmlnode *xn_state = xmlnode_get_child(node, "state");
3485 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3486 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3487 publication->category = g_strdup(name);
3488 publication->instance = instance;
3489 publication->container = container;
3490 publication->version = version;
3492 if (!sip->user_state_publications) {
3493 sip->user_state_publications = g_hash_table_new_full(
3494 g_str_hash, g_str_equal,
3495 g_free, (GDestroyNotify)free_publication);
3497 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3498 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3499 key, version);
3503 if (sipe_is_our_publication(sip, key)) {
3504 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3506 publication->category = g_strdup(name);
3507 publication->instance = instance;
3508 publication->container = container;
3509 publication->version = version;
3511 /* filling publication->availability */
3512 if (sipe_strequal(name, "state")) {
3513 xmlnode *xn_state = xmlnode_get_child(node, "state");
3514 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3516 if (xn_avail) {
3517 gchar *avail_str = xmlnode_get_data(xn_avail);
3518 if (avail_str) {
3519 publication->availability = atoi(avail_str);
3521 g_free(avail_str);
3523 /* for calendarState */
3524 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3525 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3526 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3528 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3529 if (xn_activity) {
3530 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3531 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3533 event->is_meeting = TRUE;
3536 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3537 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3539 publication->cal_event_hash = sipe_cal_event_hash(event);
3540 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3541 publication->cal_event_hash);
3542 sipe_cal_event_free(event);
3545 /* filling publication->note */
3546 if (sipe_strequal(name, "note")) {
3547 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3549 if (!has_note_cleaned) {
3550 has_note_cleaned = TRUE;
3552 g_free(sip->note);
3553 sip->note = NULL;
3554 sip->note_since = publish_time;
3556 do_update_status = TRUE;
3559 g_free(publication->note);
3560 publication->note = NULL;
3561 if (xn_body) {
3562 char *tmp;
3564 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3565 g_free(tmp);
3566 if (publish_time >= sip->note_since) {
3567 g_free(sip->note);
3568 sip->note = g_strdup(publication->note);
3569 sip->note_since = publish_time;
3570 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3572 do_update_status = TRUE;
3577 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3578 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3579 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3580 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3581 if (xn_free_busy) {
3582 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3583 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3585 if (xn_working_hours) {
3586 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3590 if (!cat_publications) {
3591 cat_publications = g_hash_table_new_full(
3592 g_str_hash, g_str_equal,
3593 g_free, (GDestroyNotify)free_publication);
3594 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3595 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3597 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3598 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3600 g_free(key);
3602 /* aggregateState (not an our publication) from 2-nd container */
3603 if (sipe_strequal(name, "state") && container == 2) {
3604 xmlnode *xn_state = xmlnode_get_child(node, "state");
3606 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3607 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3608 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3610 if (xn_avail) {
3611 gchar *avail_str = xmlnode_get_data(xn_avail);
3612 if (avail_str) {
3613 aggreg_avail = atoi(avail_str);
3615 g_free(avail_str);
3618 if (xn_activity) {
3619 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3621 aggreg_activity = sipe_get_activity_by_token(activity_token);
3624 do_update_status = TRUE;
3628 /* userProperties published by server from AD */
3629 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3630 xmlnode *line;
3631 /* line, for Remote Call Control (RCC) */
3632 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3633 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3634 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3635 gchar *line_uri;
3637 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3639 line_uri = xmlnode_get_data(line);
3640 if (line_uri) {
3641 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3642 sip_csta_open(sip, line_uri, line_server);
3644 g_free(line_uri);
3646 break;
3650 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3651 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3653 /* containers */
3654 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3655 guint id = xmlnode_get_int_attrib(node, "id", 0);
3656 struct sipe_container *container = sipe_find_container(sip, id);
3658 if (container) {
3659 sip->containers = g_slist_remove(sip->containers, container);
3660 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3661 free_container(container);
3663 container = g_new0(struct sipe_container, 1);
3664 container->id = id;
3665 container->version = xmlnode_get_int_attrib(node, "version", 0);
3666 sip->containers = g_slist_append(sip->containers, container);
3667 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3669 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3670 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3671 member->type = xmlnode_get_attrib(node2, "type");
3672 member->value = xmlnode_get_attrib(node2, "value");
3673 container->members = g_slist_append(container->members, member);
3674 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3675 member->type, member->value ? member->value : "");
3679 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3680 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3681 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3682 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3683 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3684 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3685 /* initial set-up to let counterparties see your status */
3686 if (sameEnterpriseAL < 0) {
3687 struct sipe_container *container = sipe_find_container(sip, 200);
3688 guint version = container ? container->version : 0;
3689 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3691 if (federatedAL < 0) {
3692 struct sipe_container *container = sipe_find_container(sip, 100);
3693 guint version = container ? container->version : 0;
3694 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3696 sip->access_level_set = TRUE;
3699 /* subscribers */
3700 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3701 const char *user;
3702 const char *acknowledged;
3703 gchar *hdr;
3704 gchar *body;
3706 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3707 if (!user) continue;
3708 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3709 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3710 uri = sip_uri_from_name(user);
3712 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3714 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3715 if(!g_ascii_strcasecmp(acknowledged,"false")){
3716 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3717 if (!purple_find_buddy(sip->account, uri)) {
3718 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3721 hdr = g_strdup_printf(
3722 "Contact: %s\r\n"
3723 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3725 body = g_strdup_printf(
3726 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3727 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3728 "</setSubscribers>", user);
3730 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3731 g_free(body);
3732 g_free(hdr);
3734 g_free(display_name);
3735 g_free(uri);
3738 g_free(contact);
3739 xmlnode_free(xml);
3741 /* Publish initial state if not yet.
3742 * Assuming this happens on initial responce to subscription to roaming-self
3743 * so we've already updated our roaming data in full.
3744 * Only for 2007+
3746 if (!sip->initial_state_published) {
3747 send_publish_category_initial(sip);
3748 sip->initial_state_published = TRUE;
3749 /* dalayed run */
3750 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3751 do_update_status = FALSE;
3752 } else if (aggreg_avail) {
3754 g_free(sip->status);
3755 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3756 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3757 } else {
3758 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3762 if (do_update_status) {
3763 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3764 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3767 g_free(to);
3770 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3772 gchar *to = sip_uri_self(sip);
3773 gchar *tmp = get_contact(sip);
3774 gchar *hdr = g_strdup_printf(
3775 "Event: vnd-microsoft-roaming-ACL\r\n"
3776 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3777 "Supported: com.microsoft.autoextend\r\n"
3778 "Supported: ms-benotify\r\n"
3779 "Proxy-Require: ms-benotify\r\n"
3780 "Supported: ms-piggyback-first-notify\r\n"
3781 "Contact: %s\r\n", tmp);
3782 g_free(tmp);
3784 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3785 g_free(to);
3786 g_free(hdr);
3790 * To request for presence information about the user, access level settings that have already been configured by the user
3791 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3792 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3795 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3797 gchar *to = sip_uri_self(sip);
3798 gchar *tmp = get_contact(sip);
3799 gchar *hdr = g_strdup_printf(
3800 "Event: vnd-microsoft-roaming-self\r\n"
3801 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3802 "Supported: ms-benotify\r\n"
3803 "Proxy-Require: ms-benotify\r\n"
3804 "Supported: ms-piggyback-first-notify\r\n"
3805 "Contact: %s\r\n"
3806 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3808 gchar *body=g_strdup(
3809 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3810 "<roaming type=\"categories\"/>"
3811 "<roaming type=\"containers\"/>"
3812 "<roaming type=\"subscribers\"/></roamingList>");
3814 g_free(tmp);
3815 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3816 g_free(body);
3817 g_free(to);
3818 g_free(hdr);
3822 * For 2005 version
3824 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3826 gchar *to = sip_uri_self(sip);
3827 gchar *tmp = get_contact(sip);
3828 gchar *hdr = g_strdup_printf(
3829 "Event: vnd-microsoft-provisioning\r\n"
3830 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3831 "Supported: com.microsoft.autoextend\r\n"
3832 "Supported: ms-benotify\r\n"
3833 "Proxy-Require: ms-benotify\r\n"
3834 "Supported: ms-piggyback-first-notify\r\n"
3835 "Expires: 0\r\n"
3836 "Contact: %s\r\n", tmp);
3838 g_free(tmp);
3839 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3840 g_free(to);
3841 g_free(hdr);
3844 /** Subscription for provisioning information to help with initial
3845 * configuration. This subscription is a one-time query (denoted by the Expires header,
3846 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3847 * configuration, meeting policies, and policy settings that Communicator must enforce.
3848 * TODO: for what we need this information.
3851 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3853 gchar *to = sip_uri_self(sip);
3854 gchar *tmp = get_contact(sip);
3855 gchar *hdr = g_strdup_printf(
3856 "Event: vnd-microsoft-provisioning-v2\r\n"
3857 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3858 "Supported: com.microsoft.autoextend\r\n"
3859 "Supported: ms-benotify\r\n"
3860 "Proxy-Require: ms-benotify\r\n"
3861 "Supported: ms-piggyback-first-notify\r\n"
3862 "Expires: 0\r\n"
3863 "Contact: %s\r\n"
3864 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3865 gchar *body = g_strdup(
3866 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3867 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3868 "<provisioningGroup name=\"ucPolicy\"/>"
3869 "</provisioningGroupList>");
3871 g_free(tmp);
3872 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3873 g_free(body);
3874 g_free(to);
3875 g_free(hdr);
3878 static void
3879 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3880 gpointer value, gpointer user_data)
3882 struct sip_subscription *subscription = value;
3883 struct sip_dialog *dialog = &subscription->dialog;
3884 struct sipe_account_data *sip = user_data;
3885 gchar *tmp = get_contact(sip);
3886 gchar *hdr = g_strdup_printf(
3887 "Event: %s\r\n"
3888 "Expires: 0\r\n"
3889 "Contact: %s\r\n", subscription->event, tmp);
3890 g_free(tmp);
3892 /* Rate limit to max. 25 requests per seconds */
3893 g_usleep(1000000 / 25);
3895 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3896 g_free(hdr);
3899 /* IM Session (INVITE and MESSAGE methods) */
3901 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3902 static gchar *
3903 get_end_points (struct sipe_account_data *sip,
3904 struct sip_session *session)
3906 gchar *res;
3908 if (session == NULL) {
3909 return NULL;
3912 res = g_strdup_printf("<sip:%s>", sip->username);
3914 SIPE_DIALOG_FOREACH {
3915 gchar *tmp = res;
3916 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3917 g_free(tmp);
3919 if (dialog->theirepid) {
3920 tmp = res;
3921 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3922 g_free(tmp);
3924 } SIPE_DIALOG_FOREACH_END;
3926 return res;
3929 static gboolean
3930 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3931 struct sipmsg *msg,
3932 SIPE_UNUSED_PARAMETER struct transaction *trans)
3934 gboolean ret = TRUE;
3936 if (msg->response != 200) {
3937 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3938 return FALSE;
3941 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3943 return ret;
3947 * Asks UA/proxy about its capabilities.
3949 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3951 gchar *to = sip_uri(who);
3952 gchar *contact = get_contact(sip);
3953 gchar *request = g_strdup_printf(
3954 "Accept: application/sdp\r\n"
3955 "Contact: %s\r\n", contact);
3956 g_free(contact);
3958 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3960 g_free(to);
3961 g_free(request);
3964 static void
3965 sipe_notify_user(struct sipe_account_data *sip,
3966 struct sip_session *session,
3967 PurpleMessageFlags flags,
3968 const gchar *message)
3970 PurpleConversation *conv;
3972 if (!session->conv) {
3973 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3974 } else {
3975 conv = session->conv;
3977 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3980 void
3981 sipe_present_info(struct sipe_account_data *sip,
3982 struct sip_session *session,
3983 const gchar *message)
3985 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3988 static void
3989 sipe_present_err(struct sipe_account_data *sip,
3990 struct sip_session *session,
3991 const gchar *message)
3993 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3996 void
3997 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3998 struct sip_session *session,
3999 int sip_error,
4000 int sip_warning,
4001 const gchar *who,
4002 const gchar *message)
4004 char *msg, *msg_tmp, *msg_tmp2;
4005 const char *label;
4007 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
4008 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4009 g_free(msg_tmp);
4010 /* Service unavailable; Server Internal Error; Server Time-out */
4011 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4012 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4013 g_free(msg);
4014 msg = NULL;
4015 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4016 label = _("This message was not delivered to %s because the service is not available");
4017 } else if (sip_error == 486) { /* Busy Here */
4018 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4019 } else {
4020 label = _("This message was not delivered to %s because one or more recipients are offline");
4023 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4024 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4025 msg ? ":" : "",
4026 msg ? msg : "");
4027 sipe_present_err(sip, session, msg_tmp);
4028 g_free(msg_tmp2);
4029 g_free(msg_tmp);
4030 g_free(msg);
4034 static gboolean
4035 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4036 SIPE_UNUSED_PARAMETER struct transaction *trans)
4038 gboolean ret = TRUE;
4039 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4040 struct sip_session *session = sipe_session_find_im(sip, with);
4041 struct sip_dialog *dialog;
4042 gchar *cseq;
4043 char *key;
4044 struct queued_message *message;
4046 if (!session) {
4047 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
4048 g_free(with);
4049 return FALSE;
4052 dialog = sipe_dialog_find(session, with);
4053 if (!dialog) {
4054 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
4055 g_free(with);
4056 return FALSE;
4059 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4060 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4061 g_free(cseq);
4062 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4064 if (msg->response >= 400) {
4065 PurpleBuddy *pbuddy;
4066 const char *alias = with;
4067 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4068 int warning = -1;
4070 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
4072 if (warn_hdr) {
4073 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4074 if (parts[0]) {
4075 warning = atoi(parts[0]);
4077 g_strfreev(parts);
4080 /* cancel file transfer as rejected by server */
4081 if (msg->response == 606 && /* Not acceptable all. */
4082 warning == 309 && /* Message contents not allowed by policy */
4083 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4085 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4086 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4087 sipe_utils_nameval_free(parsed_body);
4090 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4091 alias = purple_buddy_get_alias(pbuddy);
4094 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4096 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4097 if (msg->response == 408 || /* Request timeout */
4098 msg->response == 480 || /* Temporarily Unavailable */
4099 msg->response == 481) { /* Call/Transaction Does Not Exist */
4100 purple_debug_info("sipe", "process_message_response: assuming dangling IM session, dropping it.\n");
4101 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4104 ret = FALSE;
4105 } else {
4106 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4107 if (message_id) {
4108 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4109 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4110 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4113 g_hash_table_remove(session->unconfirmed_messages, key);
4114 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4115 key, g_hash_table_size(session->unconfirmed_messages));
4118 g_free(key);
4119 g_free(with);
4121 if (ret) sipe_im_process_queue(sip, session);
4122 return ret;
4125 static gboolean
4126 sipe_is_election_finished(struct sip_session *session);
4128 static void
4129 sipe_election_result(struct sipe_account_data *sip,
4130 void *sess);
4132 static gboolean
4133 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4134 SIPE_UNUSED_PARAMETER struct transaction *trans)
4136 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4137 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4138 struct sip_dialog *dialog;
4139 struct sip_session *session;
4141 session = sipe_session_find_chat_by_callid(sip, callid);
4142 if (!session) {
4143 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4144 return FALSE;
4147 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4148 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4149 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4150 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4152 if (xn_request_rm_response) {
4153 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4154 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4156 dialog = sipe_dialog_find(session, with);
4157 if (!dialog) {
4158 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4159 xmlnode_free(xn_action);
4160 return FALSE;
4163 if (allow && !g_strcasecmp(allow, "true")) {
4164 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4165 dialog->election_vote = 1;
4166 } else if (allow && !g_strcasecmp(allow, "false")) {
4167 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4168 dialog->election_vote = -1;
4171 if (sipe_is_election_finished(session)) {
4172 sipe_election_result(sip, session);
4175 } else if (xn_set_rm_response) {
4178 xmlnode_free(xn_action);
4182 return TRUE;
4185 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4187 gchar *hdr;
4188 gchar *tmp;
4189 char *msgtext = NULL;
4190 const gchar *msgr = "";
4191 gchar *tmp2 = NULL;
4193 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4194 char *msgformat;
4195 gchar *msgr_value;
4197 sipe_parse_html(msg, &msgformat, &msgtext);
4198 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4200 msgr_value = sipmsg_get_msgr_string(msgformat);
4201 g_free(msgformat);
4202 if (msgr_value) {
4203 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4204 g_free(msgr_value);
4206 } else {
4207 msgtext = g_strdup(msg);
4210 tmp = get_contact(sip);
4211 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4212 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4213 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4214 if (content_type == NULL)
4215 content_type = "text/plain";
4217 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4218 g_free(tmp);
4219 g_free(tmp2);
4221 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4222 g_free(msgtext);
4223 g_free(hdr);
4227 void
4228 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4230 GSList *entry2 = session->outgoing_message_queue;
4231 while (entry2) {
4232 struct queued_message *msg = entry2->data;
4234 /* for multiparty chat or conference */
4235 if (session->is_multiparty || session->focus_uri) {
4236 gchar *who = sip_uri_self(sip);
4237 serv_got_chat_in(sip->gc, session->chat_id, who,
4238 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4239 g_free(who);
4242 SIPE_DIALOG_FOREACH {
4243 char *key;
4244 struct queued_message *message;
4246 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4248 message = g_new0(struct queued_message,1);
4249 message->body = g_strdup(msg->body);
4250 if (msg->content_type != NULL)
4251 message->content_type = g_strdup(msg->content_type);
4253 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4254 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4255 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4256 key, g_hash_table_size(session->unconfirmed_messages));
4257 g_free(key);
4259 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4260 } SIPE_DIALOG_FOREACH_END;
4262 entry2 = sipe_session_dequeue_message(session);
4266 static void
4267 sipe_refer_notify(struct sipe_account_data *sip,
4268 struct sip_session *session,
4269 const gchar *who,
4270 int status,
4271 const gchar *desc)
4273 gchar *hdr;
4274 gchar *body;
4275 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4277 hdr = g_strdup_printf(
4278 "Event: refer\r\n"
4279 "Subscription-State: %s\r\n"
4280 "Content-Type: message/sipfrag\r\n",
4281 status >= 200 ? "terminated" : "active");
4283 body = g_strdup_printf(
4284 "SIP/2.0 %d %s\r\n",
4285 status, desc);
4287 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4289 g_free(hdr);
4290 g_free(body);
4293 static gboolean
4294 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4296 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4297 struct sip_session *session;
4298 struct sip_dialog *dialog;
4299 char *cseq;
4300 char *key;
4301 struct queued_message *message;
4302 struct sipmsg *request_msg = trans->msg;
4304 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4305 gchar *referred_by;
4307 session = sipe_session_find_chat_by_callid(sip, callid);
4308 if (!session) {
4309 session = sipe_session_find_im(sip, with);
4311 if (!session) {
4312 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4313 g_free(with);
4314 return FALSE;
4317 dialog = sipe_dialog_find(session, with);
4318 if (!dialog) {
4319 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4320 g_free(with);
4321 return FALSE;
4324 sipe_dialog_parse(dialog, msg, TRUE);
4326 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4327 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4328 g_free(cseq);
4329 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4331 if (msg->response != 200) {
4332 PurpleBuddy *pbuddy;
4333 const char *alias = with;
4334 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4335 int warning = -1;
4337 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4339 if (warn_hdr) {
4340 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4341 if (parts[0]) {
4342 warning = atoi(parts[0]);
4344 g_strfreev(parts);
4347 /* cancel file transfer as rejected by server */
4348 if (msg->response == 606 && /* Not acceptable all. */
4349 warning == 309 && /* Message contents not allowed by policy */
4350 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4352 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4353 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4354 sipe_utils_nameval_free(parsed_body);
4357 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4358 alias = purple_buddy_get_alias(pbuddy);
4361 if (message) {
4362 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4363 } else {
4364 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4365 sipe_present_err(sip, session, tmp_msg);
4366 g_free(tmp_msg);
4369 sipe_dialog_remove(session, with);
4371 g_free(key);
4372 g_free(with);
4373 return FALSE;
4376 dialog->cseq = 0;
4377 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4378 dialog->outgoing_invite = NULL;
4379 dialog->is_established = TRUE;
4381 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4382 if (referred_by) {
4383 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4384 g_free(referred_by);
4387 /* add user to chat if it is a multiparty session */
4388 if (session->is_multiparty) {
4389 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4390 with, NULL,
4391 PURPLE_CBFLAGS_NONE, TRUE);
4394 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4395 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4396 sipe_session_dequeue_message(session);
4399 sipe_im_process_queue(sip, session);
4401 g_hash_table_remove(session->unconfirmed_messages, key);
4402 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4403 key, g_hash_table_size(session->unconfirmed_messages));
4405 g_free(key);
4406 g_free(with);
4407 return TRUE;
4411 void
4412 sipe_invite(struct sipe_account_data *sip,
4413 struct sip_session *session,
4414 const gchar *who,
4415 const gchar *msg_body,
4416 const gchar *msg_content_type,
4417 const gchar *referred_by,
4418 const gboolean is_triggered)
4420 gchar *hdr;
4421 gchar *to;
4422 gchar *contact;
4423 gchar *body;
4424 gchar *self;
4425 char *ms_text_format = NULL;
4426 gchar *roster_manager;
4427 gchar *end_points;
4428 gchar *referred_by_str;
4429 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4431 if (dialog && dialog->is_established) {
4432 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4433 return;
4436 if (!dialog) {
4437 dialog = sipe_dialog_add(session);
4438 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4439 dialog->with = g_strdup(who);
4442 if (!(dialog->ourtag)) {
4443 dialog->ourtag = gentag();
4446 to = sip_uri(who);
4448 if (msg_body) {
4449 char *msgtext = NULL;
4450 char *base64_msg;
4451 const gchar *msgr = "";
4452 char *key;
4453 struct queued_message *message;
4454 gchar *tmp = NULL;
4456 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4457 char *msgformat;
4458 gchar *msgr_value;
4460 sipe_parse_html(msg_body, &msgformat, &msgtext);
4461 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4463 msgr_value = sipmsg_get_msgr_string(msgformat);
4464 g_free(msgformat);
4465 if (msgr_value) {
4466 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4467 g_free(msgr_value);
4469 } else {
4470 msgtext = g_strdup(msg_body);
4473 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4474 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4475 msg_content_type ? msg_content_type : "text/plain",
4476 msgr,
4477 base64_msg);
4478 g_free(msgtext);
4479 g_free(tmp);
4480 g_free(base64_msg);
4482 message = g_new0(struct queued_message,1);
4483 message->body = g_strdup(msg_body);
4484 if (msg_content_type != NULL)
4485 message->content_type = g_strdup(msg_content_type);
4487 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4488 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4489 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4490 key, g_hash_table_size(session->unconfirmed_messages));
4491 g_free(key);
4494 contact = get_contact(sip);
4495 end_points = get_end_points(sip, session);
4496 self = sip_uri_self(sip);
4497 roster_manager = g_strdup_printf(
4498 "Roster-Manager: %s\r\n"
4499 "EndPoints: %s\r\n",
4500 self,
4501 end_points);
4502 referred_by_str = referred_by ?
4503 g_strdup_printf(
4504 "Referred-By: %s\r\n",
4505 referred_by)
4506 : g_strdup("");
4507 hdr = g_strdup_printf(
4508 "Supported: ms-sender\r\n"
4509 "%s"
4510 "%s"
4511 "%s"
4512 "%s"
4513 "Contact: %s\r\n%s"
4514 "Content-Type: application/sdp\r\n",
4515 sipe_strequal(session->roster_manager, self) ? roster_manager : "",
4516 referred_by_str,
4517 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4518 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4519 contact,
4520 ms_text_format ? ms_text_format : "");
4521 g_free(ms_text_format);
4522 g_free(self);
4524 body = g_strdup_printf(
4525 "v=0\r\n"
4526 "o=- 0 0 IN IP4 %s\r\n"
4527 "s=session\r\n"
4528 "c=IN IP4 %s\r\n"
4529 "t=0 0\r\n"
4530 "m=%s %d sip null\r\n"
4531 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4532 purple_network_get_my_ip(-1),
4533 purple_network_get_my_ip(-1),
4534 sip->ocs2007 ? "message" : "x-ms-message",
4535 sip->realport);
4537 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4538 to, to, hdr, body, dialog, process_invite_response);
4540 g_free(to);
4541 g_free(roster_manager);
4542 g_free(end_points);
4543 g_free(referred_by_str);
4544 g_free(body);
4545 g_free(hdr);
4546 g_free(contact);
4549 static void
4550 sipe_refer(struct sipe_account_data *sip,
4551 struct sip_session *session,
4552 const gchar *who)
4554 gchar *hdr;
4555 gchar *contact;
4556 gchar *epid = get_epid(sip);
4557 struct sip_dialog *dialog = sipe_dialog_find(session,
4558 session->roster_manager);
4559 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4561 contact = get_contact(sip);
4562 hdr = g_strdup_printf(
4563 "Contact: %s\r\n"
4564 "Refer-to: <%s>\r\n"
4565 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4566 "Require: com.microsoft.rtc-multiparty\r\n",
4567 contact,
4568 who,
4569 sip->username,
4570 ourtag ? ";tag=" : "",
4571 ourtag ? ourtag : "",
4572 epid);
4573 g_free(epid);
4575 send_sip_request(sip->gc, "REFER",
4576 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4578 g_free(hdr);
4579 g_free(contact);
4582 static void
4583 sipe_send_election_request_rm(struct sipe_account_data *sip,
4584 struct sip_dialog *dialog,
4585 int bid)
4587 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4589 gchar *body = g_strdup_printf(
4590 "<?xml version=\"1.0\"?>\r\n"
4591 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4592 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4593 sip->username, bid);
4595 send_sip_request(sip->gc, "INFO",
4596 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4598 g_free(body);
4601 static void
4602 sipe_send_election_set_rm(struct sipe_account_data *sip,
4603 struct sip_dialog *dialog)
4605 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4607 gchar *body = g_strdup_printf(
4608 "<?xml version=\"1.0\"?>\r\n"
4609 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4610 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4611 sip->username);
4613 send_sip_request(sip->gc, "INFO",
4614 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4616 g_free(body);
4619 static void
4620 sipe_session_close(struct sipe_account_data *sip,
4621 struct sip_session * session)
4623 if (session && session->focus_uri) {
4624 sipe_conf_immcu_closed(sip, session);
4625 conf_session_close(sip, session);
4628 if (session) {
4629 SIPE_DIALOG_FOREACH {
4630 /* @TODO slow down BYE message sending rate */
4631 /* @see single subscription code */
4632 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4633 } SIPE_DIALOG_FOREACH_END;
4635 sipe_session_remove(sip, session);
4639 static void
4640 sipe_session_close_all(struct sipe_account_data *sip)
4642 GSList *entry;
4643 while ((entry = sip->sessions) != NULL) {
4644 sipe_session_close(sip, entry->data);
4648 static void
4649 sipe_convo_closed(PurpleConnection * gc, const char *who)
4651 struct sipe_account_data *sip = gc->proto_data;
4653 purple_debug_info("sipe", "conversation with %s closed\n", who);
4654 sipe_session_close(sip, sipe_session_find_im(sip, who));
4657 static void
4658 sipe_chat_leave (PurpleConnection *gc, int id)
4660 struct sipe_account_data *sip = gc->proto_data;
4661 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4663 sipe_session_close(sip, session);
4666 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4667 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4669 struct sipe_account_data *sip = gc->proto_data;
4670 struct sip_session *session;
4671 struct sip_dialog *dialog;
4672 gchar *uri = sip_uri(who);
4674 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4676 session = sipe_session_find_or_add_im(sip, uri);
4677 dialog = sipe_dialog_find(session, uri);
4679 // Queue the message
4680 sipe_session_enqueue_message(session, what, NULL);
4682 if (dialog && !dialog->outgoing_invite) {
4683 sipe_im_process_queue(sip, session);
4684 } else if (!dialog || !dialog->outgoing_invite) {
4685 // Need to send the INVITE to get the outgoing dialog setup
4686 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4689 g_free(uri);
4690 return 1;
4693 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4694 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4696 struct sipe_account_data *sip = gc->proto_data;
4697 struct sip_session *session;
4699 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4701 session = sipe_session_find_chat_by_id(sip, id);
4703 // Queue the message
4704 if (session && session->dialogs) {
4705 sipe_session_enqueue_message(session,what,NULL);
4706 sipe_im_process_queue(sip, session);
4707 } else if (sip) {
4708 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4709 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4711 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4712 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4714 if (sip->ocs2007) {
4715 struct sip_session *session = sipe_session_add_chat(sip);
4717 session->is_multiparty = FALSE;
4718 session->focus_uri = g_strdup(proto_chat_id);
4719 sipe_session_enqueue_message(session, what, NULL);
4720 sipe_invite_conf_focus(sip, session);
4724 return 1;
4727 /* End IM Session (INVITE and MESSAGE methods) */
4729 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4731 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4732 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4733 gchar *from;
4734 struct sip_session *session;
4736 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4738 /* Call Control protocol */
4739 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4741 process_incoming_info_csta(sip, msg);
4742 return;
4745 from = parse_from(sipmsg_find_header(msg, "From"));
4746 session = sipe_session_find_chat_by_callid(sip, callid);
4747 if (!session) {
4748 session = sipe_session_find_im(sip, from);
4750 if (!session) {
4751 g_free(from);
4752 return;
4755 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4757 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4758 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4759 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4761 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4763 if (xn_request_rm) {
4764 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4765 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4766 gchar *body = g_strdup_printf(
4767 "<?xml version=\"1.0\"?>\r\n"
4768 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4769 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4770 sip->username,
4771 session->bid < bid ? "true" : "false");
4772 send_sip_response(sip->gc, msg, 200, "OK", body);
4773 g_free(body);
4774 } else if (xn_set_rm) {
4775 gchar *body;
4776 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4777 g_free(session->roster_manager);
4778 session->roster_manager = g_strdup(rm);
4780 body = g_strdup_printf(
4781 "<?xml version=\"1.0\"?>\r\n"
4782 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4783 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4784 sip->username);
4785 send_sip_response(sip->gc, msg, 200, "OK", body);
4786 g_free(body);
4788 xmlnode_free(xn_action);
4791 else
4793 /* looks like purple lacks typing notification for chat */
4794 if (!session->is_multiparty && !session->focus_uri) {
4795 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4796 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4797 "status");
4798 if (sipe_strequal(status, "type")) {
4799 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4800 } else if (sipe_strequal(status, "idle")) {
4801 serv_got_typing_stopped(sip->gc, from);
4803 xmlnode_free(xn_keyboard_activity);
4806 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4808 g_free(from);
4811 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4813 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4814 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4815 struct sip_session *session;
4816 struct sip_dialog *dialog;
4818 /* collect dialog identification
4819 * we need callid, ourtag and theirtag to unambiguously identify dialog
4821 /* take data before 'msg' will be modified by send_sip_response */
4822 dialog = g_new0(struct sip_dialog, 1);
4823 dialog->callid = g_strdup(callid);
4824 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4825 dialog->with = g_strdup(from);
4826 sipe_dialog_parse(dialog, msg, FALSE);
4828 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4830 session = sipe_session_find_chat_by_callid(sip, callid);
4831 if (!session) {
4832 session = sipe_session_find_im(sip, from);
4834 if (!session) {
4835 sipe_dialog_free(dialog);
4836 g_free(from);
4837 return;
4840 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4841 g_free(session->roster_manager);
4842 session->roster_manager = NULL;
4845 /* This what BYE is essentially for - terminating dialog */
4846 sipe_dialog_remove_3(session, dialog);
4847 sipe_dialog_free(dialog);
4848 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4849 sipe_conf_immcu_closed(sip, session);
4850 } else if (session->is_multiparty) {
4851 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4854 g_free(from);
4857 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4859 gchar *self = sip_uri_self(sip);
4860 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4861 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4862 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4863 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4864 struct sip_session *session;
4865 struct sip_dialog *dialog;
4867 session = sipe_session_find_chat_by_callid(sip, callid);
4868 dialog = sipe_dialog_find(session, from);
4870 if (!session || !dialog || !session->roster_manager || !sipe_strequal(session->roster_manager, self)) {
4871 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4872 } else {
4873 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4875 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4878 g_free(self);
4879 g_free(from);
4880 g_free(refer_to);
4881 g_free(referred_by);
4884 static unsigned int
4885 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4887 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4888 struct sip_session *session;
4889 struct sip_dialog *dialog;
4891 if (state == PURPLE_NOT_TYPING)
4892 return 0;
4894 session = sipe_session_find_im(sip, who);
4895 dialog = sipe_dialog_find(session, who);
4897 if (session && dialog && dialog->is_established) {
4898 send_sip_request(gc, "INFO", who, who,
4899 "Content-Type: application/xml\r\n",
4900 SIPE_SEND_TYPING, dialog, NULL);
4902 return SIPE_TYPING_SEND_TIMEOUT;
4905 static gboolean resend_timeout(struct sipe_account_data *sip)
4907 GSList *tmp = sip->transactions;
4908 time_t currtime = time(NULL);
4909 while (tmp) {
4910 struct transaction *trans = tmp->data;
4911 tmp = tmp->next;
4912 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4913 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4914 /* TODO 408 */
4915 } else {
4916 if ((currtime - trans->time > 2) && trans->retries == 0) {
4917 trans->retries++;
4918 sendout_sipmsg(sip, trans->msg);
4922 return TRUE;
4925 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4926 SIPE_UNUSED_PARAMETER void *unused)
4928 /* register again when security token expires */
4929 /* we have to start a new authentication as the security token
4930 * is almost expired by sending a not signed REGISTER message */
4931 purple_debug_info("sipe", "do a full reauthentication\n");
4932 sipe_auth_free(&sip->registrar);
4933 sipe_auth_free(&sip->proxy);
4934 sip->registerstatus = 0;
4935 do_register(sip);
4936 sip->reauthenticate_set = FALSE;
4939 static gboolean
4940 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4941 struct sipmsg *msg,
4942 GSList *parsed_body)
4944 gboolean found = FALSE;
4946 if (parsed_body) {
4947 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4949 if (sipe_strequal(invitation_command, "INVITE")) {
4950 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4951 found = TRUE;
4952 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4953 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4954 found = TRUE;
4955 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4956 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4957 found = TRUE;
4960 return found;
4963 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4965 gchar *from;
4966 const gchar *contenttype;
4967 gboolean found = FALSE;
4969 from = parse_from(sipmsg_find_header(msg, "From"));
4971 if (!from) return;
4973 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4975 contenttype = sipmsg_find_header(msg, "Content-Type");
4976 if (g_str_has_prefix(contenttype, "text/plain")
4977 || g_str_has_prefix(contenttype, "text/html")
4978 || g_str_has_prefix(contenttype, "multipart/related")
4979 || g_str_has_prefix(contenttype, "multipart/alternative"))
4981 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4982 gchar *html = get_html_message(contenttype, msg->body);
4984 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4985 if (!session) {
4986 session = sipe_session_find_im(sip, from);
4989 if (session && session->focus_uri) { /* a conference */
4990 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4991 gchar *sender = parse_from(tmp);
4992 g_free(tmp);
4993 serv_got_chat_in(sip->gc, session->chat_id, sender,
4994 PURPLE_MESSAGE_RECV, html, time(NULL));
4995 g_free(sender);
4996 } else if (session && session->is_multiparty) { /* a multiparty chat */
4997 serv_got_chat_in(sip->gc, session->chat_id, from,
4998 PURPLE_MESSAGE_RECV, html, time(NULL));
4999 } else {
5000 serv_got_im(sip->gc, from, html, 0, time(NULL));
5002 g_free(html);
5003 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5004 found = TRUE;
5006 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5007 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5008 xmlnode *state;
5009 gchar *statedata;
5011 if (!isc) {
5012 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
5013 g_free(from);
5014 return;
5017 state = xmlnode_get_child(isc, "state");
5019 if (!state) {
5020 purple_debug_info("sipe", "process_incoming_message: no state found\n");
5021 xmlnode_free(isc);
5022 g_free(from);
5023 return;
5026 statedata = xmlnode_get_data(state);
5027 if (statedata) {
5028 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5029 else serv_got_typing_stopped(sip->gc, from);
5031 g_free(statedata);
5033 xmlnode_free(isc);
5034 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5035 found = TRUE;
5036 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5037 GSList *body = sipe_ft_parse_msg_body(msg->body);
5038 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5039 sipe_utils_nameval_free(body);
5040 if (found) {
5041 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5044 if (!found) {
5045 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5046 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5047 if (!session) {
5048 session = sipe_session_find_im(sip, from);
5050 if (session) {
5051 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5052 from);
5053 sipe_present_err(sip, session, errmsg);
5054 g_free(errmsg);
5057 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
5058 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5060 g_free(from);
5063 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5065 gchar *body;
5066 gchar *newTag;
5067 const gchar *oldHeader;
5068 gchar *newHeader;
5069 gboolean is_multiparty = FALSE;
5070 gboolean is_triggered = FALSE;
5071 gboolean was_multiparty = TRUE;
5072 gboolean just_joined = FALSE;
5073 gchar *from;
5074 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5075 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5076 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5077 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5078 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5079 GSList *end_points = NULL;
5080 char *tmp = NULL;
5081 struct sip_session *session;
5082 const gchar *ms_text_format;
5084 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
5085 g_free(tmp);
5087 /* Invitation to join conference */
5088 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5089 process_incoming_invite_conf(sip, msg);
5090 return;
5093 /* Only accept text invitations */
5094 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5095 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5096 return;
5099 // TODO There *must* be a better way to clean up the To header to add a tag...
5100 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
5101 oldHeader = sipmsg_find_header(msg, "To");
5102 newTag = gentag();
5103 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5104 sipmsg_remove_header_now(msg, "To");
5105 sipmsg_add_header_now(msg, "To", newHeader);
5106 g_free(newHeader);
5108 if (end_points_hdr) {
5109 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5111 if (g_slist_length(end_points) > 2) {
5112 is_multiparty = TRUE;
5115 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5116 is_triggered = TRUE;
5117 is_multiparty = TRUE;
5120 session = sipe_session_find_chat_by_callid(sip, callid);
5121 /* Convert to multiparty */
5122 if (session && is_multiparty && !session->is_multiparty) {
5123 g_free(session->with);
5124 session->with = NULL;
5125 was_multiparty = FALSE;
5126 session->is_multiparty = TRUE;
5127 session->chat_id = rand();
5130 if (!session && is_multiparty) {
5131 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5133 /* IM session */
5134 from = parse_from(sipmsg_find_header(msg, "From"));
5135 if (!session) {
5136 session = sipe_session_find_or_add_im(sip, from);
5139 if (session) {
5140 g_free(session->callid);
5141 session->callid = g_strdup(callid);
5143 session->is_multiparty = is_multiparty;
5144 if (roster_manager) {
5145 session->roster_manager = g_strdup(roster_manager);
5149 if (is_multiparty && end_points) {
5150 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5151 GSList *entry = end_points;
5152 while (entry) {
5153 struct sip_dialog *dialog;
5154 struct sipendpoint *end_point = entry->data;
5155 entry = entry->next;
5157 if (!g_strcasecmp(from, end_point->contact) ||
5158 !g_strcasecmp(to, end_point->contact))
5159 continue;
5161 dialog = sipe_dialog_find(session, end_point->contact);
5162 if (dialog) {
5163 g_free(dialog->theirepid);
5164 dialog->theirepid = end_point->epid;
5165 end_point->epid = NULL;
5166 } else {
5167 dialog = sipe_dialog_add(session);
5169 dialog->callid = g_strdup(session->callid);
5170 dialog->with = end_point->contact;
5171 end_point->contact = NULL;
5172 dialog->theirepid = end_point->epid;
5173 end_point->epid = NULL;
5175 just_joined = TRUE;
5177 /* send triggered INVITE */
5178 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5181 g_free(to);
5184 if (end_points) {
5185 GSList *entry = end_points;
5186 while (entry) {
5187 struct sipendpoint *end_point = entry->data;
5188 entry = entry->next;
5189 g_free(end_point->contact);
5190 g_free(end_point->epid);
5191 g_free(end_point);
5193 g_slist_free(end_points);
5196 if (session) {
5197 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5198 if (dialog) {
5199 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5200 sipe_dialog_parse_routes(dialog, msg, FALSE);
5201 } else {
5202 dialog = sipe_dialog_add(session);
5204 dialog->callid = g_strdup(session->callid);
5205 dialog->with = g_strdup(from);
5206 sipe_dialog_parse(dialog, msg, FALSE);
5208 if (!dialog->ourtag) {
5209 dialog->ourtag = newTag;
5210 newTag = NULL;
5213 just_joined = TRUE;
5215 } else {
5216 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5218 g_free(newTag);
5220 if (is_multiparty && !session->conv) {
5221 gchar *chat_title = sipe_chat_get_name(callid);
5222 gchar *self = sip_uri_self(sip);
5223 /* create prpl chat */
5224 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5225 session->chat_title = g_strdup(chat_title);
5226 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5227 /* add self */
5228 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5229 self, NULL,
5230 PURPLE_CBFLAGS_NONE, FALSE);
5231 g_free(chat_title);
5232 g_free(self);
5235 if (is_multiparty && !was_multiparty) {
5236 /* add current IM counterparty to chat */
5237 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5238 sipe_dialog_first(session)->with, NULL,
5239 PURPLE_CBFLAGS_NONE, FALSE);
5242 /* add inviting party to chat */
5243 if (just_joined && session->conv) {
5244 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5245 from, NULL,
5246 PURPLE_CBFLAGS_NONE, TRUE);
5249 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5251 /* This used only in 2005 official client, not 2007 or Reuters.
5252 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5253 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5255 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5256 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5257 if (is_multiparty ||
5258 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5260 if (ms_text_format) {
5261 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5263 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5264 if (tmp) {
5265 gchar *body = (gchar *) purple_base64_decode(tmp, NULL);
5267 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5269 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5270 sipe_utils_nameval_free(parsed_body);
5271 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5273 g_free(tmp);
5275 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5277 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5278 gchar *html = get_html_message(ms_text_format, NULL);
5279 if (html) {
5280 if (is_multiparty) {
5281 serv_got_chat_in(sip->gc, session->chat_id, from,
5282 PURPLE_MESSAGE_RECV, html, time(NULL));
5283 } else {
5284 serv_got_im(sip->gc, from, html, 0, time(NULL));
5286 g_free(html);
5287 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5293 g_free(from);
5295 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5296 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5297 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5299 body = g_strdup_printf(
5300 "v=0\r\n"
5301 "o=- 0 0 IN IP4 %s\r\n"
5302 "s=session\r\n"
5303 "c=IN IP4 %s\r\n"
5304 "t=0 0\r\n"
5305 "m=%s %d sip sip:%s\r\n"
5306 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5307 purple_network_get_my_ip(-1),
5308 purple_network_get_my_ip(-1),
5309 sip->ocs2007 ? "message" : "x-ms-message",
5310 sip->realport,
5311 sip->username);
5312 send_sip_response(sip->gc, msg, 200, "OK", body);
5313 g_free(body);
5316 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5318 gchar *body;
5320 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5321 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5322 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5324 body = g_strdup_printf(
5325 "v=0\r\n"
5326 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5327 "s=session\r\n"
5328 "c=IN IP4 0.0.0.0\r\n"
5329 "t=0 0\r\n"
5330 "m=%s %d sip sip:%s\r\n"
5331 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5332 sip->ocs2007 ? "message" : "x-ms-message",
5333 sip->realport,
5334 sip->username);
5335 send_sip_response(sip->gc, msg, 200, "OK", body);
5336 g_free(body);
5339 static const char*
5340 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5342 const char *res = "NTLM";
5343 #ifdef USE_KERBEROS
5344 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5345 res = "Kerberos";
5347 #else
5348 (void) sip; /* make compiler happy */
5349 #endif
5350 return res;
5353 static void sipe_connection_cleanup(struct sipe_account_data *);
5354 static void create_connection(struct sipe_account_data *, gchar *, int);
5356 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5357 SIPE_UNUSED_PARAMETER struct transaction *trans)
5359 gchar *tmp;
5360 const gchar *expires_header;
5361 int expires, i;
5362 GSList *hdr = msg->headers;
5363 struct sipnameval *elem;
5365 expires_header = sipmsg_find_header(msg, "Expires");
5366 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5367 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5369 switch (msg->response) {
5370 case 200:
5371 if (expires == 0) {
5372 sip->registerstatus = 0;
5373 } else {
5374 const gchar *contact_hdr;
5375 gchar *gruu = NULL;
5376 gchar *epid;
5377 gchar *uuid;
5378 gchar *timeout;
5379 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5380 const char *auth_scheme;
5382 if (!sip->reregister_set) {
5383 gchar *action_name = g_strdup_printf("<%s>", "registration");
5384 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5385 g_free(action_name);
5386 sip->reregister_set = TRUE;
5389 sip->registerstatus = 3;
5391 if (server_hdr && !sip->server_version) {
5392 sip->server_version = g_strdup(server_hdr);
5393 g_free(default_ua);
5394 default_ua = NULL;
5397 auth_scheme = sipe_get_auth_scheme_name(sip);
5398 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5400 if (tmp) {
5401 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5402 fill_auth(tmp, &sip->registrar);
5405 if (!sip->reauthenticate_set) {
5406 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5407 guint reauth_timeout;
5408 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5409 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5410 reauth_timeout = sip->registrar.expires - 300;
5411 } else {
5412 /* NTLM: we have to reauthenticate as our security token expires
5413 after eight hours (be five minutes early) */
5414 reauth_timeout = (8 * 3600) - 300;
5416 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5417 g_free(action_name);
5418 sip->reauthenticate_set = TRUE;
5421 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5423 epid = get_epid(sip);
5424 uuid = generateUUIDfromEPID(epid);
5425 g_free(epid);
5427 // There can be multiple Contact headers (one per location where the user is logged in) so
5428 // make sure to only get the one for this uuid
5429 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5430 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5431 if (valid_contact) {
5432 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5433 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5434 g_free(valid_contact);
5435 break;
5436 } else {
5437 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5440 g_free(uuid);
5442 g_free(sip->contact);
5443 if(gruu) {
5444 sip->contact = g_strdup_printf("<%s>", gruu);
5445 g_free(gruu);
5446 } else {
5447 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5448 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
5450 sip->ocs2007 = FALSE;
5451 sip->batched_support = FALSE;
5453 while(hdr)
5455 elem = hdr->data;
5456 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5457 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5458 /* We interpret this as OCS2007+ indicator */
5459 sip->ocs2007 = TRUE;
5460 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5462 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5463 sip->batched_support = TRUE;
5464 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5467 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5468 gchar **caps = g_strsplit(elem->value,",",0);
5469 i = 0;
5470 while (caps[i]) {
5471 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5472 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5473 i++;
5475 g_strfreev(caps);
5477 hdr = g_slist_next(hdr);
5480 /* rejoin open chats to be able to use them by continue to send messages */
5481 purple_conversation_foreach(sipe_rejoin_chat);
5483 /* subscriptions */
5484 if (!sip->subscribed) { //do it just once, not every re-register
5486 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5487 (GCompareFunc)g_ascii_strcasecmp)) {
5488 sipe_subscribe_roaming_contacts(sip);
5491 /* For 2007+ it does not make sence to subscribe to:
5492 * vnd-microsoft-roaming-ACL
5493 * vnd-microsoft-provisioning (not v2)
5494 * presence.wpending
5495 * These are for backward compatibility.
5497 if (sip->ocs2007)
5499 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5500 (GCompareFunc)g_ascii_strcasecmp)) {
5501 sipe_subscribe_roaming_self(sip);
5503 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5504 (GCompareFunc)g_ascii_strcasecmp)) {
5505 sipe_subscribe_roaming_provisioning_v2(sip);
5508 /* For 2005- servers */
5509 else
5511 //sipe_options_request(sip, sip->sipdomain);
5513 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5514 (GCompareFunc)g_ascii_strcasecmp)) {
5515 sipe_subscribe_roaming_acl(sip);
5517 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5518 (GCompareFunc)g_ascii_strcasecmp)) {
5519 sipe_subscribe_roaming_provisioning(sip);
5521 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5522 (GCompareFunc)g_ascii_strcasecmp)) {
5523 sipe_subscribe_presence_wpending(sip, msg);
5526 /* For 2007+ we publish our initial statuses and calendar data only after
5527 * received our existing publications in sipe_process_roaming_self()
5528 * Only in this case we know versions of current publications made
5529 * on our behalf.
5531 /* For 2005- we publish our initial statuses only after
5532 * received our existing UserInfo data in response to
5533 * self subscription.
5534 * Only in this case we won't override existing UserInfo data
5535 * set earlier or by other client on our behalf.
5539 sip->subscribed = TRUE;
5542 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5543 "timeout=", ";", NULL);
5544 if (timeout != NULL) {
5545 sscanf(timeout, "%u", &sip->keepalive_timeout);
5546 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5547 sip->keepalive_timeout);
5548 g_free(timeout);
5551 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5553 break;
5554 case 301:
5556 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5558 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5559 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5560 gchar **tmp;
5561 gchar *hostname;
5562 int port = 0;
5563 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5564 int i = 1;
5566 tmp = g_strsplit(parts[0], ":", 0);
5567 hostname = g_strdup(tmp[0]);
5568 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5569 g_strfreev(tmp);
5571 while (parts[i]) {
5572 tmp = g_strsplit(parts[i], "=", 0);
5573 if (tmp[1]) {
5574 if (g_strcasecmp("transport", tmp[0]) == 0) {
5575 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5576 transport = SIPE_TRANSPORT_TCP;
5577 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5578 transport = SIPE_TRANSPORT_UDP;
5582 g_strfreev(tmp);
5583 i++;
5585 g_strfreev(parts);
5587 /* Close old connection */
5588 sipe_connection_cleanup(sip);
5590 /* Create new connection */
5591 sip->transport = transport;
5592 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5593 hostname, port, TRANSPORT_DESCRIPTOR);
5594 create_connection(sip, hostname, port);
5596 g_free(redirect);
5598 break;
5599 case 401:
5600 if (sip->registerstatus != 2) {
5601 const char *auth_scheme;
5602 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5603 if (sip->registrar.retries > 3) {
5604 sip->gc->wants_to_die = TRUE;
5605 purple_connection_error(sip->gc, _("Wrong password"));
5606 return TRUE;
5609 auth_scheme = sipe_get_auth_scheme_name(sip);
5610 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5612 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5613 if (!tmp) {
5614 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5615 sip->gc->wants_to_die = TRUE;
5616 purple_connection_error(sip->gc, tmp2);
5617 g_free(tmp2);
5618 return TRUE;
5620 fill_auth(tmp, &sip->registrar);
5621 sip->registerstatus = 2;
5622 if (sip->account->disconnecting) {
5623 do_register_exp(sip, 0);
5624 } else {
5625 do_register(sip);
5628 break;
5629 case 403:
5631 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5632 gchar **reason = NULL;
5633 gchar *warning;
5634 if (diagnostics != NULL) {
5635 /* Example header:
5636 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5638 reason = g_strsplit(diagnostics, "\"", 0);
5640 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5641 (reason && reason[1]) ? reason[1] : _("no reason given"));
5642 g_strfreev(reason);
5644 sip->gc->wants_to_die = TRUE;
5645 purple_connection_error(sip->gc, warning);
5646 g_free(warning);
5647 return TRUE;
5649 break;
5650 case 404:
5652 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5653 gchar *reason = NULL;
5654 gchar *warning;
5655 if (diagnostics != NULL) {
5656 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5658 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5659 diagnostics ? (reason ? reason : _("no reason given")) :
5660 _("SIP is either not enabled for the destination URI or it does not exist"));
5661 g_free(reason);
5663 sip->gc->wants_to_die = TRUE;
5664 purple_connection_error(sip->gc, warning);
5665 g_free(warning);
5666 return TRUE;
5668 break;
5669 case 503:
5670 case 504: /* Server time-out */
5672 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5673 gchar *reason = NULL;
5674 gchar *warning;
5675 if (diagnostics != NULL) {
5676 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5678 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5679 g_free(reason);
5681 sip->gc->wants_to_die = TRUE;
5682 purple_connection_error(sip->gc, warning);
5683 g_free(warning);
5684 return TRUE;
5686 break;
5688 return TRUE;
5692 * Returns 2005-style activity and Availability.
5694 * @param status Sipe statis id.
5696 static void
5697 sipe_get_act_avail_by_status_2005(const char *status,
5698 int *activity,
5699 int *availability)
5701 int avail = 300; /* online */
5702 int act = 400; /* Available */
5704 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5705 act = 100;
5706 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5707 // act = 150;
5708 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5709 act = 300;
5710 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5711 act = 400;
5712 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5713 // act = 500;
5714 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5715 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5716 act = 600;
5717 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5718 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5719 avail = 0; /* offline */
5720 act = 100;
5721 } else {
5722 act = 400; /* Available */
5725 if (activity) *activity = act;
5726 if (availability) *availability = avail;
5730 * [MS-SIP] 2.2.1
5732 * @param activity 2005 aggregated activity. Ex.: 600
5733 * @param availablity 2005 aggregated availablity. Ex.: 300
5735 static const char *
5736 sipe_get_status_by_act_avail_2005(const int activity,
5737 const int availablity,
5738 char **activity_desc)
5740 const char *status_id = NULL;
5741 const char *act = NULL;
5743 if (activity < 150) {
5744 status_id = SIPE_STATUS_ID_AWAY;
5745 } else if (activity < 200) {
5746 //status_id = SIPE_STATUS_ID_LUNCH;
5747 status_id = SIPE_STATUS_ID_AWAY;
5748 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5749 } else if (activity < 300) {
5750 //status_id = SIPE_STATUS_ID_IDLE;
5751 status_id = SIPE_STATUS_ID_AWAY;
5752 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5753 } else if (activity < 400) {
5754 status_id = SIPE_STATUS_ID_BRB;
5755 } else if (activity < 500) {
5756 status_id = SIPE_STATUS_ID_AVAILABLE;
5757 } else if (activity < 600) {
5758 //status_id = SIPE_STATUS_ID_ON_PHONE;
5759 status_id = SIPE_STATUS_ID_BUSY;
5760 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5761 } else if (activity < 700) {
5762 status_id = SIPE_STATUS_ID_BUSY;
5763 } else if (activity < 800) {
5764 status_id = SIPE_STATUS_ID_AWAY;
5765 } else {
5766 status_id = SIPE_STATUS_ID_AVAILABLE;
5769 if (availablity < 100)
5770 status_id = SIPE_STATUS_ID_OFFLINE;
5772 if (activity_desc && act) {
5773 g_free(*activity_desc);
5774 *activity_desc = g_strdup(act);
5777 return status_id;
5781 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5783 static const char*
5784 sipe_get_status_by_availability(int avail,
5785 char** activity_desc)
5787 const char *status;
5788 const char *act = NULL;
5790 if (avail < 3000) {
5791 status = SIPE_STATUS_ID_OFFLINE;
5792 } else if (avail < 4500) {
5793 status = SIPE_STATUS_ID_AVAILABLE;
5794 } else if (avail < 6000) {
5795 //status = SIPE_STATUS_ID_IDLE;
5796 status = SIPE_STATUS_ID_AVAILABLE;
5797 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5798 } else if (avail < 7500) {
5799 status = SIPE_STATUS_ID_BUSY;
5800 } else if (avail < 9000) {
5801 //status = SIPE_STATUS_ID_BUSYIDLE;
5802 status = SIPE_STATUS_ID_BUSY;
5803 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5804 } else if (avail < 12000) {
5805 status = SIPE_STATUS_ID_DND;
5806 } else if (avail < 15000) {
5807 status = SIPE_STATUS_ID_BRB;
5808 } else if (avail < 18000) {
5809 status = SIPE_STATUS_ID_AWAY;
5810 } else {
5811 status = SIPE_STATUS_ID_OFFLINE;
5814 if (activity_desc && act) {
5815 g_free(*activity_desc);
5816 *activity_desc = g_strdup(act);
5819 return status;
5823 * Returns 2007-style availability value
5825 * @param sipe_status_id (in)
5826 * @param activity_token (out) Must be g_free()'d after use if consumed.
5828 static int
5829 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5831 int availability;
5832 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5834 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5835 availability = 15500;
5836 if (!activity_token || !(*activity_token)) {
5837 activity = SIPE_ACTIVITY_AWAY;
5839 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5840 availability = 12500;
5841 activity = SIPE_ACTIVITY_BRB;
5842 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5843 availability = 9500;
5844 activity = SIPE_ACTIVITY_DND;
5845 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5846 availability = 6500;
5847 if (!activity_token || !(*activity_token)) {
5848 activity = SIPE_ACTIVITY_BUSY;
5850 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5851 availability = 3500;
5852 activity = SIPE_ACTIVITY_ONLINE;
5853 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5854 availability = 0;
5855 } else {
5856 // Offline or invisible
5857 availability = 18500;
5858 activity = SIPE_ACTIVITY_OFFLINE;
5861 if (activity_token) {
5862 *activity_token = g_strdup(sipe_activity_map[activity].token);
5864 return availability;
5867 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5869 const char *uri;
5870 xmlnode *xn_categories;
5871 xmlnode *xn_category;
5872 xmlnode *xn_node;
5873 const char *status = NULL;
5874 gboolean do_update_status = FALSE;
5875 gboolean has_note_cleaned = FALSE;
5876 gboolean has_free_busy_cleaned = FALSE;
5878 xn_categories = xmlnode_from_str(data, len);
5879 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5881 for (xn_category = xmlnode_get_child(xn_categories, "category");
5882 xn_category ;
5883 xn_category = xmlnode_get_next_twin(xn_category) )
5885 const char *tmp;
5886 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5887 time_t publish_time = (tmp = xmlnode_get_attrib(xn_category, "publishTime")) ?
5888 sipe_utils_str_to_time(tmp) : 0;
5890 /* contactCard */
5891 if (sipe_strequal(attrVar, "contactCard"))
5893 xmlnode *node;
5894 /* identity - Display Name and email */
5895 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5896 if (node) {
5897 char* display_name = xmlnode_get_data(
5898 xmlnode_get_descendant(node, "name", "displayName", NULL));
5899 char* email = xmlnode_get_data(
5900 xmlnode_get_child(node, "email"));
5902 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5903 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5905 g_free(display_name);
5906 g_free(email);
5908 /* company */
5909 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5910 if (node) {
5911 char* company = xmlnode_get_data(node);
5912 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5913 g_free(company);
5915 /* department */
5916 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5917 if (node) {
5918 char* department = xmlnode_get_data(node);
5919 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5920 g_free(department);
5922 /* title */
5923 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5924 if (node) {
5925 char* title = xmlnode_get_data(node);
5926 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5927 g_free(title);
5929 /* office */
5930 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5931 if (node) {
5932 char* office = xmlnode_get_data(node);
5933 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5934 g_free(office);
5936 /* site (url) */
5937 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5938 if (node) {
5939 char* site = xmlnode_get_data(node);
5940 sipe_update_user_info(sip, uri, SITE_PROP, site);
5941 g_free(site);
5943 /* phone */
5944 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5945 node;
5946 node = xmlnode_get_next_twin(node))
5948 const char *phone_type = xmlnode_get_attrib(node, "type");
5949 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5950 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5952 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5954 g_free(phone);
5955 g_free(phone_display_string);
5957 /* address */
5958 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5959 node;
5960 node = xmlnode_get_next_twin(node))
5962 if (sipe_strequal(xmlnode_get_attrib(node, "type"), "work")) {
5963 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5964 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5965 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5966 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5967 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5969 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5970 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5971 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5972 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5973 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5975 g_free(street);
5976 g_free(city);
5977 g_free(state);
5978 g_free(zipcode);
5979 g_free(country_code);
5981 break;
5985 /* note */
5986 else if (sipe_strequal(attrVar, "note"))
5988 if (uri) {
5989 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5991 if (!has_note_cleaned) {
5992 has_note_cleaned = TRUE;
5994 g_free(sbuddy->note);
5995 sbuddy->note = NULL;
5996 sbuddy->is_oof_note = FALSE;
5997 sbuddy->note_since = publish_time;
5999 do_update_status = TRUE;
6001 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6002 /* clean up in case no 'note' element is supplied
6003 * which indicate note removal in client
6005 g_free(sbuddy->note);
6006 sbuddy->note = NULL;
6007 sbuddy->is_oof_note = FALSE;
6008 sbuddy->note_since = publish_time;
6010 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
6011 if (xn_node) {
6012 char *tmp;
6013 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
6014 g_free(tmp);
6015 sbuddy->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_node, "type"), "OOF");
6016 sbuddy->note_since = publish_time;
6018 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
6019 uri, sbuddy->note ? sbuddy->note : "");
6021 /* to trigger UI refresh in case no status info is supplied in this update */
6022 do_update_status = TRUE;
6026 /* state */
6027 else if(sipe_strequal(attrVar, "state"))
6029 char *tmp;
6030 int availability;
6031 xmlnode *xn_availability;
6032 xmlnode *xn_activity;
6033 xmlnode *xn_meeting_subject;
6034 xmlnode *xn_meeting_location;
6035 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6037 xn_node = xmlnode_get_child(xn_category, "state");
6038 if (!xn_node) continue;
6039 xn_availability = xmlnode_get_child(xn_node, "availability");
6040 if (!xn_availability) continue;
6041 xn_activity = xmlnode_get_child(xn_node, "activity");
6042 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
6043 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
6045 tmp = xmlnode_get_data(xn_availability);
6046 availability = atoi(tmp);
6047 g_free(tmp);
6049 /* activity, meeting_subject, meeting_location */
6050 if (sbuddy) {
6051 char *tmp = NULL;
6053 /* activity */
6054 g_free(sbuddy->activity);
6055 sbuddy->activity = NULL;
6056 if (xn_activity) {
6057 const char *token = xmlnode_get_attrib(xn_activity, "token");
6058 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
6060 /* from token */
6061 if (!is_empty(token)) {
6062 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6064 /* from custom element */
6065 if (xn_custom) {
6066 char *custom = xmlnode_get_data(xn_custom);
6068 if (!is_empty(custom)) {
6069 sbuddy->activity = custom;
6070 custom = NULL;
6072 g_free(custom);
6075 /* meeting_subject */
6076 g_free(sbuddy->meeting_subject);
6077 sbuddy->meeting_subject = NULL;
6078 if (xn_meeting_subject) {
6079 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
6081 if (!is_empty(meeting_subject)) {
6082 sbuddy->meeting_subject = meeting_subject;
6083 meeting_subject = NULL;
6085 g_free(meeting_subject);
6087 /* meeting_location */
6088 g_free(sbuddy->meeting_location);
6089 sbuddy->meeting_location = NULL;
6090 if (xn_meeting_location) {
6091 char *meeting_location = xmlnode_get_data(xn_meeting_location);
6093 if (!is_empty(meeting_location)) {
6094 sbuddy->meeting_location = meeting_location;
6095 meeting_location = NULL;
6097 g_free(meeting_location);
6100 status = sipe_get_status_by_availability(availability, &tmp);
6101 if (sbuddy->activity && tmp) {
6102 char *tmp2 = sbuddy->activity;
6104 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6105 g_free(tmp);
6106 g_free(tmp2);
6107 } else if (tmp) {
6108 sbuddy->activity = tmp;
6112 do_update_status = TRUE;
6114 /* calendarData */
6115 else if(sipe_strequal(attrVar, "calendarData"))
6117 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6118 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
6119 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
6121 if (sbuddy && xn_free_busy) {
6122 if (!has_free_busy_cleaned) {
6123 has_free_busy_cleaned = TRUE;
6125 g_free(sbuddy->cal_start_time);
6126 sbuddy->cal_start_time = NULL;
6128 g_free(sbuddy->cal_free_busy_base64);
6129 sbuddy->cal_free_busy_base64 = NULL;
6131 g_free(sbuddy->cal_free_busy);
6132 sbuddy->cal_free_busy = NULL;
6134 sbuddy->cal_free_busy_published = publish_time;
6137 if (publish_time >= sbuddy->cal_free_busy_published) {
6138 g_free(sbuddy->cal_start_time);
6139 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
6141 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
6142 15 : 0;
6144 g_free(sbuddy->cal_free_busy_base64);
6145 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
6147 g_free(sbuddy->cal_free_busy);
6148 sbuddy->cal_free_busy = NULL;
6150 sbuddy->cal_free_busy_published = publish_time;
6152 purple_debug_info("sipe", "process_incoming_notify_rlmi: startTime=%s granularity=%d cal_free_busy_base64=\n%s\n", sbuddy->cal_start_time, sbuddy->cal_granularity, sbuddy->cal_free_busy_base64);
6156 if (sbuddy && xn_working_hours) {
6157 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6162 if (do_update_status) {
6163 if (!status) { /* no status category in this update, using contact's current status */
6164 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6165 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6166 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6167 status = purple_status_get_id(pstatus);
6170 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
6171 sipe_got_user_status(sip, uri, status);
6174 xmlnode_free(xn_categories);
6177 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6179 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6180 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
6181 payload->host = g_strdup(host);
6182 payload->buddies = server;
6183 sipe_subscribe_presence_batched_routed(sip, payload);
6184 sipe_subscribe_presence_batched_routed_free(payload);
6187 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6189 xmlnode *xn_list;
6190 xmlnode *xn_resource;
6191 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6192 g_free, NULL);
6193 GSList *server;
6194 gchar *host;
6196 xn_list = xmlnode_from_str(data, len);
6198 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6199 xn_resource;
6200 xn_resource = xmlnode_get_next_twin(xn_resource) )
6202 const char *uri, *state;
6203 xmlnode *xn_instance;
6205 xn_instance = xmlnode_get_child(xn_resource, "instance");
6206 if (!xn_instance) continue;
6208 uri = xmlnode_get_attrib(xn_resource, "uri");
6209 state = xmlnode_get_attrib(xn_instance, "state");
6210 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6212 if (strstr(state, "resubscribe")) {
6213 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6215 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6216 gchar *user = g_strdup(uri);
6217 host = g_strdup(poolFqdn);
6218 server = g_hash_table_lookup(servers, host);
6219 server = g_slist_append(server, user);
6220 g_hash_table_insert(servers, host, server);
6221 } else {
6222 sipe_subscribe_presence_single(sip, (void *) uri);
6227 /* Send out any deferred poolFqdn subscriptions */
6228 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6229 g_hash_table_destroy(servers);
6231 xmlnode_free(xn_list);
6234 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6236 gchar *uri;
6237 gchar *getbasic;
6238 gchar *activity = NULL;
6239 xmlnode *pidf;
6240 xmlnode *basicstatus = NULL, *tuple, *status;
6241 gboolean isonline = FALSE;
6242 xmlnode *display_name_node;
6244 pidf = xmlnode_from_str(data, len);
6245 if (!pidf) {
6246 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6247 return;
6250 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6252 if ((status = xmlnode_get_child(tuple, "status"))) {
6253 basicstatus = xmlnode_get_child(status, "basic");
6257 if (!basicstatus) {
6258 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6259 xmlnode_free(pidf);
6260 return;
6263 getbasic = xmlnode_get_data(basicstatus);
6264 if (!getbasic) {
6265 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6266 xmlnode_free(pidf);
6267 return;
6270 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6271 if (strstr(getbasic, "open")) {
6272 isonline = TRUE;
6274 g_free(getbasic);
6276 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6278 display_name_node = xmlnode_get_child(pidf, "display-name");
6279 if (display_name_node) {
6280 char * display_name = xmlnode_get_data(display_name_node);
6282 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6283 g_free(display_name);
6286 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6287 if ((status = xmlnode_get_child(tuple, "status"))) {
6288 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6289 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6290 activity = xmlnode_get_data(basicstatus);
6291 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6297 if (isonline) {
6298 const gchar * status_id = NULL;
6299 if (activity) {
6300 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6301 status_id = SIPE_STATUS_ID_BUSY;
6302 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6303 status_id = SIPE_STATUS_ID_AWAY;
6307 if (!status_id) {
6308 status_id = SIPE_STATUS_ID_AVAILABLE;
6311 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6312 sipe_got_user_status(sip, uri, status_id);
6313 } else {
6314 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6317 g_free(activity);
6318 g_free(uri);
6319 xmlnode_free(pidf);
6322 /** 2005 */
6323 static void
6324 sipe_user_info_has_updated(struct sipe_account_data *sip,
6325 xmlnode *xn_userinfo)
6327 if (sip->user_info) {
6328 xmlnode_free(sip->user_info);
6330 sip->user_info = xmlnode_copy(xn_userinfo);
6332 /* Publish initial state if not yet.
6333 * Assuming this happens on initial responce to self subscription
6334 * so we've already updated our UserInfo.
6336 if (!sip->initial_state_published) {
6337 send_presence_soap(sip, FALSE);
6338 /* dalayed run */
6339 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6343 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6345 char *activity = NULL;
6346 const char *epid;
6347 const char *status_id = NULL;
6348 const char *name;
6349 char *uri;
6350 char *self_uri = sip_uri_self(sip);
6351 int avl;
6352 int act;
6353 const char *device_name = NULL;
6354 const char *cal_start_time = NULL;
6355 const char *cal_granularity = NULL;
6356 char *cal_free_busy_base64 = NULL;
6357 struct sipe_buddy *sbuddy;
6358 xmlnode *node;
6359 xmlnode *xn_presentity;
6360 xmlnode *xn_availability;
6361 xmlnode *xn_activity;
6362 xmlnode *xn_display_name;
6363 xmlnode *xn_email;
6364 xmlnode *xn_phone_number;
6365 xmlnode *xn_userinfo;
6366 xmlnode *xn_note;
6367 xmlnode *xn_oof;
6368 xmlnode *xn_state;
6369 xmlnode *xn_contact;
6370 char *note;
6371 char *free_activity;
6372 int user_avail;
6373 const char *user_avail_nil;
6374 int res_avail;
6375 time_t user_avail_since = 0;
6376 time_t activity_since = 0;
6378 /* fix for Reuters environment on Linux */
6379 if (data && strstr(data, "encoding=\"utf-16\"")) {
6380 char *tmp_data;
6381 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6382 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6383 g_free(tmp_data);
6384 } else {
6385 xn_presentity = xmlnode_from_str(data, len);
6388 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6389 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6390 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6391 xn_email = xmlnode_get_child(xn_presentity, "email");
6392 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6393 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6394 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6395 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6396 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6397 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6398 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6399 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6400 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6401 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6403 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6404 user_avail = 0;
6405 user_avail_since = 0;
6408 free_activity = NULL;
6410 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6411 uri = sip_uri_from_name(name);
6412 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6413 epid = xmlnode_get_attrib(xn_availability, "epid");
6414 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6416 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6417 res_avail = sipe_get_availability_by_status(status_id, NULL);
6418 if (user_avail > res_avail) {
6419 res_avail = user_avail;
6420 status_id = sipe_get_status_by_availability(user_avail, NULL);
6423 if (xn_display_name) {
6424 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6425 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6426 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6427 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6428 char *tel_uri = sip_to_tel_uri(phone_number);
6430 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6431 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6432 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6433 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6435 g_free(tel_uri);
6436 g_free(phone_label);
6437 g_free(phone_number);
6438 g_free(email);
6439 g_free(display_name);
6442 if (xn_contact) {
6443 /* tel */
6444 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6446 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6447 const char *phone_type = xmlnode_get_attrib(node, "type");
6448 char* phone = xmlnode_get_data(node);
6450 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6452 g_free(phone);
6456 /* devicePresence */
6457 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6458 xmlnode *xn_device_name;
6459 xmlnode *xn_calendar_info;
6460 xmlnode *xn_state;
6461 char *state;
6463 /* deviceName */
6464 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6465 xn_device_name = xmlnode_get_child(node, "deviceName");
6466 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6469 /* calendarInfo */
6470 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6471 if (xn_calendar_info) {
6472 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6474 if (cal_start_time) {
6475 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6476 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6478 if (cal_start_time_t_tmp > cal_start_time_t) {
6479 cal_start_time = cal_start_time_tmp;
6480 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6481 g_free(cal_free_busy_base64);
6482 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6484 purple_debug_info("sipe", "process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s\n", cal_start_time, cal_granularity, cal_free_busy_base64);
6486 } else {
6487 cal_start_time = cal_start_time_tmp;
6488 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6489 g_free(cal_free_busy_base64);
6490 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6492 purple_debug_info("sipe", "process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s\n", cal_start_time, cal_granularity, cal_free_busy_base64);
6496 /* state */
6497 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6498 if (xn_state) {
6499 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6500 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6502 state = xmlnode_get_data(xn_state);
6503 if (dev_avail_since > user_avail_since &&
6504 dev_avail >= res_avail)
6506 res_avail = dev_avail;
6507 if (!is_empty(state))
6509 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6510 g_free(activity);
6511 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6512 } else if (sipe_strequal(state, "presenting")) {
6513 g_free(activity);
6514 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6515 } else {
6516 activity = state;
6517 state = NULL;
6519 activity_since = dev_avail_since;
6521 status_id = sipe_get_status_by_availability(res_avail, &activity);
6523 g_free(state);
6527 /* oof */
6528 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6529 g_free(activity);
6530 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6531 activity_since = 0;
6534 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6535 if (sbuddy)
6537 g_free(sbuddy->activity);
6538 sbuddy->activity = activity;
6539 activity = NULL;
6541 sbuddy->activity_since = activity_since;
6543 sbuddy->user_avail = user_avail;
6544 sbuddy->user_avail_since = user_avail_since;
6546 g_free(sbuddy->note);
6547 sbuddy->note = NULL;
6548 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6550 sbuddy->is_oof_note = (xn_oof != NULL);
6552 g_free(sbuddy->device_name);
6553 sbuddy->device_name = NULL;
6554 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6556 if (!is_empty(cal_free_busy_base64)) {
6557 g_free(sbuddy->cal_start_time);
6558 sbuddy->cal_start_time = g_strdup(cal_start_time);
6560 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6562 g_free(sbuddy->cal_free_busy_base64);
6563 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6564 cal_free_busy_base64 = NULL;
6566 g_free(sbuddy->cal_free_busy);
6567 sbuddy->cal_free_busy = NULL;
6570 sbuddy->last_non_cal_status_id = status_id;
6571 g_free(sbuddy->last_non_cal_activity);
6572 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6574 if (sipe_strequal(sbuddy->name, self_uri)) {
6575 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6577 sip->is_oof_note = sbuddy->is_oof_note;
6579 g_free(sip->note);
6580 sip->note = g_strdup(sbuddy->note);
6582 sip->note_since = time(NULL);
6585 g_free(sip->status);
6586 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6589 g_free(cal_free_busy_base64);
6590 g_free(activity);
6592 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6593 sipe_got_user_status(sip, uri, status_id);
6595 if (!sip->ocs2007 && sipe_strequal(self_uri, uri)) {
6596 sipe_user_info_has_updated(sip, xn_userinfo);
6599 g_free(note);
6600 xmlnode_free(xn_presentity);
6601 g_free(uri);
6602 g_free(self_uri);
6605 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6607 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6609 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6611 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6612 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6614 const char *content = msg->body;
6615 unsigned length = msg->bodylen;
6616 PurpleMimeDocument *mime = NULL;
6618 if (strstr(ctype, "multipart"))
6620 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6621 const char *content_type;
6622 GList* parts;
6623 mime = purple_mime_document_parse(doc);
6624 parts = purple_mime_document_get_parts(mime);
6625 while(parts) {
6626 content = purple_mime_part_get_data(parts->data);
6627 length = purple_mime_part_get_length(parts->data);
6628 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6629 if(content_type && strstr(content_type,"application/rlmi+xml"))
6631 process_incoming_notify_rlmi_resub(sip, content, length);
6633 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6635 process_incoming_notify_msrtc(sip, content, length);
6637 else
6639 process_incoming_notify_rlmi(sip, content, length);
6641 parts = parts->next;
6643 g_free(doc);
6645 if (mime)
6647 purple_mime_document_free(mime);
6650 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6652 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6654 else if(strstr(ctype, "application/rlmi+xml"))
6656 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6659 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6661 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6663 else
6665 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6669 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6671 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6672 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6674 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6676 if (ctype &&
6677 strstr(ctype, "multipart") &&
6678 (strstr(ctype, "application/rlmi+xml") ||
6679 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6680 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6681 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6682 GList *parts = purple_mime_document_get_parts(mime);
6683 GSList *buddies = NULL;
6684 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6686 while (parts) {
6687 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6688 purple_mime_part_get_length(parts->data));
6690 if (xml && !sipe_strequal(xml->name, "list")) {
6691 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6693 buddies = g_slist_append(buddies, uri);
6695 xmlnode_free(xml);
6697 parts = parts->next;
6699 g_free(doc);
6700 if (mime) purple_mime_document_free(mime);
6702 payload->host = g_strdup(who);
6703 payload->buddies = buddies;
6704 sipe_schedule_action(action_name, timeout,
6705 sipe_subscribe_presence_batched_routed,
6706 sipe_subscribe_presence_batched_routed_free,
6707 sip, payload);
6708 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6710 } else {
6711 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6712 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6714 g_free(action_name);
6718 * Dispatcher for all incoming subscription information
6719 * whether it comes from NOTIFY, BENOTIFY requests or
6720 * piggy-backed to subscription's OK responce.
6722 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6723 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6725 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6727 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6728 const gchar *event = sipmsg_find_header(msg, "Event");
6729 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6730 char *tmp;
6732 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6733 event ? event : "",
6734 tmp = fix_newlines(msg->body));
6735 g_free(tmp);
6736 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6738 /* implicit subscriptions */
6739 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6740 sipe_process_imdn(sip, msg);
6743 if (event) {
6744 /* for one off subscriptions (send with Expire: 0) */
6745 if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6747 sipe_process_provisioning_v2(sip, msg);
6749 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6751 sipe_process_provisioning(sip, msg);
6753 else if (!g_ascii_strcasecmp(event, "presence"))
6755 sipe_process_presence(sip, msg);
6757 else if (!g_ascii_strcasecmp(event, "registration-notify"))
6759 sipe_process_registration_notify(sip, msg);
6762 if (!subscription_state || strstr(subscription_state, "active"))
6764 if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6766 sipe_process_roaming_contacts(sip, msg);
6768 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6770 sipe_process_roaming_self(sip, msg);
6772 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6774 sipe_process_roaming_acl(sip, msg);
6776 else if (!g_ascii_strcasecmp(event, "presence.wpending"))
6778 sipe_process_presence_wpending(sip, msg);
6780 else if (!g_ascii_strcasecmp(event, "conference"))
6782 sipe_process_conference(sip, msg);
6787 /* The server sends status 'terminated' */
6788 if (subscription_state && strstr(subscription_state, "terminated") ) {
6789 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6790 gchar *key = sipe_get_subscription_key(event, who);
6792 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6793 g_free(who);
6795 if (g_hash_table_lookup(sip->subscriptions, key)) {
6796 g_hash_table_remove(sip->subscriptions, key);
6797 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6800 g_free(key);
6803 if (!request && event) {
6804 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6805 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6806 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6808 if (timeout) {
6809 /* 2 min ahead of expiration */
6810 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6812 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6813 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6815 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6816 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6817 g_free(action_name);
6819 else if (!g_ascii_strcasecmp(event, "presence") &&
6820 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6822 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6823 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6825 if (sip->batched_support) {
6826 sipe_process_presence_timeout(sip, msg, who, timeout);
6828 else {
6829 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6830 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6832 g_free(action_name);
6833 g_free(who);
6838 /* The client responses on received a NOTIFY message */
6839 if (request && !benotify)
6841 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6846 * Whether user manually changed status or
6847 * it was changed automatically due to user
6848 * became inactive/active again
6850 static gboolean
6851 sipe_is_user_state(struct sipe_account_data *sip)
6853 gboolean res;
6854 time_t now = time(NULL);
6856 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6857 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6859 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6861 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6862 return res;
6865 static void
6866 send_presence_soap0(struct sipe_account_data *sip,
6867 gboolean do_publish_calendar,
6868 gboolean do_reset_status)
6870 struct sipe_ews* ews = sip->ews;
6871 int availability = 0;
6872 int activity = 0;
6873 gchar *body;
6874 gchar *tmp;
6875 gchar *tmp2 = NULL;
6876 gchar *res_note = NULL;
6877 gchar *res_oof = NULL;
6878 const gchar *note_pub = NULL;
6879 gchar *states = NULL;
6880 gchar *calendar_data = NULL;
6881 gchar *epid = get_epid(sip);
6882 time_t now = time(NULL);
6883 gchar *since_time_str = sipe_utils_time_to_str(now);
6884 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6885 const char *user_input;
6886 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6888 if (oof_note && sip->note) {
6889 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6890 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6893 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6895 if (!sip->initial_state_published ||
6896 do_reset_status)
6898 g_free(sip->status);
6899 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6902 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6904 /* Note */
6905 if (pub_oof) {
6906 note_pub = oof_note;
6907 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6908 ews->published = TRUE;
6909 } else if (sip->note) {
6910 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6911 g_free(sip->note);
6912 sip->note = NULL;
6913 sip->is_oof_note = FALSE;
6914 sip->note_since = 0;
6915 } else {
6916 note_pub = sip->note;
6917 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6921 if (note_pub)
6923 /* to protocol internal plain text format */
6924 tmp = purple_markup_strip_html(note_pub);
6925 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6926 g_free(tmp);
6929 /* User State */
6930 if (!do_reset_status) {
6931 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6933 gchar *activity_token = NULL;
6934 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6936 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6937 avail_2007,
6938 since_time_str,
6939 epid,
6940 activity_token);
6941 g_free(activity_token);
6943 else /* preserve existing publication */
6945 xmlnode *xn_states;
6946 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6947 states = xmlnode_to_str(xn_states, NULL);
6948 /* this is a hack-around to remove added newline after inner element,
6949 * state in this case, where it shouldn't be.
6950 * After several use of xmlnode_to_str, amount of added newlines
6951 * grows significantly.
6953 purple_str_strip_char(states, '\n');
6954 //purple_str_strip_char(states, '\r');
6957 } else {
6958 /* do nothing - then User state will be erased */
6960 sip->initial_state_published = TRUE;
6962 /* CalendarInfo */
6963 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6965 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6966 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6967 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6968 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6969 fb_start_str,
6970 free_busy_base64);
6971 g_free(fb_start_str);
6972 g_free(free_busy_base64);
6975 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6977 /* forming resulting XML */
6978 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6979 sip->username,
6980 availability,
6981 activity,
6982 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6983 res_note ? res_note : "",
6984 res_oof ? res_oof : "",
6985 states ? states : "",
6986 calendar_data ? calendar_data : "",
6987 epid,
6988 since_time_str,
6989 since_time_str,
6990 user_input);
6991 g_free(tmp);
6992 g_free(tmp2);
6993 g_free(res_note);
6994 g_free(states);
6995 g_free(calendar_data);
6997 send_soap_request(sip, body);
6999 g_free(body);
7000 g_free(since_time_str);
7001 g_free(epid);
7004 void
7005 send_presence_soap(struct sipe_account_data *sip,
7006 gboolean do_publish_calendar)
7008 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7012 static gboolean
7013 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7014 struct sipmsg *msg,
7015 struct transaction *trans)
7017 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7019 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7020 xmlnode *xml;
7021 xmlnode *node;
7022 gchar *fault_code;
7023 GHashTable *faults;
7024 int index_our;
7025 gboolean has_device_publication = FALSE;
7027 xml = xmlnode_from_str(msg->body, msg->bodylen);
7029 /* test if version mismatch fault */
7030 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7031 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7032 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
7033 g_free(fault_code);
7034 xmlnode_free(xml);
7035 return TRUE;
7037 g_free(fault_code);
7039 /* accumulating information about faulty versions */
7040 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7041 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7042 node;
7043 node = xmlnode_get_next_twin(node))
7045 const gchar *index = xmlnode_get_attrib(node, "index");
7046 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7048 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7049 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
7051 xmlnode_free(xml);
7053 /* here we are parsing own request to figure out what publication
7054 * referensed here only by index went wrong
7056 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7058 /* publication */
7059 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7060 index_our = 1; /* starts with 1 - our first publication */
7061 node;
7062 node = xmlnode_get_next_twin(node), index_our++)
7064 gchar *idx = g_strdup_printf("%d", index_our);
7065 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7066 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7067 g_free(idx);
7069 if (sipe_strequal("device", categoryName)) {
7070 has_device_publication = TRUE;
7073 if (curVersion) { /* fault exist on this index */
7074 const gchar *container = xmlnode_get_attrib(node, "container");
7075 const gchar *instance = xmlnode_get_attrib(node, "instance");
7076 /* key is <category><instance><container> */
7077 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7078 struct sipe_publication *publication =
7079 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
7081 purple_debug_info("sipe", "key is %s\n", key);
7083 if (publication) {
7084 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
7085 key, curVersion, publication->version);
7086 /* updating publication's version to the correct one */
7087 publication->version = atoi(curVersion);
7089 g_free(key);
7092 xmlnode_free(xml);
7093 g_hash_table_destroy(faults);
7095 /* rebublishing with right versions */
7096 if (has_device_publication) {
7097 send_publish_category_initial(sip);
7098 } else {
7099 send_presence_status(sip);
7102 return TRUE;
7106 * Returns 'device' XML part for publication.
7107 * Must be g_free'd after use.
7109 static gchar *
7110 sipe_publish_get_category_device(struct sipe_account_data *sip)
7112 gchar *uri;
7113 gchar *doc;
7114 gchar *epid = get_epid(sip);
7115 gchar *uuid = generateUUIDfromEPID(epid);
7116 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7117 /* key is <category><instance><container> */
7118 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7119 struct sipe_publication *publication =
7120 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7122 g_free(key);
7123 g_free(epid);
7125 uri = sip_uri_self(sip);
7126 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7127 device_instance,
7128 publication ? publication->version : 0,
7129 uuid,
7130 uri,
7131 "00:00:00+01:00", /* @TODO make timezone real*/
7132 sipe_get_host_name()
7135 g_free(uri);
7136 g_free(uuid);
7138 return doc;
7142 * A service method - use
7143 * - send_publish_get_category_state_machine and
7144 * - send_publish_get_category_state_user instead.
7145 * Must be g_free'd after use.
7147 static gchar *
7148 sipe_publish_get_category_state(struct sipe_account_data *sip,
7149 gboolean is_user_state)
7151 int availability = sipe_get_availability_by_status(sip->status, NULL);
7152 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7153 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7154 /* key is <category><instance><container> */
7155 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7156 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7157 struct sipe_publication *publication_2 =
7158 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7159 struct sipe_publication *publication_3 =
7160 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7162 g_free(key_2);
7163 g_free(key_3);
7165 if (publication_2 && (publication_2->availability == availability))
7167 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
7168 return NULL; /* nothing to update */
7171 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7172 instance,
7173 publication_2 ? publication_2->version : 0,
7174 availability,
7175 instance,
7176 publication_3 ? publication_3->version : 0,
7177 availability);
7181 * Only Busy and OOF calendar event are published.
7182 * Different instances are used for that.
7184 * Must be g_free'd after use.
7186 static gchar *
7187 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7188 struct sipe_cal_event *event,
7189 const char *uri,
7190 int cal_satus)
7192 gchar *start_time_str;
7193 int availability = 0;
7194 gchar *res;
7195 gchar *tmp = NULL;
7196 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7197 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7198 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7200 /* key is <category><instance><container> */
7201 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7202 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7203 struct sipe_publication *publication_2 =
7204 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7205 struct sipe_publication *publication_3 =
7206 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7208 g_free(key_2);
7209 g_free(key_3);
7211 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7212 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7213 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7214 return NULL;
7217 if (event &&
7218 publication_3 &&
7219 (publication_3->availability == availability) &&
7220 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7222 g_free(tmp);
7223 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7224 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7225 return NULL; /* nothing to update */
7227 g_free(tmp);
7229 if (event &&
7230 (event->cal_status == SIPE_CAL_BUSY ||
7231 event->cal_status == SIPE_CAL_OOF))
7233 gchar *availability_xml_str = NULL;
7234 gchar *activity_xml_str = NULL;
7236 if (event->cal_status == SIPE_CAL_BUSY) {
7237 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7240 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7241 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7242 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7243 "minAvailability=\"6500\"",
7244 "maxAvailability=\"8999\"");
7245 } else if (event->cal_status == SIPE_CAL_OOF) {
7246 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7247 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7248 "minAvailability=\"12000\"",
7249 "");
7251 start_time_str = sipe_utils_time_to_str(event->start_time);
7253 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7254 instance,
7255 publication_2 ? publication_2->version : 0,
7256 uri,
7257 start_time_str,
7258 availability_xml_str ? availability_xml_str : "",
7259 activity_xml_str ? activity_xml_str : "",
7260 event->subject ? event->subject : "",
7261 event->location ? event->location : "",
7263 instance,
7264 publication_3 ? publication_3->version : 0,
7265 uri,
7266 start_time_str,
7267 availability_xml_str ? availability_xml_str : "",
7268 activity_xml_str ? activity_xml_str : "",
7269 event->subject ? event->subject : "",
7270 event->location ? event->location : ""
7272 g_free(start_time_str);
7273 g_free(availability_xml_str);
7274 g_free(activity_xml_str);
7277 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7279 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7280 instance,
7281 publication_2 ? publication_2->version : 0,
7283 instance,
7284 publication_3 ? publication_3->version : 0
7288 return res;
7292 * Returns 'machineState' XML part for publication.
7293 * Must be g_free'd after use.
7295 static gchar *
7296 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7298 return sipe_publish_get_category_state(sip, FALSE);
7302 * Returns 'userState' XML part for publication.
7303 * Must be g_free'd after use.
7305 static gchar *
7306 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7308 return sipe_publish_get_category_state(sip, TRUE);
7312 * Returns 'note' XML part for publication.
7313 * Must be g_free'd after use.
7315 * Protocol format for Note is plain text.
7317 * @param note a note in Sipe internal HTML format
7318 * @param note_type either personal or OOF
7320 static gchar *
7321 sipe_publish_get_category_note(struct sipe_account_data *sip,
7322 const char *note, /* html */
7323 const char *note_type,
7324 time_t note_start,
7325 time_t note_end)
7327 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7328 /* key is <category><instance><container> */
7329 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7330 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7331 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7333 struct sipe_publication *publication_note_200 =
7334 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7335 struct sipe_publication *publication_note_300 =
7336 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7337 struct sipe_publication *publication_note_400 =
7338 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7340 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7341 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7342 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7343 char *res, *tmp1, *tmp2, *tmp3;
7344 char *start_time_attr;
7345 char *end_time_attr;
7347 g_free(tmp);
7348 tmp = NULL;
7349 g_free(key_note_200);
7350 g_free(key_note_300);
7351 g_free(key_note_400);
7353 /* we even need to republish empty note */
7354 if (sipe_strequal(n1, n2))
7356 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7357 g_free(n1);
7358 return NULL; /* nothing to update */
7361 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7362 g_free(tmp);
7363 tmp = NULL;
7364 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7365 g_free(tmp);
7367 if (n1) {
7368 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7369 instance,
7370 200,
7371 publication_note_200 ? publication_note_200->version : 0,
7372 note_type,
7373 start_time_attr ? start_time_attr : "",
7374 end_time_attr ? end_time_attr : "",
7375 n1);
7377 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7378 instance,
7379 300,
7380 publication_note_300 ? publication_note_300->version : 0,
7381 note_type,
7382 start_time_attr ? start_time_attr : "",
7383 end_time_attr ? end_time_attr : "",
7384 n1);
7386 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7387 instance,
7388 400,
7389 publication_note_400 ? publication_note_400->version : 0,
7390 note_type,
7391 start_time_attr ? start_time_attr : "",
7392 end_time_attr ? end_time_attr : "",
7393 n1);
7394 } else {
7395 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7396 "note",
7397 instance,
7398 200,
7399 publication_note_200 ? publication_note_200->version : 0,
7400 "static");
7401 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7402 "note",
7403 instance,
7404 300,
7405 publication_note_200 ? publication_note_200->version : 0,
7406 "static");
7407 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7408 "note",
7409 instance,
7410 400,
7411 publication_note_200 ? publication_note_200->version : 0,
7412 "static");
7414 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7416 g_free(start_time_attr);
7417 g_free(end_time_attr);
7418 g_free(tmp1);
7419 g_free(tmp2);
7420 g_free(tmp3);
7421 g_free(n1);
7423 return res;
7427 * Returns 'calendarData' XML part with WorkingHours for publication.
7428 * Must be g_free'd after use.
7430 static gchar *
7431 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7433 struct sipe_ews* ews = sip->ews;
7435 /* key is <category><instance><container> */
7436 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7437 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7438 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7439 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7440 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7441 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7443 struct sipe_publication *publication_cal_1 =
7444 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7445 struct sipe_publication *publication_cal_100 =
7446 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7447 struct sipe_publication *publication_cal_200 =
7448 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7449 struct sipe_publication *publication_cal_300 =
7450 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7451 struct sipe_publication *publication_cal_400 =
7452 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7453 struct sipe_publication *publication_cal_32000 =
7454 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7456 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7457 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7459 g_free(key_cal_1);
7460 g_free(key_cal_100);
7461 g_free(key_cal_200);
7462 g_free(key_cal_300);
7463 g_free(key_cal_400);
7464 g_free(key_cal_32000);
7466 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7467 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7468 return NULL;
7471 if (sipe_strequal(n1, n2))
7473 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7474 return NULL; /* nothing to update */
7477 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7478 /* 1 */
7479 publication_cal_1 ? publication_cal_1->version : 0,
7480 ews->email,
7481 ews->working_hours_xml_str,
7482 /* 100 - Public */
7483 publication_cal_100 ? publication_cal_100->version : 0,
7484 /* 200 - Company */
7485 publication_cal_200 ? publication_cal_200->version : 0,
7486 ews->email,
7487 ews->working_hours_xml_str,
7488 /* 300 - Team */
7489 publication_cal_300 ? publication_cal_300->version : 0,
7490 ews->email,
7491 ews->working_hours_xml_str,
7492 /* 400 - Personal */
7493 publication_cal_400 ? publication_cal_400->version : 0,
7494 ews->email,
7495 ews->working_hours_xml_str,
7496 /* 32000 - Blocked */
7497 publication_cal_32000 ? publication_cal_32000->version : 0
7502 * Returns 'calendarData' XML part with FreeBusy for publication.
7503 * Must be g_free'd after use.
7505 static gchar *
7506 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7508 struct sipe_ews* ews = sip->ews;
7509 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7510 char *fb_start_str;
7511 char *free_busy_base64;
7512 const char *st;
7513 const char *fb;
7514 char *res;
7516 /* key is <category><instance><container> */
7517 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7518 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7519 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7520 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7521 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7522 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7524 struct sipe_publication *publication_cal_1 =
7525 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7526 struct sipe_publication *publication_cal_100 =
7527 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7528 struct sipe_publication *publication_cal_200 =
7529 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7530 struct sipe_publication *publication_cal_300 =
7531 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7532 struct sipe_publication *publication_cal_400 =
7533 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7534 struct sipe_publication *publication_cal_32000 =
7535 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7537 g_free(key_cal_1);
7538 g_free(key_cal_100);
7539 g_free(key_cal_200);
7540 g_free(key_cal_300);
7541 g_free(key_cal_400);
7542 g_free(key_cal_32000);
7544 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7545 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7546 return NULL;
7549 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7550 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7552 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7553 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7555 /* we will rebuplish the same data to refresh publication time,
7556 * so if data from multiple sources, most recent will be choosen
7558 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7560 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7561 // g_free(fb_start_str);
7562 // g_free(free_busy_base64);
7563 // return NULL; /* nothing to update */
7566 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7567 /* 1 */
7568 cal_data_instance,
7569 publication_cal_1 ? publication_cal_1->version : 0,
7570 /* 100 - Public */
7571 cal_data_instance,
7572 publication_cal_100 ? publication_cal_100->version : 0,
7573 /* 200 - Company */
7574 cal_data_instance,
7575 publication_cal_200 ? publication_cal_200->version : 0,
7576 ews->email,
7577 fb_start_str,
7578 free_busy_base64,
7579 /* 300 - Team */
7580 cal_data_instance,
7581 publication_cal_300 ? publication_cal_300->version : 0,
7582 ews->email,
7583 fb_start_str,
7584 free_busy_base64,
7585 /* 400 - Personal */
7586 cal_data_instance,
7587 publication_cal_400 ? publication_cal_400->version : 0,
7588 ews->email,
7589 fb_start_str,
7590 free_busy_base64,
7591 /* 32000 - Blocked */
7592 cal_data_instance,
7593 publication_cal_32000 ? publication_cal_32000->version : 0
7596 g_free(fb_start_str);
7597 g_free(free_busy_base64);
7598 return res;
7601 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7603 gchar *uri;
7604 gchar *doc;
7605 gchar *tmp;
7606 gchar *hdr;
7608 uri = sip_uri_self(sip);
7609 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7610 uri,
7611 publications);
7613 tmp = get_contact(sip);
7614 hdr = g_strdup_printf("Contact: %s\r\n"
7615 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7617 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7619 g_free(tmp);
7620 g_free(hdr);
7621 g_free(uri);
7622 g_free(doc);
7625 static void
7626 send_publish_category_initial(struct sipe_account_data *sip)
7628 gchar *pub_device = sipe_publish_get_category_device(sip);
7629 gchar *pub_machine;
7630 gchar *publications;
7632 g_free(sip->status);
7633 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7635 pub_machine = sipe_publish_get_category_state_machine(sip);
7636 publications = g_strdup_printf("%s%s",
7637 pub_device,
7638 pub_machine ? pub_machine : "");
7639 g_free(pub_device);
7640 g_free(pub_machine);
7642 send_presence_publish(sip, publications);
7643 g_free(publications);
7646 static void
7647 send_presence_category_publish(struct sipe_account_data *sip)
7649 gchar *pub_state = sipe_is_user_state(sip) ?
7650 sipe_publish_get_category_state_user(sip) :
7651 sipe_publish_get_category_state_machine(sip);
7652 gchar *pub_note = sipe_publish_get_category_note(sip,
7653 sip->note,
7654 sip->is_oof_note ? "OOF" : "personal",
7657 gchar *publications;
7659 if (!pub_state && !pub_note) {
7660 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7661 return;
7664 publications = g_strdup_printf("%s%s",
7665 pub_state ? pub_state : "",
7666 pub_note ? pub_note : "");
7668 g_free(pub_state);
7669 g_free(pub_note);
7671 send_presence_publish(sip, publications);
7672 g_free(publications);
7676 * Publishes self status
7677 * based on own calendar information.
7679 * For 2007+
7681 void
7682 publish_calendar_status_self(struct sipe_account_data *sip)
7684 struct sipe_cal_event* event = NULL;
7685 gchar *pub_cal_working_hours = NULL;
7686 gchar *pub_cal_free_busy = NULL;
7687 gchar *pub_calendar = NULL;
7688 gchar *pub_calendar2 = NULL;
7689 gchar *pub_oof_note = NULL;
7690 const gchar *oof_note;
7691 time_t oof_start = 0;
7692 time_t oof_end = 0;
7694 if (!sip->ews) {
7695 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7696 return;
7699 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7700 if (sip->ews->cal_events) {
7701 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7704 if (!event) {
7705 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7706 } else {
7707 char *desc = sipe_cal_event_describe(event);
7708 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7709 g_free(desc);
7712 /* Logic
7713 if OOF
7714 OOF publish, Busy clean
7715 ilse if Busy
7716 OOF clean, Busy publish
7717 else
7718 OOF clean, Busy clean
7720 if (event && event->cal_status == SIPE_CAL_OOF) {
7721 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7722 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7723 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7724 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7725 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7726 } else {
7727 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7728 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7731 oof_note = sipe_ews_get_oof_note(sip->ews);
7732 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7733 oof_start = sip->ews->oof_start;
7734 oof_end = sip->ews->oof_end;
7736 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7738 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7739 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7741 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7742 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7743 } else {
7744 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7745 pub_cal_working_hours ? pub_cal_working_hours : "",
7746 pub_cal_free_busy ? pub_cal_free_busy : "",
7747 pub_calendar ? pub_calendar : "",
7748 pub_calendar2 ? pub_calendar2 : "",
7749 pub_oof_note ? pub_oof_note : "");
7751 send_presence_publish(sip, publications);
7752 g_free(publications);
7755 g_free(pub_cal_working_hours);
7756 g_free(pub_cal_free_busy);
7757 g_free(pub_calendar);
7758 g_free(pub_calendar2);
7759 g_free(pub_oof_note);
7761 /* repeat scheduling */
7762 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7765 static void send_presence_status(struct sipe_account_data *sip)
7767 PurpleStatus * status = purple_account_get_active_status(sip->account);
7769 if (!status) return;
7771 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7772 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7773 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7775 if (sip->ocs2007) {
7776 send_presence_category_publish(sip);
7777 } else {
7778 send_presence_soap(sip, FALSE);
7782 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7784 gboolean found = FALSE;
7785 const char *method = msg->method ? msg->method : "NOT FOUND";
7786 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7787 if (msg->response == 0) { /* request */
7788 if (sipe_strequal(method, "MESSAGE")) {
7789 process_incoming_message(sip, msg);
7790 found = TRUE;
7791 } else if (sipe_strequal(method, "NOTIFY")) {
7792 purple_debug_info("sipe","send->process_incoming_notify\n");
7793 process_incoming_notify(sip, msg, TRUE, FALSE);
7794 found = TRUE;
7795 } else if (sipe_strequal(method, "BENOTIFY")) {
7796 purple_debug_info("sipe","send->process_incoming_benotify\n");
7797 process_incoming_notify(sip, msg, TRUE, TRUE);
7798 found = TRUE;
7799 } else if (sipe_strequal(method, "INVITE")) {
7800 process_incoming_invite(sip, msg);
7801 found = TRUE;
7802 } else if (sipe_strequal(method, "REFER")) {
7803 process_incoming_refer(sip, msg);
7804 found = TRUE;
7805 } else if (sipe_strequal(method, "OPTIONS")) {
7806 process_incoming_options(sip, msg);
7807 found = TRUE;
7808 } else if (sipe_strequal(method, "INFO")) {
7809 process_incoming_info(sip, msg);
7810 found = TRUE;
7811 } else if (sipe_strequal(method, "ACK")) {
7812 // ACK's don't need any response
7813 found = TRUE;
7814 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7815 // LCS 2005 sends us these - just respond 200 OK
7816 found = TRUE;
7817 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7818 } else if (sipe_strequal(method, "BYE")) {
7819 process_incoming_bye(sip, msg);
7820 found = TRUE;
7821 } else {
7822 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7824 } else { /* response */
7825 struct transaction *trans = transactions_find(sip, msg);
7826 if (trans) {
7827 if (msg->response == 407) {
7828 gchar *resend, *auth;
7829 const gchar *ptmp;
7831 if (sip->proxy.retries > 30) return;
7832 sip->proxy.retries++;
7833 /* do proxy authentication */
7835 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7837 fill_auth(ptmp, &sip->proxy);
7838 auth = auth_header(sip, &sip->proxy, trans->msg);
7839 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7840 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7841 g_free(auth);
7842 resend = sipmsg_to_string(trans->msg);
7843 /* resend request */
7844 sendout_pkt(sip->gc, resend);
7845 g_free(resend);
7846 } else {
7847 if (msg->response < 200) {
7848 /* ignore provisional response */
7849 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7850 } else {
7851 sip->proxy.retries = 0;
7852 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7853 if (msg->response == 401)
7855 sip->registrar.retries++;
7857 else
7859 sip->registrar.retries = 0;
7861 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7862 } else {
7863 if (msg->response == 401) {
7864 gchar *resend, *auth, *ptmp;
7865 const char* auth_scheme;
7867 if (sip->registrar.retries > 4) return;
7868 sip->registrar.retries++;
7870 auth_scheme = sipe_get_auth_scheme_name(sip);
7871 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7873 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7874 if (!ptmp) {
7875 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7876 sip->gc->wants_to_die = TRUE;
7877 purple_connection_error(sip->gc, tmp2);
7878 g_free(tmp2);
7879 return;
7882 fill_auth(ptmp, &sip->registrar);
7883 auth = auth_header(sip, &sip->registrar, trans->msg);
7884 sipmsg_remove_header_now(trans->msg, "Authorization");
7885 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7886 g_free(auth);
7887 resend = sipmsg_to_string(trans->msg);
7888 /* resend request */
7889 sendout_pkt(sip->gc, resend);
7890 g_free(resend);
7894 if (trans->callback) {
7895 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7896 /* call the callback to process response*/
7897 (trans->callback)(sip, msg, trans);
7900 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7901 transactions_remove(sip, trans);
7905 found = TRUE;
7906 } else {
7907 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7910 if (!found) {
7911 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7915 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7917 char *cur;
7918 char *dummy;
7919 char *tmp;
7920 struct sipmsg *msg;
7921 int restlen;
7922 cur = conn->inbuf;
7924 /* according to the RFC remove CRLF at the beginning */
7925 while (*cur == '\r' || *cur == '\n') {
7926 cur++;
7928 if (cur != conn->inbuf) {
7929 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7930 conn->inbufused = strlen(conn->inbuf);
7933 /* Received a full Header? */
7934 sip->processing_input = TRUE;
7935 while (sip->processing_input &&
7936 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7937 time_t currtime = time(NULL);
7938 cur += 2;
7939 cur[0] = '\0';
7940 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7941 g_free(tmp);
7942 msg = sipmsg_parse_header(conn->inbuf);
7943 cur[0] = '\r';
7944 cur += 2;
7945 restlen = conn->inbufused - (cur - conn->inbuf);
7946 if (msg && restlen >= msg->bodylen) {
7947 dummy = g_malloc(msg->bodylen + 1);
7948 memcpy(dummy, cur, msg->bodylen);
7949 dummy[msg->bodylen] = '\0';
7950 msg->body = dummy;
7951 cur += msg->bodylen;
7952 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7953 conn->inbufused = strlen(conn->inbuf);
7954 } else {
7955 if (msg){
7956 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7957 sipmsg_free(msg);
7959 return;
7962 /*if (msg->body) {
7963 purple_debug_info("sipe", "body:\n%s", msg->body);
7966 // Verify the signature before processing it
7967 if (sip->registrar.gssapi_context) {
7968 struct sipmsg_breakdown msgbd;
7969 gchar *signature_input_str;
7970 gchar *rspauth;
7971 msgbd.msg = msg;
7972 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7973 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
7975 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7977 if (rspauth != NULL) {
7978 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7979 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7980 process_input_message(sip, msg);
7981 } else {
7982 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7983 purple_connection_error(sip->gc, _("Invalid message signature received"));
7984 sip->gc->wants_to_die = TRUE;
7986 } else if (msg->response == 401) {
7987 purple_connection_error(sip->gc, _("Wrong password"));
7988 sip->gc->wants_to_die = TRUE;
7990 g_free(signature_input_str);
7992 g_free(rspauth);
7993 sipmsg_breakdown_free(&msgbd);
7994 } else {
7995 process_input_message(sip, msg);
7998 sipmsg_free(msg);
8002 static void sipe_udp_process(gpointer data, gint source,
8003 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8005 PurpleConnection *gc = data;
8006 struct sipe_account_data *sip = gc->proto_data;
8007 int len;
8009 static char buffer[65536];
8010 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8011 time_t currtime = time(NULL);
8012 struct sipmsg *msg;
8013 buffer[len] = '\0';
8014 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
8015 msg = sipmsg_parse_msg(buffer);
8016 if (msg) process_input_message(sip, msg);
8020 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8022 struct sipe_account_data *sip = gc->proto_data;
8023 PurpleSslConnection *gsc = sip->gsc;
8025 purple_debug_error("sipe", "%s",debug);
8026 purple_connection_error(gc, msg);
8028 /* Invalidate this connection. Next send will open a new one */
8029 if (gsc) {
8030 connection_remove(sip, gsc->fd);
8031 purple_ssl_close(gsc);
8033 sip->gsc = NULL;
8034 sip->fd = -1;
8037 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8038 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8040 PurpleConnection *gc = data;
8041 struct sipe_account_data *sip;
8042 struct sip_connection *conn;
8043 int readlen, len;
8044 gboolean firstread = TRUE;
8046 /* NOTE: This check *IS* necessary */
8047 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8048 purple_ssl_close(gsc);
8049 return;
8052 sip = gc->proto_data;
8053 conn = connection_find(sip, gsc->fd);
8054 if (conn == NULL) {
8055 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
8056 gc->wants_to_die = TRUE;
8057 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8058 return;
8061 /* Read all available data from the SSL connection */
8062 do {
8063 /* Increase input buffer size as needed */
8064 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8065 conn->inbuflen += SIMPLE_BUF_INC;
8066 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8067 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
8070 /* Try to read as much as there is space left in the buffer */
8071 readlen = conn->inbuflen - conn->inbufused - 1;
8072 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8074 if (len < 0 && errno == EAGAIN) {
8075 /* Try again later */
8076 return;
8077 } else if (len < 0) {
8078 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8079 return;
8080 } else if (firstread && (len == 0)) {
8081 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8082 return;
8085 conn->inbufused += len;
8086 firstread = FALSE;
8088 /* Equivalence indicates that there is possibly more data to read */
8089 } while (len == readlen);
8091 conn->inbuf[conn->inbufused] = '\0';
8092 process_input(sip, conn);
8096 static void sipe_input_cb(gpointer data, gint source,
8097 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8099 PurpleConnection *gc = data;
8100 struct sipe_account_data *sip = gc->proto_data;
8101 int len;
8102 struct sip_connection *conn = connection_find(sip, source);
8103 if (!conn) {
8104 purple_debug_error("sipe", "Connection not found!\n");
8105 return;
8108 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8109 conn->inbuflen += SIMPLE_BUF_INC;
8110 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8113 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8115 if (len < 0 && errno == EAGAIN)
8116 return;
8117 else if (len <= 0) {
8118 purple_debug_info("sipe", "sipe_input_cb: read error\n");
8119 connection_remove(sip, source);
8120 if (sip->fd == source) sip->fd = -1;
8121 return;
8124 conn->inbufused += len;
8125 conn->inbuf[conn->inbufused] = '\0';
8127 process_input(sip, conn);
8130 /* Callback for new connections on incoming TCP port */
8131 static void sipe_newconn_cb(gpointer data, gint source,
8132 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8134 PurpleConnection *gc = data;
8135 struct sipe_account_data *sip = gc->proto_data;
8136 struct sip_connection *conn;
8138 int newfd = accept(source, NULL, NULL);
8140 conn = connection_create(sip, newfd);
8142 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8145 static void login_cb(gpointer data, gint source,
8146 SIPE_UNUSED_PARAMETER const gchar *error_message)
8148 PurpleConnection *gc = data;
8149 struct sipe_account_data *sip;
8150 struct sip_connection *conn;
8152 if (!PURPLE_CONNECTION_IS_VALID(gc))
8154 if (source >= 0)
8155 close(source);
8156 return;
8159 if (source < 0) {
8160 purple_connection_error(gc, _("Could not connect"));
8161 return;
8164 sip = gc->proto_data;
8165 sip->fd = source;
8166 sip->last_keepalive = time(NULL);
8168 conn = connection_create(sip, source);
8170 do_register(sip);
8172 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8175 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8176 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8178 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8179 if (sip == NULL) return;
8181 do_register(sip);
8184 static guint sipe_ht_hash_nick(const char *nick)
8186 char *lc = g_utf8_strdown(nick, -1);
8187 guint bucket = g_str_hash(lc);
8188 g_free(lc);
8190 return bucket;
8193 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8195 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8198 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8200 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8202 sip->listen_data = NULL;
8204 if (listenfd == -1) {
8205 purple_connection_error(sip->gc, _("Could not create listen socket"));
8206 return;
8209 sip->fd = listenfd;
8211 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8212 sip->listenfd = sip->fd;
8214 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8216 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8217 do_register(sip);
8220 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8221 SIPE_UNUSED_PARAMETER const char *error_message)
8223 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8225 sip->query_data = NULL;
8227 if (!hosts || !hosts->data) {
8228 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8229 return;
8232 hosts = g_slist_remove(hosts, hosts->data);
8233 g_free(sip->serveraddr);
8234 sip->serveraddr = hosts->data;
8235 hosts = g_slist_remove(hosts, hosts->data);
8236 while (hosts) {
8237 void *tmp = hosts->data;
8238 hosts = g_slist_remove(hosts, tmp);
8239 hosts = g_slist_remove(hosts, tmp);
8240 g_free(tmp);
8243 /* create socket for incoming connections */
8244 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8245 sipe_udp_host_resolved_listen_cb, sip);
8246 if (sip->listen_data == NULL) {
8247 purple_connection_error(sip->gc, _("Could not create listen socket"));
8248 return;
8252 static const struct sipe_service_data *current_service = NULL;
8254 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8255 PurpleSslErrorType error,
8256 gpointer data)
8258 PurpleConnection *gc = data;
8259 struct sipe_account_data *sip;
8261 /* If the connection is already disconnected, we don't need to do anything else */
8262 if (!PURPLE_CONNECTION_IS_VALID(gc))
8263 return;
8265 sip = gc->proto_data;
8266 current_service = sip->service_data;
8267 if (current_service) {
8268 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8269 current_service->transport ? current_service->transport : "NULL",
8270 current_service->service ? current_service->service : "NULL");
8273 sip->fd = -1;
8274 sip->gsc = NULL;
8276 switch(error) {
8277 case PURPLE_SSL_CONNECT_FAILED:
8278 purple_connection_error(gc, _("Connection failed"));
8279 break;
8280 case PURPLE_SSL_HANDSHAKE_FAILED:
8281 purple_connection_error(gc, _("SSL handshake failed"));
8282 break;
8283 case PURPLE_SSL_CERTIFICATE_INVALID:
8284 purple_connection_error(gc, _("SSL certificate invalid"));
8285 break;
8289 static void
8290 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8292 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8293 PurpleProxyConnectData *connect_data;
8295 sip->listen_data = NULL;
8297 sip->listenfd = listenfd;
8298 if (sip->listenfd == -1) {
8299 purple_connection_error(sip->gc, _("Could not create listen socket"));
8300 return;
8303 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8304 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8305 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8306 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8307 sipe_newconn_cb, sip->gc);
8308 purple_debug_info("sipe", "connecting to %s port %d\n",
8309 sip->realhostname, sip->realport);
8310 /* open tcp connection to the server */
8311 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8312 sip->realport, login_cb, sip->gc);
8314 if (connect_data == NULL) {
8315 purple_connection_error(sip->gc, _("Could not create socket"));
8319 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8321 PurpleAccount *account = sip->account;
8322 PurpleConnection *gc = sip->gc;
8324 if (port == 0) {
8325 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8328 sip->realhostname = hostname;
8329 sip->realport = port;
8331 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8332 hostname, port);
8334 /* TODO: is there a good default grow size? */
8335 if (sip->transport != SIPE_TRANSPORT_UDP)
8336 sip->txbuf = purple_circ_buffer_new(0);
8338 if (sip->transport == SIPE_TRANSPORT_TLS) {
8339 /* SSL case */
8340 if (!purple_ssl_is_supported()) {
8341 gc->wants_to_die = TRUE;
8342 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8343 return;
8346 purple_debug_info("sipe", "using SSL\n");
8348 sip->gsc = purple_ssl_connect(account, hostname, port,
8349 login_cb_ssl, sipe_ssl_connect_failure, gc);
8350 if (sip->gsc == NULL) {
8351 purple_connection_error(gc, _("Could not create SSL context"));
8352 return;
8354 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8355 /* UDP case */
8356 purple_debug_info("sipe", "using UDP\n");
8358 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8359 if (sip->query_data == NULL) {
8360 purple_connection_error(gc, _("Could not resolve hostname"));
8362 } else {
8363 /* TCP case */
8364 purple_debug_info("sipe", "using TCP\n");
8365 /* create socket for incoming connections */
8366 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8367 sipe_tcp_connect_listen_cb, sip);
8368 if (sip->listen_data == NULL) {
8369 purple_connection_error(gc, _("Could not create listen socket"));
8370 return;
8375 /* Service list for autodection */
8376 static const struct sipe_service_data service_autodetect[] = {
8377 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8378 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8379 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8380 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8381 { NULL, NULL, 0 }
8384 /* Service list for SSL/TLS */
8385 static const struct sipe_service_data service_tls[] = {
8386 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8387 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8388 { NULL, NULL, 0 }
8391 /* Service list for TCP */
8392 static const struct sipe_service_data service_tcp[] = {
8393 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8394 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8395 { NULL, NULL, 0 }
8398 /* Service list for UDP */
8399 static const struct sipe_service_data service_udp[] = {
8400 { "sip", "udp", SIPE_TRANSPORT_UDP },
8401 { NULL, NULL, 0 }
8404 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8405 static void resolve_next_service(struct sipe_account_data *sip,
8406 const struct sipe_service_data *start)
8408 if (start) {
8409 sip->service_data = start;
8410 } else {
8411 sip->service_data++;
8412 if (sip->service_data->service == NULL) {
8413 gchar *hostname;
8414 /* Try connecting to the SIP hostname directly */
8415 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8416 if (sip->auto_transport) {
8417 // If SSL is supported, default to using it; OCS servers aren't configured
8418 // by default to accept TCP
8419 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8420 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8421 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8424 hostname = g_strdup(sip->sipdomain);
8425 create_connection(sip, hostname, 0);
8426 return;
8430 /* Try to resolve next service */
8431 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8432 sip->service_data->transport,
8433 sip->sipdomain,
8434 srvresolved, sip);
8437 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8439 struct sipe_account_data *sip = data;
8441 sip->srv_query_data = NULL;
8443 /* find the host to connect to */
8444 if (results) {
8445 gchar *hostname = g_strdup(resp->hostname);
8446 int port = resp->port;
8447 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8448 hostname, port);
8449 g_free(resp);
8451 sip->transport = sip->service_data->type;
8453 create_connection(sip, hostname, port);
8454 } else {
8455 resolve_next_service(sip, NULL);
8459 static void sipe_login(PurpleAccount *account)
8461 PurpleConnection *gc;
8462 struct sipe_account_data *sip;
8463 gchar **signinname_login, **userserver;
8464 const char *transport;
8465 const char *email;
8467 const char *username = purple_account_get_username(account);
8468 gc = purple_account_get_connection(account);
8470 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8472 if (strpbrk(username, "\t\v\r\n") != NULL) {
8473 gc->wants_to_die = TRUE;
8474 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8475 return;
8478 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8479 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8480 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8481 sip->gc = gc;
8482 sip->account = account;
8483 sip->reregister_set = FALSE;
8484 sip->reauthenticate_set = FALSE;
8485 sip->subscribed = FALSE;
8486 sip->subscribed_buddies = FALSE;
8487 sip->initial_state_published = FALSE;
8489 /* username format: <username>,[<optional login>] */
8490 signinname_login = g_strsplit(username, ",", 2);
8491 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8493 /* ensure that username format is name@domain */
8494 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8495 g_strfreev(signinname_login);
8496 gc->wants_to_die = TRUE;
8497 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8498 return;
8500 sip->username = g_strdup(signinname_login[0]);
8502 /* ensure that email format is name@domain if provided */
8503 email = purple_account_get_string(sip->account, "email", NULL);
8504 if (!is_empty(email) &&
8505 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8507 gc->wants_to_die = TRUE;
8508 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8509 return;
8511 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8513 /* login name specified? */
8514 if (signinname_login[1] && strlen(signinname_login[1])) {
8515 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8516 gboolean has_domain = domain_user[1] != NULL;
8517 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8518 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8519 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8520 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8521 sip->authdomain ? sip->authdomain : "", sip->authuser);
8522 g_strfreev(domain_user);
8525 userserver = g_strsplit(signinname_login[0], "@", 2);
8526 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8527 purple_connection_set_display_name(gc, userserver[0]);
8528 sip->sipdomain = g_strdup(userserver[1]);
8529 g_strfreev(userserver);
8530 g_strfreev(signinname_login);
8532 if (strchr(sip->username, ' ') != NULL) {
8533 gc->wants_to_die = TRUE;
8534 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8535 return;
8538 sip->password = g_strdup(purple_connection_get_password(gc));
8540 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8541 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8542 g_free, (GDestroyNotify)g_hash_table_destroy);
8543 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8544 g_free, (GDestroyNotify)sipe_subscription_free);
8546 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8548 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8550 g_free(sip->status);
8551 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8553 sip->auto_transport = FALSE;
8554 transport = purple_account_get_string(account, "transport", "auto");
8555 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8556 if (userserver[0]) {
8557 /* Use user specified server[:port] */
8558 int port = 0;
8560 if (userserver[1])
8561 port = atoi(userserver[1]);
8563 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8564 userserver[0], port);
8566 if (sipe_strequal(transport, "auto")) {
8567 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8568 } else if (sipe_strequal(transport, "tls")) {
8569 sip->transport = SIPE_TRANSPORT_TLS;
8570 } else if (sipe_strequal(transport, "tcp")) {
8571 sip->transport = SIPE_TRANSPORT_TCP;
8572 } else {
8573 sip->transport = SIPE_TRANSPORT_UDP;
8576 create_connection(sip, g_strdup(userserver[0]), port);
8577 } else {
8578 /* Server auto-discovery */
8579 if (sipe_strequal(transport, "auto")) {
8580 sip->auto_transport = TRUE;
8581 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8582 current_service++;
8583 resolve_next_service(sip, current_service);
8584 } else {
8585 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8587 } else if (sipe_strequal(transport, "tls")) {
8588 resolve_next_service(sip, service_tls);
8589 } else if (sipe_strequal(transport, "tcp")) {
8590 resolve_next_service(sip, service_tcp);
8591 } else {
8592 resolve_next_service(sip, service_udp);
8595 g_strfreev(userserver);
8598 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8600 connection_free_all(sip);
8602 g_free(sip->epid);
8603 sip->epid = NULL;
8605 if (sip->query_data != NULL)
8606 purple_dnsquery_destroy(sip->query_data);
8607 sip->query_data = NULL;
8609 if (sip->srv_query_data != NULL)
8610 purple_srv_cancel(sip->srv_query_data);
8611 sip->srv_query_data = NULL;
8613 if (sip->listen_data != NULL)
8614 purple_network_listen_cancel(sip->listen_data);
8615 sip->listen_data = NULL;
8617 if (sip->gsc != NULL)
8618 purple_ssl_close(sip->gsc);
8619 sip->gsc = NULL;
8621 sipe_auth_free(&sip->registrar);
8622 sipe_auth_free(&sip->proxy);
8624 if (sip->txbuf)
8625 purple_circ_buffer_destroy(sip->txbuf);
8626 sip->txbuf = NULL;
8628 g_free(sip->realhostname);
8629 sip->realhostname = NULL;
8631 g_free(sip->server_version);
8632 sip->server_version = NULL;
8634 if (sip->listenpa)
8635 purple_input_remove(sip->listenpa);
8636 sip->listenpa = 0;
8637 if (sip->tx_handler)
8638 purple_input_remove(sip->tx_handler);
8639 sip->tx_handler = 0;
8640 if (sip->resendtimeout)
8641 purple_timeout_remove(sip->resendtimeout);
8642 sip->resendtimeout = 0;
8643 if (sip->timeouts) {
8644 GSList *entry = sip->timeouts;
8645 while (entry) {
8646 struct scheduled_action *sched_action = entry->data;
8647 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8648 purple_timeout_remove(sched_action->timeout_handler);
8649 if (sched_action->destroy) {
8650 (*sched_action->destroy)(sched_action->payload);
8652 g_free(sched_action->name);
8653 g_free(sched_action);
8654 entry = entry->next;
8657 g_slist_free(sip->timeouts);
8659 if (sip->allow_events) {
8660 GSList *entry = sip->allow_events;
8661 while (entry) {
8662 g_free(entry->data);
8663 entry = entry->next;
8666 g_slist_free(sip->allow_events);
8668 if (sip->containers) {
8669 GSList *entry = sip->containers;
8670 while (entry) {
8671 free_container((struct sipe_container *)entry->data);
8672 entry = entry->next;
8675 g_slist_free(sip->containers);
8677 if (sip->contact)
8678 g_free(sip->contact);
8679 sip->contact = NULL;
8680 if (sip->regcallid)
8681 g_free(sip->regcallid);
8682 sip->regcallid = NULL;
8684 if (sip->serveraddr)
8685 g_free(sip->serveraddr);
8686 sip->serveraddr = NULL;
8688 if (sip->focus_factory_uri)
8689 g_free(sip->focus_factory_uri);
8690 sip->focus_factory_uri = NULL;
8692 sip->fd = -1;
8693 sip->processing_input = FALSE;
8695 if (sip->ews) {
8696 sipe_ews_free(sip->ews);
8698 sip->ews = NULL;
8702 * A callback for g_hash_table_foreach_remove
8704 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8705 SIPE_UNUSED_PARAMETER gpointer user_data)
8707 sipe_free_buddy((struct sipe_buddy *) buddy);
8709 /* We must return TRUE as the key/value have already been deleted */
8710 return(TRUE);
8713 static void sipe_close(PurpleConnection *gc)
8715 struct sipe_account_data *sip = gc->proto_data;
8717 if (sip) {
8718 /* leave all conversations */
8719 sipe_session_close_all(sip);
8720 sipe_session_remove_all(sip);
8722 if (sip->csta) {
8723 sip_csta_close(sip);
8726 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8727 /* unsubscribe all */
8728 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8730 /* unregister */
8731 do_register_exp(sip, 0);
8734 sipe_connection_cleanup(sip);
8735 g_free(sip->sipdomain);
8736 g_free(sip->username);
8737 g_free(sip->email);
8738 g_free(sip->password);
8739 g_free(sip->authdomain);
8740 g_free(sip->authuser);
8741 g_free(sip->status);
8742 g_free(sip->note);
8744 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8745 g_hash_table_destroy(sip->buddies);
8746 g_hash_table_destroy(sip->our_publications);
8747 g_hash_table_destroy(sip->user_state_publications);
8748 g_hash_table_destroy(sip->subscriptions);
8749 g_hash_table_destroy(sip->filetransfers);
8751 if (sip->groups) {
8752 GSList *entry = sip->groups;
8753 while (entry) {
8754 struct sipe_group *group = entry->data;
8755 g_free(group->name);
8756 g_free(group);
8757 entry = entry->next;
8760 g_slist_free(sip->groups);
8762 if (sip->our_publication_keys) {
8763 GSList *entry = sip->our_publication_keys;
8764 while (entry) {
8765 g_free(entry->data);
8766 entry = entry->next;
8769 g_slist_free(sip->our_publication_keys);
8771 while (sip->transactions)
8772 transactions_remove(sip, sip->transactions->data);
8774 g_free(gc->proto_data);
8775 gc->proto_data = NULL;
8778 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8779 SIPE_UNUSED_PARAMETER void *user_data)
8781 PurpleAccount *acct = purple_connection_get_account(gc);
8782 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8783 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8784 if (conv == NULL)
8785 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8786 purple_conversation_present(conv);
8787 g_free(id);
8790 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8791 SIPE_UNUSED_PARAMETER void *user_data)
8794 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8795 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8798 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8799 SIPE_UNUSED_PARAMETER struct transaction *trans)
8801 PurpleNotifySearchResults *results;
8802 PurpleNotifySearchColumn *column;
8803 xmlnode *searchResults;
8804 xmlnode *mrow;
8805 int match_count = 0;
8806 gboolean more = FALSE;
8807 gchar *secondary;
8809 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8811 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8812 if (!searchResults) {
8813 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8814 return FALSE;
8817 results = purple_notify_searchresults_new();
8819 if (results == NULL) {
8820 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8821 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8823 xmlnode_free(searchResults);
8824 return FALSE;
8827 column = purple_notify_searchresults_column_new(_("User name"));
8828 purple_notify_searchresults_column_add(results, column);
8830 column = purple_notify_searchresults_column_new(_("Name"));
8831 purple_notify_searchresults_column_add(results, column);
8833 column = purple_notify_searchresults_column_new(_("Company"));
8834 purple_notify_searchresults_column_add(results, column);
8836 column = purple_notify_searchresults_column_new(_("Country"));
8837 purple_notify_searchresults_column_add(results, column);
8839 column = purple_notify_searchresults_column_new(_("Email"));
8840 purple_notify_searchresults_column_add(results, column);
8842 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8843 GList *row = NULL;
8845 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8846 row = g_list_append(row, g_strdup(uri_parts[1]));
8847 g_strfreev(uri_parts);
8849 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8850 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8851 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8852 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8854 purple_notify_searchresults_row_add(results, row);
8855 match_count++;
8858 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8859 char *data = xmlnode_get_data_unescaped(mrow);
8860 more = (g_strcasecmp(data, "true") == 0);
8861 g_free(data);
8864 secondary = g_strdup_printf(
8865 dngettext(GETTEXT_PACKAGE,
8866 "Found %d contact%s:",
8867 "Found %d contacts%s:", match_count),
8868 match_count, more ? _(" (more matched your query)") : "");
8870 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8871 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8872 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8874 g_free(secondary);
8875 xmlnode_free(searchResults);
8876 return TRUE;
8879 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8881 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8882 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8883 unsigned i = 0;
8885 if (!attrs) return;
8887 do {
8888 PurpleRequestField *field = entries->data;
8889 const char *id = purple_request_field_get_id(field);
8890 const char *value = purple_request_field_string_get_value(field);
8892 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8894 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8895 } while ((entries = g_list_next(entries)) != NULL);
8896 attrs[i] = NULL;
8898 if (i > 0) {
8899 struct sipe_account_data *sip = gc->proto_data;
8900 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8901 gchar *query = g_strjoinv(NULL, attrs);
8902 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8903 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8904 send_soap_request_with_cb(sip, domain_uri, body,
8905 (TransCallback) process_search_contact_response, NULL);
8906 g_free(domain_uri);
8907 g_free(body);
8908 g_free(query);
8911 g_strfreev(attrs);
8914 static void sipe_show_find_contact(PurplePluginAction *action)
8916 PurpleConnection *gc = (PurpleConnection *) action->context;
8917 PurpleRequestFields *fields;
8918 PurpleRequestFieldGroup *group;
8919 PurpleRequestField *field;
8921 fields = purple_request_fields_new();
8922 group = purple_request_field_group_new(NULL);
8923 purple_request_fields_add_group(fields, group);
8925 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8926 purple_request_field_group_add_field(group, field);
8927 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8928 purple_request_field_group_add_field(group, field);
8929 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8930 purple_request_field_group_add_field(group, field);
8931 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8932 purple_request_field_group_add_field(group, field);
8934 purple_request_fields(gc,
8935 _("Search"),
8936 _("Search for a contact"),
8937 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8938 fields,
8939 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8940 _("_Cancel"), NULL,
8941 purple_connection_get_account(gc), NULL, NULL, gc);
8944 static void sipe_show_about_plugin(PurplePluginAction *action)
8946 PurpleConnection *gc = (PurpleConnection *) action->context;
8947 char *tmp = g_strdup_printf(
8949 * Non-translatable parts, like markup, are hard-coded
8950 * into the format string. This requires more translatable
8951 * texts but it makes the translations less error prone.
8953 "<b><font size=\"+1\">SIPE " SIPE_VERSION " </font></b><br/>"
8954 "<br/>"
8955 /* 1 */ "%s:<br/>"
8956 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8957 "<li> - MS Office Communications Server 2007</li><br/>"
8958 "<li> - MS Live Communications Server 2005</li><br/>"
8959 "<li> - MS Live Communications Server 2003</li><br/>"
8960 "<li> - Reuters Messaging</li><br/>"
8961 "<br/>"
8962 /* 2 */ "%s: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8963 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8964 /* 5 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8965 /* 6 */ "%s: GPLv2+<br/>"
8966 "<br/>"
8967 /* 7 */ "%s:<br/>"
8968 " - CERN<br/>"
8969 " - Reuters Messaging network<br/>"
8970 " - Deutsche Bank<br/>"
8971 " - Merrill Lynch<br/>"
8972 " - Wachovia<br/>"
8973 " - Intel<br/>"
8974 " - Nokia<br/>"
8975 " - HP<br/>"
8976 " - Symantec<br/>"
8977 " - Accenture<br/>"
8978 " - Siemens<br/>"
8979 " - Alcatel-Lucent<br/>"
8980 " - BT<br/>"
8981 "<br/>"
8982 /* 8,9 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
8983 "<br/>"
8984 /* 10 */ "<b>%s:</b><br/>"
8985 " - Anibal Avelar<br/>"
8986 " - Gabriel Burt<br/>"
8987 " - Stefan Becker<br/>"
8988 " - pier11<br/>"
8989 " - Jakub Adam<br/>"
8990 " - Tomáš Hrabčík<br/>"
8991 "<br/>"
8992 /* 11 */ "%s<br/>"
8994 /* The next 11 texts make up the SIPE about note text */
8995 /* About note, part 1/11: introduction */
8996 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
8997 /* About note, part 2/11: home page URL (label) */
8998 _("Home"),
8999 /* About note, part 3/11: support forum URL (label) */
9000 _("Support"),
9001 /* About note, part 4/11: support forum name (hyperlink text) */
9002 _("Help Forum"),
9003 /* About note, part 5/11: translation service URL (label) */
9004 _("Translations"),
9005 /* About note, part 6/11: license type (label) */
9006 _("License"),
9007 /* About note, part 7/11: known users */
9008 _("We support users in such organizations as"),
9009 /* About note, part 8/11: translation request, text before Transifex.net URL */
9010 /* append a space if text is not empty */
9011 _("Please help us to translate SIPE to your native language here at "),
9012 /* About note, part 9/11: translation request, text after Transifex.net URL */
9013 /* start with a space if text is not empty */
9014 _(" using convenient web interface"),
9015 /* About note, part 10/11: author list (header) */
9016 _("Authors"),
9017 /* About note, part 11/11: Localization credit */
9018 /* PLEASE NOTE: do *NOT* simply translate the english original */
9019 /* but write something similar to the following sentence: */
9020 /* "Localization for <language name> (<language code>): <name>" */
9021 _("Original texts in English (en): SIPE developers")
9023 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9024 g_free(tmp);
9027 static void sipe_republish_calendar(PurplePluginAction *action)
9029 PurpleConnection *gc = (PurpleConnection *) action->context;
9030 struct sipe_account_data *sip = gc->proto_data;
9032 sipe_update_calendar(sip);
9035 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9036 gpointer value,
9037 GString* str)
9039 struct sipe_publication *publication = value;
9041 g_string_append_printf( str,
9042 SIPE_PUB_XML_PUBLICATION_CLEAR,
9043 publication->category,
9044 publication->instance,
9045 publication->container,
9046 publication->version,
9047 "static");
9050 static void sipe_reset_status(PurplePluginAction *action)
9052 PurpleConnection *gc = (PurpleConnection *) action->context;
9053 struct sipe_account_data *sip = gc->proto_data;
9055 if (sip->ocs2007) /* 2007+ */
9057 GString* str = g_string_new(NULL);
9058 gchar *publications;
9060 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9061 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
9062 return;
9065 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9066 publications = g_string_free(str, FALSE);
9068 send_presence_publish(sip, publications);
9069 g_free(publications);
9071 else /* 2005 */
9073 send_presence_soap0(sip, FALSE, TRUE);
9077 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9078 gpointer context)
9080 PurpleConnection *gc = (PurpleConnection *)context;
9081 struct sipe_account_data *sip = gc->proto_data;
9082 GList *menu = NULL;
9083 PurplePluginAction *act;
9084 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9086 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9087 menu = g_list_prepend(menu, act);
9089 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9090 menu = g_list_prepend(menu, act);
9092 if (sipe_strequal(calendar, "EXCH")) {
9093 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9094 menu = g_list_prepend(menu, act);
9097 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9098 menu = g_list_prepend(menu, act);
9100 menu = g_list_reverse(menu);
9102 return menu;
9105 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9109 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9111 return TRUE;
9115 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9117 return TRUE;
9121 static char *sipe_status_text(PurpleBuddy *buddy)
9123 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9124 const PurpleStatus *status = purple_presence_get_active_status(presence);
9125 const char *status_id = purple_status_get_id(status);
9126 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9127 struct sipe_buddy *sbuddy;
9128 char *text = NULL;
9130 if (!sip) return NULL; /* happens on pidgin exit */
9132 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9133 if (sbuddy) {
9134 const char *activity_str = sbuddy->activity ?
9135 sbuddy->activity :
9136 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9137 purple_status_get_name(status) : NULL;
9139 if (activity_str && sbuddy->note)
9141 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9143 else if (activity_str)
9145 text = g_strdup(activity_str);
9147 else if (sbuddy->note)
9149 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9153 return text;
9156 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9158 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9159 const PurpleStatus *status = purple_presence_get_active_status(presence);
9160 struct sipe_account_data *sip;
9161 struct sipe_buddy *sbuddy;
9162 char *note = NULL;
9163 gboolean is_oof_note = FALSE;
9164 char *activity = NULL;
9165 char *calendar = NULL;
9166 char *meeting_subject = NULL;
9167 char *meeting_location = NULL;
9169 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9170 if (sip) //happens on pidgin exit
9172 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9173 if (sbuddy)
9175 note = sbuddy->note;
9176 is_oof_note = sbuddy->is_oof_note;
9177 activity = sbuddy->activity;
9178 calendar = sipe_cal_get_description(sbuddy);
9179 meeting_subject = sbuddy->meeting_subject;
9180 meeting_location = sbuddy->meeting_location;
9184 //Layout
9185 if (purple_presence_is_online(presence))
9187 const char *status_str = activity ? activity : purple_status_get_name(status);
9189 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9191 if (purple_presence_is_online(presence) &&
9192 !is_empty(calendar))
9194 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9196 g_free(calendar);
9197 if (!is_empty(meeting_location))
9199 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9201 if (!is_empty(meeting_subject))
9203 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9206 if (note)
9208 char *tmp = g_strdup_printf("<i>%s</i>", note);
9209 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9211 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9212 g_free(tmp);
9217 #if PURPLE_VERSION_CHECK(2,5,0)
9218 static GHashTable *
9219 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9221 GHashTable *table;
9222 table = g_hash_table_new(g_str_hash, g_str_equal);
9223 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9224 return table;
9226 #endif
9228 static PurpleBuddy *
9229 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9231 PurpleBuddy *clone;
9232 const gchar *server_alias, *email;
9233 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9235 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9237 purple_blist_add_buddy(clone, NULL, group, NULL);
9239 server_alias = purple_buddy_get_server_alias(buddy);
9240 if (server_alias) {
9241 purple_blist_server_alias_buddy(clone, server_alias);
9244 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9245 if (email) {
9246 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9249 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9250 //for UI to update;
9251 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9252 return clone;
9255 static void
9256 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9258 PurpleBuddy *buddy, *b;
9259 PurpleConnection *gc;
9260 PurpleGroup * group = purple_find_group(group_name);
9262 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9264 buddy = (PurpleBuddy *)node;
9266 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9267 gc = purple_account_get_connection(buddy->account);
9269 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9270 if (!b){
9271 purple_blist_add_buddy_clone(group, buddy);
9274 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9277 static void
9278 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9280 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9282 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9284 /* 2007+ conference */
9285 if (sip->ocs2007)
9287 sipe_conf_add(sip, buddy->name);
9289 else /* 2005- multiparty chat */
9291 gchar *self = sip_uri_self(sip);
9292 struct sip_session *session;
9294 session = sipe_session_add_chat(sip);
9295 session->chat_title = sipe_chat_get_name(session->callid);
9296 session->roster_manager = g_strdup(self);
9298 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9299 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9300 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9301 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9303 g_free(self);
9307 static gboolean
9308 sipe_is_election_finished(struct sip_session *session)
9310 gboolean res = TRUE;
9312 SIPE_DIALOG_FOREACH {
9313 if (dialog->election_vote == 0) {
9314 res = FALSE;
9315 break;
9317 } SIPE_DIALOG_FOREACH_END;
9319 if (res) {
9320 session->is_voting_in_progress = FALSE;
9322 return res;
9325 static void
9326 sipe_election_start(struct sipe_account_data *sip,
9327 struct sip_session *session)
9329 int election_timeout;
9331 if (session->is_voting_in_progress) {
9332 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9333 return;
9334 } else {
9335 session->is_voting_in_progress = TRUE;
9337 session->bid = rand();
9339 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9341 SIPE_DIALOG_FOREACH {
9342 /* reset election_vote for each chat participant */
9343 dialog->election_vote = 0;
9345 /* send RequestRM to each chat participant*/
9346 sipe_send_election_request_rm(sip, dialog, session->bid);
9347 } SIPE_DIALOG_FOREACH_END;
9349 election_timeout = 15; /* sec */
9350 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9354 * @param who a URI to whom to invite to chat
9356 void
9357 sipe_invite_to_chat(struct sipe_account_data *sip,
9358 struct sip_session *session,
9359 const gchar *who)
9361 /* a conference */
9362 if (session->focus_uri)
9364 sipe_invite_conf(sip, session, who);
9366 else /* a multi-party chat */
9368 gchar *self = sip_uri_self(sip);
9369 if (session->roster_manager) {
9370 if (sipe_strequal(session->roster_manager, self)) {
9371 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9372 } else {
9373 sipe_refer(sip, session, who);
9375 } else {
9376 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9378 session->pending_invite_queue = slist_insert_unique_sorted(
9379 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9381 sipe_election_start(sip, session);
9383 g_free(self);
9387 void
9388 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9389 struct sip_session *session)
9391 gchar *invitee;
9392 GSList *entry = session->pending_invite_queue;
9394 while (entry) {
9395 invitee = entry->data;
9396 sipe_invite_to_chat(sip, session, invitee);
9397 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9398 g_free(invitee);
9402 static void
9403 sipe_election_result(struct sipe_account_data *sip,
9404 void *sess)
9406 struct sip_session *session = (struct sip_session *)sess;
9407 gchar *rival;
9408 gboolean has_won = TRUE;
9410 if (session->roster_manager) {
9411 purple_debug_info("sipe",
9412 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9413 return;
9416 session->is_voting_in_progress = FALSE;
9418 SIPE_DIALOG_FOREACH {
9419 if (dialog->election_vote < 0) {
9420 has_won = FALSE;
9421 rival = dialog->with;
9422 break;
9424 } SIPE_DIALOG_FOREACH_END;
9426 if (has_won) {
9427 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9429 session->roster_manager = sip_uri_self(sip);
9431 SIPE_DIALOG_FOREACH {
9432 /* send SetRM to each chat participant*/
9433 sipe_send_election_set_rm(sip, dialog);
9434 } SIPE_DIALOG_FOREACH_END;
9435 } else {
9436 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9438 session->bid = 0;
9440 sipe_process_pending_invite_queue(sip, session);
9444 * For 2007+ conference only.
9446 static void
9447 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9449 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9450 struct sip_session *session;
9452 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9453 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9455 session = sipe_session_find_chat_by_title(sip, chat_title);
9457 sipe_conf_modify_user_role(sip, session, buddy->name);
9461 * For 2007+ conference only.
9463 static void
9464 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9466 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9467 struct sip_session *session;
9469 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9470 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9472 session = sipe_session_find_chat_by_title(sip, chat_title);
9474 sipe_conf_delete_user(sip, session, buddy->name);
9477 static void
9478 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9480 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9481 struct sip_session *session;
9483 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9484 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9486 session = sipe_session_find_chat_by_title(sip, chat_title);
9488 sipe_invite_to_chat(sip, session, buddy->name);
9491 static void
9492 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9494 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9496 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9497 if (phone) {
9498 char *tel_uri = sip_to_tel_uri(phone);
9500 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9501 sip_csta_make_call(sip, tel_uri);
9503 g_free(tel_uri);
9507 static void
9508 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9510 const gchar *email;
9511 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9513 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9514 if (email)
9516 char *mailto = g_strdup_printf("mailto:%s", email);
9517 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9518 #ifndef _WIN32
9520 pid_t pid;
9521 char *const parmList[] = {"xdg-email", mailto, NULL};
9522 if ((pid = fork()) == -1)
9524 purple_debug_info("sipe", "fork() error\n");
9526 else if (pid == 0)
9528 execvp(parmList[0], parmList);
9529 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9532 #else
9534 BOOL ret;
9535 _flushall();
9536 errno = 0;
9537 //@TODO resolve env variable %WINDIR% first
9538 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9539 if (errno)
9541 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9544 #endif
9546 g_free(mailto);
9548 else
9550 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9555 * A menu which appear when right-clicking on buddy in contact list.
9557 static GList *
9558 sipe_buddy_menu(PurpleBuddy *buddy)
9560 PurpleBlistNode *g_node;
9561 PurpleGroup *group, *gr_parent;
9562 PurpleMenuAction *act;
9563 GList *menu = NULL;
9564 GList *menu_groups = NULL;
9565 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9566 const char *email;
9567 const char *phone;
9568 const char *phone_disp_str;
9569 gchar *self = sip_uri_self(sip);
9571 SIPE_SESSION_FOREACH {
9572 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9574 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9576 PurpleConvChatBuddyFlags flags;
9577 PurpleConvChatBuddyFlags flags_us;
9579 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9580 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9581 if (session->focus_uri
9582 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9583 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9585 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9586 act = purple_menu_action_new(label,
9587 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9588 session->chat_title, NULL);
9589 g_free(label);
9590 menu = g_list_prepend(menu, act);
9593 if (session->focus_uri
9594 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9596 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9597 act = purple_menu_action_new(label,
9598 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9599 session->chat_title, NULL);
9600 g_free(label);
9601 menu = g_list_prepend(menu, act);
9604 else
9606 if (!session->focus_uri
9607 || (session->focus_uri && !session->locked))
9609 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9610 act = purple_menu_action_new(label,
9611 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9612 session->chat_title, NULL);
9613 g_free(label);
9614 menu = g_list_prepend(menu, act);
9618 } SIPE_SESSION_FOREACH_END;
9620 act = purple_menu_action_new(_("New chat"),
9621 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9622 NULL, NULL);
9623 menu = g_list_prepend(menu, act);
9625 if (sip->csta && !sip->csta->line_status) {
9626 gchar *tmp = NULL;
9627 /* work phone */
9628 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9629 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9630 if (phone) {
9631 gchar *label = g_strdup_printf(_("Work %s"),
9632 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9633 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9634 g_free(tmp);
9635 tmp = NULL;
9636 g_free(label);
9637 menu = g_list_prepend(menu, act);
9640 /* mobile phone */
9641 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9642 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9643 if (phone) {
9644 gchar *label = g_strdup_printf(_("Mobile %s"),
9645 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9646 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9647 g_free(tmp);
9648 tmp = NULL;
9649 g_free(label);
9650 menu = g_list_prepend(menu, act);
9653 /* home phone */
9654 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9655 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9656 if (phone) {
9657 gchar *label = g_strdup_printf(_("Home %s"),
9658 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9659 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9660 g_free(tmp);
9661 tmp = NULL;
9662 g_free(label);
9663 menu = g_list_prepend(menu, act);
9666 /* other phone */
9667 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9668 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9669 if (phone) {
9670 gchar *label = g_strdup_printf(_("Other %s"),
9671 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9672 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9673 g_free(tmp);
9674 tmp = NULL;
9675 g_free(label);
9676 menu = g_list_prepend(menu, act);
9679 /* custom1 phone */
9680 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9681 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9682 if (phone) {
9683 gchar *label = g_strdup_printf(_("Custom1 %s"),
9684 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9685 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9686 g_free(tmp);
9687 tmp = NULL;
9688 g_free(label);
9689 menu = g_list_prepend(menu, act);
9693 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9694 if (email) {
9695 act = purple_menu_action_new(_("Send email..."),
9696 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9697 NULL, NULL);
9698 menu = g_list_prepend(menu, act);
9701 gr_parent = purple_buddy_get_group(buddy);
9702 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9703 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9704 continue;
9706 group = (PurpleGroup *)g_node;
9707 if (group == gr_parent)
9708 continue;
9710 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9711 continue;
9713 act = purple_menu_action_new(purple_group_get_name(group),
9714 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9715 group->name, NULL);
9716 menu_groups = g_list_prepend(menu_groups, act);
9718 menu_groups = g_list_reverse(menu_groups);
9720 act = purple_menu_action_new(_("Copy to"),
9721 NULL,
9722 NULL, menu_groups);
9723 menu = g_list_prepend(menu, act);
9724 menu = g_list_reverse(menu);
9726 g_free(self);
9727 return menu;
9730 static void
9731 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9733 struct sipe_account_data *sip = chat->account->gc->proto_data;
9734 struct sip_session *session;
9736 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9737 sipe_conf_modify_conference_lock(sip, session, locked);
9740 static void
9741 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9743 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9744 sipe_conf_modify_lock(chat, FALSE);
9747 static void
9748 sipe_chat_menu_lock_cb(PurpleChat *chat)
9750 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9751 sipe_conf_modify_lock(chat, TRUE);
9754 static GList *
9755 sipe_chat_menu(PurpleChat *chat)
9757 PurpleMenuAction *act;
9758 PurpleConvChatBuddyFlags flags_us;
9759 GList *menu = NULL;
9760 struct sipe_account_data *sip = chat->account->gc->proto_data;
9761 struct sip_session *session;
9762 gchar *self;
9764 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9765 if (!session) return NULL;
9767 self = sip_uri_self(sip);
9768 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9770 if (session->focus_uri
9771 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9773 if (session->locked) {
9774 act = purple_menu_action_new(_("Unlock"),
9775 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9776 NULL, NULL);
9777 menu = g_list_prepend(menu, act);
9778 } else {
9779 act = purple_menu_action_new(_("Lock"),
9780 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9781 NULL, NULL);
9782 menu = g_list_prepend(menu, act);
9786 menu = g_list_reverse(menu);
9788 g_free(self);
9789 return menu;
9792 static GList *
9793 sipe_blist_node_menu(PurpleBlistNode *node)
9795 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9796 return sipe_buddy_menu((PurpleBuddy *) node);
9797 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9798 return sipe_chat_menu((PurpleChat *)node);
9799 } else {
9800 return NULL;
9804 static gboolean
9805 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9807 char *uri = trans->payload->data;
9809 PurpleNotifyUserInfo *info;
9810 PurpleBuddy *pbuddy = NULL;
9811 struct sipe_buddy *sbuddy;
9812 const char *alias = NULL;
9813 char *device_name = NULL;
9814 char *server_alias = NULL;
9815 char *phone_number = NULL;
9816 char *email = NULL;
9817 const char *site;
9818 char *first_name = NULL;
9819 char *last_name = NULL;
9821 if (!sip) return FALSE;
9823 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9825 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9826 alias = purple_buddy_get_local_alias(pbuddy);
9828 //will query buddy UA's capabilities and send answer to log
9829 sipe_options_request(sip, uri);
9831 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9832 if (sbuddy) {
9833 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9836 info = purple_notify_user_info_new();
9838 if (msg->response != 200) {
9839 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9840 } else {
9841 xmlnode *searchResults;
9842 xmlnode *mrow;
9844 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9845 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9846 if (!searchResults) {
9847 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9848 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9849 const char *value;
9850 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9851 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9852 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9854 /* For 2007 system we will take this from ContactCard -
9855 * it has cleaner tel: URIs at least
9857 if (!sip->ocs2007) {
9858 char *tel_uri = sip_to_tel_uri(phone_number);
9859 /* trims its parameters, so call first */
9860 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9861 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9862 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9863 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9864 g_free(tel_uri);
9867 if (server_alias && strlen(server_alias) > 0) {
9868 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9870 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9871 purple_notify_user_info_add_pair(info, _("Job title"), value);
9873 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9874 purple_notify_user_info_add_pair(info, _("Office"), value);
9876 if (phone_number && strlen(phone_number) > 0) {
9877 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9879 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9880 purple_notify_user_info_add_pair(info, _("Company"), value);
9882 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9883 purple_notify_user_info_add_pair(info, _("City"), value);
9885 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9886 purple_notify_user_info_add_pair(info, _("State"), value);
9888 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9889 purple_notify_user_info_add_pair(info, _("Country"), value);
9891 if (email && strlen(email) > 0) {
9892 purple_notify_user_info_add_pair(info, _("Email address"), email);
9896 xmlnode_free(searchResults);
9899 purple_notify_user_info_add_section_break(info);
9901 if (is_empty(server_alias)) {
9902 g_free(server_alias);
9903 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9904 if (server_alias) {
9905 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9909 /* present alias if it differs from server alias */
9910 if (alias && !sipe_strequal(alias, server_alias))
9912 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9915 if (is_empty(email)) {
9916 g_free(email);
9917 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9918 if (email) {
9919 purple_notify_user_info_add_pair(info, _("Email address"), email);
9923 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9924 if (site) {
9925 purple_notify_user_info_add_pair(info, _("Site"), site);
9928 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9929 if (first_name && last_name) {
9930 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9932 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9933 g_free(link);
9935 g_free(first_name);
9936 g_free(last_name);
9938 if (device_name) {
9939 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9942 /* show a buddy's user info in a nice dialog box */
9943 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9944 uri, /* buddy's URI */
9945 info, /* body */
9946 NULL, /* callback called when dialog closed */
9947 NULL); /* userdata for callback */
9949 g_free(phone_number);
9950 g_free(server_alias);
9951 g_free(email);
9952 g_free(device_name);
9954 return TRUE;
9958 * AD search first, LDAP based
9960 static void sipe_get_info(PurpleConnection *gc, const char *username)
9962 struct sipe_account_data *sip = gc->proto_data;
9963 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9964 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9965 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9966 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9968 payload->destroy = g_free;
9969 payload->data = g_strdup(username);
9971 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9972 send_soap_request_with_cb(sip, domain_uri, body,
9973 (TransCallback) process_get_info_response, payload);
9974 g_free(domain_uri);
9975 g_free(body);
9976 g_free(row);
9979 static PurplePlugin *my_protocol = NULL;
9981 static PurplePluginProtocolInfo prpl_info =
9983 OPT_PROTO_CHAT_TOPIC,
9984 NULL, /* user_splits */
9985 NULL, /* protocol_options */
9986 NO_BUDDY_ICONS, /* icon_spec */
9987 sipe_list_icon, /* list_icon */
9988 NULL, /* list_emblems */
9989 sipe_status_text, /* status_text */
9990 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9991 sipe_status_types, /* away_states */
9992 sipe_blist_node_menu, /* blist_node_menu */
9993 NULL, /* chat_info */
9994 NULL, /* chat_info_defaults */
9995 sipe_login, /* login */
9996 sipe_close, /* close */
9997 sipe_im_send, /* send_im */
9998 NULL, /* set_info */ // TODO maybe
9999 sipe_send_typing, /* send_typing */
10000 sipe_get_info, /* get_info */
10001 sipe_set_status, /* set_status */
10002 sipe_set_idle, /* set_idle */
10003 NULL, /* change_passwd */
10004 sipe_add_buddy, /* add_buddy */
10005 NULL, /* add_buddies */
10006 sipe_remove_buddy, /* remove_buddy */
10007 NULL, /* remove_buddies */
10008 sipe_add_permit, /* add_permit */
10009 sipe_add_deny, /* add_deny */
10010 sipe_add_deny, /* rem_permit */
10011 sipe_add_permit, /* rem_deny */
10012 dummy_permit_deny, /* set_permit_deny */
10013 NULL, /* join_chat */
10014 NULL, /* reject_chat */
10015 NULL, /* get_chat_name */
10016 sipe_chat_invite, /* chat_invite */
10017 sipe_chat_leave, /* chat_leave */
10018 NULL, /* chat_whisper */
10019 sipe_chat_send, /* chat_send */
10020 sipe_keep_alive, /* keepalive */
10021 NULL, /* register_user */
10022 NULL, /* get_cb_info */ // deprecated
10023 NULL, /* get_cb_away */ // deprecated
10024 sipe_alias_buddy, /* alias_buddy */
10025 sipe_group_buddy, /* group_buddy */
10026 sipe_rename_group, /* rename_group */
10027 NULL, /* buddy_free */
10028 sipe_convo_closed, /* convo_closed */
10029 purple_normalize_nocase, /* normalize */
10030 NULL, /* set_buddy_icon */
10031 sipe_remove_group, /* remove_group */
10032 NULL, /* get_cb_real_name */ // TODO?
10033 NULL, /* set_chat_topic */
10034 NULL, /* find_blist_chat */
10035 NULL, /* roomlist_get_list */
10036 NULL, /* roomlist_cancel */
10037 NULL, /* roomlist_expand_category */
10038 NULL, /* can_receive_file */
10039 sipe_ft_send_file, /* send_file */
10040 sipe_ft_new_xfer, /* new_xfer */
10041 NULL, /* offline_message */
10042 NULL, /* whiteboard_prpl_ops */
10043 sipe_send_raw, /* send_raw */
10044 NULL, /* roomlist_room_serialize */
10045 NULL, /* unregister_user */
10046 NULL, /* send_attention */
10047 NULL, /* get_attention_types */
10048 #if !PURPLE_VERSION_CHECK(2,5,0)
10049 /* Backward compatibility when compiling against 2.4.x API */
10050 (void (*)(void)) /* _purple_reserved4 */
10051 #endif
10052 sizeof(PurplePluginProtocolInfo), /* struct_size */
10053 #if PURPLE_VERSION_CHECK(2,5,0)
10054 sipe_get_account_text_table, /* get_account_text_table */
10055 #if PURPLE_VERSION_CHECK(2,6,0)
10056 NULL, /* initiate_media */
10057 NULL, /* get_media_caps */
10058 #endif
10059 #endif
10063 static PurplePluginInfo info = {
10064 PURPLE_PLUGIN_MAGIC,
10065 PURPLE_MAJOR_VERSION,
10066 PURPLE_MINOR_VERSION,
10067 PURPLE_PLUGIN_PROTOCOL, /**< type */
10068 NULL, /**< ui_requirement */
10069 0, /**< flags */
10070 NULL, /**< dependencies */
10071 PURPLE_PRIORITY_DEFAULT, /**< priority */
10072 "prpl-sipe", /**< id */
10073 "Office Communicator", /**< name */
10074 SIPE_VERSION, /**< version */
10075 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10076 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10077 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10078 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10079 "Gabriel Burt <gburt@novell.com>, " /**< author */
10080 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10081 "pier11 <pier11@operamail.com>", /**< author */
10082 "http://sipe.sourceforge.net/", /**< homepage */
10083 sipe_plugin_load, /**< load */
10084 sipe_plugin_unload, /**< unload */
10085 sipe_plugin_destroy, /**< destroy */
10086 NULL, /**< ui_info */
10087 &prpl_info, /**< extra_info */
10088 NULL,
10089 sipe_actions,
10090 NULL,
10091 NULL,
10092 NULL,
10093 NULL
10096 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
10098 GList *entry;
10100 sip_sec_destroy();
10102 entry = prpl_info.protocol_options;
10103 while (entry) {
10104 purple_account_option_destroy(entry->data);
10105 entry = g_list_delete_link(entry, entry);
10107 prpl_info.protocol_options = NULL;
10109 entry = prpl_info.user_splits;
10110 while (entry) {
10111 purple_account_user_split_destroy(entry->data);
10112 entry = g_list_delete_link(entry, entry);
10114 prpl_info.user_splits = NULL;
10117 static void init_plugin(PurplePlugin *plugin)
10119 PurpleAccountUserSplit *split;
10120 PurpleAccountOption *option;
10122 srand(time(NULL));
10123 sip_sec_init();
10125 #ifdef ENABLE_NLS
10126 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
10127 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
10128 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
10129 textdomain(GETTEXT_PACKAGE);
10130 #endif
10132 purple_plugin_register(plugin);
10134 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
10135 purple_account_user_split_set_reverse(split, FALSE);
10136 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
10138 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
10139 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10141 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
10142 purple_account_option_add_list_item(option, _("Auto"), "auto");
10143 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
10144 purple_account_option_add_list_item(option, _("TCP"), "tcp");
10145 purple_account_option_add_list_item(option, _("UDP"), "udp");
10146 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10148 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
10149 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
10151 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
10152 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10154 #ifdef USE_KERBEROS
10155 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
10156 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10158 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
10159 * No login/password is taken into account if this option present,
10160 * instead used default credentials stored in OS.
10162 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
10163 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10164 #endif
10166 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
10167 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
10168 purple_account_option_add_list_item(option, _("None"), "NONE");
10169 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10171 /** Example: https://server.company.com/EWS/Exchange.asmx */
10172 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
10173 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10175 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
10176 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10178 /** Example: DOMAIN\user or user@company.com */
10179 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
10180 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10182 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
10183 purple_account_option_set_masked(option, TRUE);
10184 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10186 my_protocol = plugin;
10189 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
10192 Local Variables:
10193 mode: c
10194 c-file-style: "bsd"
10195 indent-tabs-mode: t
10196 tab-width: 8
10197 End: