Add arm architecture to User Agent string
[siplcs.git] / src / core / sipe.c
blobf3e937339f0ad5ca8bb01ff0875719229f01e137
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 static struct sipe_activity_map_struct
134 sipe_activity type;
135 const char *token;
136 const char *desc;
137 const char *status_id;
139 } const sipe_activity_map[] =
141 /* This has nothing to do with Availability numbers, like 3500 (online).
142 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
144 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
145 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
146 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
147 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
148 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
149 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
150 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
151 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
152 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
153 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
154 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
155 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
156 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
157 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
158 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
160 /** @param x is sipe_activity */
161 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
164 /* Action name templates */
165 #define ACTION_NAME_PRESENCE "<presence><%s>"
167 static sipe_activity
168 sipe_get_activity_by_token(const char *token)
170 int i;
172 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
174 if (sipe_strequal(token, sipe_activity_map[i].token))
175 return sipe_activity_map[i].type;
178 return sipe_activity_map[0].type;
181 static const char *
182 sipe_get_activity_desc_by_token(const char *token)
184 if (!token) return NULL;
186 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
189 /** Allows to send typed messages from chat window again after account reinstantiation. */
190 static void
191 sipe_rejoin_chat(PurpleConversation *conv)
193 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
194 PURPLE_CONV_CHAT(conv)->left)
196 PURPLE_CONV_CHAT(conv)->left = FALSE;
197 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
201 static char *genbranch()
203 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
204 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
205 rand() & 0xFFFF, rand() & 0xFFFF);
209 static char *default_ua = NULL;
210 static const char*
211 sipe_get_useragent(struct sipe_account_data *sip)
213 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
214 if (is_empty(useragent)) {
215 if (!default_ua) {
216 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
217 /* ref: lzodefs.h */
218 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
219 #define SIPE_TARGET_PLATFORM "linux"
220 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
221 #define SIPE_TARGET_PLATFORM "bsd"
222 #elif defined(__APPLE__) || defined(__MACOS__)
223 #define SIPE_TARGET_PLATFORM "macosx"
224 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
225 #define SIPE_TARGET_PLATFORM "aix"
226 #elif defined(__solaris__) || defined(__sun)
227 #define SIPE_TARGET_PLATFORM "sun"
228 #elif defined(_WIN32)
229 #define SIPE_TARGET_PLATFORM "win"
230 #elif defined(__CYGWIN__)
231 #define SIPE_TARGET_PLATFORM "cygwin"
232 #elif defined(__hpux__)
233 #define SIPE_TARGET_PLATFORM "hpux"
234 #elif defined(__sgi__)
235 #define SIPE_TARGET_PLATFORM "irix"
236 #else
237 #define SIPE_TARGET_PLATFORM "unknown"
238 #endif
240 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
241 #define SIPE_TARGET_ARCH "x86_64"
242 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
243 #define SIPE_TARGET_ARCH "i386"
244 #elif defined(__ppc64__)
245 #define SIPE_TARGET_ARCH "ppc64"
246 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
247 #define SIPE_TARGET_ARCH "ppc"
248 #elif defined(__hppa__) || defined(__hppa)
249 #define SIPE_TARGET_ARCH "hppa"
250 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
251 #define SIPE_TARGET_ARCH "mips"
252 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
253 #define SIPE_TARGET_ARCH "s390"
254 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
255 #define SIPE_TARGET_ARCH "sparc"
256 #elif defined(__arm__)
257 #define SIPE_TARGET_ARCH "arm"
258 #else
259 #define SIPE_TARGET_ARCH "other"
260 #endif
262 default_ua = g_strdup_printf("Purple/%s Sipe/" SIPE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
263 purple_core_get_version(),
264 sip->server_version ? sip->server_version : "");
266 useragent = default_ua;
268 return useragent;
271 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
272 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
274 return "sipe";
277 static void sipe_plugin_destroy(PurplePlugin *plugin);
279 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
281 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
282 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
283 gpointer data);
285 static void sipe_close(PurpleConnection *gc);
287 static void send_presence_status(struct sipe_account_data *sip);
289 static void sendout_pkt(PurpleConnection *gc, const char *buf);
291 static void sipe_keep_alive(PurpleConnection *gc)
293 struct sipe_account_data *sip = gc->proto_data;
294 if (sip->transport == SIPE_TRANSPORT_UDP) {
295 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
296 gchar buf[2] = {0, 0};
297 purple_debug_info("sipe", "sending keep alive\n");
298 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
299 } else {
300 time_t now = time(NULL);
301 if ((sip->keepalive_timeout > 0) &&
302 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
303 #if PURPLE_VERSION_CHECK(2,4,0)
304 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
305 #endif
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 (not user settable) */
2405 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE,
2406 NULL,
2407 NULL,
2408 FALSE);
2410 return types;
2414 * A callback for g_hash_table_foreach
2416 static void
2417 sipe_buddy_subscribe_cb(char *buddy_name,
2418 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2419 struct sipe_account_data *sip)
2421 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2422 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2423 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2424 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2426 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2427 g_free(action_name);
2431 * Removes entries from purple buddy list
2432 * that does not correspond ones in the roaming contact list.
2434 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2435 GSList *buddies = purple_find_buddies(sip->account, NULL);
2436 GSList *entry = buddies;
2437 struct sipe_buddy *buddy;
2438 PurpleBuddy *b;
2439 PurpleGroup *g;
2441 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2442 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2443 while (entry) {
2444 b = entry->data;
2445 g = purple_buddy_get_group(b);
2446 buddy = g_hash_table_lookup(sip->buddies, b->name);
2447 if(buddy) {
2448 gboolean in_sipe_groups = FALSE;
2449 GSList *entry2 = buddy->groups;
2450 while (entry2) {
2451 struct sipe_group *group = entry2->data;
2452 if (sipe_strequal(group->name, g->name)) {
2453 in_sipe_groups = TRUE;
2454 break;
2456 entry2 = entry2->next;
2458 if(!in_sipe_groups) {
2459 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2460 purple_blist_remove_buddy(b);
2462 } else {
2463 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2464 purple_blist_remove_buddy(b);
2466 entry = entry->next;
2468 g_slist_free(buddies);
2471 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2473 int len = msg->bodylen;
2475 const gchar *tmp = sipmsg_find_header(msg, "Event");
2476 xmlnode *item;
2477 xmlnode *isc;
2478 const gchar *contacts_delta;
2479 xmlnode *group_node;
2480 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2481 return FALSE;
2484 /* Convert the contact from XML to Purple Buddies */
2485 isc = xmlnode_from_str(msg->body, len);
2486 if (!isc) {
2487 return FALSE;
2490 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2491 if (contacts_delta) {
2492 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2495 if (sipe_strequal(isc->name, "contactList")) {
2497 /* Parse groups */
2498 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2499 struct sipe_group * group = g_new0(struct sipe_group, 1);
2500 const char *name = xmlnode_get_attrib(group_node, "name");
2502 if (g_str_has_prefix(name, "~")) {
2503 name = _("Other Contacts");
2505 group->name = g_strdup(name);
2506 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2508 sipe_group_add(sip, group);
2511 // Make sure we have at least one group
2512 if (g_slist_length(sip->groups) == 0) {
2513 struct sipe_group * group = g_new0(struct sipe_group, 1);
2514 PurpleGroup *purple_group;
2515 group->name = g_strdup(_("Other Contacts"));
2516 group->id = 1;
2517 purple_group = purple_group_new(group->name);
2518 purple_blist_add_group(purple_group, NULL);
2519 sip->groups = g_slist_append(sip->groups, group);
2522 /* Parse contacts */
2523 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2524 const gchar *uri = xmlnode_get_attrib(item, "uri");
2525 const gchar *name = xmlnode_get_attrib(item, "name");
2526 gchar *buddy_name;
2527 struct sipe_buddy *buddy = NULL;
2528 gchar *tmp;
2529 gchar **item_groups;
2530 int i = 0;
2532 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2533 tmp = sip_uri_from_name(uri);
2534 buddy_name = g_ascii_strdown(tmp, -1);
2535 g_free(tmp);
2537 /* assign to group Other Contacts if nothing else received */
2538 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2539 if(is_empty(tmp)) {
2540 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2541 g_free(tmp);
2542 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2544 item_groups = g_strsplit(tmp, " ", 0);
2545 g_free(tmp);
2547 while (item_groups[i]) {
2548 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2550 // If couldn't find the right group for this contact, just put them in the first group we have
2551 if (group == NULL && g_slist_length(sip->groups) > 0) {
2552 group = sip->groups->data;
2555 if (group != NULL) {
2556 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2557 if (!b){
2558 b = purple_buddy_new(sip->account, buddy_name, uri);
2559 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2561 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2564 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2565 if (name != NULL && strlen(name) != 0) {
2566 purple_blist_alias_buddy(b, name);
2568 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2572 if (!buddy) {
2573 buddy = g_new0(struct sipe_buddy, 1);
2574 buddy->name = g_strdup(b->name);
2575 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2578 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2580 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2581 } else {
2582 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2583 name);
2586 i++;
2587 } // while, contact groups
2588 g_strfreev(item_groups);
2589 g_free(buddy_name);
2591 } // for, contacts
2593 sipe_cleanup_local_blist(sip);
2595 /* Add self-contact if not there yet. 2005 systems. */
2596 /* This will resemble subscription to roaming_self in 2007 systems */
2597 if (!sip->ocs2007) {
2598 gchar *self_uri = sip_uri_self(sip);
2599 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2601 if (!buddy) {
2602 buddy = g_new0(struct sipe_buddy, 1);
2603 buddy->name = g_strdup(self_uri);
2604 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2606 g_free(self_uri);
2609 xmlnode_free(isc);
2611 /* subscribe to buddies */
2612 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2613 if (sip->batched_support) {
2614 sipe_subscribe_presence_batched(sip, NULL);
2615 } else {
2616 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2618 sip->subscribed_buddies = TRUE;
2620 /* for 2005 systems schedule contacts' status update
2621 * based on their calendar information
2623 if (!sip->ocs2007) {
2624 sipe_sched_calendar_status_update(sip, time(NULL));
2627 return 0;
2631 * Subscribe roaming contacts
2633 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2635 gchar *to = sip_uri_self(sip);
2636 gchar *tmp = get_contact(sip);
2637 gchar *hdr = g_strdup_printf(
2638 "Event: vnd-microsoft-roaming-contacts\r\n"
2639 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2640 "Supported: com.microsoft.autoextend\r\n"
2641 "Supported: ms-benotify\r\n"
2642 "Proxy-Require: ms-benotify\r\n"
2643 "Supported: ms-piggyback-first-notify\r\n"
2644 "Contact: %s\r\n", tmp);
2645 g_free(tmp);
2647 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2648 g_free(to);
2649 g_free(hdr);
2652 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2653 SIPE_UNUSED_PARAMETER void *unused)
2655 gchar *key;
2656 struct sip_dialog *dialog;
2657 gchar *to = sip_uri_self(sip);
2658 gchar *tmp = get_contact(sip);
2659 gchar *hdr = g_strdup_printf(
2660 "Event: presence.wpending\r\n"
2661 "Accept: text/xml+msrtc.wpending\r\n"
2662 "Supported: com.microsoft.autoextend\r\n"
2663 "Supported: ms-benotify\r\n"
2664 "Proxy-Require: ms-benotify\r\n"
2665 "Supported: ms-piggyback-first-notify\r\n"
2666 "Contact: %s\r\n", tmp);
2667 g_free(tmp);
2669 /* Subscription is identified by <event> key */
2670 key = g_strdup_printf("<%s>", "presence.wpending");
2671 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2672 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2674 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2676 g_free(to);
2677 g_free(hdr);
2678 g_free(key);
2682 * Fires on deregistration event initiated by server.
2683 * [MS-SIPREGE] SIP extension.
2686 // 2007 Example
2688 // Content-Type: text/registration-event
2689 // subscription-state: terminated;expires=0
2690 // ms-diagnostics-public: 4141;reason="User disabled"
2692 // deregistered;event=rejected
2694 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2696 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2697 gchar *event = NULL;
2698 gchar *reason = NULL;
2699 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2700 gchar *warning;
2702 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2703 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2705 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2706 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2707 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2708 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2709 } else {
2710 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2711 return;
2714 if (diagnostics != NULL) {
2715 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2716 } else { // for LCS2005
2717 int error_id = 0;
2718 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2719 error_id = 4140; // [MS-SIPREGE]
2720 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2721 reason = g_strdup(_("you are already signed in at another location"));
2722 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2723 error_id = 4141;
2724 reason = g_strdup(_("user disabled")); // [MS-OCER]
2725 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2726 error_id = 4142;
2727 reason = g_strdup(_("user moved")); // [MS-OCER]
2730 g_free(event);
2731 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2732 g_free(reason);
2734 sip->gc->wants_to_die = TRUE;
2735 purple_connection_error(sip->gc, warning);
2736 g_free(warning);
2740 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2742 xmlnode *xn_provision_group_list;
2743 xmlnode *node;
2745 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2747 /* provisionGroup */
2748 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2749 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2750 g_free(sip->focus_factory_uri);
2751 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2752 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2753 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2754 break;
2757 xmlnode_free(xn_provision_group_list);
2760 /** for 2005 system */
2761 static void
2762 sipe_process_provisioning(struct sipe_account_data *sip,
2763 struct sipmsg *msg)
2765 xmlnode *xn_provision;
2766 xmlnode *node;
2768 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2769 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2770 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2771 if ((node = xmlnode_get_child(node, "line"))) {
2772 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2773 const gchar *server = xmlnode_get_attrib(node, "server");
2774 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2775 sip_csta_open(sip, line_uri, server);
2778 xmlnode_free(xn_provision);
2781 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2783 const gchar *contacts_delta;
2784 xmlnode *xml;
2786 xml = xmlnode_from_str(msg->body, msg->bodylen);
2787 if (!xml)
2789 return;
2792 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2793 if (contacts_delta)
2795 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2798 xmlnode_free(xml);
2801 static void
2802 free_container(struct sipe_container *container)
2804 GSList *entry;
2806 if (!container) return;
2808 entry = container->members;
2809 while (entry) {
2810 void *data = entry->data;
2811 entry = g_slist_remove(entry, data);
2812 g_free(data);
2814 g_free(container);
2818 * Finds locally stored MS-PRES container member
2820 static struct sipe_container_member *
2821 sipe_find_container_member(struct sipe_container *container,
2822 const gchar *type,
2823 const gchar *value)
2825 struct sipe_container_member *member;
2826 GSList *entry;
2828 if (container == NULL || type == NULL) {
2829 return NULL;
2832 entry = container->members;
2833 while (entry) {
2834 member = entry->data;
2835 if (!g_strcasecmp(member->type, type)
2836 && ((!member->value && !value)
2837 || (value && member->value && !g_strcasecmp(member->value, value)))
2839 return member;
2841 entry = entry->next;
2843 return NULL;
2847 * Finds locally stored MS-PRES container by id
2849 static struct sipe_container *
2850 sipe_find_container(struct sipe_account_data *sip,
2851 guint id)
2853 struct sipe_container *container;
2854 GSList *entry;
2856 if (sip == NULL) {
2857 return NULL;
2860 entry = sip->containers;
2861 while (entry) {
2862 container = entry->data;
2863 if (id == container->id) {
2864 return container;
2866 entry = entry->next;
2868 return NULL;
2872 * Access Levels
2873 * 32000 - Blocked
2874 * 400 - Personal
2875 * 300 - Team
2876 * 200 - Company
2877 * 100 - Public
2879 static int
2880 sipe_find_access_level(struct sipe_account_data *sip,
2881 const gchar *type,
2882 const gchar *value)
2884 guint containers[] = {32000, 400, 300, 200, 100};
2885 int i = 0;
2887 for (i = 0; i < 5; i++) {
2888 struct sipe_container_member *member;
2889 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2890 if (!container) continue;
2892 member = sipe_find_container_member(container, type, value);
2893 if (member) {
2894 return containers[i];
2898 return -1;
2901 static void
2902 sipe_send_set_container_members(struct sipe_account_data *sip,
2903 guint container_id,
2904 guint container_version,
2905 const gchar* action,
2906 const gchar* type,
2907 const gchar* value)
2909 gchar *self = sip_uri_self(sip);
2910 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2911 gchar *contact;
2912 gchar *hdr;
2913 gchar *body = g_strdup_printf(
2914 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2915 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2916 "</setContainerMembers>",
2917 container_id,
2918 container_version,
2919 action,
2920 type,
2921 value_str);
2922 g_free(value_str);
2924 contact = get_contact(sip);
2925 hdr = g_strdup_printf("Contact: %s\r\n"
2926 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2927 g_free(contact);
2929 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2931 g_free(hdr);
2932 g_free(body);
2933 g_free(self);
2936 static void
2937 free_publication(struct sipe_publication *publication)
2939 g_free(publication->category);
2940 g_free(publication->cal_event_hash);
2941 g_free(publication->note);
2943 g_free(publication->working_hours_xml_str);
2944 g_free(publication->fb_start_str);
2945 g_free(publication->free_busy_base64);
2947 g_free(publication);
2950 /* key is <category><instance><container> */
2951 static gboolean
2952 sipe_is_our_publication(struct sipe_account_data *sip,
2953 const gchar *key)
2955 GSList *entry;
2957 /* filling keys for our publications if not yet cached */
2958 if (!sip->our_publication_keys) {
2959 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2960 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2961 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2962 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2963 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2964 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2965 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2967 purple_debug_info("sipe", "* Our Publication Instances *\n");
2968 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2969 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2970 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2971 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2972 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2973 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2974 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2975 purple_debug_info("sipe", "\tNote : %u\n", 0);
2976 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2978 /* device */
2979 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2980 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2982 /* state:machineState */
2983 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2984 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2985 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2986 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2988 /* state:userState */
2989 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2990 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2991 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2992 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2994 /* state:calendarState */
2995 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2996 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2997 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2998 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3000 /* state:calendarState OOF */
3001 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3002 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3003 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3004 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3006 /* note */
3007 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3008 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3009 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3010 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3011 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3012 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3014 /* note OOF */
3015 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3016 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3017 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3018 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3019 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3020 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3022 /* calendarData:WorkingHours */
3023 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3024 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3025 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3026 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3027 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3028 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3029 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3030 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3031 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3032 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3033 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3034 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3036 /* calendarData:FreeBusy */
3037 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3038 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3039 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3040 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3041 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3042 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3043 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3044 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3045 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3046 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3047 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3048 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3050 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
3051 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3054 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3056 entry = sip->our_publication_keys;
3057 while (entry) {
3058 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3059 if (sipe_strequal(entry->data, key)) {
3060 return TRUE;
3062 entry = entry->next;
3064 return FALSE;
3067 /** Property names to store in blist.xml */
3068 #define ALIAS_PROP "alias"
3069 #define EMAIL_PROP "email"
3070 #define PHONE_PROP "phone"
3071 #define PHONE_DISPLAY_PROP "phone-display"
3072 #define PHONE_MOBILE_PROP "phone-mobile"
3073 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3074 #define PHONE_HOME_PROP "phone-home"
3075 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3076 #define PHONE_OTHER_PROP "phone-other"
3077 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3078 #define PHONE_CUSTOM1_PROP "phone-custom1"
3079 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3080 #define SITE_PROP "site"
3081 #define COMPANY_PROP "company"
3082 #define DEPARTMENT_PROP "department"
3083 #define TITLE_PROP "title"
3084 #define OFFICE_PROP "office"
3085 /** implies work address */
3086 #define ADDRESS_STREET_PROP "address-street"
3087 #define ADDRESS_CITY_PROP "address-city"
3088 #define ADDRESS_STATE_PROP "address-state"
3089 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3090 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3093 * Tries to figure out user first and last name
3094 * based on Display Name and email properties.
3096 * Allocates memory - must be g_free()'d
3098 * Examples to parse:
3099 * First Last
3100 * First Last - Company Name
3101 * Last, First
3102 * Last, First M.
3103 * Last, First (C)(STP) (Company)
3104 * first.last@company.com (preprocessed as "first last")
3105 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3107 * Unusable examples:
3108 * user@company.com (preprocessed as "user")
3109 * first.m.last@company.com (preprocessed as "first m last")
3110 * user.company.com@reuters.net (preprocessed as "user company com")
3112 static void
3113 sipe_get_first_last_names(struct sipe_account_data *sip,
3114 const char *uri,
3115 char **first_name,
3116 char **last_name)
3118 PurpleBuddy *p_buddy;
3119 char *display_name;
3120 const char *email;
3121 const char *first, *last;
3122 char *tmp;
3123 char **parts;
3124 gboolean has_comma = FALSE;
3126 if (!sip || !uri) return;
3128 p_buddy = purple_find_buddy(sip->account, uri);
3130 if (!p_buddy) return;
3132 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3133 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3135 if (!display_name && !email) return;
3137 /* if no display name, make "first last anything_else" out of email */
3138 if (email && !display_name) {
3139 display_name = g_strndup(email, strstr(email, "@") - email);
3140 display_name = purple_strreplace((tmp = display_name), ".", " ");
3141 g_free(tmp);
3144 if (display_name) {
3145 has_comma = (strstr(display_name, ",") != NULL);
3146 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3147 g_free(tmp);
3148 display_name = purple_strreplace((tmp = display_name), ",", " ");
3149 g_free(tmp);
3152 parts = g_strsplit(display_name, " ", 0);
3154 if (!parts[0] || !parts[1]) {
3155 g_free(display_name);
3156 g_strfreev(parts);
3157 return;
3160 if (has_comma) {
3161 last = parts[0];
3162 first = parts[1];
3163 } else {
3164 first = parts[0];
3165 last = parts[1];
3168 if (first_name) {
3169 *first_name = g_strstrip(g_strdup(first));
3172 if (last_name) {
3173 *last_name = g_strstrip(g_strdup(last));
3176 g_free(display_name);
3177 g_strfreev(parts);
3181 * Update user information
3183 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3184 * @param property_name
3185 * @param property_value may be modified to strip white space
3187 static void
3188 sipe_update_user_info(struct sipe_account_data *sip,
3189 const char *uri,
3190 const char *property_name,
3191 char *property_value)
3193 GSList *buddies, *entry;
3195 if (!property_name || strlen(property_name) == 0) return;
3197 if (property_value)
3198 property_value = g_strstrip(property_value);
3200 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3201 while (entry) {
3202 const char *prop_str;
3203 const char *server_alias;
3204 PurpleBuddy *p_buddy = entry->data;
3206 /* for Display Name */
3207 if (sipe_strequal(property_name, ALIAS_PROP)) {
3208 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3209 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3210 purple_blist_alias_buddy(p_buddy, property_value);
3213 server_alias = purple_buddy_get_server_alias(p_buddy);
3214 if (!is_empty(property_value) &&
3215 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3217 purple_blist_server_alias_buddy(p_buddy, property_value);
3220 /* for other properties */
3221 else {
3222 if (!is_empty(property_value)) {
3223 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3224 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3225 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3230 entry = entry->next;
3232 g_slist_free(buddies);
3236 * Update user phone
3237 * Suitable for both 2005 and 2007 systems.
3239 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3240 * @param phone_type
3241 * @param phone may be modified to strip white space
3242 * @param phone_display_string may be modified to strip white space
3244 static void
3245 sipe_update_user_phone(struct sipe_account_data *sip,
3246 const char *uri,
3247 const gchar *phone_type,
3248 gchar *phone,
3249 gchar *phone_display_string)
3251 const char *phone_node = PHONE_PROP; /* work phone by default */
3252 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3254 if(!phone || strlen(phone) == 0) return;
3256 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3257 phone_node = PHONE_MOBILE_PROP;
3258 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3259 } else if (sipe_strequal(phone_type, "home")) {
3260 phone_node = PHONE_HOME_PROP;
3261 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3262 } else if (sipe_strequal(phone_type, "other")) {
3263 phone_node = PHONE_OTHER_PROP;
3264 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3265 } else if (sipe_strequal(phone_type, "custom1")) {
3266 phone_node = PHONE_CUSTOM1_PROP;
3267 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3270 sipe_update_user_info(sip, uri, phone_node, phone);
3271 if (phone_display_string) {
3272 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3276 static void
3277 sipe_update_calendar(struct sipe_account_data *sip)
3279 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3281 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3283 if (sipe_strequal(calendar, "EXCH")) {
3284 sipe_ews_update_calendar(sip);
3287 /* schedule repeat */
3288 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3290 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3294 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3295 * by using standard Purple's means of signals and saved statuses.
3297 * Thus all UI elements get updated: Status Button with Note, docklet.
3298 * This is ablolutely important as both our status and note can come
3299 * inbound (roaming) or be updated programmatically (e.g. based on our
3300 * calendar data).
3302 static void
3303 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3304 const char *status_id,
3305 const char *message,
3306 time_t do_not_publish[])
3308 PurpleStatus *status = purple_account_get_active_status(account);
3309 gboolean changed = TRUE;
3311 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3312 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3314 changed = FALSE;
3317 if (purple_savedstatus_is_idleaway()) {
3318 changed = FALSE;
3321 if (changed) {
3322 PurpleSavedStatus *saved_status;
3323 const PurpleStatusType *acct_status_type =
3324 purple_status_type_find_with_id(account->status_types, status_id);
3325 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3326 sipe_activity activity = sipe_get_activity_by_token(status_id);
3328 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3329 if (saved_status) {
3330 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3333 /* If this type+message is unique then create a new transient saved status
3334 * Ref: gtkstatusbox.c
3336 if (!saved_status) {
3337 GList *tmp;
3338 GList *active_accts = purple_accounts_get_all_active();
3340 saved_status = purple_savedstatus_new(NULL, primitive);
3341 purple_savedstatus_set_message(saved_status, message);
3343 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3344 purple_savedstatus_set_substatus(saved_status,
3345 (PurpleAccount *)tmp->data, acct_status_type, message);
3347 g_list_free(active_accts);
3350 do_not_publish[activity] = time(NULL);
3351 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3352 status_id, (int)do_not_publish[activity]);
3354 /* Set the status for each account */
3355 purple_savedstatus_activate(saved_status);
3359 struct hash_table_delete_payload {
3360 GHashTable *hash_table;
3361 guint container;
3364 static void
3365 sipe_remove_category_container_publications_cb(const char *name,
3366 struct sipe_publication *publication,
3367 struct hash_table_delete_payload *payload)
3369 if (publication->container == payload->container) {
3370 g_hash_table_remove(payload->hash_table, name);
3373 static void
3374 sipe_remove_category_container_publications(GHashTable *our_publications,
3375 const char *category,
3376 guint container)
3378 struct hash_table_delete_payload payload;
3379 payload.hash_table = g_hash_table_lookup(our_publications, category);
3381 if (!payload.hash_table) return;
3383 payload.container = container;
3384 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3387 static void
3388 send_publish_category_initial(struct sipe_account_data *sip);
3391 * When we receive some self (BE) NOTIFY with a new subscriber
3392 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3395 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3397 gchar *contact;
3398 gchar *to;
3399 xmlnode *xml;
3400 xmlnode *node;
3401 xmlnode *node2;
3402 char *display_name = NULL;
3403 char *uri;
3404 GSList *category_names = NULL;
3405 int aggreg_avail = 0;
3406 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3407 gboolean do_update_status = FALSE;
3408 gboolean has_note_cleaned = FALSE;
3410 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3412 xml = xmlnode_from_str(msg->body, msg->bodylen);
3413 if (!xml) return;
3415 contact = get_contact(sip);
3416 to = sip_uri_self(sip);
3419 /* categories */
3420 /* set list of categories participating in this XML */
3421 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3422 const gchar *name = xmlnode_get_attrib(node, "name");
3423 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3425 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3426 category_names ? (int) g_slist_length(category_names) : -1);
3427 /* drop category information */
3428 if (category_names) {
3429 GSList *entry = category_names;
3430 while (entry) {
3431 GHashTable *cat_publications;
3432 const gchar *category = entry->data;
3433 entry = entry->next;
3434 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3435 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3436 if (cat_publications) {
3437 g_hash_table_remove(sip->our_publications, category);
3438 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3442 g_slist_free(category_names);
3443 /* filling our categories reflected in roaming data */
3444 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3445 const char *tmp;
3446 const gchar *name = xmlnode_get_attrib(node, "name");
3447 guint container = xmlnode_get_int_attrib(node, "container", -1);
3448 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3449 guint version = xmlnode_get_int_attrib(node, "version", 0);
3450 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3451 sipe_utils_str_to_time(tmp) : 0;
3452 gchar *key;
3453 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3455 /* Ex. clear note: <category name="note"/> */
3456 if (container == (guint)-1) {
3457 g_free(sip->note);
3458 sip->note = NULL;
3459 do_update_status = TRUE;
3460 continue;
3463 /* Ex. clear note: <category name="note" container="200"/> */
3464 if (instance == (guint)-1) {
3465 if (container == 200) {
3466 g_free(sip->note);
3467 sip->note = NULL;
3468 do_update_status = TRUE;
3470 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3471 sipe_remove_category_container_publications(
3472 sip->our_publications, name, container);
3473 continue;
3476 /* key is <category><instance><container> */
3477 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3478 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3480 /* capture all userState publication for later clean up if required */
3481 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3482 xmlnode *xn_state = xmlnode_get_child(node, "state");
3484 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3485 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3486 publication->category = g_strdup(name);
3487 publication->instance = instance;
3488 publication->container = container;
3489 publication->version = version;
3491 if (!sip->user_state_publications) {
3492 sip->user_state_publications = g_hash_table_new_full(
3493 g_str_hash, g_str_equal,
3494 g_free, (GDestroyNotify)free_publication);
3496 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3497 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3498 key, version);
3502 if (sipe_is_our_publication(sip, key)) {
3503 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3505 publication->category = g_strdup(name);
3506 publication->instance = instance;
3507 publication->container = container;
3508 publication->version = version;
3510 /* filling publication->availability */
3511 if (sipe_strequal(name, "state")) {
3512 xmlnode *xn_state = xmlnode_get_child(node, "state");
3513 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3515 if (xn_avail) {
3516 gchar *avail_str = xmlnode_get_data(xn_avail);
3517 if (avail_str) {
3518 publication->availability = atoi(avail_str);
3520 g_free(avail_str);
3522 /* for calendarState */
3523 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3524 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3525 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3527 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3528 if (xn_activity) {
3529 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3530 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3532 event->is_meeting = TRUE;
3535 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3536 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3538 publication->cal_event_hash = sipe_cal_event_hash(event);
3539 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3540 publication->cal_event_hash);
3541 sipe_cal_event_free(event);
3544 /* filling publication->note */
3545 if (sipe_strequal(name, "note")) {
3546 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3548 if (!has_note_cleaned) {
3549 has_note_cleaned = TRUE;
3551 g_free(sip->note);
3552 sip->note = NULL;
3553 sip->note_since = publish_time;
3555 do_update_status = TRUE;
3558 g_free(publication->note);
3559 publication->note = NULL;
3560 if (xn_body) {
3561 char *tmp;
3563 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3564 g_free(tmp);
3565 if (publish_time >= sip->note_since) {
3566 g_free(sip->note);
3567 sip->note = g_strdup(publication->note);
3568 sip->note_since = publish_time;
3569 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3571 do_update_status = TRUE;
3576 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3577 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3578 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3579 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3580 if (xn_free_busy) {
3581 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3582 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3584 if (xn_working_hours) {
3585 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3589 if (!cat_publications) {
3590 cat_publications = g_hash_table_new_full(
3591 g_str_hash, g_str_equal,
3592 g_free, (GDestroyNotify)free_publication);
3593 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3594 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3596 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3597 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3599 g_free(key);
3601 /* aggregateState (not an our publication) from 2-nd container */
3602 if (sipe_strequal(name, "state") && container == 2) {
3603 xmlnode *xn_state = xmlnode_get_child(node, "state");
3605 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3606 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3607 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3609 if (xn_avail) {
3610 gchar *avail_str = xmlnode_get_data(xn_avail);
3611 if (avail_str) {
3612 aggreg_avail = atoi(avail_str);
3614 g_free(avail_str);
3617 if (xn_activity) {
3618 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3620 aggreg_activity = sipe_get_activity_by_token(activity_token);
3623 do_update_status = TRUE;
3627 /* userProperties published by server from AD */
3628 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3629 xmlnode *line;
3630 /* line, for Remote Call Control (RCC) */
3631 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3632 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3633 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3634 gchar *line_uri;
3636 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3638 line_uri = xmlnode_get_data(line);
3639 if (line_uri) {
3640 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3641 sip_csta_open(sip, line_uri, line_server);
3643 g_free(line_uri);
3645 break;
3649 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3650 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3652 /* containers */
3653 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3654 guint id = xmlnode_get_int_attrib(node, "id", 0);
3655 struct sipe_container *container = sipe_find_container(sip, id);
3657 if (container) {
3658 sip->containers = g_slist_remove(sip->containers, container);
3659 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3660 free_container(container);
3662 container = g_new0(struct sipe_container, 1);
3663 container->id = id;
3664 container->version = xmlnode_get_int_attrib(node, "version", 0);
3665 sip->containers = g_slist_append(sip->containers, container);
3666 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3668 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3669 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3670 member->type = xmlnode_get_attrib(node2, "type");
3671 member->value = xmlnode_get_attrib(node2, "value");
3672 container->members = g_slist_append(container->members, member);
3673 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3674 member->type, member->value ? member->value : "");
3678 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3679 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3680 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3681 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3682 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3683 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3684 /* initial set-up to let counterparties see your status */
3685 if (sameEnterpriseAL < 0) {
3686 struct sipe_container *container = sipe_find_container(sip, 200);
3687 guint version = container ? container->version : 0;
3688 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3690 if (federatedAL < 0) {
3691 struct sipe_container *container = sipe_find_container(sip, 100);
3692 guint version = container ? container->version : 0;
3693 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3695 sip->access_level_set = TRUE;
3698 /* subscribers */
3699 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3700 const char *user;
3701 const char *acknowledged;
3702 gchar *hdr;
3703 gchar *body;
3705 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3706 if (!user) continue;
3707 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3708 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3709 uri = sip_uri_from_name(user);
3711 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3713 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3714 if(!g_ascii_strcasecmp(acknowledged,"false")){
3715 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3716 if (!purple_find_buddy(sip->account, uri)) {
3717 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3720 hdr = g_strdup_printf(
3721 "Contact: %s\r\n"
3722 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3724 body = g_strdup_printf(
3725 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3726 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3727 "</setSubscribers>", user);
3729 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3730 g_free(body);
3731 g_free(hdr);
3733 g_free(display_name);
3734 g_free(uri);
3737 g_free(contact);
3738 xmlnode_free(xml);
3740 /* Publish initial state if not yet.
3741 * Assuming this happens on initial responce to subscription to roaming-self
3742 * so we've already updated our roaming data in full.
3743 * Only for 2007+
3745 if (!sip->initial_state_published) {
3746 send_publish_category_initial(sip);
3747 sip->initial_state_published = TRUE;
3748 /* dalayed run */
3749 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3750 do_update_status = FALSE;
3751 } else if (aggreg_avail) {
3753 g_free(sip->status);
3754 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3755 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3756 } else {
3757 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3761 if (do_update_status) {
3762 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3763 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3766 g_free(to);
3769 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3771 gchar *to = sip_uri_self(sip);
3772 gchar *tmp = get_contact(sip);
3773 gchar *hdr = g_strdup_printf(
3774 "Event: vnd-microsoft-roaming-ACL\r\n"
3775 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3776 "Supported: com.microsoft.autoextend\r\n"
3777 "Supported: ms-benotify\r\n"
3778 "Proxy-Require: ms-benotify\r\n"
3779 "Supported: ms-piggyback-first-notify\r\n"
3780 "Contact: %s\r\n", tmp);
3781 g_free(tmp);
3783 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3784 g_free(to);
3785 g_free(hdr);
3789 * To request for presence information about the user, access level settings that have already been configured by the user
3790 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3791 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3794 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3796 gchar *to = sip_uri_self(sip);
3797 gchar *tmp = get_contact(sip);
3798 gchar *hdr = g_strdup_printf(
3799 "Event: vnd-microsoft-roaming-self\r\n"
3800 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3801 "Supported: ms-benotify\r\n"
3802 "Proxy-Require: ms-benotify\r\n"
3803 "Supported: ms-piggyback-first-notify\r\n"
3804 "Contact: %s\r\n"
3805 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3807 gchar *body=g_strdup(
3808 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3809 "<roaming type=\"categories\"/>"
3810 "<roaming type=\"containers\"/>"
3811 "<roaming type=\"subscribers\"/></roamingList>");
3813 g_free(tmp);
3814 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3815 g_free(body);
3816 g_free(to);
3817 g_free(hdr);
3821 * For 2005 version
3823 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3825 gchar *to = sip_uri_self(sip);
3826 gchar *tmp = get_contact(sip);
3827 gchar *hdr = g_strdup_printf(
3828 "Event: vnd-microsoft-provisioning\r\n"
3829 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3830 "Supported: com.microsoft.autoextend\r\n"
3831 "Supported: ms-benotify\r\n"
3832 "Proxy-Require: ms-benotify\r\n"
3833 "Supported: ms-piggyback-first-notify\r\n"
3834 "Expires: 0\r\n"
3835 "Contact: %s\r\n", tmp);
3837 g_free(tmp);
3838 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3839 g_free(to);
3840 g_free(hdr);
3843 /** Subscription for provisioning information to help with initial
3844 * configuration. This subscription is a one-time query (denoted by the Expires header,
3845 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3846 * configuration, meeting policies, and policy settings that Communicator must enforce.
3847 * TODO: for what we need this information.
3850 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3852 gchar *to = sip_uri_self(sip);
3853 gchar *tmp = get_contact(sip);
3854 gchar *hdr = g_strdup_printf(
3855 "Event: vnd-microsoft-provisioning-v2\r\n"
3856 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3857 "Supported: com.microsoft.autoextend\r\n"
3858 "Supported: ms-benotify\r\n"
3859 "Proxy-Require: ms-benotify\r\n"
3860 "Supported: ms-piggyback-first-notify\r\n"
3861 "Expires: 0\r\n"
3862 "Contact: %s\r\n"
3863 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3864 gchar *body = g_strdup(
3865 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3866 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3867 "<provisioningGroup name=\"ucPolicy\"/>"
3868 "</provisioningGroupList>");
3870 g_free(tmp);
3871 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3872 g_free(body);
3873 g_free(to);
3874 g_free(hdr);
3877 static void
3878 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3879 gpointer value, gpointer user_data)
3881 struct sip_subscription *subscription = value;
3882 struct sip_dialog *dialog = &subscription->dialog;
3883 struct sipe_account_data *sip = user_data;
3884 gchar *tmp = get_contact(sip);
3885 gchar *hdr = g_strdup_printf(
3886 "Event: %s\r\n"
3887 "Expires: 0\r\n"
3888 "Contact: %s\r\n", subscription->event, tmp);
3889 g_free(tmp);
3891 /* Rate limit to max. 25 requests per seconds */
3892 g_usleep(1000000 / 25);
3894 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3895 g_free(hdr);
3898 /* IM Session (INVITE and MESSAGE methods) */
3900 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3901 static gchar *
3902 get_end_points (struct sipe_account_data *sip,
3903 struct sip_session *session)
3905 gchar *res;
3907 if (session == NULL) {
3908 return NULL;
3911 res = g_strdup_printf("<sip:%s>", sip->username);
3913 SIPE_DIALOG_FOREACH {
3914 gchar *tmp = res;
3915 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3916 g_free(tmp);
3918 if (dialog->theirepid) {
3919 tmp = res;
3920 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3921 g_free(tmp);
3923 } SIPE_DIALOG_FOREACH_END;
3925 return res;
3928 static gboolean
3929 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3930 struct sipmsg *msg,
3931 SIPE_UNUSED_PARAMETER struct transaction *trans)
3933 gboolean ret = TRUE;
3935 if (msg->response != 200) {
3936 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3937 return FALSE;
3940 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3942 return ret;
3946 * Asks UA/proxy about its capabilities.
3948 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3950 gchar *to = sip_uri(who);
3951 gchar *contact = get_contact(sip);
3952 gchar *request = g_strdup_printf(
3953 "Accept: application/sdp\r\n"
3954 "Contact: %s\r\n", contact);
3955 g_free(contact);
3957 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3959 g_free(to);
3960 g_free(request);
3963 static void
3964 sipe_notify_user(struct sipe_account_data *sip,
3965 struct sip_session *session,
3966 PurpleMessageFlags flags,
3967 const gchar *message)
3969 PurpleConversation *conv;
3971 if (!session->conv) {
3972 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3973 } else {
3974 conv = session->conv;
3976 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3979 void
3980 sipe_present_info(struct sipe_account_data *sip,
3981 struct sip_session *session,
3982 const gchar *message)
3984 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3987 static void
3988 sipe_present_err(struct sipe_account_data *sip,
3989 struct sip_session *session,
3990 const gchar *message)
3992 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3995 void
3996 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3997 struct sip_session *session,
3998 int sip_error,
3999 int sip_warning,
4000 const gchar *who,
4001 const gchar *message)
4003 char *msg, *msg_tmp, *msg_tmp2;
4004 const char *label;
4006 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
4007 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4008 g_free(msg_tmp);
4009 /* Service unavailable; Server Internal Error; Server Time-out */
4010 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4011 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4012 g_free(msg);
4013 msg = NULL;
4014 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4015 label = _("This message was not delivered to %s because the service is not available");
4016 } else if (sip_error == 486) { /* Busy Here */
4017 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4018 } else {
4019 label = _("This message was not delivered to %s because one or more recipients are offline");
4022 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4023 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4024 msg ? ":" : "",
4025 msg ? msg : "");
4026 sipe_present_err(sip, session, msg_tmp);
4027 g_free(msg_tmp2);
4028 g_free(msg_tmp);
4029 g_free(msg);
4033 static gboolean
4034 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4035 SIPE_UNUSED_PARAMETER struct transaction *trans)
4037 gboolean ret = TRUE;
4038 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4039 struct sip_session *session = sipe_session_find_im(sip, with);
4040 struct sip_dialog *dialog;
4041 gchar *cseq;
4042 char *key;
4043 struct queued_message *message;
4045 if (!session) {
4046 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
4047 g_free(with);
4048 return FALSE;
4051 dialog = sipe_dialog_find(session, with);
4052 if (!dialog) {
4053 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
4054 g_free(with);
4055 return FALSE;
4058 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4059 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4060 g_free(cseq);
4061 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4063 if (msg->response >= 400) {
4064 PurpleBuddy *pbuddy;
4065 const char *alias = with;
4066 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4067 int warning = -1;
4069 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
4071 if (warn_hdr) {
4072 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4073 if (parts[0]) {
4074 warning = atoi(parts[0]);
4076 g_strfreev(parts);
4079 /* cancel file transfer as rejected by server */
4080 if (msg->response == 606 && /* Not acceptable all. */
4081 warning == 309 && /* Message contents not allowed by policy */
4082 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4084 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4085 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4086 sipe_utils_nameval_free(parsed_body);
4089 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4090 alias = purple_buddy_get_alias(pbuddy);
4093 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4094 ret = FALSE;
4095 } else {
4096 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4097 if (message_id) {
4098 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4099 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4100 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4103 g_hash_table_remove(session->unconfirmed_messages, key);
4104 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4105 key, g_hash_table_size(session->unconfirmed_messages));
4108 g_free(key);
4109 g_free(with);
4111 if (ret) sipe_im_process_queue(sip, session);
4112 return ret;
4115 static gboolean
4116 sipe_is_election_finished(struct sip_session *session);
4118 static void
4119 sipe_election_result(struct sipe_account_data *sip,
4120 void *sess);
4122 static gboolean
4123 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4124 SIPE_UNUSED_PARAMETER struct transaction *trans)
4126 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4127 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4128 struct sip_dialog *dialog;
4129 struct sip_session *session;
4131 session = sipe_session_find_chat_by_callid(sip, callid);
4132 if (!session) {
4133 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4134 return FALSE;
4137 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4138 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4139 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4140 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4142 if (xn_request_rm_response) {
4143 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4144 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4146 dialog = sipe_dialog_find(session, with);
4147 if (!dialog) {
4148 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4149 xmlnode_free(xn_action);
4150 return FALSE;
4153 if (allow && !g_strcasecmp(allow, "true")) {
4154 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4155 dialog->election_vote = 1;
4156 } else if (allow && !g_strcasecmp(allow, "false")) {
4157 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4158 dialog->election_vote = -1;
4161 if (sipe_is_election_finished(session)) {
4162 sipe_election_result(sip, session);
4165 } else if (xn_set_rm_response) {
4168 xmlnode_free(xn_action);
4172 return TRUE;
4175 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4177 gchar *hdr;
4178 gchar *tmp;
4179 char *msgtext = NULL;
4180 const gchar *msgr = "";
4181 gchar *tmp2 = NULL;
4183 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4184 char *msgformat;
4185 gchar *msgr_value;
4187 sipe_parse_html(msg, &msgformat, &msgtext);
4188 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4190 msgr_value = sipmsg_get_msgr_string(msgformat);
4191 g_free(msgformat);
4192 if (msgr_value) {
4193 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4194 g_free(msgr_value);
4196 } else {
4197 msgtext = g_strdup(msg);
4200 tmp = get_contact(sip);
4201 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4202 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4203 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4204 if (content_type == NULL)
4205 content_type = "text/plain";
4207 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4208 g_free(tmp);
4209 g_free(tmp2);
4211 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4212 g_free(msgtext);
4213 g_free(hdr);
4217 void
4218 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4220 GSList *entry2 = session->outgoing_message_queue;
4221 while (entry2) {
4222 struct queued_message *msg = entry2->data;
4224 /* for multiparty chat or conference */
4225 if (session->is_multiparty || session->focus_uri) {
4226 gchar *who = sip_uri_self(sip);
4227 serv_got_chat_in(sip->gc, session->chat_id, who,
4228 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4229 g_free(who);
4232 SIPE_DIALOG_FOREACH {
4233 char *key;
4234 struct queued_message *message;
4236 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4238 message = g_new0(struct queued_message,1);
4239 message->body = g_strdup(msg->body);
4240 if (msg->content_type != NULL)
4241 message->content_type = g_strdup(msg->content_type);
4243 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4244 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4245 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4246 key, g_hash_table_size(session->unconfirmed_messages));
4247 g_free(key);
4249 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4250 } SIPE_DIALOG_FOREACH_END;
4252 entry2 = sipe_session_dequeue_message(session);
4256 static void
4257 sipe_refer_notify(struct sipe_account_data *sip,
4258 struct sip_session *session,
4259 const gchar *who,
4260 int status,
4261 const gchar *desc)
4263 gchar *hdr;
4264 gchar *body;
4265 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4267 hdr = g_strdup_printf(
4268 "Event: refer\r\n"
4269 "Subscription-State: %s\r\n"
4270 "Content-Type: message/sipfrag\r\n",
4271 status >= 200 ? "terminated" : "active");
4273 body = g_strdup_printf(
4274 "SIP/2.0 %d %s\r\n",
4275 status, desc);
4277 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4279 g_free(hdr);
4280 g_free(body);
4283 static gboolean
4284 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4286 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4287 struct sip_session *session;
4288 struct sip_dialog *dialog;
4289 char *cseq;
4290 char *key;
4291 struct queued_message *message;
4292 struct sipmsg *request_msg = trans->msg;
4294 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4295 gchar *referred_by;
4297 session = sipe_session_find_chat_by_callid(sip, callid);
4298 if (!session) {
4299 session = sipe_session_find_im(sip, with);
4301 if (!session) {
4302 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4303 g_free(with);
4304 return FALSE;
4307 dialog = sipe_dialog_find(session, with);
4308 if (!dialog) {
4309 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4310 g_free(with);
4311 return FALSE;
4314 sipe_dialog_parse(dialog, msg, TRUE);
4316 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4317 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4318 g_free(cseq);
4319 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4321 if (msg->response != 200) {
4322 PurpleBuddy *pbuddy;
4323 const char *alias = with;
4324 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4325 int warning = -1;
4327 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4329 if (warn_hdr) {
4330 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4331 if (parts[0]) {
4332 warning = atoi(parts[0]);
4334 g_strfreev(parts);
4338 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4339 alias = purple_buddy_get_alias(pbuddy);
4342 if (message) {
4343 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4344 } else {
4345 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4346 sipe_present_err(sip, session, tmp_msg);
4347 g_free(tmp_msg);
4350 sipe_dialog_remove(session, with);
4352 g_free(key);
4353 g_free(with);
4354 return FALSE;
4357 dialog->cseq = 0;
4358 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4359 dialog->outgoing_invite = NULL;
4360 dialog->is_established = TRUE;
4362 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4363 if (referred_by) {
4364 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4365 g_free(referred_by);
4368 /* add user to chat if it is a multiparty session */
4369 if (session->is_multiparty) {
4370 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4371 with, NULL,
4372 PURPLE_CBFLAGS_NONE, TRUE);
4375 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4376 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4377 sipe_session_dequeue_message(session);
4380 sipe_im_process_queue(sip, session);
4382 g_hash_table_remove(session->unconfirmed_messages, key);
4383 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4384 key, g_hash_table_size(session->unconfirmed_messages));
4386 g_free(key);
4387 g_free(with);
4388 return TRUE;
4392 void
4393 sipe_invite(struct sipe_account_data *sip,
4394 struct sip_session *session,
4395 const gchar *who,
4396 const gchar *msg_body,
4397 const gchar *msg_content_type,
4398 const gchar *referred_by,
4399 const gboolean is_triggered)
4401 gchar *hdr;
4402 gchar *to;
4403 gchar *contact;
4404 gchar *body;
4405 gchar *self;
4406 char *ms_text_format = NULL;
4407 gchar *roster_manager;
4408 gchar *end_points;
4409 gchar *referred_by_str;
4410 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4412 if (dialog && dialog->is_established) {
4413 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4414 return;
4417 if (!dialog) {
4418 dialog = sipe_dialog_add(session);
4419 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4420 dialog->with = g_strdup(who);
4423 if (!(dialog->ourtag)) {
4424 dialog->ourtag = gentag();
4427 to = sip_uri(who);
4429 if (msg_body) {
4430 char *msgtext = NULL;
4431 char *base64_msg;
4432 const gchar *msgr = "";
4433 char *key;
4434 struct queued_message *message;
4435 gchar *tmp = NULL;
4437 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4438 char *msgformat;
4439 gchar *msgr_value;
4441 sipe_parse_html(msg_body, &msgformat, &msgtext);
4442 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4444 msgr_value = sipmsg_get_msgr_string(msgformat);
4445 g_free(msgformat);
4446 if (msgr_value) {
4447 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4448 g_free(msgr_value);
4450 } else {
4451 msgtext = g_strdup(msg_body);
4454 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4455 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4456 msg_content_type ? msg_content_type : "text/plain",
4457 msgr,
4458 base64_msg);
4459 g_free(msgtext);
4460 g_free(tmp);
4461 g_free(base64_msg);
4463 message = g_new0(struct queued_message,1);
4464 message->body = g_strdup(msg_body);
4465 if (msg_content_type != NULL)
4466 message->content_type = g_strdup(msg_content_type);
4468 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4469 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4470 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4471 key, g_hash_table_size(session->unconfirmed_messages));
4472 g_free(key);
4475 contact = get_contact(sip);
4476 end_points = get_end_points(sip, session);
4477 self = sip_uri_self(sip);
4478 roster_manager = g_strdup_printf(
4479 "Roster-Manager: %s\r\n"
4480 "EndPoints: %s\r\n",
4481 self,
4482 end_points);
4483 referred_by_str = referred_by ?
4484 g_strdup_printf(
4485 "Referred-By: %s\r\n",
4486 referred_by)
4487 : g_strdup("");
4488 hdr = g_strdup_printf(
4489 "Supported: ms-sender\r\n"
4490 "%s"
4491 "%s"
4492 "%s"
4493 "%s"
4494 "Contact: %s\r\n%s"
4495 "Content-Type: application/sdp\r\n",
4496 sipe_strequal(session->roster_manager, self) ? roster_manager : "",
4497 referred_by_str,
4498 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4499 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4500 contact,
4501 ms_text_format ? ms_text_format : "");
4502 g_free(ms_text_format);
4503 g_free(self);
4505 body = g_strdup_printf(
4506 "v=0\r\n"
4507 "o=- 0 0 IN IP4 %s\r\n"
4508 "s=session\r\n"
4509 "c=IN IP4 %s\r\n"
4510 "t=0 0\r\n"
4511 "m=%s %d sip null\r\n"
4512 "a=accept-types:text/plain text/html image/gif "
4513 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4514 purple_network_get_my_ip(-1),
4515 purple_network_get_my_ip(-1),
4516 sip->ocs2007 ? "message" : "x-ms-message",
4517 sip->realport);
4519 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4520 to, to, hdr, body, dialog, process_invite_response);
4522 g_free(to);
4523 g_free(roster_manager);
4524 g_free(end_points);
4525 g_free(referred_by_str);
4526 g_free(body);
4527 g_free(hdr);
4528 g_free(contact);
4531 static void
4532 sipe_refer(struct sipe_account_data *sip,
4533 struct sip_session *session,
4534 const gchar *who)
4536 gchar *hdr;
4537 gchar *contact;
4538 gchar *epid = get_epid(sip);
4539 struct sip_dialog *dialog = sipe_dialog_find(session,
4540 session->roster_manager);
4541 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4543 contact = get_contact(sip);
4544 hdr = g_strdup_printf(
4545 "Contact: %s\r\n"
4546 "Refer-to: <%s>\r\n"
4547 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4548 "Require: com.microsoft.rtc-multiparty\r\n",
4549 contact,
4550 who,
4551 sip->username,
4552 ourtag ? ";tag=" : "",
4553 ourtag ? ourtag : "",
4554 epid);
4555 g_free(epid);
4557 send_sip_request(sip->gc, "REFER",
4558 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4560 g_free(hdr);
4561 g_free(contact);
4564 static void
4565 sipe_send_election_request_rm(struct sipe_account_data *sip,
4566 struct sip_dialog *dialog,
4567 int bid)
4569 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4571 gchar *body = g_strdup_printf(
4572 "<?xml version=\"1.0\"?>\r\n"
4573 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4574 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4575 sip->username, bid);
4577 send_sip_request(sip->gc, "INFO",
4578 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4580 g_free(body);
4583 static void
4584 sipe_send_election_set_rm(struct sipe_account_data *sip,
4585 struct sip_dialog *dialog)
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 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4593 sip->username);
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_session_close(struct sipe_account_data *sip,
4603 struct sip_session * session)
4605 if (session && session->focus_uri) {
4606 sipe_conf_immcu_closed(sip, session);
4607 conf_session_close(sip, session);
4610 if (session) {
4611 SIPE_DIALOG_FOREACH {
4612 /* @TODO slow down BYE message sending rate */
4613 /* @see single subscription code */
4614 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4615 } SIPE_DIALOG_FOREACH_END;
4617 sipe_session_remove(sip, session);
4621 static void
4622 sipe_session_close_all(struct sipe_account_data *sip)
4624 GSList *entry;
4625 while ((entry = sip->sessions) != NULL) {
4626 sipe_session_close(sip, entry->data);
4630 static void
4631 sipe_convo_closed(PurpleConnection * gc, const char *who)
4633 struct sipe_account_data *sip = gc->proto_data;
4635 purple_debug_info("sipe", "conversation with %s closed\n", who);
4636 sipe_session_close(sip, sipe_session_find_im(sip, who));
4639 static void
4640 sipe_chat_leave (PurpleConnection *gc, int id)
4642 struct sipe_account_data *sip = gc->proto_data;
4643 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4645 sipe_session_close(sip, session);
4648 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4649 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4651 struct sipe_account_data *sip = gc->proto_data;
4652 struct sip_session *session;
4653 struct sip_dialog *dialog;
4654 gchar *uri = sip_uri(who);
4656 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4658 session = sipe_session_find_or_add_im(sip, uri);
4659 dialog = sipe_dialog_find(session, uri);
4661 // Queue the message
4662 sipe_session_enqueue_message(session, what, NULL);
4664 if (dialog && !dialog->outgoing_invite) {
4665 sipe_im_process_queue(sip, session);
4666 } else if (!dialog || !dialog->outgoing_invite) {
4667 // Need to send the INVITE to get the outgoing dialog setup
4668 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4671 g_free(uri);
4672 return 1;
4675 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4676 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4678 struct sipe_account_data *sip = gc->proto_data;
4679 struct sip_session *session;
4681 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4683 session = sipe_session_find_chat_by_id(sip, id);
4685 // Queue the message
4686 if (session && session->dialogs) {
4687 sipe_session_enqueue_message(session,what,NULL);
4688 sipe_im_process_queue(sip, session);
4689 } else if (sip) {
4690 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4691 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4693 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4694 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4696 if (sip->ocs2007) {
4697 struct sip_session *session = sipe_session_add_chat(sip);
4699 session->is_multiparty = FALSE;
4700 session->focus_uri = g_strdup(proto_chat_id);
4701 sipe_session_enqueue_message(session, what, NULL);
4702 sipe_invite_conf_focus(sip, session);
4706 return 1;
4709 /* End IM Session (INVITE and MESSAGE methods) */
4711 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4713 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4714 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4715 gchar *from;
4716 struct sip_session *session;
4718 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4720 /* Call Control protocol */
4721 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4723 process_incoming_info_csta(sip, msg);
4724 return;
4727 from = parse_from(sipmsg_find_header(msg, "From"));
4728 session = sipe_session_find_chat_by_callid(sip, callid);
4729 if (!session) {
4730 session = sipe_session_find_im(sip, from);
4732 if (!session) {
4733 g_free(from);
4734 return;
4737 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4739 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4740 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4741 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4743 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4745 if (xn_request_rm) {
4746 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4747 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4748 gchar *body = g_strdup_printf(
4749 "<?xml version=\"1.0\"?>\r\n"
4750 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4751 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4752 sip->username,
4753 session->bid < bid ? "true" : "false");
4754 send_sip_response(sip->gc, msg, 200, "OK", body);
4755 g_free(body);
4756 } else if (xn_set_rm) {
4757 gchar *body;
4758 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4759 g_free(session->roster_manager);
4760 session->roster_manager = g_strdup(rm);
4762 body = g_strdup_printf(
4763 "<?xml version=\"1.0\"?>\r\n"
4764 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4765 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4766 sip->username);
4767 send_sip_response(sip->gc, msg, 200, "OK", body);
4768 g_free(body);
4770 xmlnode_free(xn_action);
4773 else
4775 /* looks like purple lacks typing notification for chat */
4776 if (!session->is_multiparty && !session->focus_uri) {
4777 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4778 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4779 "status");
4780 if (sipe_strequal(status, "type")) {
4781 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4782 } else if (sipe_strequal(status, "idle")) {
4783 serv_got_typing_stopped(sip->gc, from);
4785 xmlnode_free(xn_keyboard_activity);
4788 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4790 g_free(from);
4793 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4795 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4796 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4797 struct sip_session *session;
4798 struct sip_dialog *dialog;
4800 /* collect dialog identification
4801 * we need callid, ourtag and theirtag to unambiguously identify dialog
4803 /* take data before 'msg' will be modified by send_sip_response */
4804 dialog = g_new0(struct sip_dialog, 1);
4805 dialog->callid = g_strdup(callid);
4806 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4807 dialog->with = g_strdup(from);
4808 sipe_dialog_parse(dialog, msg, FALSE);
4810 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4812 session = sipe_session_find_chat_by_callid(sip, callid);
4813 if (!session) {
4814 session = sipe_session_find_im(sip, from);
4816 if (!session) {
4817 sipe_dialog_free(dialog);
4818 g_free(from);
4819 return;
4822 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4823 g_free(session->roster_manager);
4824 session->roster_manager = NULL;
4827 /* This what BYE is essentially for - terminating dialog */
4828 sipe_dialog_remove_3(session, dialog);
4829 sipe_dialog_free(dialog);
4830 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4831 sipe_conf_immcu_closed(sip, session);
4832 } else if (session->is_multiparty) {
4833 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4836 g_free(from);
4839 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4841 gchar *self = sip_uri_self(sip);
4842 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4843 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4844 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4845 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4846 struct sip_session *session;
4847 struct sip_dialog *dialog;
4849 session = sipe_session_find_chat_by_callid(sip, callid);
4850 dialog = sipe_dialog_find(session, from);
4852 if (!session || !dialog || !session->roster_manager || !sipe_strequal(session->roster_manager, self)) {
4853 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4854 } else {
4855 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4857 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4860 g_free(self);
4861 g_free(from);
4862 g_free(refer_to);
4863 g_free(referred_by);
4866 static unsigned int
4867 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4869 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4870 struct sip_session *session;
4871 struct sip_dialog *dialog;
4873 if (state == PURPLE_NOT_TYPING)
4874 return 0;
4876 session = sipe_session_find_im(sip, who);
4877 dialog = sipe_dialog_find(session, who);
4879 if (session && dialog && dialog->is_established) {
4880 send_sip_request(gc, "INFO", who, who,
4881 "Content-Type: application/xml\r\n",
4882 SIPE_SEND_TYPING, dialog, NULL);
4884 return SIPE_TYPING_SEND_TIMEOUT;
4887 static gboolean resend_timeout(struct sipe_account_data *sip)
4889 GSList *tmp = sip->transactions;
4890 time_t currtime = time(NULL);
4891 while (tmp) {
4892 struct transaction *trans = tmp->data;
4893 tmp = tmp->next;
4894 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4895 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4896 /* TODO 408 */
4897 } else {
4898 if ((currtime - trans->time > 2) && trans->retries == 0) {
4899 trans->retries++;
4900 sendout_sipmsg(sip, trans->msg);
4904 return TRUE;
4907 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4908 SIPE_UNUSED_PARAMETER void *unused)
4910 /* register again when security token expires */
4911 /* we have to start a new authentication as the security token
4912 * is almost expired by sending a not signed REGISTER message */
4913 purple_debug_info("sipe", "do a full reauthentication\n");
4914 sipe_auth_free(&sip->registrar);
4915 sipe_auth_free(&sip->proxy);
4916 sip->registerstatus = 0;
4917 do_register(sip);
4918 sip->reauthenticate_set = FALSE;
4921 static gboolean
4922 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4923 struct sipmsg *msg,
4924 GSList *parsed_body)
4926 gboolean found = FALSE;
4928 if (parsed_body) {
4929 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4931 if (sipe_strequal(invitation_command, "INVITE")) {
4932 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4933 found = TRUE;
4934 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4935 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4936 found = TRUE;
4937 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4938 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4939 found = TRUE;
4942 return found;
4945 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4947 gchar *from;
4948 const gchar *contenttype;
4949 gboolean found = FALSE;
4951 from = parse_from(sipmsg_find_header(msg, "From"));
4953 if (!from) return;
4955 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4957 contenttype = sipmsg_find_header(msg, "Content-Type");
4958 if (g_str_has_prefix(contenttype, "text/plain")
4959 || g_str_has_prefix(contenttype, "text/html")
4960 || g_str_has_prefix(contenttype, "multipart/related")
4961 || g_str_has_prefix(contenttype, "multipart/alternative"))
4963 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4964 gchar *html = get_html_message(contenttype, msg->body);
4966 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4967 if (!session) {
4968 session = sipe_session_find_im(sip, from);
4971 if (session && session->focus_uri) { /* a conference */
4972 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4973 gchar *sender = parse_from(tmp);
4974 g_free(tmp);
4975 serv_got_chat_in(sip->gc, session->chat_id, sender,
4976 PURPLE_MESSAGE_RECV, html, time(NULL));
4977 g_free(sender);
4978 } else if (session && session->is_multiparty) { /* a multiparty chat */
4979 serv_got_chat_in(sip->gc, session->chat_id, from,
4980 PURPLE_MESSAGE_RECV, html, time(NULL));
4981 } else {
4982 serv_got_im(sip->gc, from, html, 0, time(NULL));
4984 g_free(html);
4985 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4986 found = TRUE;
4988 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
4989 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4990 xmlnode *state;
4991 gchar *statedata;
4993 if (!isc) {
4994 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4995 g_free(from);
4996 return;
4999 state = xmlnode_get_child(isc, "state");
5001 if (!state) {
5002 purple_debug_info("sipe", "process_incoming_message: no state found\n");
5003 xmlnode_free(isc);
5004 g_free(from);
5005 return;
5008 statedata = xmlnode_get_data(state);
5009 if (statedata) {
5010 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5011 else serv_got_typing_stopped(sip->gc, from);
5013 g_free(statedata);
5015 xmlnode_free(isc);
5016 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5017 found = TRUE;
5018 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5019 GSList *body = sipe_ft_parse_msg_body(msg->body);
5020 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5021 sipe_utils_nameval_free(body);
5022 if (found) {
5023 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5026 if (!found) {
5027 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5028 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5029 if (!session) {
5030 session = sipe_session_find_im(sip, from);
5032 if (session) {
5033 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5034 from);
5035 sipe_present_err(sip, session, errmsg);
5036 g_free(errmsg);
5039 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
5040 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5042 g_free(from);
5045 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5047 gchar *body;
5048 gchar *newTag;
5049 const gchar *oldHeader;
5050 gchar *newHeader;
5051 gboolean is_multiparty = FALSE;
5052 gboolean is_triggered = FALSE;
5053 gboolean was_multiparty = TRUE;
5054 gboolean just_joined = FALSE;
5055 gchar *from;
5056 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5057 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5058 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5059 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5060 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5061 GSList *end_points = NULL;
5062 char *tmp = NULL;
5063 struct sip_session *session;
5064 const gchar *ms_text_format;
5066 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
5067 g_free(tmp);
5069 /* Invitation to join conference */
5070 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5071 process_incoming_invite_conf(sip, msg);
5072 return;
5075 /* Only accept text invitations */
5076 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5077 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5078 return;
5081 // TODO There *must* be a better way to clean up the To header to add a tag...
5082 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
5083 oldHeader = sipmsg_find_header(msg, "To");
5084 newTag = gentag();
5085 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5086 sipmsg_remove_header_now(msg, "To");
5087 sipmsg_add_header_now(msg, "To", newHeader);
5088 g_free(newHeader);
5090 if (end_points_hdr) {
5091 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5093 if (g_slist_length(end_points) > 2) {
5094 is_multiparty = TRUE;
5097 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5098 is_triggered = TRUE;
5099 is_multiparty = TRUE;
5102 session = sipe_session_find_chat_by_callid(sip, callid);
5103 /* Convert to multiparty */
5104 if (session && is_multiparty && !session->is_multiparty) {
5105 g_free(session->with);
5106 session->with = NULL;
5107 was_multiparty = FALSE;
5108 session->is_multiparty = TRUE;
5109 session->chat_id = rand();
5112 if (!session && is_multiparty) {
5113 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5115 /* IM session */
5116 from = parse_from(sipmsg_find_header(msg, "From"));
5117 if (!session) {
5118 session = sipe_session_find_or_add_im(sip, from);
5121 if (session) {
5122 g_free(session->callid);
5123 session->callid = g_strdup(callid);
5125 session->is_multiparty = is_multiparty;
5126 if (roster_manager) {
5127 session->roster_manager = g_strdup(roster_manager);
5131 if (is_multiparty && end_points) {
5132 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5133 GSList *entry = end_points;
5134 while (entry) {
5135 struct sip_dialog *dialog;
5136 struct sipendpoint *end_point = entry->data;
5137 entry = entry->next;
5139 if (!g_strcasecmp(from, end_point->contact) ||
5140 !g_strcasecmp(to, end_point->contact))
5141 continue;
5143 dialog = sipe_dialog_find(session, end_point->contact);
5144 if (dialog) {
5145 g_free(dialog->theirepid);
5146 dialog->theirepid = end_point->epid;
5147 end_point->epid = NULL;
5148 } else {
5149 dialog = sipe_dialog_add(session);
5151 dialog->callid = g_strdup(session->callid);
5152 dialog->with = end_point->contact;
5153 end_point->contact = NULL;
5154 dialog->theirepid = end_point->epid;
5155 end_point->epid = NULL;
5157 just_joined = TRUE;
5159 /* send triggered INVITE */
5160 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5163 g_free(to);
5166 if (end_points) {
5167 GSList *entry = end_points;
5168 while (entry) {
5169 struct sipendpoint *end_point = entry->data;
5170 entry = entry->next;
5171 g_free(end_point->contact);
5172 g_free(end_point->epid);
5173 g_free(end_point);
5175 g_slist_free(end_points);
5178 if (session) {
5179 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5180 if (dialog) {
5181 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5182 sipe_dialog_parse_routes(dialog, msg, FALSE);
5183 } else {
5184 dialog = sipe_dialog_add(session);
5186 dialog->callid = g_strdup(session->callid);
5187 dialog->with = g_strdup(from);
5188 sipe_dialog_parse(dialog, msg, FALSE);
5190 if (!dialog->ourtag) {
5191 dialog->ourtag = newTag;
5192 newTag = NULL;
5195 just_joined = TRUE;
5197 } else {
5198 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5200 g_free(newTag);
5202 if (is_multiparty && !session->conv) {
5203 gchar *chat_title = sipe_chat_get_name(callid);
5204 gchar *self = sip_uri_self(sip);
5205 /* create prpl chat */
5206 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5207 session->chat_title = g_strdup(chat_title);
5208 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5209 /* add self */
5210 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5211 self, NULL,
5212 PURPLE_CBFLAGS_NONE, FALSE);
5213 g_free(chat_title);
5214 g_free(self);
5217 if (is_multiparty && !was_multiparty) {
5218 /* add current IM counterparty to chat */
5219 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5220 sipe_dialog_first(session)->with, NULL,
5221 PURPLE_CBFLAGS_NONE, FALSE);
5224 /* add inviting party to chat */
5225 if (just_joined && session->conv) {
5226 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5227 from, NULL,
5228 PURPLE_CBFLAGS_NONE, TRUE);
5231 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5233 /* This used only in 2005 official client, not 2007 or Reuters.
5234 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5235 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5237 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5238 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5239 if (is_multiparty ||
5240 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5242 if (ms_text_format) {
5243 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5245 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5246 if (tmp) {
5247 gchar *body = (gchar *) purple_base64_decode(tmp, NULL);
5249 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5251 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5252 sipe_utils_nameval_free(parsed_body);
5253 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5255 g_free(tmp);
5257 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5259 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5260 gchar *html = get_html_message(ms_text_format, NULL);
5261 if (html) {
5262 if (is_multiparty) {
5263 serv_got_chat_in(sip->gc, session->chat_id, from,
5264 PURPLE_MESSAGE_RECV, html, time(NULL));
5265 } else {
5266 serv_got_im(sip->gc, from, html, 0, time(NULL));
5268 g_free(html);
5269 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5275 g_free(from);
5277 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5278 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5279 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5281 body = g_strdup_printf(
5282 "v=0\r\n"
5283 "o=- 0 0 IN IP4 %s\r\n"
5284 "s=session\r\n"
5285 "c=IN IP4 %s\r\n"
5286 "t=0 0\r\n"
5287 "m=%s %d sip sip:%s\r\n"
5288 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5289 purple_network_get_my_ip(-1),
5290 purple_network_get_my_ip(-1),
5291 sip->ocs2007 ? "message" : "x-ms-message",
5292 sip->realport,
5293 sip->username);
5294 send_sip_response(sip->gc, msg, 200, "OK", body);
5295 g_free(body);
5298 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5300 gchar *body;
5302 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5303 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5304 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5306 body = g_strdup_printf(
5307 "v=0\r\n"
5308 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5309 "s=session\r\n"
5310 "c=IN IP4 0.0.0.0\r\n"
5311 "t=0 0\r\n"
5312 "m=%s %d sip sip:%s\r\n"
5313 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5314 sip->ocs2007 ? "message" : "x-ms-message",
5315 sip->realport,
5316 sip->username);
5317 send_sip_response(sip->gc, msg, 200, "OK", body);
5318 g_free(body);
5321 static const char*
5322 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5324 const char *res = "NTLM";
5325 #ifdef USE_KERBEROS
5326 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5327 res = "Kerberos";
5329 #else
5330 (void) sip; /* make compiler happy */
5331 #endif
5332 return res;
5335 static void sipe_connection_cleanup(struct sipe_account_data *);
5336 static void create_connection(struct sipe_account_data *, gchar *, int);
5338 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5339 SIPE_UNUSED_PARAMETER struct transaction *trans)
5341 gchar *tmp;
5342 const gchar *expires_header;
5343 int expires, i;
5344 GSList *hdr = msg->headers;
5345 struct sipnameval *elem;
5347 expires_header = sipmsg_find_header(msg, "Expires");
5348 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5349 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5351 switch (msg->response) {
5352 case 200:
5353 if (expires == 0) {
5354 sip->registerstatus = 0;
5355 } else {
5356 const gchar *contact_hdr;
5357 gchar *gruu = NULL;
5358 gchar *epid;
5359 gchar *uuid;
5360 gchar *timeout;
5361 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5362 const char *auth_scheme;
5364 if (!sip->reregister_set) {
5365 gchar *action_name = g_strdup_printf("<%s>", "registration");
5366 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5367 g_free(action_name);
5368 sip->reregister_set = TRUE;
5371 sip->registerstatus = 3;
5373 if (server_hdr && !sip->server_version) {
5374 sip->server_version = g_strdup(server_hdr);
5375 g_free(default_ua);
5376 default_ua = NULL;
5379 auth_scheme = sipe_get_auth_scheme_name(sip);
5380 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5382 if (tmp) {
5383 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5384 fill_auth(tmp, &sip->registrar);
5387 if (!sip->reauthenticate_set) {
5388 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5389 guint reauth_timeout;
5390 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5391 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5392 reauth_timeout = sip->registrar.expires - 300;
5393 } else {
5394 /* NTLM: we have to reauthenticate as our security token expires
5395 after eight hours (be five minutes early) */
5396 reauth_timeout = (8 * 3600) - 300;
5398 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5399 g_free(action_name);
5400 sip->reauthenticate_set = TRUE;
5403 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5405 epid = get_epid(sip);
5406 uuid = generateUUIDfromEPID(epid);
5407 g_free(epid);
5409 // There can be multiple Contact headers (one per location where the user is logged in) so
5410 // make sure to only get the one for this uuid
5411 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5412 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5413 if (valid_contact) {
5414 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5415 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5416 g_free(valid_contact);
5417 break;
5418 } else {
5419 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5422 g_free(uuid);
5424 g_free(sip->contact);
5425 if(gruu) {
5426 sip->contact = g_strdup_printf("<%s>", gruu);
5427 g_free(gruu);
5428 } else {
5429 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5430 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);
5432 sip->ocs2007 = FALSE;
5433 sip->batched_support = FALSE;
5435 while(hdr)
5437 elem = hdr->data;
5438 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5439 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5440 /* We interpret this as OCS2007+ indicator */
5441 sip->ocs2007 = TRUE;
5442 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5444 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5445 sip->batched_support = TRUE;
5446 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5449 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5450 gchar **caps = g_strsplit(elem->value,",",0);
5451 i = 0;
5452 while (caps[i]) {
5453 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5454 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5455 i++;
5457 g_strfreev(caps);
5459 hdr = g_slist_next(hdr);
5462 /* rejoin open chats to be able to use them by continue to send messages */
5463 purple_conversation_foreach(sipe_rejoin_chat);
5465 /* subscriptions */
5466 if (!sip->subscribed) { //do it just once, not every re-register
5468 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5469 (GCompareFunc)g_ascii_strcasecmp)) {
5470 sipe_subscribe_roaming_contacts(sip);
5473 /* For 2007+ it does not make sence to subscribe to:
5474 * vnd-microsoft-roaming-ACL
5475 * vnd-microsoft-provisioning (not v2)
5476 * presence.wpending
5477 * These are for backward compatibility.
5479 if (sip->ocs2007)
5481 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5482 (GCompareFunc)g_ascii_strcasecmp)) {
5483 sipe_subscribe_roaming_self(sip);
5485 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5486 (GCompareFunc)g_ascii_strcasecmp)) {
5487 sipe_subscribe_roaming_provisioning_v2(sip);
5490 /* For 2005- servers */
5491 else
5493 //sipe_options_request(sip, sip->sipdomain);
5495 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5496 (GCompareFunc)g_ascii_strcasecmp)) {
5497 sipe_subscribe_roaming_acl(sip);
5499 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5500 (GCompareFunc)g_ascii_strcasecmp)) {
5501 sipe_subscribe_roaming_provisioning(sip);
5503 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5504 (GCompareFunc)g_ascii_strcasecmp)) {
5505 sipe_subscribe_presence_wpending(sip, msg);
5508 /* For 2007+ we publish our initial statuses and calendar data only after
5509 * received our existing publications in sipe_process_roaming_self()
5510 * Only in this case we know versions of current publications made
5511 * on our behalf.
5513 /* For 2005- we publish our initial statuses only after
5514 * received our existing UserInfo data in response to
5515 * self subscription.
5516 * Only in this case we won't override existing UserInfo data
5517 * set earlier or by other client on our behalf.
5521 sip->subscribed = TRUE;
5524 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5525 "timeout=", ";", NULL);
5526 if (timeout != NULL) {
5527 sscanf(timeout, "%u", &sip->keepalive_timeout);
5528 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5529 sip->keepalive_timeout);
5530 g_free(timeout);
5533 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5535 break;
5536 case 301:
5538 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5540 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5541 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5542 gchar **tmp;
5543 gchar *hostname;
5544 int port = 0;
5545 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5546 int i = 1;
5548 tmp = g_strsplit(parts[0], ":", 0);
5549 hostname = g_strdup(tmp[0]);
5550 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5551 g_strfreev(tmp);
5553 while (parts[i]) {
5554 tmp = g_strsplit(parts[i], "=", 0);
5555 if (tmp[1]) {
5556 if (g_strcasecmp("transport", tmp[0]) == 0) {
5557 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5558 transport = SIPE_TRANSPORT_TCP;
5559 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5560 transport = SIPE_TRANSPORT_UDP;
5564 g_strfreev(tmp);
5565 i++;
5567 g_strfreev(parts);
5569 /* Close old connection */
5570 sipe_connection_cleanup(sip);
5572 /* Create new connection */
5573 sip->transport = transport;
5574 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5575 hostname, port, TRANSPORT_DESCRIPTOR);
5576 create_connection(sip, hostname, port);
5578 g_free(redirect);
5580 break;
5581 case 401:
5582 if (sip->registerstatus != 2) {
5583 const char *auth_scheme;
5584 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5585 if (sip->registrar.retries > 3) {
5586 sip->gc->wants_to_die = TRUE;
5587 purple_connection_error(sip->gc, _("Wrong password"));
5588 return TRUE;
5591 auth_scheme = sipe_get_auth_scheme_name(sip);
5592 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5594 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5595 if (!tmp) {
5596 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5597 sip->gc->wants_to_die = TRUE;
5598 purple_connection_error(sip->gc, tmp2);
5599 g_free(tmp2);
5600 return TRUE;
5602 fill_auth(tmp, &sip->registrar);
5603 sip->registerstatus = 2;
5604 if (sip->account->disconnecting) {
5605 do_register_exp(sip, 0);
5606 } else {
5607 do_register(sip);
5610 break;
5611 case 403:
5613 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5614 gchar **reason = NULL;
5615 gchar *warning;
5616 if (diagnostics != NULL) {
5617 /* Example header:
5618 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5620 reason = g_strsplit(diagnostics, "\"", 0);
5622 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5623 (reason && reason[1]) ? reason[1] : _("no reason given"));
5624 g_strfreev(reason);
5626 sip->gc->wants_to_die = TRUE;
5627 purple_connection_error(sip->gc, warning);
5628 g_free(warning);
5629 return TRUE;
5631 break;
5632 case 404:
5634 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5635 gchar *reason = NULL;
5636 gchar *warning;
5637 if (diagnostics != NULL) {
5638 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5640 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5641 diagnostics ? (reason ? reason : _("no reason given")) :
5642 _("SIP is either not enabled for the destination URI or it does not exist"));
5643 g_free(reason);
5645 sip->gc->wants_to_die = TRUE;
5646 purple_connection_error(sip->gc, warning);
5647 g_free(warning);
5648 return TRUE;
5650 break;
5651 case 503:
5652 case 504: /* Server time-out */
5654 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5655 gchar *reason = NULL;
5656 gchar *warning;
5657 if (diagnostics != NULL) {
5658 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5660 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
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;
5670 return TRUE;
5674 * Returns 2005-style activity and Availability.
5676 * @param status Sipe statis id.
5678 static void
5679 sipe_get_act_avail_by_status_2005(const char *status,
5680 int *activity,
5681 int *availability)
5683 int avail = 300; /* online */
5684 int act = 400; /* Available */
5686 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5687 act = 100;
5688 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5689 // act = 150;
5690 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5691 act = 300;
5692 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5693 act = 400;
5694 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5695 // act = 500;
5696 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5697 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5698 act = 600;
5699 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5700 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5701 avail = 0; /* offline */
5702 act = 100;
5703 } else {
5704 act = 400; /* Available */
5707 if (activity) *activity = act;
5708 if (availability) *availability = avail;
5712 * [MS-SIP] 2.2.1
5714 * @param activity 2005 aggregated activity. Ex.: 600
5715 * @param availablity 2005 aggregated availablity. Ex.: 300
5717 static const char *
5718 sipe_get_status_by_act_avail_2005(const int activity,
5719 const int availablity,
5720 char **activity_desc)
5722 const char *status_id = NULL;
5723 const char *act = NULL;
5725 if (activity < 150) {
5726 status_id = SIPE_STATUS_ID_AWAY;
5727 } else if (activity < 200) {
5728 //status_id = SIPE_STATUS_ID_LUNCH;
5729 status_id = SIPE_STATUS_ID_AWAY;
5730 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5731 } else if (activity < 300) {
5732 //status_id = SIPE_STATUS_ID_IDLE;
5733 status_id = SIPE_STATUS_ID_AWAY;
5734 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5735 } else if (activity < 400) {
5736 status_id = SIPE_STATUS_ID_BRB;
5737 } else if (activity < 500) {
5738 status_id = SIPE_STATUS_ID_AVAILABLE;
5739 } else if (activity < 600) {
5740 //status_id = SIPE_STATUS_ID_ON_PHONE;
5741 status_id = SIPE_STATUS_ID_BUSY;
5742 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5743 } else if (activity < 700) {
5744 status_id = SIPE_STATUS_ID_BUSY;
5745 } else if (activity < 800) {
5746 status_id = SIPE_STATUS_ID_AWAY;
5747 } else {
5748 status_id = SIPE_STATUS_ID_AVAILABLE;
5751 if (availablity < 100)
5752 status_id = SIPE_STATUS_ID_OFFLINE;
5754 if (activity_desc && act) {
5755 g_free(*activity_desc);
5756 *activity_desc = g_strdup(act);
5759 return status_id;
5763 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5765 static const char*
5766 sipe_get_status_by_availability(int avail,
5767 char** activity_desc)
5769 const char *status;
5770 const char *act = NULL;
5772 if (avail < 3000) {
5773 status = SIPE_STATUS_ID_OFFLINE;
5774 } else if (avail < 4500) {
5775 status = SIPE_STATUS_ID_AVAILABLE;
5776 } else if (avail < 6000) {
5777 //status = SIPE_STATUS_ID_IDLE;
5778 status = SIPE_STATUS_ID_AVAILABLE;
5779 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5780 } else if (avail < 7500) {
5781 status = SIPE_STATUS_ID_BUSY;
5782 } else if (avail < 9000) {
5783 //status = SIPE_STATUS_ID_BUSYIDLE;
5784 status = SIPE_STATUS_ID_BUSY;
5785 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5786 } else if (avail < 12000) {
5787 status = SIPE_STATUS_ID_DND;
5788 } else if (avail < 15000) {
5789 status = SIPE_STATUS_ID_BRB;
5790 } else if (avail < 18000) {
5791 status = SIPE_STATUS_ID_AWAY;
5792 } else {
5793 status = SIPE_STATUS_ID_OFFLINE;
5796 if (activity_desc && act) {
5797 g_free(*activity_desc);
5798 *activity_desc = g_strdup(act);
5801 return status;
5805 * Returns 2007-style availability value
5807 * @param sipe_status_id (in)
5808 * @param activity_token (out) Must be g_free()'d after use if consumed.
5810 static int
5811 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5813 int availability;
5814 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5816 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5817 availability = 15500;
5818 if (!activity_token || !(*activity_token)) {
5819 activity = SIPE_ACTIVITY_AWAY;
5821 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5822 availability = 12500;
5823 activity = SIPE_ACTIVITY_BRB;
5824 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5825 availability = 9500;
5826 activity = SIPE_ACTIVITY_DND;
5827 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5828 availability = 6500;
5829 if (!activity_token || !(*activity_token)) {
5830 activity = SIPE_ACTIVITY_BUSY;
5832 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5833 availability = 3500;
5834 activity = SIPE_ACTIVITY_ONLINE;
5835 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5836 availability = 0;
5837 } else {
5838 // Offline or invisible
5839 availability = 18500;
5840 activity = SIPE_ACTIVITY_OFFLINE;
5843 if (activity_token) {
5844 *activity_token = g_strdup(sipe_activity_map[activity].token);
5846 return availability;
5849 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5851 const char *uri;
5852 xmlnode *xn_categories;
5853 xmlnode *xn_category;
5854 xmlnode *xn_node;
5855 const char *status = NULL;
5856 gboolean do_update_status = FALSE;
5857 gboolean has_note_cleaned = FALSE;
5858 gboolean has_free_busy_cleaned = FALSE;
5860 xn_categories = xmlnode_from_str(data, len);
5861 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5863 for (xn_category = xmlnode_get_child(xn_categories, "category");
5864 xn_category ;
5865 xn_category = xmlnode_get_next_twin(xn_category) )
5867 const char *tmp;
5868 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5869 time_t publish_time = (tmp = xmlnode_get_attrib(xn_category, "publishTime")) ?
5870 sipe_utils_str_to_time(tmp) : 0;
5872 /* contactCard */
5873 if (sipe_strequal(attrVar, "contactCard"))
5875 xmlnode *node;
5876 /* identity - Display Name and email */
5877 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5878 if (node) {
5879 char* display_name = xmlnode_get_data(
5880 xmlnode_get_descendant(node, "name", "displayName", NULL));
5881 char* email = xmlnode_get_data(
5882 xmlnode_get_child(node, "email"));
5884 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5885 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5887 g_free(display_name);
5888 g_free(email);
5890 /* company */
5891 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5892 if (node) {
5893 char* company = xmlnode_get_data(node);
5894 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5895 g_free(company);
5897 /* department */
5898 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5899 if (node) {
5900 char* department = xmlnode_get_data(node);
5901 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5902 g_free(department);
5904 /* title */
5905 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5906 if (node) {
5907 char* title = xmlnode_get_data(node);
5908 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5909 g_free(title);
5911 /* office */
5912 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5913 if (node) {
5914 char* office = xmlnode_get_data(node);
5915 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5916 g_free(office);
5918 /* site (url) */
5919 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5920 if (node) {
5921 char* site = xmlnode_get_data(node);
5922 sipe_update_user_info(sip, uri, SITE_PROP, site);
5923 g_free(site);
5925 /* phone */
5926 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5927 node;
5928 node = xmlnode_get_next_twin(node))
5930 const char *phone_type = xmlnode_get_attrib(node, "type");
5931 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5932 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5934 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5936 g_free(phone);
5937 g_free(phone_display_string);
5939 /* address */
5940 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5941 node;
5942 node = xmlnode_get_next_twin(node))
5944 if (sipe_strequal(xmlnode_get_attrib(node, "type"), "work")) {
5945 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5946 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5947 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5948 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5949 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5951 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5952 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5953 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5954 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5955 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5957 g_free(street);
5958 g_free(city);
5959 g_free(state);
5960 g_free(zipcode);
5961 g_free(country_code);
5963 break;
5967 /* note */
5968 else if (sipe_strequal(attrVar, "note"))
5970 if (uri) {
5971 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5973 if (!has_note_cleaned) {
5974 has_note_cleaned = TRUE;
5976 g_free(sbuddy->note);
5977 sbuddy->note = NULL;
5978 sbuddy->is_oof_note = FALSE;
5979 sbuddy->note_since = publish_time;
5981 do_update_status = TRUE;
5983 if (sbuddy && (publish_time >= sbuddy->note_since)) {
5984 /* clean up in case no 'note' element is supplied
5985 * which indicate note removal in client
5987 g_free(sbuddy->note);
5988 sbuddy->note = NULL;
5989 sbuddy->is_oof_note = FALSE;
5990 sbuddy->note_since = publish_time;
5992 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
5993 if (xn_node) {
5994 char *tmp;
5995 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
5996 g_free(tmp);
5997 sbuddy->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_node, "type"), "OOF");
5998 sbuddy->note_since = publish_time;
6000 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
6001 uri, sbuddy->note ? sbuddy->note : "");
6003 /* to trigger UI refresh in case no status info is supplied in this update */
6004 do_update_status = TRUE;
6008 /* state */
6009 else if(sipe_strequal(attrVar, "state"))
6011 char *tmp;
6012 int availability;
6013 xmlnode *xn_availability;
6014 xmlnode *xn_activity;
6015 xmlnode *xn_meeting_subject;
6016 xmlnode *xn_meeting_location;
6017 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6019 xn_node = xmlnode_get_child(xn_category, "state");
6020 if (!xn_node) continue;
6021 xn_availability = xmlnode_get_child(xn_node, "availability");
6022 if (!xn_availability) continue;
6023 xn_activity = xmlnode_get_child(xn_node, "activity");
6024 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
6025 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
6027 tmp = xmlnode_get_data(xn_availability);
6028 availability = atoi(tmp);
6029 g_free(tmp);
6031 /* activity, meeting_subject, meeting_location */
6032 if (sbuddy) {
6033 char *tmp = NULL;
6035 /* activity */
6036 g_free(sbuddy->activity);
6037 sbuddy->activity = NULL;
6038 if (xn_activity) {
6039 const char *token = xmlnode_get_attrib(xn_activity, "token");
6040 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
6042 /* from token */
6043 if (!is_empty(token)) {
6044 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6046 /* from custom element */
6047 if (xn_custom) {
6048 char *custom = xmlnode_get_data(xn_custom);
6050 if (!is_empty(custom)) {
6051 sbuddy->activity = custom;
6052 custom = NULL;
6054 g_free(custom);
6057 /* meeting_subject */
6058 g_free(sbuddy->meeting_subject);
6059 sbuddy->meeting_subject = NULL;
6060 if (xn_meeting_subject) {
6061 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
6063 if (!is_empty(meeting_subject)) {
6064 sbuddy->meeting_subject = meeting_subject;
6065 meeting_subject = NULL;
6067 g_free(meeting_subject);
6069 /* meeting_location */
6070 g_free(sbuddy->meeting_location);
6071 sbuddy->meeting_location = NULL;
6072 if (xn_meeting_location) {
6073 char *meeting_location = xmlnode_get_data(xn_meeting_location);
6075 if (!is_empty(meeting_location)) {
6076 sbuddy->meeting_location = meeting_location;
6077 meeting_location = NULL;
6079 g_free(meeting_location);
6082 status = sipe_get_status_by_availability(availability, &tmp);
6083 if (sbuddy->activity && tmp) {
6084 char *tmp2 = sbuddy->activity;
6086 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6087 g_free(tmp);
6088 g_free(tmp2);
6089 } else if (tmp) {
6090 sbuddy->activity = tmp;
6094 do_update_status = TRUE;
6096 /* calendarData */
6097 else if(sipe_strequal(attrVar, "calendarData"))
6099 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6100 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
6101 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
6103 if (sbuddy && xn_free_busy) {
6104 if (!has_free_busy_cleaned) {
6105 has_free_busy_cleaned = TRUE;
6107 g_free(sbuddy->cal_start_time);
6108 sbuddy->cal_start_time = NULL;
6110 g_free(sbuddy->cal_free_busy_base64);
6111 sbuddy->cal_free_busy_base64 = NULL;
6113 g_free(sbuddy->cal_free_busy);
6114 sbuddy->cal_free_busy = NULL;
6116 sbuddy->cal_free_busy_published = publish_time;
6119 if (publish_time >= sbuddy->cal_free_busy_published) {
6120 g_free(sbuddy->cal_start_time);
6121 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
6123 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
6124 15 : 0;
6126 g_free(sbuddy->cal_free_busy_base64);
6127 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
6129 g_free(sbuddy->cal_free_busy);
6130 sbuddy->cal_free_busy = NULL;
6132 sbuddy->cal_free_busy_published = publish_time;
6134 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);
6138 if (sbuddy && xn_working_hours) {
6139 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6144 if (do_update_status) {
6145 if (!status) { /* no status category in this update, using contact's current status */
6146 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6147 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6148 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6149 status = purple_status_get_id(pstatus);
6152 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
6153 sipe_got_user_status(sip, uri, status);
6156 xmlnode_free(xn_categories);
6159 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6161 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6162 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
6163 payload->host = g_strdup(host);
6164 payload->buddies = server;
6165 sipe_subscribe_presence_batched_routed(sip, payload);
6166 sipe_subscribe_presence_batched_routed_free(payload);
6169 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6171 xmlnode *xn_list;
6172 xmlnode *xn_resource;
6173 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6174 g_free, NULL);
6175 GSList *server;
6176 gchar *host;
6178 xn_list = xmlnode_from_str(data, len);
6180 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6181 xn_resource;
6182 xn_resource = xmlnode_get_next_twin(xn_resource) )
6184 const char *uri, *state;
6185 xmlnode *xn_instance;
6187 xn_instance = xmlnode_get_child(xn_resource, "instance");
6188 if (!xn_instance) continue;
6190 uri = xmlnode_get_attrib(xn_resource, "uri");
6191 state = xmlnode_get_attrib(xn_instance, "state");
6192 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6194 if (strstr(state, "resubscribe")) {
6195 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6197 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6198 gchar *user = g_strdup(uri);
6199 host = g_strdup(poolFqdn);
6200 server = g_hash_table_lookup(servers, host);
6201 server = g_slist_append(server, user);
6202 g_hash_table_insert(servers, host, server);
6203 } else {
6204 sipe_subscribe_presence_single(sip, (void *) uri);
6209 /* Send out any deferred poolFqdn subscriptions */
6210 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6211 g_hash_table_destroy(servers);
6213 xmlnode_free(xn_list);
6216 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6218 gchar *uri;
6219 gchar *getbasic;
6220 gchar *activity = NULL;
6221 xmlnode *pidf;
6222 xmlnode *basicstatus = NULL, *tuple, *status;
6223 gboolean isonline = FALSE;
6224 xmlnode *display_name_node;
6226 pidf = xmlnode_from_str(data, len);
6227 if (!pidf) {
6228 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6229 return;
6232 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6234 if ((status = xmlnode_get_child(tuple, "status"))) {
6235 basicstatus = xmlnode_get_child(status, "basic");
6239 if (!basicstatus) {
6240 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6241 xmlnode_free(pidf);
6242 return;
6245 getbasic = xmlnode_get_data(basicstatus);
6246 if (!getbasic) {
6247 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6248 xmlnode_free(pidf);
6249 return;
6252 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6253 if (strstr(getbasic, "open")) {
6254 isonline = TRUE;
6256 g_free(getbasic);
6258 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6260 display_name_node = xmlnode_get_child(pidf, "display-name");
6261 if (display_name_node) {
6262 char * display_name = xmlnode_get_data(display_name_node);
6264 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6265 g_free(display_name);
6268 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6269 if ((status = xmlnode_get_child(tuple, "status"))) {
6270 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6271 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6272 activity = xmlnode_get_data(basicstatus);
6273 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6279 if (isonline) {
6280 const gchar * status_id = NULL;
6281 if (activity) {
6282 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6283 status_id = SIPE_STATUS_ID_BUSY;
6284 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6285 status_id = SIPE_STATUS_ID_AWAY;
6289 if (!status_id) {
6290 status_id = SIPE_STATUS_ID_AVAILABLE;
6293 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6294 sipe_got_user_status(sip, uri, status_id);
6295 } else {
6296 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6299 g_free(activity);
6300 g_free(uri);
6301 xmlnode_free(pidf);
6304 /** 2005 */
6305 static void
6306 sipe_user_info_has_updated(struct sipe_account_data *sip,
6307 xmlnode *xn_userinfo)
6309 if (sip->user_info) {
6310 xmlnode_free(sip->user_info);
6312 sip->user_info = xmlnode_copy(xn_userinfo);
6314 /* Publish initial state if not yet.
6315 * Assuming this happens on initial responce to self subscription
6316 * so we've already updated our UserInfo.
6318 if (!sip->initial_state_published) {
6319 send_presence_soap(sip, FALSE);
6320 /* dalayed run */
6321 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6325 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6327 char *activity = NULL;
6328 const char *epid;
6329 const char *status_id = NULL;
6330 const char *name;
6331 char *uri;
6332 char *self_uri = sip_uri_self(sip);
6333 int avl;
6334 int act;
6335 const char *device_name = NULL;
6336 const char *cal_start_time = NULL;
6337 const char *cal_granularity = NULL;
6338 char *cal_free_busy_base64 = NULL;
6339 struct sipe_buddy *sbuddy;
6340 xmlnode *node;
6341 xmlnode *xn_presentity;
6342 xmlnode *xn_availability;
6343 xmlnode *xn_activity;
6344 xmlnode *xn_display_name;
6345 xmlnode *xn_email;
6346 xmlnode *xn_phone_number;
6347 xmlnode *xn_userinfo;
6348 xmlnode *xn_note;
6349 xmlnode *xn_oof;
6350 xmlnode *xn_state;
6351 xmlnode *xn_contact;
6352 char *note;
6353 char *free_activity;
6354 int user_avail;
6355 const char *user_avail_nil;
6356 int res_avail;
6357 time_t user_avail_since = 0;
6358 time_t activity_since = 0;
6360 /* fix for Reuters environment on Linux */
6361 if (data && strstr(data, "encoding=\"utf-16\"")) {
6362 char *tmp_data;
6363 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6364 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6365 g_free(tmp_data);
6366 } else {
6367 xn_presentity = xmlnode_from_str(data, len);
6370 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6371 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6372 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6373 xn_email = xmlnode_get_child(xn_presentity, "email");
6374 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6375 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6376 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6377 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6378 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6379 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6380 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6381 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6382 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6383 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6385 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6386 user_avail = 0;
6387 user_avail_since = 0;
6390 free_activity = NULL;
6392 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6393 uri = sip_uri_from_name(name);
6394 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6395 epid = xmlnode_get_attrib(xn_availability, "epid");
6396 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6398 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6399 res_avail = sipe_get_availability_by_status(status_id, NULL);
6400 if (user_avail > res_avail) {
6401 res_avail = user_avail;
6402 status_id = sipe_get_status_by_availability(user_avail, NULL);
6405 if (xn_display_name) {
6406 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6407 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6408 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6409 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6410 char *tel_uri = sip_to_tel_uri(phone_number);
6412 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6413 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6414 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6415 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6417 g_free(tel_uri);
6418 g_free(phone_label);
6419 g_free(phone_number);
6420 g_free(email);
6421 g_free(display_name);
6424 if (xn_contact) {
6425 /* tel */
6426 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6428 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6429 const char *phone_type = xmlnode_get_attrib(node, "type");
6430 char* phone = xmlnode_get_data(node);
6432 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6434 g_free(phone);
6438 /* devicePresence */
6439 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6440 xmlnode *xn_device_name;
6441 xmlnode *xn_calendar_info;
6442 xmlnode *xn_state;
6443 char *state;
6445 /* deviceName */
6446 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6447 xn_device_name = xmlnode_get_child(node, "deviceName");
6448 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6451 /* calendarInfo */
6452 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6453 if (xn_calendar_info) {
6454 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6456 if (cal_start_time) {
6457 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6458 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6460 if (cal_start_time_t_tmp > cal_start_time_t) {
6461 cal_start_time = cal_start_time_tmp;
6462 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6463 g_free(cal_free_busy_base64);
6464 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6466 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);
6468 } else {
6469 cal_start_time = cal_start_time_tmp;
6470 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6471 g_free(cal_free_busy_base64);
6472 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6474 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);
6478 /* state */
6479 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6480 if (xn_state) {
6481 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6482 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6484 state = xmlnode_get_data(xn_state);
6485 if (dev_avail_since > user_avail_since &&
6486 dev_avail >= res_avail)
6488 res_avail = dev_avail;
6489 if (!is_empty(state))
6491 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6492 g_free(activity);
6493 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6494 } else if (sipe_strequal(state, "presenting")) {
6495 g_free(activity);
6496 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6497 } else {
6498 activity = state;
6499 state = NULL;
6501 activity_since = dev_avail_since;
6503 status_id = sipe_get_status_by_availability(res_avail, &activity);
6505 g_free(state);
6509 /* oof */
6510 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6511 g_free(activity);
6512 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6513 activity_since = 0;
6516 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6517 if (sbuddy)
6519 g_free(sbuddy->activity);
6520 sbuddy->activity = activity;
6521 activity = NULL;
6523 sbuddy->activity_since = activity_since;
6525 sbuddy->user_avail = user_avail;
6526 sbuddy->user_avail_since = user_avail_since;
6528 g_free(sbuddy->note);
6529 sbuddy->note = NULL;
6530 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6532 sbuddy->is_oof_note = (xn_oof != NULL);
6534 g_free(sbuddy->device_name);
6535 sbuddy->device_name = NULL;
6536 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6538 if (!is_empty(cal_free_busy_base64)) {
6539 g_free(sbuddy->cal_start_time);
6540 sbuddy->cal_start_time = g_strdup(cal_start_time);
6542 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6544 g_free(sbuddy->cal_free_busy_base64);
6545 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6546 cal_free_busy_base64 = NULL;
6548 g_free(sbuddy->cal_free_busy);
6549 sbuddy->cal_free_busy = NULL;
6552 sbuddy->last_non_cal_status_id = status_id;
6553 g_free(sbuddy->last_non_cal_activity);
6554 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6556 if (sipe_strequal(sbuddy->name, self_uri)) {
6557 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6559 sip->is_oof_note = sbuddy->is_oof_note;
6561 g_free(sip->note);
6562 sip->note = g_strdup(sbuddy->note);
6564 sip->note_since = time(NULL);
6567 g_free(sip->status);
6568 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6571 g_free(cal_free_busy_base64);
6572 g_free(activity);
6574 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6575 sipe_got_user_status(sip, uri, status_id);
6577 if (!sip->ocs2007 && sipe_strequal(self_uri, uri)) {
6578 sipe_user_info_has_updated(sip, xn_userinfo);
6581 g_free(note);
6582 xmlnode_free(xn_presentity);
6583 g_free(uri);
6584 g_free(self_uri);
6587 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6589 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6591 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6593 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6594 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6596 const char *content = msg->body;
6597 unsigned length = msg->bodylen;
6598 PurpleMimeDocument *mime = NULL;
6600 if (strstr(ctype, "multipart"))
6602 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6603 const char *content_type;
6604 GList* parts;
6605 mime = purple_mime_document_parse(doc);
6606 parts = purple_mime_document_get_parts(mime);
6607 while(parts) {
6608 content = purple_mime_part_get_data(parts->data);
6609 length = purple_mime_part_get_length(parts->data);
6610 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6611 if(content_type && strstr(content_type,"application/rlmi+xml"))
6613 process_incoming_notify_rlmi_resub(sip, content, length);
6615 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6617 process_incoming_notify_msrtc(sip, content, length);
6619 else
6621 process_incoming_notify_rlmi(sip, content, length);
6623 parts = parts->next;
6625 g_free(doc);
6627 if (mime)
6629 purple_mime_document_free(mime);
6632 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6634 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6636 else if(strstr(ctype, "application/rlmi+xml"))
6638 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6641 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6643 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6645 else
6647 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6651 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6653 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6654 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6656 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6658 if (ctype &&
6659 strstr(ctype, "multipart") &&
6660 (strstr(ctype, "application/rlmi+xml") ||
6661 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6662 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6663 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6664 GList *parts = purple_mime_document_get_parts(mime);
6665 GSList *buddies = NULL;
6666 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6668 while (parts) {
6669 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6670 purple_mime_part_get_length(parts->data));
6672 if (xml && !sipe_strequal(xml->name, "list")) {
6673 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6675 buddies = g_slist_append(buddies, uri);
6677 xmlnode_free(xml);
6679 parts = parts->next;
6681 g_free(doc);
6682 if (mime) purple_mime_document_free(mime);
6684 payload->host = g_strdup(who);
6685 payload->buddies = buddies;
6686 sipe_schedule_action(action_name, timeout,
6687 sipe_subscribe_presence_batched_routed,
6688 sipe_subscribe_presence_batched_routed_free,
6689 sip, payload);
6690 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6692 } else {
6693 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6694 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6696 g_free(action_name);
6700 * Dispatcher for all incoming subscription information
6701 * whether it comes from NOTIFY, BENOTIFY requests or
6702 * piggy-backed to subscription's OK responce.
6704 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6705 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6707 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6709 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6710 const gchar *event = sipmsg_find_header(msg, "Event");
6711 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6712 char *tmp;
6714 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6715 event ? event : "",
6716 tmp = fix_newlines(msg->body));
6717 g_free(tmp);
6718 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6720 /* implicit subscriptions */
6721 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6722 sipe_process_imdn(sip, msg);
6725 if (event) {
6726 /* for one off subscriptions (send with Expire: 0) */
6727 if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6729 sipe_process_provisioning_v2(sip, msg);
6731 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6733 sipe_process_provisioning(sip, msg);
6735 else if (!g_ascii_strcasecmp(event, "presence"))
6737 sipe_process_presence(sip, msg);
6739 else if (!g_ascii_strcasecmp(event, "registration-notify"))
6741 sipe_process_registration_notify(sip, msg);
6744 if (!subscription_state || strstr(subscription_state, "active"))
6746 if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6748 sipe_process_roaming_contacts(sip, msg);
6750 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6752 sipe_process_roaming_self(sip, msg);
6754 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6756 sipe_process_roaming_acl(sip, msg);
6758 else if (!g_ascii_strcasecmp(event, "presence.wpending"))
6760 sipe_process_presence_wpending(sip, msg);
6762 else if (!g_ascii_strcasecmp(event, "conference"))
6764 sipe_process_conference(sip, msg);
6769 /* The server sends status 'terminated' */
6770 if (subscription_state && strstr(subscription_state, "terminated") ) {
6771 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6772 gchar *key = sipe_get_subscription_key(event, who);
6774 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6775 g_free(who);
6777 if (g_hash_table_lookup(sip->subscriptions, key)) {
6778 g_hash_table_remove(sip->subscriptions, key);
6779 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6782 g_free(key);
6785 if (!request && event) {
6786 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6787 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6788 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6790 if (timeout) {
6791 /* 2 min ahead of expiration */
6792 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6794 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6795 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6797 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6798 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6799 g_free(action_name);
6801 else if (!g_ascii_strcasecmp(event, "presence") &&
6802 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6804 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6805 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6807 if (sip->batched_support) {
6808 sipe_process_presence_timeout(sip, msg, who, timeout);
6810 else {
6811 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6812 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6814 g_free(action_name);
6815 g_free(who);
6820 /* The client responses on received a NOTIFY message */
6821 if (request && !benotify)
6823 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6828 * Whether user manually changed status or
6829 * it was changed automatically due to user
6830 * became inactive/active again
6832 static gboolean
6833 sipe_is_user_state(struct sipe_account_data *sip)
6835 gboolean res;
6836 time_t now = time(NULL);
6838 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6839 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6841 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6843 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6844 return res;
6847 static void
6848 send_presence_soap0(struct sipe_account_data *sip,
6849 gboolean do_publish_calendar,
6850 gboolean do_reset_status)
6852 struct sipe_ews* ews = sip->ews;
6853 int availability = 0;
6854 int activity = 0;
6855 gchar *body;
6856 gchar *tmp;
6857 gchar *tmp2 = NULL;
6858 gchar *res_note = NULL;
6859 gchar *res_oof = NULL;
6860 const gchar *note_pub = NULL;
6861 gchar *states = NULL;
6862 gchar *calendar_data = NULL;
6863 gchar *epid = get_epid(sip);
6864 time_t now = time(NULL);
6865 gchar *since_time_str = sipe_utils_time_to_str(now);
6866 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6867 const char *user_input;
6868 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6870 if (oof_note && sip->note) {
6871 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6872 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6875 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6877 if (!sip->initial_state_published ||
6878 do_reset_status)
6880 g_free(sip->status);
6881 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6884 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6886 /* Note */
6887 if (pub_oof) {
6888 note_pub = oof_note;
6889 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6890 ews->published = TRUE;
6891 } else if (sip->note) {
6892 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6893 g_free(sip->note);
6894 sip->note = NULL;
6895 sip->is_oof_note = FALSE;
6896 sip->note_since = 0;
6897 } else {
6898 note_pub = sip->note;
6899 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6903 if (note_pub)
6905 /* to protocol internal plain text format */
6906 tmp = purple_markup_strip_html(note_pub);
6907 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6908 g_free(tmp);
6911 /* User State */
6912 if (!do_reset_status) {
6913 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6915 gchar *activity_token = NULL;
6916 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6918 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6919 avail_2007,
6920 since_time_str,
6921 epid,
6922 activity_token);
6923 g_free(activity_token);
6925 else /* preserve existing publication */
6927 xmlnode *xn_states;
6928 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6929 states = xmlnode_to_str(xn_states, NULL);
6930 /* this is a hack-around to remove added newline after inner element,
6931 * state in this case, where it shouldn't be.
6932 * After several use of xmlnode_to_str, amount of added newlines
6933 * grows significantly.
6935 purple_str_strip_char(states, '\n');
6936 //purple_str_strip_char(states, '\r');
6939 } else {
6940 /* do nothing - then User state will be erased */
6942 sip->initial_state_published = TRUE;
6944 /* CalendarInfo */
6945 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6947 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6948 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6949 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6950 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6951 fb_start_str,
6952 free_busy_base64);
6953 g_free(fb_start_str);
6954 g_free(free_busy_base64);
6957 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6959 /* forming resulting XML */
6960 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6961 sip->username,
6962 availability,
6963 activity,
6964 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6965 res_note ? res_note : "",
6966 res_oof ? res_oof : "",
6967 states ? states : "",
6968 calendar_data ? calendar_data : "",
6969 epid,
6970 since_time_str,
6971 since_time_str,
6972 user_input);
6973 g_free(tmp);
6974 g_free(tmp2);
6975 g_free(res_note);
6976 g_free(states);
6977 g_free(calendar_data);
6979 send_soap_request(sip, body);
6981 g_free(body);
6982 g_free(since_time_str);
6983 g_free(epid);
6986 void
6987 send_presence_soap(struct sipe_account_data *sip,
6988 gboolean do_publish_calendar)
6990 return send_presence_soap0(sip, do_publish_calendar, FALSE);
6994 static gboolean
6995 process_send_presence_category_publish_response(struct sipe_account_data *sip,
6996 struct sipmsg *msg,
6997 struct transaction *trans)
6999 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7001 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7002 xmlnode *xml;
7003 xmlnode *node;
7004 gchar *fault_code;
7005 GHashTable *faults;
7006 int index_our;
7007 gboolean has_device_publication = FALSE;
7009 xml = xmlnode_from_str(msg->body, msg->bodylen);
7011 /* test if version mismatch fault */
7012 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7013 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7014 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
7015 g_free(fault_code);
7016 xmlnode_free(xml);
7017 return TRUE;
7019 g_free(fault_code);
7021 /* accumulating information about faulty versions */
7022 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7023 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7024 node;
7025 node = xmlnode_get_next_twin(node))
7027 const gchar *index = xmlnode_get_attrib(node, "index");
7028 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7030 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7031 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
7033 xmlnode_free(xml);
7035 /* here we are parsing own request to figure out what publication
7036 * referensed here only by index went wrong
7038 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7040 /* publication */
7041 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7042 index_our = 1; /* starts with 1 - our first publication */
7043 node;
7044 node = xmlnode_get_next_twin(node), index_our++)
7046 gchar *idx = g_strdup_printf("%d", index_our);
7047 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7048 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7049 g_free(idx);
7051 if (sipe_strequal("device", categoryName)) {
7052 has_device_publication = TRUE;
7055 if (curVersion) { /* fault exist on this index */
7056 const gchar *container = xmlnode_get_attrib(node, "container");
7057 const gchar *instance = xmlnode_get_attrib(node, "instance");
7058 /* key is <category><instance><container> */
7059 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7060 struct sipe_publication *publication =
7061 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
7063 purple_debug_info("sipe", "key is %s\n", key);
7065 if (publication) {
7066 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
7067 key, curVersion, publication->version);
7068 /* updating publication's version to the correct one */
7069 publication->version = atoi(curVersion);
7071 g_free(key);
7074 xmlnode_free(xml);
7075 g_hash_table_destroy(faults);
7077 /* rebublishing with right versions */
7078 if (has_device_publication) {
7079 send_publish_category_initial(sip);
7080 } else {
7081 send_presence_status(sip);
7084 return TRUE;
7088 * Returns 'device' XML part for publication.
7089 * Must be g_free'd after use.
7091 static gchar *
7092 sipe_publish_get_category_device(struct sipe_account_data *sip)
7094 gchar *uri;
7095 gchar *doc;
7096 gchar *epid = get_epid(sip);
7097 gchar *uuid = generateUUIDfromEPID(epid);
7098 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7099 /* key is <category><instance><container> */
7100 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7101 struct sipe_publication *publication =
7102 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7104 g_free(key);
7105 g_free(epid);
7107 uri = sip_uri_self(sip);
7108 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7109 device_instance,
7110 publication ? publication->version : 0,
7111 uuid,
7112 uri,
7113 "00:00:00+01:00", /* @TODO make timezone real*/
7114 sipe_get_host_name()
7117 g_free(uri);
7118 g_free(uuid);
7120 return doc;
7124 * A service method - use
7125 * - send_publish_get_category_state_machine and
7126 * - send_publish_get_category_state_user instead.
7127 * Must be g_free'd after use.
7129 static gchar *
7130 sipe_publish_get_category_state(struct sipe_account_data *sip,
7131 gboolean is_user_state)
7133 int availability = sipe_get_availability_by_status(sip->status, NULL);
7134 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7135 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7136 /* key is <category><instance><container> */
7137 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7138 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7139 struct sipe_publication *publication_2 =
7140 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7141 struct sipe_publication *publication_3 =
7142 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7144 g_free(key_2);
7145 g_free(key_3);
7147 if (publication_2 && (publication_2->availability == availability))
7149 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
7150 return NULL; /* nothing to update */
7153 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7154 instance,
7155 publication_2 ? publication_2->version : 0,
7156 availability,
7157 instance,
7158 publication_3 ? publication_3->version : 0,
7159 availability);
7163 * Only Busy and OOF calendar event are published.
7164 * Different instances are used for that.
7166 * Must be g_free'd after use.
7168 static gchar *
7169 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7170 struct sipe_cal_event *event,
7171 const char *uri,
7172 int cal_satus)
7174 gchar *start_time_str;
7175 int availability = 0;
7176 gchar *res;
7177 gchar *tmp = NULL;
7178 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7179 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7180 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7182 /* key is <category><instance><container> */
7183 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7184 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7185 struct sipe_publication *publication_2 =
7186 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7187 struct sipe_publication *publication_3 =
7188 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7190 g_free(key_2);
7191 g_free(key_3);
7193 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7194 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7195 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7196 return NULL;
7199 if (event &&
7200 publication_3 &&
7201 (publication_3->availability == availability) &&
7202 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7204 g_free(tmp);
7205 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7206 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7207 return NULL; /* nothing to update */
7209 g_free(tmp);
7211 if (event &&
7212 (event->cal_status == SIPE_CAL_BUSY ||
7213 event->cal_status == SIPE_CAL_OOF))
7215 gchar *availability_xml_str = NULL;
7216 gchar *activity_xml_str = NULL;
7218 if (event->cal_status == SIPE_CAL_BUSY) {
7219 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7222 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7223 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7224 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7225 "minAvailability=\"6500\"",
7226 "maxAvailability=\"8999\"");
7227 } else if (event->cal_status == SIPE_CAL_OOF) {
7228 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7229 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7230 "minAvailability=\"12000\"",
7231 "");
7233 start_time_str = sipe_utils_time_to_str(event->start_time);
7235 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7236 instance,
7237 publication_2 ? publication_2->version : 0,
7238 uri,
7239 start_time_str,
7240 availability_xml_str ? availability_xml_str : "",
7241 activity_xml_str ? activity_xml_str : "",
7242 event->subject ? event->subject : "",
7243 event->location ? event->location : "",
7245 instance,
7246 publication_3 ? publication_3->version : 0,
7247 uri,
7248 start_time_str,
7249 availability_xml_str ? availability_xml_str : "",
7250 activity_xml_str ? activity_xml_str : "",
7251 event->subject ? event->subject : "",
7252 event->location ? event->location : ""
7254 g_free(start_time_str);
7255 g_free(availability_xml_str);
7256 g_free(activity_xml_str);
7259 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7261 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7262 instance,
7263 publication_2 ? publication_2->version : 0,
7265 instance,
7266 publication_3 ? publication_3->version : 0
7270 return res;
7274 * Returns 'machineState' XML part for publication.
7275 * Must be g_free'd after use.
7277 static gchar *
7278 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7280 return sipe_publish_get_category_state(sip, FALSE);
7284 * Returns 'userState' XML part for publication.
7285 * Must be g_free'd after use.
7287 static gchar *
7288 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7290 return sipe_publish_get_category_state(sip, TRUE);
7294 * Returns 'note' XML part for publication.
7295 * Must be g_free'd after use.
7297 * Protocol format for Note is plain text.
7299 * @param note a note in Sipe internal HTML format
7300 * @param note_type either personal or OOF
7302 static gchar *
7303 sipe_publish_get_category_note(struct sipe_account_data *sip,
7304 const char *note, /* html */
7305 const char *note_type,
7306 time_t note_start,
7307 time_t note_end)
7309 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7310 /* key is <category><instance><container> */
7311 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7312 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7313 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7315 struct sipe_publication *publication_note_200 =
7316 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7317 struct sipe_publication *publication_note_300 =
7318 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7319 struct sipe_publication *publication_note_400 =
7320 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7322 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7323 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7324 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7325 char *res, *tmp1, *tmp2, *tmp3;
7326 char *start_time_attr;
7327 char *end_time_attr;
7329 g_free(tmp);
7330 tmp = NULL;
7331 g_free(key_note_200);
7332 g_free(key_note_300);
7333 g_free(key_note_400);
7335 /* we even need to republish empty note */
7336 if (sipe_strequal(n1, n2))
7338 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7339 g_free(n1);
7340 return NULL; /* nothing to update */
7343 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7344 g_free(tmp);
7345 tmp = NULL;
7346 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7347 g_free(tmp);
7349 if (n1) {
7350 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7351 instance,
7352 200,
7353 publication_note_200 ? publication_note_200->version : 0,
7354 note_type,
7355 start_time_attr ? start_time_attr : "",
7356 end_time_attr ? end_time_attr : "",
7357 n1);
7359 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7360 instance,
7361 300,
7362 publication_note_300 ? publication_note_300->version : 0,
7363 note_type,
7364 start_time_attr ? start_time_attr : "",
7365 end_time_attr ? end_time_attr : "",
7366 n1);
7368 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7369 instance,
7370 400,
7371 publication_note_400 ? publication_note_400->version : 0,
7372 note_type,
7373 start_time_attr ? start_time_attr : "",
7374 end_time_attr ? end_time_attr : "",
7375 n1);
7376 } else {
7377 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7378 "note",
7379 instance,
7380 200,
7381 publication_note_200 ? publication_note_200->version : 0,
7382 "static");
7383 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7384 "note",
7385 instance,
7386 300,
7387 publication_note_200 ? publication_note_200->version : 0,
7388 "static");
7389 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7390 "note",
7391 instance,
7392 400,
7393 publication_note_200 ? publication_note_200->version : 0,
7394 "static");
7396 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7398 g_free(start_time_attr);
7399 g_free(end_time_attr);
7400 g_free(tmp1);
7401 g_free(tmp2);
7402 g_free(tmp3);
7403 g_free(n1);
7405 return res;
7409 * Returns 'calendarData' XML part with WorkingHours for publication.
7410 * Must be g_free'd after use.
7412 static gchar *
7413 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7415 struct sipe_ews* ews = sip->ews;
7417 /* key is <category><instance><container> */
7418 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7419 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7420 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7421 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7422 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7423 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7425 struct sipe_publication *publication_cal_1 =
7426 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7427 struct sipe_publication *publication_cal_100 =
7428 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7429 struct sipe_publication *publication_cal_200 =
7430 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7431 struct sipe_publication *publication_cal_300 =
7432 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7433 struct sipe_publication *publication_cal_400 =
7434 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7435 struct sipe_publication *publication_cal_32000 =
7436 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7438 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7439 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7441 g_free(key_cal_1);
7442 g_free(key_cal_100);
7443 g_free(key_cal_200);
7444 g_free(key_cal_300);
7445 g_free(key_cal_400);
7446 g_free(key_cal_32000);
7448 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7449 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7450 return NULL;
7453 if (sipe_strequal(n1, n2))
7455 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7456 return NULL; /* nothing to update */
7459 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7460 /* 1 */
7461 publication_cal_1 ? publication_cal_1->version : 0,
7462 ews->email,
7463 ews->working_hours_xml_str,
7464 /* 100 - Public */
7465 publication_cal_100 ? publication_cal_100->version : 0,
7466 /* 200 - Company */
7467 publication_cal_200 ? publication_cal_200->version : 0,
7468 ews->email,
7469 ews->working_hours_xml_str,
7470 /* 300 - Team */
7471 publication_cal_300 ? publication_cal_300->version : 0,
7472 ews->email,
7473 ews->working_hours_xml_str,
7474 /* 400 - Personal */
7475 publication_cal_400 ? publication_cal_400->version : 0,
7476 ews->email,
7477 ews->working_hours_xml_str,
7478 /* 32000 - Blocked */
7479 publication_cal_32000 ? publication_cal_32000->version : 0
7484 * Returns 'calendarData' XML part with FreeBusy for publication.
7485 * Must be g_free'd after use.
7487 static gchar *
7488 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7490 struct sipe_ews* ews = sip->ews;
7491 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7492 char *fb_start_str;
7493 char *free_busy_base64;
7494 const char *st;
7495 const char *fb;
7496 char *res;
7498 /* key is <category><instance><container> */
7499 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7500 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7501 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7502 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7503 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7504 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7506 struct sipe_publication *publication_cal_1 =
7507 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7508 struct sipe_publication *publication_cal_100 =
7509 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7510 struct sipe_publication *publication_cal_200 =
7511 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7512 struct sipe_publication *publication_cal_300 =
7513 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7514 struct sipe_publication *publication_cal_400 =
7515 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7516 struct sipe_publication *publication_cal_32000 =
7517 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7519 g_free(key_cal_1);
7520 g_free(key_cal_100);
7521 g_free(key_cal_200);
7522 g_free(key_cal_300);
7523 g_free(key_cal_400);
7524 g_free(key_cal_32000);
7526 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7527 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7528 return NULL;
7531 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7532 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7534 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7535 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7537 /* we will rebuplish the same data to refresh publication time,
7538 * so if data from multiple sources, most recent will be choosen
7540 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7542 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7543 // g_free(fb_start_str);
7544 // g_free(free_busy_base64);
7545 // return NULL; /* nothing to update */
7548 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7549 /* 1 */
7550 cal_data_instance,
7551 publication_cal_1 ? publication_cal_1->version : 0,
7552 /* 100 - Public */
7553 cal_data_instance,
7554 publication_cal_100 ? publication_cal_100->version : 0,
7555 /* 200 - Company */
7556 cal_data_instance,
7557 publication_cal_200 ? publication_cal_200->version : 0,
7558 ews->email,
7559 fb_start_str,
7560 free_busy_base64,
7561 /* 300 - Team */
7562 cal_data_instance,
7563 publication_cal_300 ? publication_cal_300->version : 0,
7564 ews->email,
7565 fb_start_str,
7566 free_busy_base64,
7567 /* 400 - Personal */
7568 cal_data_instance,
7569 publication_cal_400 ? publication_cal_400->version : 0,
7570 ews->email,
7571 fb_start_str,
7572 free_busy_base64,
7573 /* 32000 - Blocked */
7574 cal_data_instance,
7575 publication_cal_32000 ? publication_cal_32000->version : 0
7578 g_free(fb_start_str);
7579 g_free(free_busy_base64);
7580 return res;
7583 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7585 gchar *uri;
7586 gchar *doc;
7587 gchar *tmp;
7588 gchar *hdr;
7590 uri = sip_uri_self(sip);
7591 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7592 uri,
7593 publications);
7595 tmp = get_contact(sip);
7596 hdr = g_strdup_printf("Contact: %s\r\n"
7597 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7599 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7601 g_free(tmp);
7602 g_free(hdr);
7603 g_free(uri);
7604 g_free(doc);
7607 static void
7608 send_publish_category_initial(struct sipe_account_data *sip)
7610 gchar *pub_device = sipe_publish_get_category_device(sip);
7611 gchar *pub_machine;
7612 gchar *publications;
7614 g_free(sip->status);
7615 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7617 pub_machine = sipe_publish_get_category_state_machine(sip);
7618 publications = g_strdup_printf("%s%s",
7619 pub_device,
7620 pub_machine ? pub_machine : "");
7621 g_free(pub_device);
7622 g_free(pub_machine);
7624 send_presence_publish(sip, publications);
7625 g_free(publications);
7628 static void
7629 send_presence_category_publish(struct sipe_account_data *sip)
7631 gchar *pub_state = sipe_is_user_state(sip) ?
7632 sipe_publish_get_category_state_user(sip) :
7633 sipe_publish_get_category_state_machine(sip);
7634 gchar *pub_note = sipe_publish_get_category_note(sip,
7635 sip->note,
7636 sip->is_oof_note ? "OOF" : "personal",
7639 gchar *publications;
7641 if (!pub_state && !pub_note) {
7642 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7643 return;
7646 publications = g_strdup_printf("%s%s",
7647 pub_state ? pub_state : "",
7648 pub_note ? pub_note : "");
7650 g_free(pub_state);
7651 g_free(pub_note);
7653 send_presence_publish(sip, publications);
7654 g_free(publications);
7658 * Publishes self status
7659 * based on own calendar information.
7661 * For 2007+
7663 void
7664 publish_calendar_status_self(struct sipe_account_data *sip)
7666 struct sipe_cal_event* event = NULL;
7667 gchar *pub_cal_working_hours = NULL;
7668 gchar *pub_cal_free_busy = NULL;
7669 gchar *pub_calendar = NULL;
7670 gchar *pub_calendar2 = NULL;
7671 gchar *pub_oof_note = NULL;
7672 const gchar *oof_note;
7673 time_t oof_start = 0;
7674 time_t oof_end = 0;
7676 if (!sip->ews) {
7677 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7678 return;
7681 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7682 if (sip->ews->cal_events) {
7683 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7686 if (!event) {
7687 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7688 } else {
7689 char *desc = sipe_cal_event_describe(event);
7690 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7691 g_free(desc);
7694 /* Logic
7695 if OOF
7696 OOF publish, Busy clean
7697 ilse if Busy
7698 OOF clean, Busy publish
7699 else
7700 OOF clean, Busy clean
7702 if (event && event->cal_status == SIPE_CAL_OOF) {
7703 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7704 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7705 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7706 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7707 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7708 } else {
7709 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7710 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7713 oof_note = sipe_ews_get_oof_note(sip->ews);
7714 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7715 oof_start = sip->ews->oof_start;
7716 oof_end = sip->ews->oof_end;
7718 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7720 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7721 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7723 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7724 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7725 } else {
7726 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7727 pub_cal_working_hours ? pub_cal_working_hours : "",
7728 pub_cal_free_busy ? pub_cal_free_busy : "",
7729 pub_calendar ? pub_calendar : "",
7730 pub_calendar2 ? pub_calendar2 : "",
7731 pub_oof_note ? pub_oof_note : "");
7733 send_presence_publish(sip, publications);
7734 g_free(publications);
7737 g_free(pub_cal_working_hours);
7738 g_free(pub_cal_free_busy);
7739 g_free(pub_calendar);
7740 g_free(pub_calendar2);
7741 g_free(pub_oof_note);
7743 /* repeat scheduling */
7744 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7747 static void send_presence_status(struct sipe_account_data *sip)
7749 PurpleStatus * status = purple_account_get_active_status(sip->account);
7751 if (!status) return;
7753 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7754 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7755 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7757 if (sip->ocs2007) {
7758 send_presence_category_publish(sip);
7759 } else {
7760 send_presence_soap(sip, FALSE);
7764 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7766 gboolean found = FALSE;
7767 const char *method = msg->method ? msg->method : "NOT FOUND";
7768 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7769 if (msg->response == 0) { /* request */
7770 if (sipe_strequal(method, "MESSAGE")) {
7771 process_incoming_message(sip, msg);
7772 found = TRUE;
7773 } else if (sipe_strequal(method, "NOTIFY")) {
7774 purple_debug_info("sipe","send->process_incoming_notify\n");
7775 process_incoming_notify(sip, msg, TRUE, FALSE);
7776 found = TRUE;
7777 } else if (sipe_strequal(method, "BENOTIFY")) {
7778 purple_debug_info("sipe","send->process_incoming_benotify\n");
7779 process_incoming_notify(sip, msg, TRUE, TRUE);
7780 found = TRUE;
7781 } else if (sipe_strequal(method, "INVITE")) {
7782 process_incoming_invite(sip, msg);
7783 found = TRUE;
7784 } else if (sipe_strequal(method, "REFER")) {
7785 process_incoming_refer(sip, msg);
7786 found = TRUE;
7787 } else if (sipe_strequal(method, "OPTIONS")) {
7788 process_incoming_options(sip, msg);
7789 found = TRUE;
7790 } else if (sipe_strequal(method, "INFO")) {
7791 process_incoming_info(sip, msg);
7792 found = TRUE;
7793 } else if (sipe_strequal(method, "ACK")) {
7794 // ACK's don't need any response
7795 found = TRUE;
7796 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7797 // LCS 2005 sends us these - just respond 200 OK
7798 found = TRUE;
7799 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7800 } else if (sipe_strequal(method, "BYE")) {
7801 process_incoming_bye(sip, msg);
7802 found = TRUE;
7803 } else {
7804 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7806 } else { /* response */
7807 struct transaction *trans = transactions_find(sip, msg);
7808 if (trans) {
7809 if (msg->response == 407) {
7810 gchar *resend, *auth;
7811 const gchar *ptmp;
7813 if (sip->proxy.retries > 30) return;
7814 sip->proxy.retries++;
7815 /* do proxy authentication */
7817 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7819 fill_auth(ptmp, &sip->proxy);
7820 auth = auth_header(sip, &sip->proxy, trans->msg);
7821 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7822 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7823 g_free(auth);
7824 resend = sipmsg_to_string(trans->msg);
7825 /* resend request */
7826 sendout_pkt(sip->gc, resend);
7827 g_free(resend);
7828 } else {
7829 if (msg->response < 200) {
7830 /* ignore provisional response */
7831 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7832 } else {
7833 sip->proxy.retries = 0;
7834 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7835 if (msg->response == 401)
7837 sip->registrar.retries++;
7839 else
7841 sip->registrar.retries = 0;
7843 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7844 } else {
7845 if (msg->response == 401) {
7846 gchar *resend, *auth, *ptmp;
7847 const char* auth_scheme;
7849 if (sip->registrar.retries > 4) return;
7850 sip->registrar.retries++;
7852 auth_scheme = sipe_get_auth_scheme_name(sip);
7853 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7855 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7856 if (!ptmp) {
7857 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7858 sip->gc->wants_to_die = TRUE;
7859 purple_connection_error(sip->gc, tmp2);
7860 g_free(tmp2);
7861 return;
7864 fill_auth(ptmp, &sip->registrar);
7865 auth = auth_header(sip, &sip->registrar, trans->msg);
7866 sipmsg_remove_header_now(trans->msg, "Authorization");
7867 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7868 g_free(auth);
7869 resend = sipmsg_to_string(trans->msg);
7870 /* resend request */
7871 sendout_pkt(sip->gc, resend);
7872 g_free(resend);
7876 if (trans->callback) {
7877 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7878 /* call the callback to process response*/
7879 (trans->callback)(sip, msg, trans);
7882 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7883 transactions_remove(sip, trans);
7887 found = TRUE;
7888 } else {
7889 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7892 if (!found) {
7893 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7897 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7899 char *cur;
7900 char *dummy;
7901 char *tmp;
7902 struct sipmsg *msg;
7903 int restlen;
7904 cur = conn->inbuf;
7906 /* according to the RFC remove CRLF at the beginning */
7907 while (*cur == '\r' || *cur == '\n') {
7908 cur++;
7910 if (cur != conn->inbuf) {
7911 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7912 conn->inbufused = strlen(conn->inbuf);
7915 /* Received a full Header? */
7916 sip->processing_input = TRUE;
7917 while (sip->processing_input &&
7918 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7919 time_t currtime = time(NULL);
7920 cur += 2;
7921 cur[0] = '\0';
7922 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7923 g_free(tmp);
7924 msg = sipmsg_parse_header(conn->inbuf);
7925 cur[0] = '\r';
7926 cur += 2;
7927 restlen = conn->inbufused - (cur - conn->inbuf);
7928 if (msg && restlen >= msg->bodylen) {
7929 dummy = g_malloc(msg->bodylen + 1);
7930 memcpy(dummy, cur, msg->bodylen);
7931 dummy[msg->bodylen] = '\0';
7932 msg->body = dummy;
7933 cur += msg->bodylen;
7934 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7935 conn->inbufused = strlen(conn->inbuf);
7936 } else {
7937 if (msg){
7938 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7939 sipmsg_free(msg);
7941 return;
7944 /*if (msg->body) {
7945 purple_debug_info("sipe", "body:\n%s", msg->body);
7948 // Verify the signature before processing it
7949 if (sip->registrar.gssapi_context) {
7950 struct sipmsg_breakdown msgbd;
7951 gchar *signature_input_str;
7952 gchar *rspauth;
7953 msgbd.msg = msg;
7954 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7955 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
7957 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7959 if (rspauth != NULL) {
7960 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7961 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7962 process_input_message(sip, msg);
7963 } else {
7964 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7965 purple_connection_error(sip->gc, _("Invalid message signature received"));
7966 sip->gc->wants_to_die = TRUE;
7968 } else if (msg->response == 401) {
7969 purple_connection_error(sip->gc, _("Wrong password"));
7970 sip->gc->wants_to_die = TRUE;
7972 g_free(signature_input_str);
7974 g_free(rspauth);
7975 sipmsg_breakdown_free(&msgbd);
7976 } else {
7977 process_input_message(sip, msg);
7980 sipmsg_free(msg);
7984 static void sipe_udp_process(gpointer data, gint source,
7985 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
7987 PurpleConnection *gc = data;
7988 struct sipe_account_data *sip = gc->proto_data;
7989 int len;
7991 static char buffer[65536];
7992 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
7993 time_t currtime = time(NULL);
7994 struct sipmsg *msg;
7995 buffer[len] = '\0';
7996 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
7997 msg = sipmsg_parse_msg(buffer);
7998 if (msg) process_input_message(sip, msg);
8002 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8004 struct sipe_account_data *sip = gc->proto_data;
8005 PurpleSslConnection *gsc = sip->gsc;
8007 purple_debug_error("sipe", "%s",debug);
8008 purple_connection_error(gc, msg);
8010 /* Invalidate this connection. Next send will open a new one */
8011 if (gsc) {
8012 connection_remove(sip, gsc->fd);
8013 purple_ssl_close(gsc);
8015 sip->gsc = NULL;
8016 sip->fd = -1;
8019 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8020 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8022 PurpleConnection *gc = data;
8023 struct sipe_account_data *sip;
8024 struct sip_connection *conn;
8025 int readlen, len;
8026 gboolean firstread = TRUE;
8028 /* NOTE: This check *IS* necessary */
8029 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8030 purple_ssl_close(gsc);
8031 return;
8034 sip = gc->proto_data;
8035 conn = connection_find(sip, gsc->fd);
8036 if (conn == NULL) {
8037 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
8038 gc->wants_to_die = TRUE;
8039 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8040 return;
8043 /* Read all available data from the SSL connection */
8044 do {
8045 /* Increase input buffer size as needed */
8046 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8047 conn->inbuflen += SIMPLE_BUF_INC;
8048 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8049 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
8052 /* Try to read as much as there is space left in the buffer */
8053 readlen = conn->inbuflen - conn->inbufused - 1;
8054 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8056 if (len < 0 && errno == EAGAIN) {
8057 /* Try again later */
8058 return;
8059 } else if (len < 0) {
8060 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8061 return;
8062 } else if (firstread && (len == 0)) {
8063 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8064 return;
8067 conn->inbufused += len;
8068 firstread = FALSE;
8070 /* Equivalence indicates that there is possibly more data to read */
8071 } while (len == readlen);
8073 conn->inbuf[conn->inbufused] = '\0';
8074 process_input(sip, conn);
8078 static void sipe_input_cb(gpointer data, gint source,
8079 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8081 PurpleConnection *gc = data;
8082 struct sipe_account_data *sip = gc->proto_data;
8083 int len;
8084 struct sip_connection *conn = connection_find(sip, source);
8085 if (!conn) {
8086 purple_debug_error("sipe", "Connection not found!\n");
8087 return;
8090 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8091 conn->inbuflen += SIMPLE_BUF_INC;
8092 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8095 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8097 if (len < 0 && errno == EAGAIN)
8098 return;
8099 else if (len <= 0) {
8100 purple_debug_info("sipe", "sipe_input_cb: read error\n");
8101 connection_remove(sip, source);
8102 if (sip->fd == source) sip->fd = -1;
8103 return;
8106 conn->inbufused += len;
8107 conn->inbuf[conn->inbufused] = '\0';
8109 process_input(sip, conn);
8112 /* Callback for new connections on incoming TCP port */
8113 static void sipe_newconn_cb(gpointer data, gint source,
8114 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8116 PurpleConnection *gc = data;
8117 struct sipe_account_data *sip = gc->proto_data;
8118 struct sip_connection *conn;
8120 int newfd = accept(source, NULL, NULL);
8122 conn = connection_create(sip, newfd);
8124 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8127 static void login_cb(gpointer data, gint source,
8128 SIPE_UNUSED_PARAMETER const gchar *error_message)
8130 PurpleConnection *gc = data;
8131 struct sipe_account_data *sip;
8132 struct sip_connection *conn;
8134 if (!PURPLE_CONNECTION_IS_VALID(gc))
8136 if (source >= 0)
8137 close(source);
8138 return;
8141 if (source < 0) {
8142 purple_connection_error(gc, _("Could not connect"));
8143 return;
8146 sip = gc->proto_data;
8147 sip->fd = source;
8148 sip->last_keepalive = time(NULL);
8150 conn = connection_create(sip, source);
8152 do_register(sip);
8154 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8157 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8158 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8160 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8161 if (sip == NULL) return;
8163 do_register(sip);
8166 static guint sipe_ht_hash_nick(const char *nick)
8168 char *lc = g_utf8_strdown(nick, -1);
8169 guint bucket = g_str_hash(lc);
8170 g_free(lc);
8172 return bucket;
8175 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8177 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8180 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8182 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8184 sip->listen_data = NULL;
8186 if (listenfd == -1) {
8187 purple_connection_error(sip->gc, _("Could not create listen socket"));
8188 return;
8191 sip->fd = listenfd;
8193 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8194 sip->listenfd = sip->fd;
8196 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8198 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8199 do_register(sip);
8202 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8203 SIPE_UNUSED_PARAMETER const char *error_message)
8205 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8207 sip->query_data = NULL;
8209 if (!hosts || !hosts->data) {
8210 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8211 return;
8214 hosts = g_slist_remove(hosts, hosts->data);
8215 g_free(sip->serveraddr);
8216 sip->serveraddr = hosts->data;
8217 hosts = g_slist_remove(hosts, hosts->data);
8218 while (hosts) {
8219 void *tmp = hosts->data;
8220 hosts = g_slist_remove(hosts, tmp);
8221 hosts = g_slist_remove(hosts, tmp);
8222 g_free(tmp);
8225 /* create socket for incoming connections */
8226 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8227 sipe_udp_host_resolved_listen_cb, sip);
8228 if (sip->listen_data == NULL) {
8229 purple_connection_error(sip->gc, _("Could not create listen socket"));
8230 return;
8234 static const struct sipe_service_data *current_service = NULL;
8236 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8237 PurpleSslErrorType error,
8238 gpointer data)
8240 PurpleConnection *gc = data;
8241 struct sipe_account_data *sip;
8243 /* If the connection is already disconnected, we don't need to do anything else */
8244 if (!PURPLE_CONNECTION_IS_VALID(gc))
8245 return;
8247 sip = gc->proto_data;
8248 current_service = sip->service_data;
8249 if (current_service) {
8250 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8251 current_service->transport ? current_service->transport : "NULL",
8252 current_service->service ? current_service->service : "NULL");
8255 sip->fd = -1;
8256 sip->gsc = NULL;
8258 switch(error) {
8259 case PURPLE_SSL_CONNECT_FAILED:
8260 purple_connection_error(gc, _("Connection failed"));
8261 break;
8262 case PURPLE_SSL_HANDSHAKE_FAILED:
8263 purple_connection_error(gc, _("SSL handshake failed"));
8264 break;
8265 case PURPLE_SSL_CERTIFICATE_INVALID:
8266 purple_connection_error(gc, _("SSL certificate invalid"));
8267 break;
8271 static void
8272 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8274 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8275 PurpleProxyConnectData *connect_data;
8277 sip->listen_data = NULL;
8279 sip->listenfd = listenfd;
8280 if (sip->listenfd == -1) {
8281 purple_connection_error(sip->gc, _("Could not create listen socket"));
8282 return;
8285 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8286 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8287 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8288 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8289 sipe_newconn_cb, sip->gc);
8290 purple_debug_info("sipe", "connecting to %s port %d\n",
8291 sip->realhostname, sip->realport);
8292 /* open tcp connection to the server */
8293 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8294 sip->realport, login_cb, sip->gc);
8296 if (connect_data == NULL) {
8297 purple_connection_error(sip->gc, _("Could not create socket"));
8301 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8303 PurpleAccount *account = sip->account;
8304 PurpleConnection *gc = sip->gc;
8306 if (port == 0) {
8307 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8310 sip->realhostname = hostname;
8311 sip->realport = port;
8313 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8314 hostname, port);
8316 /* TODO: is there a good default grow size? */
8317 if (sip->transport != SIPE_TRANSPORT_UDP)
8318 sip->txbuf = purple_circ_buffer_new(0);
8320 if (sip->transport == SIPE_TRANSPORT_TLS) {
8321 /* SSL case */
8322 if (!purple_ssl_is_supported()) {
8323 gc->wants_to_die = TRUE;
8324 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8325 return;
8328 purple_debug_info("sipe", "using SSL\n");
8330 sip->gsc = purple_ssl_connect(account, hostname, port,
8331 login_cb_ssl, sipe_ssl_connect_failure, gc);
8332 if (sip->gsc == NULL) {
8333 purple_connection_error(gc, _("Could not create SSL context"));
8334 return;
8336 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8337 /* UDP case */
8338 purple_debug_info("sipe", "using UDP\n");
8340 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8341 if (sip->query_data == NULL) {
8342 purple_connection_error(gc, _("Could not resolve hostname"));
8344 } else {
8345 /* TCP case */
8346 purple_debug_info("sipe", "using TCP\n");
8347 /* create socket for incoming connections */
8348 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8349 sipe_tcp_connect_listen_cb, sip);
8350 if (sip->listen_data == NULL) {
8351 purple_connection_error(gc, _("Could not create listen socket"));
8352 return;
8357 /* Service list for autodection */
8358 static const struct sipe_service_data service_autodetect[] = {
8359 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8360 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8361 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8362 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8363 { NULL, NULL, 0 }
8366 /* Service list for SSL/TLS */
8367 static const struct sipe_service_data service_tls[] = {
8368 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8369 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8370 { NULL, NULL, 0 }
8373 /* Service list for TCP */
8374 static const struct sipe_service_data service_tcp[] = {
8375 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8376 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8377 { NULL, NULL, 0 }
8380 /* Service list for UDP */
8381 static const struct sipe_service_data service_udp[] = {
8382 { "sip", "udp", SIPE_TRANSPORT_UDP },
8383 { NULL, NULL, 0 }
8386 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8387 static void resolve_next_service(struct sipe_account_data *sip,
8388 const struct sipe_service_data *start)
8390 if (start) {
8391 sip->service_data = start;
8392 } else {
8393 sip->service_data++;
8394 if (sip->service_data->service == NULL) {
8395 gchar *hostname;
8396 /* Try connecting to the SIP hostname directly */
8397 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8398 if (sip->auto_transport) {
8399 // If SSL is supported, default to using it; OCS servers aren't configured
8400 // by default to accept TCP
8401 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8402 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8403 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8406 hostname = g_strdup(sip->sipdomain);
8407 create_connection(sip, hostname, 0);
8408 return;
8412 /* Try to resolve next service */
8413 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8414 sip->service_data->transport,
8415 sip->sipdomain,
8416 srvresolved, sip);
8419 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8421 struct sipe_account_data *sip = data;
8423 sip->srv_query_data = NULL;
8425 /* find the host to connect to */
8426 if (results) {
8427 gchar *hostname = g_strdup(resp->hostname);
8428 int port = resp->port;
8429 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8430 hostname, port);
8431 g_free(resp);
8433 sip->transport = sip->service_data->type;
8435 create_connection(sip, hostname, port);
8436 } else {
8437 resolve_next_service(sip, NULL);
8441 static void sipe_login(PurpleAccount *account)
8443 PurpleConnection *gc;
8444 struct sipe_account_data *sip;
8445 gchar **signinname_login, **userserver;
8446 const char *transport;
8447 const char *email;
8449 const char *username = purple_account_get_username(account);
8450 gc = purple_account_get_connection(account);
8452 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8454 if (strpbrk(username, "\t\v\r\n") != NULL) {
8455 gc->wants_to_die = TRUE;
8456 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8457 return;
8460 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8461 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8462 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8463 sip->gc = gc;
8464 sip->account = account;
8465 sip->reregister_set = FALSE;
8466 sip->reauthenticate_set = FALSE;
8467 sip->subscribed = FALSE;
8468 sip->subscribed_buddies = FALSE;
8469 sip->initial_state_published = FALSE;
8471 /* username format: <username>,[<optional login>] */
8472 signinname_login = g_strsplit(username, ",", 2);
8473 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8475 /* ensure that username format is name@domain */
8476 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8477 g_strfreev(signinname_login);
8478 gc->wants_to_die = TRUE;
8479 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8480 return;
8482 sip->username = g_strdup(signinname_login[0]);
8484 /* ensure that email format is name@domain if provided */
8485 email = purple_account_get_string(sip->account, "email", NULL);
8486 if (!is_empty(email) &&
8487 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8489 gc->wants_to_die = TRUE;
8490 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8491 return;
8493 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8495 /* login name specified? */
8496 if (signinname_login[1] && strlen(signinname_login[1])) {
8497 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8498 gboolean has_domain = domain_user[1] != NULL;
8499 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8500 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8501 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8502 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8503 sip->authdomain ? sip->authdomain : "", sip->authuser);
8504 g_strfreev(domain_user);
8507 userserver = g_strsplit(signinname_login[0], "@", 2);
8508 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8509 purple_connection_set_display_name(gc, userserver[0]);
8510 sip->sipdomain = g_strdup(userserver[1]);
8511 g_strfreev(userserver);
8512 g_strfreev(signinname_login);
8514 if (strchr(sip->username, ' ') != NULL) {
8515 gc->wants_to_die = TRUE;
8516 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8517 return;
8520 sip->password = g_strdup(purple_connection_get_password(gc));
8522 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8523 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8524 g_free, (GDestroyNotify)g_hash_table_destroy);
8525 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8526 g_free, (GDestroyNotify)sipe_subscription_free);
8528 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8530 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8532 g_free(sip->status);
8533 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8535 sip->auto_transport = FALSE;
8536 transport = purple_account_get_string(account, "transport", "auto");
8537 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8538 if (userserver[0]) {
8539 /* Use user specified server[:port] */
8540 int port = 0;
8542 if (userserver[1])
8543 port = atoi(userserver[1]);
8545 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8546 userserver[0], port);
8548 if (sipe_strequal(transport, "auto")) {
8549 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8550 } else if (sipe_strequal(transport, "tls")) {
8551 sip->transport = SIPE_TRANSPORT_TLS;
8552 } else if (sipe_strequal(transport, "tcp")) {
8553 sip->transport = SIPE_TRANSPORT_TCP;
8554 } else {
8555 sip->transport = SIPE_TRANSPORT_UDP;
8558 create_connection(sip, g_strdup(userserver[0]), port);
8559 } else {
8560 /* Server auto-discovery */
8561 if (sipe_strequal(transport, "auto")) {
8562 sip->auto_transport = TRUE;
8563 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8564 current_service++;
8565 resolve_next_service(sip, current_service);
8566 } else {
8567 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8569 } else if (sipe_strequal(transport, "tls")) {
8570 resolve_next_service(sip, service_tls);
8571 } else if (sipe_strequal(transport, "tcp")) {
8572 resolve_next_service(sip, service_tcp);
8573 } else {
8574 resolve_next_service(sip, service_udp);
8577 g_strfreev(userserver);
8580 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8582 connection_free_all(sip);
8584 g_free(sip->epid);
8585 sip->epid = NULL;
8587 if (sip->query_data != NULL)
8588 purple_dnsquery_destroy(sip->query_data);
8589 sip->query_data = NULL;
8591 if (sip->srv_query_data != NULL)
8592 purple_srv_cancel(sip->srv_query_data);
8593 sip->srv_query_data = NULL;
8595 if (sip->listen_data != NULL)
8596 purple_network_listen_cancel(sip->listen_data);
8597 sip->listen_data = NULL;
8599 if (sip->gsc != NULL)
8600 purple_ssl_close(sip->gsc);
8601 sip->gsc = NULL;
8603 sipe_auth_free(&sip->registrar);
8604 sipe_auth_free(&sip->proxy);
8606 if (sip->txbuf)
8607 purple_circ_buffer_destroy(sip->txbuf);
8608 sip->txbuf = NULL;
8610 g_free(sip->realhostname);
8611 sip->realhostname = NULL;
8613 g_free(sip->server_version);
8614 sip->server_version = NULL;
8616 if (sip->listenpa)
8617 purple_input_remove(sip->listenpa);
8618 sip->listenpa = 0;
8619 if (sip->tx_handler)
8620 purple_input_remove(sip->tx_handler);
8621 sip->tx_handler = 0;
8622 if (sip->resendtimeout)
8623 purple_timeout_remove(sip->resendtimeout);
8624 sip->resendtimeout = 0;
8625 if (sip->timeouts) {
8626 GSList *entry = sip->timeouts;
8627 while (entry) {
8628 struct scheduled_action *sched_action = entry->data;
8629 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8630 purple_timeout_remove(sched_action->timeout_handler);
8631 if (sched_action->destroy) {
8632 (*sched_action->destroy)(sched_action->payload);
8634 g_free(sched_action->name);
8635 g_free(sched_action);
8636 entry = entry->next;
8639 g_slist_free(sip->timeouts);
8641 if (sip->allow_events) {
8642 GSList *entry = sip->allow_events;
8643 while (entry) {
8644 g_free(entry->data);
8645 entry = entry->next;
8648 g_slist_free(sip->allow_events);
8650 if (sip->containers) {
8651 GSList *entry = sip->containers;
8652 while (entry) {
8653 free_container((struct sipe_container *)entry->data);
8654 entry = entry->next;
8657 g_slist_free(sip->containers);
8659 if (sip->contact)
8660 g_free(sip->contact);
8661 sip->contact = NULL;
8662 if (sip->regcallid)
8663 g_free(sip->regcallid);
8664 sip->regcallid = NULL;
8666 if (sip->serveraddr)
8667 g_free(sip->serveraddr);
8668 sip->serveraddr = NULL;
8670 if (sip->focus_factory_uri)
8671 g_free(sip->focus_factory_uri);
8672 sip->focus_factory_uri = NULL;
8674 sip->fd = -1;
8675 sip->processing_input = FALSE;
8677 if (sip->ews) {
8678 sipe_ews_free(sip->ews);
8680 sip->ews = NULL;
8684 * A callback for g_hash_table_foreach_remove
8686 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8687 SIPE_UNUSED_PARAMETER gpointer user_data)
8689 sipe_free_buddy((struct sipe_buddy *) buddy);
8691 /* We must return TRUE as the key/value have already been deleted */
8692 return(TRUE);
8695 static void sipe_close(PurpleConnection *gc)
8697 struct sipe_account_data *sip = gc->proto_data;
8699 if (sip) {
8700 /* leave all conversations */
8701 sipe_session_close_all(sip);
8702 sipe_session_remove_all(sip);
8704 if (sip->csta) {
8705 sip_csta_close(sip);
8708 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8709 /* unsubscribe all */
8710 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8712 /* unregister */
8713 do_register_exp(sip, 0);
8716 sipe_connection_cleanup(sip);
8717 g_free(sip->sipdomain);
8718 g_free(sip->username);
8719 g_free(sip->email);
8720 g_free(sip->password);
8721 g_free(sip->authdomain);
8722 g_free(sip->authuser);
8723 g_free(sip->status);
8724 g_free(sip->note);
8726 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8727 g_hash_table_destroy(sip->buddies);
8728 g_hash_table_destroy(sip->our_publications);
8729 g_hash_table_destroy(sip->user_state_publications);
8730 g_hash_table_destroy(sip->subscriptions);
8731 g_hash_table_destroy(sip->filetransfers);
8733 if (sip->groups) {
8734 GSList *entry = sip->groups;
8735 while (entry) {
8736 struct sipe_group *group = entry->data;
8737 g_free(group->name);
8738 g_free(group);
8739 entry = entry->next;
8742 g_slist_free(sip->groups);
8744 if (sip->our_publication_keys) {
8745 GSList *entry = sip->our_publication_keys;
8746 while (entry) {
8747 g_free(entry->data);
8748 entry = entry->next;
8751 g_slist_free(sip->our_publication_keys);
8753 while (sip->transactions)
8754 transactions_remove(sip, sip->transactions->data);
8756 g_free(gc->proto_data);
8757 gc->proto_data = NULL;
8760 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8761 SIPE_UNUSED_PARAMETER void *user_data)
8763 PurpleAccount *acct = purple_connection_get_account(gc);
8764 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8765 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8766 if (conv == NULL)
8767 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8768 purple_conversation_present(conv);
8769 g_free(id);
8772 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8773 SIPE_UNUSED_PARAMETER void *user_data)
8776 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8777 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8780 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8781 SIPE_UNUSED_PARAMETER struct transaction *trans)
8783 PurpleNotifySearchResults *results;
8784 PurpleNotifySearchColumn *column;
8785 xmlnode *searchResults;
8786 xmlnode *mrow;
8787 int match_count = 0;
8788 gboolean more = FALSE;
8789 gchar *secondary;
8791 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8793 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8794 if (!searchResults) {
8795 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8796 return FALSE;
8799 results = purple_notify_searchresults_new();
8801 if (results == NULL) {
8802 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8803 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8805 xmlnode_free(searchResults);
8806 return FALSE;
8809 column = purple_notify_searchresults_column_new(_("User name"));
8810 purple_notify_searchresults_column_add(results, column);
8812 column = purple_notify_searchresults_column_new(_("Name"));
8813 purple_notify_searchresults_column_add(results, column);
8815 column = purple_notify_searchresults_column_new(_("Company"));
8816 purple_notify_searchresults_column_add(results, column);
8818 column = purple_notify_searchresults_column_new(_("Country"));
8819 purple_notify_searchresults_column_add(results, column);
8821 column = purple_notify_searchresults_column_new(_("Email"));
8822 purple_notify_searchresults_column_add(results, column);
8824 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8825 GList *row = NULL;
8827 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8828 row = g_list_append(row, g_strdup(uri_parts[1]));
8829 g_strfreev(uri_parts);
8831 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8832 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8833 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8834 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8836 purple_notify_searchresults_row_add(results, row);
8837 match_count++;
8840 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8841 char *data = xmlnode_get_data_unescaped(mrow);
8842 more = (g_strcasecmp(data, "true") == 0);
8843 g_free(data);
8846 secondary = g_strdup_printf(
8847 dngettext(GETTEXT_PACKAGE,
8848 "Found %d contact%s:",
8849 "Found %d contacts%s:", match_count),
8850 match_count, more ? _(" (more matched your query)") : "");
8852 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8853 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8854 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8856 g_free(secondary);
8857 xmlnode_free(searchResults);
8858 return TRUE;
8861 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8863 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8864 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8865 unsigned i = 0;
8867 if (!attrs) return;
8869 do {
8870 PurpleRequestField *field = entries->data;
8871 const char *id = purple_request_field_get_id(field);
8872 const char *value = purple_request_field_string_get_value(field);
8874 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8876 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8877 } while ((entries = g_list_next(entries)) != NULL);
8878 attrs[i] = NULL;
8880 if (i > 0) {
8881 struct sipe_account_data *sip = gc->proto_data;
8882 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8883 gchar *query = g_strjoinv(NULL, attrs);
8884 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8885 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8886 send_soap_request_with_cb(sip, domain_uri, body,
8887 (TransCallback) process_search_contact_response, NULL);
8888 g_free(domain_uri);
8889 g_free(body);
8890 g_free(query);
8893 g_strfreev(attrs);
8896 static void sipe_show_find_contact(PurplePluginAction *action)
8898 PurpleConnection *gc = (PurpleConnection *) action->context;
8899 PurpleRequestFields *fields;
8900 PurpleRequestFieldGroup *group;
8901 PurpleRequestField *field;
8903 fields = purple_request_fields_new();
8904 group = purple_request_field_group_new(NULL);
8905 purple_request_fields_add_group(fields, group);
8907 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8908 purple_request_field_group_add_field(group, field);
8909 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8910 purple_request_field_group_add_field(group, field);
8911 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8912 purple_request_field_group_add_field(group, field);
8913 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8914 purple_request_field_group_add_field(group, field);
8916 purple_request_fields(gc,
8917 _("Search"),
8918 _("Search for a contact"),
8919 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8920 fields,
8921 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8922 _("_Cancel"), NULL,
8923 purple_connection_get_account(gc), NULL, NULL, gc);
8926 static void sipe_show_about_plugin(PurplePluginAction *action)
8928 PurpleConnection *gc = (PurpleConnection *) action->context;
8929 char *tmp = g_strdup_printf(
8931 * Non-translatable parts, like markup, are hard-coded
8932 * into the format string. This requires more translatable
8933 * texts but it makes the translations less error prone.
8935 "<b><font size=\"+1\">SIPE " SIPE_VERSION " </font></b><br/>"
8936 "<br/>"
8937 /* 1 */ "%s:<br/>"
8938 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8939 "<li> - MS Office Communications Server 2007</li><br/>"
8940 "<li> - MS Live Communications Server 2005</li><br/>"
8941 "<li> - MS Live Communications Server 2003</li><br/>"
8942 "<li> - Reuters Messaging</li><br/>"
8943 "<br/>"
8944 /* 2 */ "%s: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8945 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8946 /* 5 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8947 /* 6 */ "%s: GPLv2+<br/>"
8948 "<br/>"
8949 /* 7 */ "%s:<br/>"
8950 " - CERN<br/>"
8951 " - Reuters Messaging network<br/>"
8952 " - Deutsche Bank<br/>"
8953 " - Merrill Lynch<br/>"
8954 " - Wachovia<br/>"
8955 " - Intel<br/>"
8956 " - Nokia<br/>"
8957 " - HP<br/>"
8958 " - Symantec<br/>"
8959 " - Accenture<br/>"
8960 " - Siemens<br/>"
8961 " - Alcatel-Lucent<br/>"
8962 " - BT<br/>"
8963 "<br/>"
8964 /* 8,9 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
8965 "<br/>"
8966 /* 10 */ "<b>%s:</b><br/>"
8967 " - Anibal Avelar<br/>"
8968 " - Gabriel Burt<br/>"
8969 " - Stefan Becker<br/>"
8970 " - pier11<br/>"
8971 " - Jakub Adam<br/>"
8972 " - Tomáš Hrabčík<br/>"
8973 "<br/>"
8974 /* 11 */ "%s<br/>"
8976 /* The next 11 texts make up the SIPE about note text */
8977 /* About note, part 1/11: introduction */
8978 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
8979 /* About note, part 2/11: home page URL (label) */
8980 _("Home"),
8981 /* About note, part 3/11: support forum URL (label) */
8982 _("Support"),
8983 /* About note, part 4/11: support forum name (hyperlink text) */
8984 _("Help Forum"),
8985 /* About note, part 5/11: translation service URL (label) */
8986 _("Translations"),
8987 /* About note, part 6/11: license type (label) */
8988 _("License"),
8989 /* About note, part 7/11: known users */
8990 _("We support users in such organizations as"),
8991 /* About note, part 8/11: translation request, text before Transifex.net URL */
8992 /* append a space if text is not empty */
8993 _("Please help us to translate SIPE to your native language here at "),
8994 /* About note, part 9/11: translation request, text after Transifex.net URL */
8995 /* start with a space if text is not empty */
8996 _(" using convenient web interface"),
8997 /* About note, part 10/11: author list (header) */
8998 _("Authors"),
8999 /* About note, part 11/11: Localization credit */
9000 /* PLEASE NOTE: do *NOT* simply translate the english original */
9001 /* but write something similar to the following sentence: */
9002 /* "Localization for <language name> (<language code>): <name>" */
9003 _("Original texts in English (en): SIPE developers")
9005 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9006 g_free(tmp);
9009 static void sipe_republish_calendar(PurplePluginAction *action)
9011 PurpleConnection *gc = (PurpleConnection *) action->context;
9012 struct sipe_account_data *sip = gc->proto_data;
9014 sipe_update_calendar(sip);
9017 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9018 gpointer value,
9019 GString* str)
9021 struct sipe_publication *publication = value;
9023 g_string_append_printf( str,
9024 SIPE_PUB_XML_PUBLICATION_CLEAR,
9025 publication->category,
9026 publication->instance,
9027 publication->container,
9028 publication->version,
9029 "static");
9032 static void sipe_reset_status(PurplePluginAction *action)
9034 PurpleConnection *gc = (PurpleConnection *) action->context;
9035 struct sipe_account_data *sip = gc->proto_data;
9037 if (sip->ocs2007) /* 2007+ */
9039 GString* str = g_string_new(NULL);
9040 gchar *publications;
9042 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9043 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
9044 return;
9047 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9048 publications = g_string_free(str, FALSE);
9050 send_presence_publish(sip, publications);
9051 g_free(publications);
9053 else /* 2005 */
9055 send_presence_soap0(sip, FALSE, TRUE);
9059 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9060 gpointer context)
9062 PurpleConnection *gc = (PurpleConnection *)context;
9063 struct sipe_account_data *sip = gc->proto_data;
9064 GList *menu = NULL;
9065 PurplePluginAction *act;
9066 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9068 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9069 menu = g_list_prepend(menu, act);
9071 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9072 menu = g_list_prepend(menu, act);
9074 if (sipe_strequal(calendar, "EXCH")) {
9075 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9076 menu = g_list_prepend(menu, act);
9079 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9080 menu = g_list_prepend(menu, act);
9082 menu = g_list_reverse(menu);
9084 return menu;
9087 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9091 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9093 return TRUE;
9097 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9099 return TRUE;
9103 static char *sipe_status_text(PurpleBuddy *buddy)
9105 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9106 const PurpleStatus *status = purple_presence_get_active_status(presence);
9107 const char *status_id = purple_status_get_id(status);
9108 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9109 struct sipe_buddy *sbuddy;
9110 char *text = NULL;
9112 if (!sip) return NULL; /* happens on pidgin exit */
9114 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9115 if (sbuddy) {
9116 const char *activity_str = sbuddy->activity ?
9117 sbuddy->activity :
9118 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9119 purple_status_get_name(status) : NULL;
9121 if (activity_str && sbuddy->note)
9123 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9125 else if (activity_str)
9127 text = g_strdup(activity_str);
9129 else if (sbuddy->note)
9131 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9135 return text;
9138 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9140 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9141 const PurpleStatus *status = purple_presence_get_active_status(presence);
9142 struct sipe_account_data *sip;
9143 struct sipe_buddy *sbuddy;
9144 char *note = NULL;
9145 gboolean is_oof_note = FALSE;
9146 char *activity = NULL;
9147 char *calendar = NULL;
9148 char *meeting_subject = NULL;
9149 char *meeting_location = NULL;
9151 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9152 if (sip) //happens on pidgin exit
9154 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9155 if (sbuddy)
9157 note = sbuddy->note;
9158 is_oof_note = sbuddy->is_oof_note;
9159 activity = sbuddy->activity;
9160 calendar = sipe_cal_get_description(sbuddy);
9161 meeting_subject = sbuddy->meeting_subject;
9162 meeting_location = sbuddy->meeting_location;
9166 //Layout
9167 if (purple_presence_is_online(presence))
9169 const char *status_str = activity ? activity : purple_status_get_name(status);
9171 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9173 if (purple_presence_is_online(presence) &&
9174 !is_empty(calendar))
9176 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9178 g_free(calendar);
9179 if (!is_empty(meeting_location))
9181 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9183 if (!is_empty(meeting_subject))
9185 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9188 if (note)
9190 char *tmp = g_strdup_printf("<i>%s</i>", note);
9191 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9193 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9194 g_free(tmp);
9199 #if PURPLE_VERSION_CHECK(2,5,0)
9200 static GHashTable *
9201 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9203 GHashTable *table;
9204 table = g_hash_table_new(g_str_hash, g_str_equal);
9205 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9206 return table;
9208 #endif
9210 static PurpleBuddy *
9211 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9213 PurpleBuddy *clone;
9214 const gchar *server_alias, *email;
9215 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9217 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9219 purple_blist_add_buddy(clone, NULL, group, NULL);
9221 server_alias = purple_buddy_get_server_alias(buddy);
9222 if (server_alias) {
9223 purple_blist_server_alias_buddy(clone, server_alias);
9226 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9227 if (email) {
9228 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9231 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9232 //for UI to update;
9233 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9234 return clone;
9237 static void
9238 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9240 PurpleBuddy *buddy, *b;
9241 PurpleConnection *gc;
9242 PurpleGroup * group = purple_find_group(group_name);
9244 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9246 buddy = (PurpleBuddy *)node;
9248 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9249 gc = purple_account_get_connection(buddy->account);
9251 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9252 if (!b){
9253 purple_blist_add_buddy_clone(group, buddy);
9256 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9259 static void
9260 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9262 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9264 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9266 /* 2007+ conference */
9267 if (sip->ocs2007)
9269 sipe_conf_add(sip, buddy->name);
9271 else /* 2005- multiparty chat */
9273 gchar *self = sip_uri_self(sip);
9274 struct sip_session *session;
9276 session = sipe_session_add_chat(sip);
9277 session->chat_title = sipe_chat_get_name(session->callid);
9278 session->roster_manager = g_strdup(self);
9280 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9281 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9282 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9283 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9285 g_free(self);
9289 static gboolean
9290 sipe_is_election_finished(struct sip_session *session)
9292 gboolean res = TRUE;
9294 SIPE_DIALOG_FOREACH {
9295 if (dialog->election_vote == 0) {
9296 res = FALSE;
9297 break;
9299 } SIPE_DIALOG_FOREACH_END;
9301 if (res) {
9302 session->is_voting_in_progress = FALSE;
9304 return res;
9307 static void
9308 sipe_election_start(struct sipe_account_data *sip,
9309 struct sip_session *session)
9311 int election_timeout;
9313 if (session->is_voting_in_progress) {
9314 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9315 return;
9316 } else {
9317 session->is_voting_in_progress = TRUE;
9319 session->bid = rand();
9321 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9323 SIPE_DIALOG_FOREACH {
9324 /* reset election_vote for each chat participant */
9325 dialog->election_vote = 0;
9327 /* send RequestRM to each chat participant*/
9328 sipe_send_election_request_rm(sip, dialog, session->bid);
9329 } SIPE_DIALOG_FOREACH_END;
9331 election_timeout = 15; /* sec */
9332 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9336 * @param who a URI to whom to invite to chat
9338 void
9339 sipe_invite_to_chat(struct sipe_account_data *sip,
9340 struct sip_session *session,
9341 const gchar *who)
9343 /* a conference */
9344 if (session->focus_uri)
9346 sipe_invite_conf(sip, session, who);
9348 else /* a multi-party chat */
9350 gchar *self = sip_uri_self(sip);
9351 if (session->roster_manager) {
9352 if (sipe_strequal(session->roster_manager, self)) {
9353 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9354 } else {
9355 sipe_refer(sip, session, who);
9357 } else {
9358 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9360 session->pending_invite_queue = slist_insert_unique_sorted(
9361 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9363 sipe_election_start(sip, session);
9365 g_free(self);
9369 void
9370 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9371 struct sip_session *session)
9373 gchar *invitee;
9374 GSList *entry = session->pending_invite_queue;
9376 while (entry) {
9377 invitee = entry->data;
9378 sipe_invite_to_chat(sip, session, invitee);
9379 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9380 g_free(invitee);
9384 static void
9385 sipe_election_result(struct sipe_account_data *sip,
9386 void *sess)
9388 struct sip_session *session = (struct sip_session *)sess;
9389 gchar *rival;
9390 gboolean has_won = TRUE;
9392 if (session->roster_manager) {
9393 purple_debug_info("sipe",
9394 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9395 return;
9398 session->is_voting_in_progress = FALSE;
9400 SIPE_DIALOG_FOREACH {
9401 if (dialog->election_vote < 0) {
9402 has_won = FALSE;
9403 rival = dialog->with;
9404 break;
9406 } SIPE_DIALOG_FOREACH_END;
9408 if (has_won) {
9409 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9411 session->roster_manager = sip_uri_self(sip);
9413 SIPE_DIALOG_FOREACH {
9414 /* send SetRM to each chat participant*/
9415 sipe_send_election_set_rm(sip, dialog);
9416 } SIPE_DIALOG_FOREACH_END;
9417 } else {
9418 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9420 session->bid = 0;
9422 sipe_process_pending_invite_queue(sip, session);
9426 * For 2007+ conference only.
9428 static void
9429 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9431 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9432 struct sip_session *session;
9434 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9435 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9437 session = sipe_session_find_chat_by_title(sip, chat_title);
9439 sipe_conf_modify_user_role(sip, session, buddy->name);
9443 * For 2007+ conference only.
9445 static void
9446 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9448 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9449 struct sip_session *session;
9451 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9452 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9454 session = sipe_session_find_chat_by_title(sip, chat_title);
9456 sipe_conf_delete_user(sip, session, buddy->name);
9459 static void
9460 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9462 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9463 struct sip_session *session;
9465 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9466 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9468 session = sipe_session_find_chat_by_title(sip, chat_title);
9470 sipe_invite_to_chat(sip, session, buddy->name);
9473 static void
9474 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9476 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9478 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9479 if (phone) {
9480 char *tel_uri = sip_to_tel_uri(phone);
9482 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9483 sip_csta_make_call(sip, tel_uri);
9485 g_free(tel_uri);
9489 static void
9490 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9492 const gchar *email;
9493 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9495 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9496 if (email)
9498 char *mailto = g_strdup_printf("mailto:%s", email);
9499 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9500 #ifndef _WIN32
9502 pid_t pid;
9503 char *const parmList[] = {"xdg-email", mailto, NULL};
9504 if ((pid = fork()) == -1)
9506 purple_debug_info("sipe", "fork() error\n");
9508 else if (pid == 0)
9510 execvp(parmList[0], parmList);
9511 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9514 #else
9516 BOOL ret;
9517 _flushall();
9518 errno = 0;
9519 //@TODO resolve env variable %WINDIR% first
9520 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9521 if (errno)
9523 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9526 #endif
9528 g_free(mailto);
9530 else
9532 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9537 * A menu which appear when right-clicking on buddy in contact list.
9539 static GList *
9540 sipe_buddy_menu(PurpleBuddy *buddy)
9542 PurpleBlistNode *g_node;
9543 PurpleGroup *group, *gr_parent;
9544 PurpleMenuAction *act;
9545 GList *menu = NULL;
9546 GList *menu_groups = NULL;
9547 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9548 const char *email;
9549 const char *phone;
9550 const char *phone_disp_str;
9551 gchar *self = sip_uri_self(sip);
9553 SIPE_SESSION_FOREACH {
9554 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9556 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9558 PurpleConvChatBuddyFlags flags;
9559 PurpleConvChatBuddyFlags flags_us;
9561 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9562 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9563 if (session->focus_uri
9564 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9565 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9567 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9568 act = purple_menu_action_new(label,
9569 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9570 session->chat_title, NULL);
9571 g_free(label);
9572 menu = g_list_prepend(menu, act);
9575 if (session->focus_uri
9576 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9578 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9579 act = purple_menu_action_new(label,
9580 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9581 session->chat_title, NULL);
9582 g_free(label);
9583 menu = g_list_prepend(menu, act);
9586 else
9588 if (!session->focus_uri
9589 || (session->focus_uri && !session->locked))
9591 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9592 act = purple_menu_action_new(label,
9593 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9594 session->chat_title, NULL);
9595 g_free(label);
9596 menu = g_list_prepend(menu, act);
9600 } SIPE_SESSION_FOREACH_END;
9602 act = purple_menu_action_new(_("New chat"),
9603 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9604 NULL, NULL);
9605 menu = g_list_prepend(menu, act);
9607 if (sip->csta && !sip->csta->line_status) {
9608 gchar *tmp = NULL;
9609 /* work phone */
9610 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9611 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9612 if (phone) {
9613 gchar *label = g_strdup_printf(_("Work %s"),
9614 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9615 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9616 g_free(tmp);
9617 tmp = NULL;
9618 g_free(label);
9619 menu = g_list_prepend(menu, act);
9622 /* mobile phone */
9623 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9624 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9625 if (phone) {
9626 gchar *label = g_strdup_printf(_("Mobile %s"),
9627 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9628 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9629 g_free(tmp);
9630 tmp = NULL;
9631 g_free(label);
9632 menu = g_list_prepend(menu, act);
9635 /* home phone */
9636 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9637 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9638 if (phone) {
9639 gchar *label = g_strdup_printf(_("Home %s"),
9640 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9641 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9642 g_free(tmp);
9643 tmp = NULL;
9644 g_free(label);
9645 menu = g_list_prepend(menu, act);
9648 /* other phone */
9649 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9650 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9651 if (phone) {
9652 gchar *label = g_strdup_printf(_("Other %s"),
9653 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9654 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9655 g_free(tmp);
9656 tmp = NULL;
9657 g_free(label);
9658 menu = g_list_prepend(menu, act);
9661 /* custom1 phone */
9662 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9663 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9664 if (phone) {
9665 gchar *label = g_strdup_printf(_("Custom1 %s"),
9666 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9667 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9668 g_free(tmp);
9669 tmp = NULL;
9670 g_free(label);
9671 menu = g_list_prepend(menu, act);
9675 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9676 if (email) {
9677 act = purple_menu_action_new(_("Send email..."),
9678 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9679 NULL, NULL);
9680 menu = g_list_prepend(menu, act);
9683 gr_parent = purple_buddy_get_group(buddy);
9684 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9685 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9686 continue;
9688 group = (PurpleGroup *)g_node;
9689 if (group == gr_parent)
9690 continue;
9692 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9693 continue;
9695 act = purple_menu_action_new(purple_group_get_name(group),
9696 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9697 group->name, NULL);
9698 menu_groups = g_list_prepend(menu_groups, act);
9700 menu_groups = g_list_reverse(menu_groups);
9702 act = purple_menu_action_new(_("Copy to"),
9703 NULL,
9704 NULL, menu_groups);
9705 menu = g_list_prepend(menu, act);
9706 menu = g_list_reverse(menu);
9708 g_free(self);
9709 return menu;
9712 static void
9713 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9715 struct sipe_account_data *sip = chat->account->gc->proto_data;
9716 struct sip_session *session;
9718 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9719 sipe_conf_modify_conference_lock(sip, session, locked);
9722 static void
9723 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9725 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9726 sipe_conf_modify_lock(chat, FALSE);
9729 static void
9730 sipe_chat_menu_lock_cb(PurpleChat *chat)
9732 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9733 sipe_conf_modify_lock(chat, TRUE);
9736 static GList *
9737 sipe_chat_menu(PurpleChat *chat)
9739 PurpleMenuAction *act;
9740 PurpleConvChatBuddyFlags flags_us;
9741 GList *menu = NULL;
9742 struct sipe_account_data *sip = chat->account->gc->proto_data;
9743 struct sip_session *session;
9744 gchar *self;
9746 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9747 if (!session) return NULL;
9749 self = sip_uri_self(sip);
9750 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9752 if (session->focus_uri
9753 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9755 if (session->locked) {
9756 act = purple_menu_action_new(_("Unlock"),
9757 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9758 NULL, NULL);
9759 menu = g_list_prepend(menu, act);
9760 } else {
9761 act = purple_menu_action_new(_("Lock"),
9762 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9763 NULL, NULL);
9764 menu = g_list_prepend(menu, act);
9768 menu = g_list_reverse(menu);
9770 g_free(self);
9771 return menu;
9774 static GList *
9775 sipe_blist_node_menu(PurpleBlistNode *node)
9777 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9778 return sipe_buddy_menu((PurpleBuddy *) node);
9779 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9780 return sipe_chat_menu((PurpleChat *)node);
9781 } else {
9782 return NULL;
9786 static gboolean
9787 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9789 char *uri = trans->payload->data;
9791 PurpleNotifyUserInfo *info;
9792 PurpleBuddy *pbuddy = NULL;
9793 struct sipe_buddy *sbuddy;
9794 const char *alias = NULL;
9795 char *device_name = NULL;
9796 char *server_alias = NULL;
9797 char *phone_number = NULL;
9798 char *email = NULL;
9799 const char *site;
9800 char *first_name = NULL;
9801 char *last_name = NULL;
9803 if (!sip) return FALSE;
9805 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9807 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9808 alias = purple_buddy_get_local_alias(pbuddy);
9810 //will query buddy UA's capabilities and send answer to log
9811 sipe_options_request(sip, uri);
9813 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9814 if (sbuddy) {
9815 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9818 info = purple_notify_user_info_new();
9820 if (msg->response != 200) {
9821 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9822 } else {
9823 xmlnode *searchResults;
9824 xmlnode *mrow;
9826 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9827 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9828 if (!searchResults) {
9829 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9830 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9831 const char *value;
9832 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9833 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9834 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9836 /* For 2007 system we will take this from ContactCard -
9837 * it has cleaner tel: URIs at least
9839 if (!sip->ocs2007) {
9840 char *tel_uri = sip_to_tel_uri(phone_number);
9841 /* trims its parameters, so call first */
9842 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9843 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9844 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9845 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9846 g_free(tel_uri);
9849 if (server_alias && strlen(server_alias) > 0) {
9850 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9852 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9853 purple_notify_user_info_add_pair(info, _("Job title"), value);
9855 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9856 purple_notify_user_info_add_pair(info, _("Office"), value);
9858 if (phone_number && strlen(phone_number) > 0) {
9859 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9861 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9862 purple_notify_user_info_add_pair(info, _("Company"), value);
9864 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9865 purple_notify_user_info_add_pair(info, _("City"), value);
9867 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9868 purple_notify_user_info_add_pair(info, _("State"), value);
9870 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9871 purple_notify_user_info_add_pair(info, _("Country"), value);
9873 if (email && strlen(email) > 0) {
9874 purple_notify_user_info_add_pair(info, _("Email address"), email);
9878 xmlnode_free(searchResults);
9881 purple_notify_user_info_add_section_break(info);
9883 if (is_empty(server_alias)) {
9884 g_free(server_alias);
9885 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9886 if (server_alias) {
9887 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9891 /* present alias if it differs from server alias */
9892 if (alias && !sipe_strequal(alias, server_alias))
9894 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9897 if (is_empty(email)) {
9898 g_free(email);
9899 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9900 if (email) {
9901 purple_notify_user_info_add_pair(info, _("Email address"), email);
9905 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9906 if (site) {
9907 purple_notify_user_info_add_pair(info, _("Site"), site);
9910 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9911 if (first_name && last_name) {
9912 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9914 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9915 g_free(link);
9917 g_free(first_name);
9918 g_free(last_name);
9920 if (device_name) {
9921 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9924 /* show a buddy's user info in a nice dialog box */
9925 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9926 uri, /* buddy's URI */
9927 info, /* body */
9928 NULL, /* callback called when dialog closed */
9929 NULL); /* userdata for callback */
9931 g_free(phone_number);
9932 g_free(server_alias);
9933 g_free(email);
9934 g_free(device_name);
9936 return TRUE;
9940 * AD search first, LDAP based
9942 static void sipe_get_info(PurpleConnection *gc, const char *username)
9944 struct sipe_account_data *sip = gc->proto_data;
9945 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9946 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9947 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9948 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9950 payload->destroy = g_free;
9951 payload->data = g_strdup(username);
9953 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9954 send_soap_request_with_cb(sip, domain_uri, body,
9955 (TransCallback) process_get_info_response, payload);
9956 g_free(domain_uri);
9957 g_free(body);
9958 g_free(row);
9961 static PurplePlugin *my_protocol = NULL;
9963 static PurplePluginProtocolInfo prpl_info =
9965 OPT_PROTO_CHAT_TOPIC,
9966 NULL, /* user_splits */
9967 NULL, /* protocol_options */
9968 NO_BUDDY_ICONS, /* icon_spec */
9969 sipe_list_icon, /* list_icon */
9970 NULL, /* list_emblems */
9971 sipe_status_text, /* status_text */
9972 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9973 sipe_status_types, /* away_states */
9974 sipe_blist_node_menu, /* blist_node_menu */
9975 NULL, /* chat_info */
9976 NULL, /* chat_info_defaults */
9977 sipe_login, /* login */
9978 sipe_close, /* close */
9979 sipe_im_send, /* send_im */
9980 NULL, /* set_info */ // TODO maybe
9981 sipe_send_typing, /* send_typing */
9982 sipe_get_info, /* get_info */
9983 sipe_set_status, /* set_status */
9984 sipe_set_idle, /* set_idle */
9985 NULL, /* change_passwd */
9986 sipe_add_buddy, /* add_buddy */
9987 NULL, /* add_buddies */
9988 sipe_remove_buddy, /* remove_buddy */
9989 NULL, /* remove_buddies */
9990 sipe_add_permit, /* add_permit */
9991 sipe_add_deny, /* add_deny */
9992 sipe_add_deny, /* rem_permit */
9993 sipe_add_permit, /* rem_deny */
9994 dummy_permit_deny, /* set_permit_deny */
9995 NULL, /* join_chat */
9996 NULL, /* reject_chat */
9997 NULL, /* get_chat_name */
9998 sipe_chat_invite, /* chat_invite */
9999 sipe_chat_leave, /* chat_leave */
10000 NULL, /* chat_whisper */
10001 sipe_chat_send, /* chat_send */
10002 sipe_keep_alive, /* keepalive */
10003 NULL, /* register_user */
10004 NULL, /* get_cb_info */ // deprecated
10005 NULL, /* get_cb_away */ // deprecated
10006 sipe_alias_buddy, /* alias_buddy */
10007 sipe_group_buddy, /* group_buddy */
10008 sipe_rename_group, /* rename_group */
10009 NULL, /* buddy_free */
10010 sipe_convo_closed, /* convo_closed */
10011 purple_normalize_nocase, /* normalize */
10012 NULL, /* set_buddy_icon */
10013 sipe_remove_group, /* remove_group */
10014 NULL, /* get_cb_real_name */ // TODO?
10015 NULL, /* set_chat_topic */
10016 NULL, /* find_blist_chat */
10017 NULL, /* roomlist_get_list */
10018 NULL, /* roomlist_cancel */
10019 NULL, /* roomlist_expand_category */
10020 NULL, /* can_receive_file */
10021 sipe_ft_send_file, /* send_file */
10022 sipe_ft_new_xfer, /* new_xfer */
10023 NULL, /* offline_message */
10024 NULL, /* whiteboard_prpl_ops */
10025 sipe_send_raw, /* send_raw */
10026 NULL, /* roomlist_room_serialize */
10027 NULL, /* unregister_user */
10028 NULL, /* send_attention */
10029 NULL, /* get_attention_types */
10030 #if !PURPLE_VERSION_CHECK(2,5,0)
10031 /* Backward compatibility when compiling against 2.4.x API */
10032 (void (*)(void)) /* _purple_reserved4 */
10033 #endif
10034 sizeof(PurplePluginProtocolInfo), /* struct_size */
10035 #if PURPLE_VERSION_CHECK(2,5,0)
10036 sipe_get_account_text_table, /* get_account_text_table */
10037 #if PURPLE_VERSION_CHECK(2,6,0)
10038 NULL, /* initiate_media */
10039 NULL, /* get_media_caps */
10040 #endif
10041 #endif
10045 static PurplePluginInfo info = {
10046 PURPLE_PLUGIN_MAGIC,
10047 PURPLE_MAJOR_VERSION,
10048 PURPLE_MINOR_VERSION,
10049 PURPLE_PLUGIN_PROTOCOL, /**< type */
10050 NULL, /**< ui_requirement */
10051 0, /**< flags */
10052 NULL, /**< dependencies */
10053 PURPLE_PRIORITY_DEFAULT, /**< priority */
10054 "prpl-sipe", /**< id */
10055 "Office Communicator", /**< name */
10056 SIPE_VERSION, /**< version */
10057 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10058 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10059 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10060 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10061 "Gabriel Burt <gburt@novell.com>, " /**< author */
10062 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10063 "pier11 <pier11@operamail.com>", /**< author */
10064 "http://sipe.sourceforge.net/", /**< homepage */
10065 sipe_plugin_load, /**< load */
10066 sipe_plugin_unload, /**< unload */
10067 sipe_plugin_destroy, /**< destroy */
10068 NULL, /**< ui_info */
10069 &prpl_info, /**< extra_info */
10070 NULL,
10071 sipe_actions,
10072 NULL,
10073 NULL,
10074 NULL,
10075 NULL
10078 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
10080 GList *entry;
10082 sip_sec_destroy();
10084 entry = prpl_info.protocol_options;
10085 while (entry) {
10086 purple_account_option_destroy(entry->data);
10087 entry = g_list_delete_link(entry, entry);
10089 prpl_info.protocol_options = NULL;
10091 entry = prpl_info.user_splits;
10092 while (entry) {
10093 purple_account_user_split_destroy(entry->data);
10094 entry = g_list_delete_link(entry, entry);
10096 prpl_info.user_splits = NULL;
10099 static void init_plugin(PurplePlugin *plugin)
10101 PurpleAccountUserSplit *split;
10102 PurpleAccountOption *option;
10104 srand(time(NULL));
10105 sip_sec_init();
10107 #ifdef ENABLE_NLS
10108 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
10109 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
10110 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
10111 textdomain(GETTEXT_PACKAGE);
10112 #endif
10114 purple_plugin_register(plugin);
10116 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
10117 purple_account_user_split_set_reverse(split, FALSE);
10118 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
10120 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
10121 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10123 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
10124 purple_account_option_add_list_item(option, _("Auto"), "auto");
10125 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
10126 purple_account_option_add_list_item(option, _("TCP"), "tcp");
10127 purple_account_option_add_list_item(option, _("UDP"), "udp");
10128 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10130 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
10131 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
10133 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
10134 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10136 #ifdef USE_KERBEROS
10137 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
10138 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10140 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
10141 * No login/password is taken into account if this option present,
10142 * instead used default credentials stored in OS.
10144 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
10145 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10146 #endif
10148 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
10149 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
10150 purple_account_option_add_list_item(option, _("None"), "NONE");
10151 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10153 /** Example: https://server.company.com/EWS/Exchange.asmx */
10154 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
10155 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10157 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
10158 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10160 /** Example: DOMAIN\user or user@company.com */
10161 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
10162 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10164 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
10165 purple_account_option_set_masked(option, TRUE);
10166 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10168 my_protocol = plugin;
10171 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
10174 Local Variables:
10175 mode: c
10176 c-file-style: "bsd"
10177 indent-tabs-mode: t
10178 tab-width: 8
10179 End: