mingw: build change
[siplcs.git] / src / core / sipe.c
blob6d7477404c43c98b6c5ba86c65b4fd65624cb9b4
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #ifdef _WIN32
41 #ifdef _DLL
42 #define _WS2TCPIP_H_
43 #define _WINSOCK2API_
44 #define _LIBC_INTERNAL_
45 #endif /* _DLL */
46 /* for network */
47 #include "libc_interface.h"
48 #else
49 #include <sys/types.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #endif /* _WIN32 */
54 #include <time.h>
55 #include <stdio.h>
56 #include <errno.h>
57 #include <string.h>
58 #include <unistd.h>
59 #include <glib.h>
61 #include "sipe-common.h"
63 #include "blist.h"
64 #include "conversation.h"
65 #include "dnsquery.h"
66 #include "debug.h"
67 #include "notify.h"
68 #include "savedstatuses.h"
69 #include "privacy.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 "core-depurple.h" /* Temporary for the core de-purple transition */
79 #include "sipe.h"
80 #include "sipe-cal.h"
81 #include "sipe-ews.h"
82 #include "sipe-chat.h"
83 #include "sipe-conf.h"
84 #include "sip-csta.h"
85 #include "sipe-dialog.h"
86 #include "sipe-nls.h"
87 #include "sipe-session.h"
88 #include "sipe-utils.h"
89 #include "sipe-ft.h"
90 #include "sipmsg.h"
91 #include "sipe-sign.h"
92 #include "dnssrv.h"
93 #include "request.h"
95 /* Backward compatibility when compiling against 2.4.x API */
96 #if !PURPLE_VERSION_CHECK(2,5,0)
97 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
98 #endif
100 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
102 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
103 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
105 /* Keep in sync with sipe_transport_type! */
106 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
107 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
109 /* Status identifiers (see also: sipe_status_types()) */
110 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
111 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
112 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
113 /* PURPLE_STATUS_UNAVAILABLE: */
114 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
115 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
116 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
117 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
118 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
119 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
120 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
121 /* PURPLE_STATUS_AWAY: */
122 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
123 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
124 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
125 /** Reuters status (user settable) */
126 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
127 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
128 /* ??? PURPLE_STATUS_MOBILE */
129 /* ??? PURPLE_STATUS_TUNE */
131 /* Status attributes (see also sipe_status_types() */
132 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
134 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
136 static struct sipe_activity_map_struct
138 sipe_activity type;
139 const char *token;
140 const char *desc;
141 const char *status_id;
143 } const sipe_activity_map[] =
145 /* This has nothing to do with Availability numbers, like 3500 (online).
146 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
148 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
149 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
150 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
151 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
152 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
153 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
154 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
155 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
156 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
157 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
158 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
159 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
160 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
161 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
162 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
164 /** @param x is sipe_activity */
165 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
168 /* Action name templates */
169 #define ACTION_NAME_PRESENCE "<presence><%s>"
171 static sipe_activity
172 sipe_get_activity_by_token(const char *token)
174 int i;
176 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
178 if (sipe_strequal(token, sipe_activity_map[i].token))
179 return sipe_activity_map[i].type;
182 return sipe_activity_map[0].type;
185 static const char *
186 sipe_get_activity_desc_by_token(const char *token)
188 if (!token) return NULL;
190 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
193 /** Allows to send typed messages from chat window again after account reinstantiation. */
194 static void
195 sipe_rejoin_chat(PurpleConversation *conv)
197 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
198 PURPLE_CONV_CHAT(conv)->left)
200 PURPLE_CONV_CHAT(conv)->left = FALSE;
201 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
205 static char *genbranch()
207 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
208 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
209 rand() & 0xFFFF, rand() & 0xFFFF);
213 static char *default_ua = NULL;
214 static const char*
215 sipe_get_useragent(struct sipe_account_data *sip)
217 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
218 if (is_empty(useragent)) {
219 if (!default_ua) {
220 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
221 /* ref: lzodefs.h */
222 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
223 #define SIPE_TARGET_PLATFORM "linux"
224 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
225 #define SIPE_TARGET_PLATFORM "bsd"
226 #elif defined(__APPLE__) || defined(__MACOS__)
227 #define SIPE_TARGET_PLATFORM "macosx"
228 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
229 #define SIPE_TARGET_PLATFORM "aix"
230 #elif defined(__solaris__) || defined(__sun)
231 #define SIPE_TARGET_PLATFORM "sun"
232 #elif defined(_WIN32)
233 #define SIPE_TARGET_PLATFORM "win"
234 #elif defined(__CYGWIN__)
235 #define SIPE_TARGET_PLATFORM "cygwin"
236 #elif defined(__hpux__)
237 #define SIPE_TARGET_PLATFORM "hpux"
238 #elif defined(__sgi__)
239 #define SIPE_TARGET_PLATFORM "irix"
240 #else
241 #define SIPE_TARGET_PLATFORM "unknown"
242 #endif
244 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
245 #define SIPE_TARGET_ARCH "x86_64"
246 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
247 #define SIPE_TARGET_ARCH "i386"
248 #elif defined(__ppc64__)
249 #define SIPE_TARGET_ARCH "ppc64"
250 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
251 #define SIPE_TARGET_ARCH "ppc"
252 #elif defined(__hppa__) || defined(__hppa)
253 #define SIPE_TARGET_ARCH "hppa"
254 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
255 #define SIPE_TARGET_ARCH "mips"
256 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
257 #define SIPE_TARGET_ARCH "s390"
258 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
259 #define SIPE_TARGET_ARCH "sparc"
260 #elif defined(__arm__)
261 #define SIPE_TARGET_ARCH "arm"
262 #else
263 #define SIPE_TARGET_ARCH "other"
264 #endif
266 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
267 purple_core_get_version(),
268 sip->server_version ? sip->server_version : "");
270 useragent = default_ua;
272 return useragent;
275 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
276 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
278 return "sipe";
281 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
283 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
284 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
285 gpointer data);
287 static void sipe_close(PurpleConnection *gc);
289 static void send_presence_status(struct sipe_account_data *sip);
291 static void sendout_pkt(PurpleConnection *gc, const char *buf);
293 static void sipe_keep_alive(PurpleConnection *gc)
295 struct sipe_account_data *sip = gc->proto_data;
296 if (sip->transport == SIPE_TRANSPORT_UDP) {
297 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
298 gchar buf[2] = {0, 0};
299 purple_debug_info("sipe", "sending keep alive\n");
300 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
301 } else {
302 time_t now = time(NULL);
303 if ((sip->keepalive_timeout > 0) &&
304 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
305 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
307 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
308 sendout_pkt(gc, "\r\n\r\n");
309 sip->last_keepalive = now;
314 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
316 struct sip_connection *ret = NULL;
317 GSList *entry = sip->openconns;
318 while (entry) {
319 ret = entry->data;
320 if (ret->fd == fd) return ret;
321 entry = entry->next;
323 return NULL;
326 static void sipe_auth_free(struct sip_auth *auth)
328 g_free(auth->opaque);
329 auth->opaque = NULL;
330 g_free(auth->realm);
331 auth->realm = NULL;
332 g_free(auth->target);
333 auth->target = NULL;
334 auth->version = 0;
335 auth->type = AUTH_TYPE_UNSET;
336 auth->retries = 0;
337 auth->expires = 0;
338 g_free(auth->gssapi_data);
339 auth->gssapi_data = NULL;
340 sip_sec_destroy_context(auth->gssapi_context);
341 auth->gssapi_context = NULL;
344 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
346 struct sip_connection *ret = g_new0(struct sip_connection, 1);
347 ret->fd = fd;
348 sip->openconns = g_slist_append(sip->openconns, ret);
349 return ret;
352 static void connection_remove(struct sipe_account_data *sip, int fd)
354 struct sip_connection *conn = connection_find(sip, fd);
355 if (conn) {
356 sip->openconns = g_slist_remove(sip->openconns, conn);
357 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
358 g_free(conn->inbuf);
359 g_free(conn);
363 static void connection_free_all(struct sipe_account_data *sip)
365 struct sip_connection *ret = NULL;
366 GSList *entry = sip->openconns;
367 while (entry) {
368 ret = entry->data;
369 connection_remove(sip, ret->fd);
370 entry = sip->openconns;
374 static void
375 sipe_make_signature(struct sipe_account_data *sip,
376 struct sipmsg *msg);
378 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
380 gchar noncecount[9];
381 const char *authuser = sip->authuser;
382 gchar *response;
383 gchar *ret;
385 if (!authuser || strlen(authuser) < 1) {
386 authuser = sip->username;
389 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
390 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
391 gchar *version_str;
393 // If we have a signature for the message, include that
394 if (msg->signature) {
395 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);
398 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
399 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
400 gchar *gssapi_data;
401 gchar *opaque;
402 gchar *sign_str = NULL;
404 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
405 &(auth->expires),
406 auth->type,
407 purple_account_get_bool(sip->account, "sso", TRUE),
408 sip->authdomain ? sip->authdomain : "",
409 authuser,
410 sip->password,
411 auth->target,
412 auth->gssapi_data);
413 if (!gssapi_data || !auth->gssapi_context) {
414 sip->gc->wants_to_die = TRUE;
415 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
416 return NULL;
419 if (auth->version > 3) {
420 sipe_make_signature(sip, msg);
421 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
422 msg->rand, msg->num, msg->signature);
423 } else {
424 sign_str = g_strdup("");
427 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
428 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
429 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);
430 g_free(opaque);
431 g_free(gssapi_data);
432 g_free(version_str);
433 g_free(sign_str);
434 return ret;
437 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
438 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
439 g_free(version_str);
440 return ret;
442 } else { /* Digest */
444 /* Calculate new session key */
445 if (!auth->opaque) {
446 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
447 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
448 authuser, auth->realm, sip->password,
449 auth->gssapi_data, NULL);
452 sprintf(noncecount, "%08d", auth->nc++);
453 response = purple_cipher_http_digest_calculate_response("md5",
454 msg->method, msg->target, NULL, NULL,
455 auth->gssapi_data, noncecount, NULL,
456 auth->opaque);
457 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
459 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);
460 g_free(response);
461 return ret;
465 static char *parse_attribute(const char *attrname, const char *source)
467 const char *tmp, *tmp2;
468 char *retval = NULL;
469 int len = strlen(attrname);
471 if (g_str_has_prefix(source, attrname)) {
472 tmp = source + len;
473 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
474 if (tmp2)
475 retval = g_strndup(tmp, tmp2 - tmp);
476 else
477 retval = g_strdup(tmp);
480 return retval;
483 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
485 int i;
486 gchar **parts;
488 if (!hdr) {
489 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
490 return;
493 if (!g_strncasecmp(hdr, "NTLM", 4)) {
494 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
495 auth->type = AUTH_TYPE_NTLM;
496 hdr += 5;
497 auth->nc = 1;
498 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
499 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
500 auth->type = AUTH_TYPE_KERBEROS;
501 hdr += 9;
502 auth->nc = 3;
503 } else {
504 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
505 auth->type = AUTH_TYPE_DIGEST;
506 hdr += 7;
509 parts = g_strsplit(hdr, "\", ", 0);
510 for (i = 0; parts[i]; i++) {
511 char *tmp;
513 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
515 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
516 g_free(auth->gssapi_data);
517 auth->gssapi_data = tmp;
519 if (auth->type == AUTH_TYPE_NTLM) {
520 /* NTLM module extracts nonce from gssapi-data */
521 auth->nc = 3;
524 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
525 /* Only used with AUTH_TYPE_DIGEST */
526 g_free(auth->gssapi_data);
527 auth->gssapi_data = tmp;
528 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
529 g_free(auth->opaque);
530 auth->opaque = tmp;
531 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
532 g_free(auth->realm);
533 auth->realm = tmp;
535 if (auth->type == AUTH_TYPE_DIGEST) {
536 /* Throw away old session key */
537 g_free(auth->opaque);
538 auth->opaque = NULL;
539 auth->nc = 1;
541 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
542 g_free(auth->target);
543 auth->target = tmp;
544 } else if ((tmp = parse_attribute("version=", parts[i]))) {
545 auth->version = atoi(tmp);
546 g_free(tmp);
548 // uncomment to revert to previous functionality if version 3+ does not work.
549 // auth->version = 2;
551 g_strfreev(parts);
553 return;
556 static void sipe_canwrite_cb(gpointer data,
557 SIPE_UNUSED_PARAMETER gint source,
558 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
560 PurpleConnection *gc = data;
561 struct sipe_account_data *sip = gc->proto_data;
562 gsize max_write;
563 gssize written;
565 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
567 if (max_write == 0) {
568 if (sip->tx_handler != 0){
569 purple_input_remove(sip->tx_handler);
570 sip->tx_handler = 0;
572 return;
575 written = write(sip->fd, sip->txbuf->outptr, max_write);
577 if (written < 0 && errno == EAGAIN)
578 written = 0;
579 else if (written <= 0) {
580 /*TODO: do we really want to disconnect on a failure to write?*/
581 purple_connection_error(gc, _("Could not write"));
582 return;
585 purple_circ_buffer_mark_read(sip->txbuf, written);
588 static void sipe_canwrite_cb_ssl(gpointer data,
589 SIPE_UNUSED_PARAMETER gint src,
590 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
592 PurpleConnection *gc = data;
593 struct sipe_account_data *sip = gc->proto_data;
594 gsize max_write;
595 gssize written;
597 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
599 if (max_write == 0) {
600 if (sip->tx_handler != 0) {
601 purple_input_remove(sip->tx_handler);
602 sip->tx_handler = 0;
603 return;
607 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
609 if (written < 0 && errno == EAGAIN)
610 written = 0;
611 else if (written <= 0) {
612 /*TODO: do we really want to disconnect on a failure to write?*/
613 purple_connection_error(gc, _("Could not write"));
614 return;
617 purple_circ_buffer_mark_read(sip->txbuf, written);
620 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
622 static void send_later_cb(gpointer data, gint source,
623 SIPE_UNUSED_PARAMETER const gchar *error)
625 PurpleConnection *gc = data;
626 struct sipe_account_data *sip;
627 struct sip_connection *conn;
629 if (!PURPLE_CONNECTION_IS_VALID(gc))
631 if (source >= 0)
632 close(source);
633 return;
636 if (source < 0) {
637 purple_connection_error(gc, _("Could not connect"));
638 return;
641 sip = gc->proto_data;
642 sip->fd = source;
643 sip->connecting = FALSE;
644 sip->last_keepalive = time(NULL);
646 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
648 /* If there is more to write now, we need to register a handler */
649 if (sip->txbuf->bufused > 0)
650 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
652 conn = connection_create(sip, source);
653 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
656 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
658 struct sipe_account_data *sip;
660 if (!PURPLE_CONNECTION_IS_VALID(gc))
662 if (gsc) purple_ssl_close(gsc);
663 return NULL;
666 sip = gc->proto_data;
667 sip->fd = gsc->fd;
668 sip->gsc = gsc;
669 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
670 sip->connecting = FALSE;
671 sip->last_keepalive = time(NULL);
673 connection_create(sip, gsc->fd);
675 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
677 return sip;
680 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
681 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
683 PurpleConnection *gc = data;
684 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
685 if (sip == NULL) return;
687 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
689 /* If there is more to write now */
690 if (sip->txbuf->bufused > 0) {
691 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
696 static void sendlater(PurpleConnection *gc, const char *buf)
698 struct sipe_account_data *sip = gc->proto_data;
700 if (!sip->connecting) {
701 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
702 if (sip->transport == SIPE_TRANSPORT_TLS){
703 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
704 } else {
705 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
706 purple_connection_error(gc, _("Could not create socket"));
709 sip->connecting = TRUE;
712 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
713 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
715 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
718 static void sendout_pkt(PurpleConnection *gc, const char *buf)
720 struct sipe_account_data *sip = gc->proto_data;
721 time_t currtime = time(NULL);
722 int writelen = strlen(buf);
723 char *tmp;
725 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
726 g_free(tmp);
727 if (sip->transport == SIPE_TRANSPORT_UDP) {
728 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
729 purple_debug_info("sipe", "could not send packet\n");
731 } else {
732 int ret;
733 if (sip->fd < 0) {
734 sendlater(gc, buf);
735 return;
738 if (sip->tx_handler) {
739 ret = -1;
740 errno = EAGAIN;
741 } else{
742 if (sip->gsc){
743 ret = purple_ssl_write(sip->gsc, buf, writelen);
744 }else{
745 ret = write(sip->fd, buf, writelen);
749 if (ret < 0 && errno == EAGAIN)
750 ret = 0;
751 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
752 sendlater(gc, buf);
753 return;
756 if (ret < writelen) {
757 if (!sip->tx_handler){
758 if (sip->gsc){
759 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
761 else{
762 sip->tx_handler = purple_input_add(sip->fd,
763 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
764 gc);
768 /* XXX: is it OK to do this? You might get part of a request sent
769 with part of another. */
770 if (sip->txbuf->bufused > 0)
771 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
773 purple_circ_buffer_append(sip->txbuf, buf + ret,
774 writelen - ret);
779 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
781 sendout_pkt(gc, buf);
782 return len;
785 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
787 GSList *tmp = msg->headers;
788 gchar *name;
789 gchar *value;
790 GString *outstr = g_string_new("");
791 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
792 while (tmp) {
793 name = ((struct sipnameval*) (tmp->data))->name;
794 value = ((struct sipnameval*) (tmp->data))->value;
795 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
796 tmp = g_slist_next(tmp);
798 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
799 sendout_pkt(sip->gc, outstr->str);
800 g_string_free(outstr, TRUE);
803 static void
804 sipe_make_signature(struct sipe_account_data *sip,
805 struct sipmsg *msg)
807 if (sip->registrar.gssapi_context) {
808 struct sipmsg_breakdown msgbd;
809 gchar *signature_input_str;
810 msgbd.msg = msg;
811 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
812 msgbd.rand = g_strdup_printf("%08x", g_random_int());
813 sip->registrar.ntlm_num++;
814 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
815 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
816 if (signature_input_str != NULL) {
817 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
818 msg->signature = signature_hex;
819 msg->rand = g_strdup(msgbd.rand);
820 msg->num = g_strdup(msgbd.num);
821 g_free(signature_input_str);
823 sipmsg_breakdown_free(&msgbd);
827 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
829 gchar * buf;
831 if (sip->registrar.type == AUTH_TYPE_UNSET) {
832 return;
835 sipe_make_signature(sip, msg);
837 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
838 buf = auth_header(sip, &sip->registrar, msg);
839 if (buf) {
840 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
842 g_free(buf);
843 } 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")) {
844 sip->registrar.nc = 3;
845 sip->registrar.type = AUTH_TYPE_NTLM;
846 #ifdef HAVE_KERBEROS
847 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
848 sip->registrar.type = AUTH_TYPE_KERBEROS;
850 #endif
853 buf = auth_header(sip, &sip->registrar, msg);
854 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
855 g_free(buf);
856 } else {
857 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
861 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
862 const char *text, const char *body)
864 gchar *name;
865 gchar *value;
866 GString *outstr = g_string_new("");
867 struct sipe_account_data *sip = gc->proto_data;
868 gchar *contact;
869 GSList *tmp;
870 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
872 /* Can return NULL! */
873 contact = get_contact(sip);
874 if (contact) {
875 sipmsg_add_header(msg, "Contact", contact);
876 g_free(contact);
879 if (body) {
880 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
881 sipmsg_add_header(msg, "Content-Length", len);
882 g_free(len);
883 } else {
884 sipmsg_add_header(msg, "Content-Length", "0");
887 msg->response = code;
889 sipmsg_strip_headers(msg, keepers);
890 sipmsg_merge_new_headers(msg);
891 sign_outgoing_message(msg, sip, msg->method);
893 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
894 tmp = msg->headers;
895 while (tmp) {
896 name = ((struct sipnameval*) (tmp->data))->name;
897 value = ((struct sipnameval*) (tmp->data))->value;
899 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
900 tmp = g_slist_next(tmp);
902 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
903 sendout_pkt(gc, outstr->str);
904 g_string_free(outstr, TRUE);
907 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
909 if (sip->transactions) {
910 sip->transactions = g_slist_remove(sip->transactions, trans);
911 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
913 if (trans->msg) sipmsg_free(trans->msg);
914 if (trans->payload) {
915 (*trans->payload->destroy)(trans->payload->data);
916 g_free(trans->payload);
918 g_free(trans->key);
919 g_free(trans);
923 static struct transaction *
924 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
926 const gchar *call_id;
927 const gchar *cseq;
928 struct transaction *trans = g_new0(struct transaction, 1);
930 trans->time = time(NULL);
931 trans->msg = (struct sipmsg *)msg;
932 call_id = sipmsg_find_header(trans->msg, "Call-ID");
933 cseq = sipmsg_find_header(trans->msg, "CSeq");
934 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
935 trans->callback = callback;
936 sip->transactions = g_slist_append(sip->transactions, trans);
937 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
938 return trans;
941 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
943 struct transaction *trans;
944 GSList *transactions = sip->transactions;
945 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
946 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
947 gchar *key;
949 if (!call_id || !cseq) {
950 purple_debug(PURPLE_DEBUG_ERROR, "sipe", "transaction_find: no Call-ID or CSeq!\n");
951 return NULL;
954 key = g_strdup_printf("<%s><%s>", call_id, cseq);
955 while (transactions) {
956 trans = transactions->data;
957 if (!g_strcasecmp(trans->key, key)) {
958 g_free(key);
959 return trans;
961 transactions = transactions->next;
964 g_free(key);
965 return NULL;
968 struct transaction *
969 send_sip_request(PurpleConnection *gc, const gchar *method,
970 const gchar *url, const gchar *to, const gchar *addheaders,
971 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
973 struct sipe_account_data *sip = gc->proto_data;
974 const char *addh = "";
975 char *buf;
976 struct sipmsg *msg;
977 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
978 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
979 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
980 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
981 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
982 gchar *route = g_strdup("");
983 gchar *epid = get_epid(sip);
984 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
985 struct transaction *trans = NULL;
987 if (dialog && dialog->routes)
989 GSList *iter = dialog->routes;
991 while(iter)
993 char *tmp = route;
994 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
995 g_free(tmp);
996 iter = g_slist_next(iter);
1000 if (!ourtag && !dialog) {
1001 ourtag = gentag();
1004 if (sipe_strequal(method, "REGISTER")) {
1005 if (sip->regcallid) {
1006 g_free(callid);
1007 callid = g_strdup(sip->regcallid);
1008 } else {
1009 sip->regcallid = g_strdup(callid);
1011 cseq = ++sip->cseq;
1014 if (addheaders) addh = addheaders;
1016 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1017 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1018 "From: <sip:%s>%s%s;epid=%s\r\n"
1019 "To: <%s>%s%s%s%s\r\n"
1020 "Max-Forwards: 70\r\n"
1021 "CSeq: %d %s\r\n"
1022 "User-Agent: %s\r\n"
1023 "Call-ID: %s\r\n"
1024 "%s%s"
1025 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1026 method,
1027 dialog && dialog->request ? dialog->request : url,
1028 TRANSPORT_DESCRIPTOR,
1029 purple_network_get_my_ip(-1),
1030 sip->listenport,
1031 branch ? ";branch=" : "",
1032 branch ? branch : "",
1033 sip->username,
1034 ourtag ? ";tag=" : "",
1035 ourtag ? ourtag : "",
1036 epid,
1038 theirtag ? ";tag=" : "",
1039 theirtag ? theirtag : "",
1040 theirepid ? ";epid=" : "",
1041 theirepid ? theirepid : "",
1042 cseq,
1043 method,
1044 sipe_get_useragent(sip),
1045 callid,
1046 route,
1047 addh,
1048 body ? (gsize) strlen(body) : 0,
1049 body ? body : "");
1052 //printf ("parsing msg buf:\n%s\n\n", buf);
1053 msg = sipmsg_parse_msg(buf);
1055 g_free(buf);
1056 g_free(ourtag);
1057 g_free(theirtag);
1058 g_free(theirepid);
1059 g_free(branch);
1060 g_free(callid);
1061 g_free(route);
1062 g_free(epid);
1064 sign_outgoing_message (msg, sip, method);
1066 buf = sipmsg_to_string (msg);
1068 /* add to ongoing transactions */
1069 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1070 if (!sipe_strequal(method, "ACK")) {
1071 trans = transactions_add_buf(sip, msg, tc);
1072 } else {
1073 sipmsg_free(msg);
1075 sendout_pkt(gc, buf);
1076 g_free(buf);
1078 return trans;
1082 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1084 static void
1085 send_soap_request_with_cb(struct sipe_account_data *sip,
1086 gchar *from0,
1087 gchar *body,
1088 TransCallback callback,
1089 struct transaction_payload *payload)
1091 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1092 gchar *contact = get_contact(sip);
1093 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1094 "Content-Type: application/SOAP+xml\r\n",contact);
1096 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1097 trans->payload = payload;
1099 g_free(from);
1100 g_free(contact);
1101 g_free(hdr);
1104 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1106 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1109 static char *get_contact_register(struct sipe_account_data *sip)
1111 char *epid = get_epid(sip);
1112 char *uuid = generateUUIDfromEPID(epid);
1113 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);
1114 g_free(uuid);
1115 g_free(epid);
1116 return(buf);
1119 static void do_register_exp(struct sipe_account_data *sip, int expire)
1121 char *uri;
1122 char *expires;
1123 char *to;
1124 char *contact;
1125 char *hdr;
1127 if (!sip->sipdomain) return;
1129 uri = sip_uri_from_name(sip->sipdomain);
1130 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1131 to = sip_uri_self(sip);
1132 contact = get_contact_register(sip);
1133 hdr = g_strdup_printf("Contact: %s\r\n"
1134 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1135 "Event: registration\r\n"
1136 "Allow-Events: presence\r\n"
1137 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1138 "%s", contact, expires);
1139 g_free(contact);
1140 g_free(expires);
1142 sip->registerstatus = 1;
1144 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1145 process_register_response);
1147 g_free(hdr);
1148 g_free(uri);
1149 g_free(to);
1152 static void do_register_cb(struct sipe_account_data *sip,
1153 SIPE_UNUSED_PARAMETER void *unused)
1155 do_register_exp(sip, -1);
1156 sip->reregister_set = FALSE;
1159 static void do_register(struct sipe_account_data *sip)
1161 do_register_exp(sip, -1);
1164 static void
1165 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1167 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1168 send_soap_request(sip, body);
1169 g_free(body);
1172 static void
1173 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1175 if (allow) {
1176 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1177 } else {
1178 purple_debug_info("sipe", "Blocking contact %s\n", who);
1181 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1184 static
1185 void sipe_auth_user_cb(void * data)
1187 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1188 if (!job) return;
1190 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1191 g_free(job);
1194 static
1195 void sipe_deny_user_cb(void * data)
1197 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1198 if (!job) return;
1200 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1201 g_free(job);
1204 static void
1205 sipe_add_permit(PurpleConnection *gc, const char *name)
1207 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1208 sipe_contact_allow_deny(sip, name, TRUE);
1211 static void
1212 sipe_add_deny(PurpleConnection *gc, const char *name)
1214 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1215 sipe_contact_allow_deny(sip, name, FALSE);
1218 /*static void
1219 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1221 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1222 sipe_contact_set_acl(sip, name, "");
1225 static void
1226 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1228 xmlnode *watchers;
1229 xmlnode *watcher;
1230 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1231 if (msg->response != 0 && msg->response != 200) return;
1233 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1235 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1236 if (!watchers) return;
1238 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1239 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1240 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1241 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1243 // TODO pull out optional displayName to pass as alias
1244 if (remote_user) {
1245 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1246 job->who = remote_user;
1247 job->sip = sip;
1248 purple_account_request_authorization(
1249 sip->account,
1250 remote_user,
1251 _("you"), /* id */
1252 alias,
1253 NULL, /* message */
1254 on_list,
1255 sipe_auth_user_cb,
1256 sipe_deny_user_cb,
1257 (void *) job);
1262 xmlnode_free(watchers);
1263 return;
1266 static void
1267 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1269 PurpleGroup * purple_group = purple_find_group(group->name);
1270 if (!purple_group) {
1271 purple_group = purple_group_new(group->name);
1272 purple_blist_add_group(purple_group, NULL);
1275 if (purple_group) {
1276 group->purple_group = purple_group;
1277 sip->groups = g_slist_append(sip->groups, group);
1278 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1279 } else {
1280 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1284 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1286 struct sipe_group *group;
1287 GSList *entry;
1288 if (sip == NULL) {
1289 return NULL;
1292 entry = sip->groups;
1293 while (entry) {
1294 group = entry->data;
1295 if (group->id == id) {
1296 return group;
1298 entry = entry->next;
1300 return NULL;
1303 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1305 struct sipe_group *group;
1306 GSList *entry;
1307 if (!sip || !name) {
1308 return NULL;
1311 entry = sip->groups;
1312 while (entry) {
1313 group = entry->data;
1314 if (sipe_strequal(group->name, name)) {
1315 return group;
1317 entry = entry->next;
1319 return NULL;
1322 static void
1323 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1325 gchar *body;
1326 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1327 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1328 send_soap_request(sip, body);
1329 g_free(body);
1330 g_free(group->name);
1331 group->name = g_strdup(name);
1335 * Only appends if no such value already stored.
1336 * Like Set in Java.
1338 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1339 GSList * res = list;
1340 if (!g_slist_find_custom(list, data, func)) {
1341 res = g_slist_insert_sorted(list, data, func);
1343 return res;
1346 static int
1347 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1348 return group1->id - group2->id;
1352 * Returns string like "2 4 7 8" - group ids buddy belong to.
1354 static gchar *
1355 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1356 int i = 0;
1357 gchar *res;
1358 //creating array from GList, converting int to gchar*
1359 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1360 GSList *entry = buddy->groups;
1362 if (!ids_arr) return NULL;
1364 while (entry) {
1365 struct sipe_group * group = entry->data;
1366 ids_arr[i] = g_strdup_printf("%d", group->id);
1367 entry = entry->next;
1368 i++;
1370 ids_arr[i] = NULL;
1371 res = g_strjoinv(" ", ids_arr);
1372 g_strfreev(ids_arr);
1373 return res;
1377 * Sends buddy update to server
1379 static void
1380 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1382 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1383 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1385 if (buddy && purple_buddy) {
1386 const char *alias = purple_buddy_get_alias(purple_buddy);
1387 gchar *groups = sipe_get_buddy_groups_string(buddy);
1388 if (groups) {
1389 gchar *body;
1390 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1392 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1393 alias, groups, "true", buddy->name, sip->contacts_delta++
1395 send_soap_request(sip, body);
1396 g_free(groups);
1397 g_free(body);
1402 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1404 if (msg->response == 200) {
1405 struct sipe_group *group;
1406 struct group_user_context *ctx = trans->payload->data;
1407 xmlnode *xml;
1408 xmlnode *node;
1409 char *group_id;
1410 struct sipe_buddy *buddy;
1412 xml = xmlnode_from_str(msg->body, msg->bodylen);
1413 if (!xml) {
1414 return FALSE;
1417 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1418 if (!node) {
1419 xmlnode_free(xml);
1420 return FALSE;
1423 group_id = xmlnode_get_data(node);
1424 if (!group_id) {
1425 xmlnode_free(xml);
1426 return FALSE;
1429 group = g_new0(struct sipe_group, 1);
1430 group->id = (int)g_ascii_strtod(group_id, NULL);
1431 g_free(group_id);
1432 group->name = g_strdup(ctx->group_name);
1434 sipe_group_add(sip, group);
1436 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1437 if (buddy) {
1438 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1441 sipe_group_set_user(sip, ctx->user_name);
1443 xmlnode_free(xml);
1444 return TRUE;
1446 return FALSE;
1449 static void sipe_group_context_destroy(gpointer data)
1451 struct group_user_context *ctx = data;
1452 g_free(ctx->group_name);
1453 g_free(ctx->user_name);
1454 g_free(ctx);
1457 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1459 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1460 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1461 gchar *body;
1462 ctx->group_name = g_strdup(name);
1463 ctx->user_name = g_strdup(who);
1464 payload->destroy = sipe_group_context_destroy;
1465 payload->data = ctx;
1467 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1468 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1469 g_free(body);
1473 * Data structure for scheduled actions
1476 struct scheduled_action {
1478 * Name of action.
1479 * Format is <Event>[<Data>...]
1480 * Example: <presence><sip:user@domain.com> or <registration>
1482 gchar *name;
1483 guint timeout_handler;
1484 gboolean repetitive;
1485 Action action;
1486 GDestroyNotify destroy;
1487 struct sipe_account_data *sip;
1488 void *payload;
1492 * A timer callback
1493 * Should return FALSE if repetitive action is not needed
1495 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1497 gboolean ret;
1498 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1499 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1500 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1501 (sched_action->action)(sched_action->sip, sched_action->payload);
1502 ret = sched_action->repetitive;
1503 if (sched_action->destroy) {
1504 (*sched_action->destroy)(sched_action->payload);
1506 g_free(sched_action->name);
1507 g_free(sched_action);
1508 return ret;
1512 * Kills action timer effectively cancelling
1513 * scheduled action
1515 * @param name of action
1517 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1519 GSList *entry;
1521 if (!sip->timeouts || !name) return;
1523 entry = sip->timeouts;
1524 while (entry) {
1525 struct scheduled_action *sched_action = entry->data;
1526 if(sipe_strequal(sched_action->name, name)) {
1527 GSList *to_delete = entry;
1528 entry = entry->next;
1529 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1530 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1531 purple_timeout_remove(sched_action->timeout_handler);
1532 if (sched_action->destroy) {
1533 (*sched_action->destroy)(sched_action->payload);
1535 g_free(sched_action->name);
1536 g_free(sched_action);
1537 } else {
1538 entry = entry->next;
1543 static void
1544 sipe_schedule_action0(const gchar *name,
1545 int timeout,
1546 gboolean isSeconds,
1547 Action action,
1548 GDestroyNotify destroy,
1549 struct sipe_account_data *sip,
1550 void *payload)
1552 struct scheduled_action *sched_action;
1554 /* Make sure each action only exists once */
1555 sipe_cancel_scheduled_action(sip, name);
1557 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1558 sched_action = g_new0(struct scheduled_action, 1);
1559 sched_action->repetitive = FALSE;
1560 sched_action->name = g_strdup(name);
1561 sched_action->action = action;
1562 sched_action->destroy = destroy;
1563 sched_action->sip = sip;
1564 sched_action->payload = payload;
1565 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1566 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1567 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1568 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1571 void
1572 sipe_schedule_action(const gchar *name,
1573 int timeout,
1574 Action action,
1575 GDestroyNotify destroy,
1576 struct sipe_account_data *sip,
1577 void *payload)
1579 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1583 * Same as sipe_schedule_action() but timeout is in milliseconds.
1585 static void
1586 sipe_schedule_action_msec(const gchar *name,
1587 int timeout,
1588 Action action,
1589 GDestroyNotify destroy,
1590 struct sipe_account_data *sip,
1591 void *payload)
1593 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1596 static void
1597 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1598 time_t calculate_from);
1600 static int
1601 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1603 static const char*
1604 sipe_get_status_by_availability(int avail,
1605 char** activity);
1607 static void
1608 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1609 const char *status_id,
1610 const char *message,
1611 time_t do_not_publish[]);
1613 static void
1614 sipe_apply_calendar_status(struct sipe_account_data *sip,
1615 struct sipe_buddy *sbuddy,
1616 const char *status_id)
1618 time_t cal_avail_since;
1619 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1620 int avail;
1621 gchar *self_uri;
1623 if (!sbuddy) return;
1625 if (cal_status < SIPE_CAL_NO_DATA) {
1626 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_status : %d for %s\n", cal_status, sbuddy->name);
1627 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1630 /* scheduled Cal update call */
1631 if (!status_id) {
1632 status_id = sbuddy->last_non_cal_status_id;
1633 g_free(sbuddy->activity);
1634 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1637 if (!status_id) {
1638 purple_debug_info("sipe", "sipe_apply_calendar_status: status_id is NULL for %s, exiting.\n",
1639 sbuddy->name ? sbuddy->name : "" );
1640 return;
1643 /* adjust to calendar status */
1644 if (cal_status != SIPE_CAL_NO_DATA) {
1645 purple_debug_info("sipe", "sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1647 if (cal_status == SIPE_CAL_BUSY
1648 && cal_avail_since > sbuddy->user_avail_since
1649 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1651 status_id = SIPE_STATUS_ID_BUSY;
1652 g_free(sbuddy->activity);
1653 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1655 avail = sipe_get_availability_by_status(status_id, NULL);
1657 purple_debug_info("sipe", "sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1658 if (cal_avail_since > sbuddy->activity_since) {
1659 if (cal_status == SIPE_CAL_OOF
1660 && avail >= 15000) /* 12000 in 2007 */
1662 g_free(sbuddy->activity);
1663 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1668 /* then set status_id actually */
1669 purple_debug_info("sipe", "sipe_apply_calendar_status: to %s for %s\n", status_id, sbuddy->name ? sbuddy->name : "" );
1670 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1672 /* set our account state to the one in roaming (including calendar info) */
1673 self_uri = sip_uri_self(sip);
1674 if (sip->initial_state_published && sipe_strequal(sbuddy->name, self_uri)) {
1675 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1676 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1679 purple_debug_info("sipe", "sipe_apply_calendar_status: switch to '%s' for the account\n", sip->status);
1680 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1682 g_free(self_uri);
1685 static void
1686 sipe_got_user_status(struct sipe_account_data *sip,
1687 const char* uri,
1688 const char *status_id)
1690 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1692 if (!sbuddy) return;
1694 /* Check if on 2005 system contact's calendar,
1695 * then set/preserve it.
1697 if (!sip->ocs2007) {
1698 sipe_apply_calendar_status(sip, sbuddy, status_id);
1699 } else {
1700 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1704 static void
1705 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1706 struct sipe_buddy *sbuddy,
1707 struct sipe_account_data *sip)
1709 sipe_apply_calendar_status(sip, sbuddy, NULL);
1713 * Updates contact's status
1714 * based on their calendar information.
1716 * Applicability: 2005 systems
1718 static void
1719 update_calendar_status(struct sipe_account_data *sip)
1721 purple_debug_info("sipe", "update_calendar_status() started.\n");
1722 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1724 /* repeat scheduling */
1725 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1729 * Schedules process of contacts' status update
1730 * based on their calendar information.
1731 * Should be scheduled to the beginning of every
1732 * 15 min interval, like:
1733 * 13:00, 13:15, 13:30, 13:45, etc.
1735 * Applicability: 2005 systems
1737 static void
1738 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1739 time_t calculate_from)
1741 int interval = 15*60;
1742 /** start of the beginning of closest 15 min interval. */
1743 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1745 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1746 asctime(localtime(&calculate_from)));
1747 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1748 asctime(localtime(&next_start)));
1750 sipe_schedule_action("<+2005-cal-status>",
1751 (int)(next_start - time(NULL)),
1752 (Action)update_calendar_status,
1753 NULL,
1754 sip,
1755 NULL);
1759 * Schedules process of self status publish
1760 * based on own calendar information.
1761 * Should be scheduled to the beginning of every
1762 * 15 min interval, like:
1763 * 13:00, 13:15, 13:30, 13:45, etc.
1765 * Applicability: 2007+ systems
1767 static void
1768 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1769 time_t calculate_from)
1771 int interval = 5*60;
1772 /** start of the beginning of closest 5 min interval. */
1773 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1775 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1776 asctime(localtime(&calculate_from)));
1777 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1778 asctime(localtime(&next_start)));
1780 sipe_schedule_action("<+2007-cal-status>",
1781 (int)(next_start - time(NULL)),
1782 (Action)publish_calendar_status_self,
1783 NULL,
1784 sip,
1785 NULL);
1788 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1790 /** Should be g_free()'d
1792 static gchar *
1793 sipe_get_subscription_key(const gchar *event,
1794 const gchar *with)
1796 gchar *key = NULL;
1798 if (is_empty(event)) return NULL;
1800 if (event && !g_ascii_strcasecmp(event, "presence")) {
1801 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1802 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1804 /* @TODO drop participated buddies' just_added flag */
1805 } else if (event) {
1806 /* Subscription is identified by <event> key */
1807 key = g_strdup_printf("<%s>", event);
1810 return key;
1813 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1814 SIPE_UNUSED_PARAMETER struct transaction *trans)
1816 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1817 const gchar *event = sipmsg_find_header(msg, "Event");
1818 gchar *key;
1820 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1821 if (!event) {
1822 struct sipmsg *request_msg = trans->msg;
1823 event = sipmsg_find_header(request_msg, "Event");
1826 key = sipe_get_subscription_key(event, with);
1828 /* 200 OK; 481 Call Leg Does Not Exist */
1829 if (key && (msg->response == 200 || msg->response == 481)) {
1830 if (g_hash_table_lookup(sip->subscriptions, key)) {
1831 g_hash_table_remove(sip->subscriptions, key);
1832 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1836 /* create/store subscription dialog if not yet */
1837 if (msg->response == 200) {
1838 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1839 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1841 if (key) {
1842 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1843 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1845 subscription->dialog.callid = g_strdup(callid);
1846 subscription->dialog.cseq = atoi(cseq);
1847 subscription->dialog.with = g_strdup(with);
1848 subscription->event = g_strdup(event);
1849 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1851 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1854 g_free(cseq);
1857 g_free(key);
1858 g_free(with);
1860 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1862 process_incoming_notify(sip, msg, FALSE, FALSE);
1864 return TRUE;
1867 static void sipe_subscribe_resource_uri(const char *name,
1868 SIPE_UNUSED_PARAMETER gpointer value,
1869 gchar **resources_uri)
1871 gchar *tmp = *resources_uri;
1872 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1873 g_free(tmp);
1876 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1878 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1879 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1880 gchar *tmp = *resources_uri;
1882 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1884 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1885 g_free(tmp);
1889 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1890 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1891 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1892 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1893 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1896 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1898 gchar *key;
1899 gchar *contact = get_contact(sip);
1900 gchar *request;
1901 gchar *content;
1902 gchar *require = "";
1903 gchar *accept = "";
1904 gchar *autoextend = "";
1905 gchar *content_type;
1906 struct sip_dialog *dialog;
1908 if (sip->ocs2007) {
1909 require = ", categoryList";
1910 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1911 content_type = "application/msrtc-adrl-categorylist+xml";
1912 content = g_strdup_printf(
1913 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1914 "<action name=\"subscribe\" id=\"63792024\">\n"
1915 "<adhocList>\n%s</adhocList>\n"
1916 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1917 "<category name=\"calendarData\"/>\n"
1918 "<category name=\"contactCard\"/>\n"
1919 "<category name=\"note\"/>\n"
1920 "<category name=\"state\"/>\n"
1921 "</categoryList>\n"
1922 "</action>\n"
1923 "</batchSub>", sip->username, resources_uri);
1924 } else {
1925 autoextend = "Supported: com.microsoft.autoextend\r\n";
1926 content_type = "application/adrl+xml";
1927 content = g_strdup_printf(
1928 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1929 "<create xmlns=\"\">\n%s</create>\n"
1930 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1932 g_free(resources_uri);
1934 request = g_strdup_printf(
1935 "Require: adhoclist%s\r\n"
1936 "Supported: eventlist\r\n"
1937 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1938 "Supported: ms-piggyback-first-notify\r\n"
1939 "%sSupported: ms-benotify\r\n"
1940 "Proxy-Require: ms-benotify\r\n"
1941 "Event: presence\r\n"
1942 "Content-Type: %s\r\n"
1943 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1944 g_free(contact);
1946 /* subscribe to buddy presence */
1947 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1948 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1949 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1950 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1952 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1954 g_free(content);
1955 g_free(to);
1956 g_free(request);
1957 g_free(key);
1960 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1961 SIPE_UNUSED_PARAMETER void *unused)
1963 gchar *to = sip_uri_self(sip);
1964 gchar *resources_uri = g_strdup("");
1965 if (sip->ocs2007) {
1966 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1967 } else {
1968 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1971 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1974 struct presence_batched_routed {
1975 gchar *host;
1976 GSList *buddies;
1979 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1981 struct presence_batched_routed *data = payload;
1982 GSList *buddies = data->buddies;
1983 while (buddies) {
1984 g_free(buddies->data);
1985 buddies = buddies->next;
1987 g_slist_free(data->buddies);
1988 g_free(data->host);
1989 g_free(payload);
1992 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1994 struct presence_batched_routed *data = payload;
1995 GSList *buddies = data->buddies;
1996 gchar *resources_uri = g_strdup("");
1997 while (buddies) {
1998 gchar *tmp = resources_uri;
1999 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2000 g_free(tmp);
2001 buddies = buddies->next;
2003 sipe_subscribe_presence_batched_to(sip, resources_uri,
2004 g_strdup(data->host));
2008 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2009 * The user sends a single SUBSCRIBE request to the subscribed contact.
2010 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2014 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2017 gchar *key;
2018 gchar *to = sip_uri((char *)buddy_name);
2019 gchar *tmp = get_contact(sip);
2020 gchar *request;
2021 gchar *content = NULL;
2022 gchar *autoextend = "";
2023 gchar *content_type = "";
2024 struct sip_dialog *dialog;
2025 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2026 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2028 if (sbuddy) sbuddy->just_added = FALSE;
2030 if (sip->ocs2007) {
2031 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2032 } else {
2033 autoextend = "Supported: com.microsoft.autoextend\r\n";
2036 request = g_strdup_printf(
2037 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2038 "Supported: ms-piggyback-first-notify\r\n"
2039 "%s%sSupported: ms-benotify\r\n"
2040 "Proxy-Require: ms-benotify\r\n"
2041 "Event: presence\r\n"
2042 "Contact: %s\r\n", autoextend, content_type, tmp);
2044 if (sip->ocs2007) {
2045 content = g_strdup_printf(
2046 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2047 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2048 "<resource uri=\"%s\"%s\n"
2049 "</adhocList>\n"
2050 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2051 "<category name=\"calendarData\"/>\n"
2052 "<category name=\"contactCard\"/>\n"
2053 "<category name=\"note\"/>\n"
2054 "<category name=\"state\"/>\n"
2055 "</categoryList>\n"
2056 "</action>\n"
2057 "</batchSub>", sip->username, to, context);
2060 g_free(tmp);
2062 /* subscribe to buddy presence */
2063 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2064 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2065 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2066 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2068 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2070 g_free(content);
2071 g_free(to);
2072 g_free(request);
2073 g_free(key);
2076 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2078 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2080 if (!purple_status_is_active(status))
2081 return;
2083 if (account->gc) {
2084 struct sipe_account_data *sip = account->gc->proto_data;
2086 if (sip) {
2087 gchar *action_name;
2088 gchar *tmp;
2089 time_t now = time(NULL);
2090 const char *status_id = purple_status_get_id(status);
2091 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2092 sipe_activity activity = sipe_get_activity_by_token(status_id);
2093 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2095 /* when other point of presence clears note, but we are keeping
2096 * state if OOF note.
2098 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2099 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2100 do_not_publish = FALSE;
2103 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2104 status_id, (int)sip->do_not_publish[activity], (int)now);
2106 sip->do_not_publish[activity] = 0;
2107 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2108 status_id, (int)sip->do_not_publish[activity]);
2110 if (do_not_publish)
2112 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2113 return;
2116 g_free(sip->status);
2117 sip->status = g_strdup(status_id);
2119 /* hack to escape apostrof before comparison */
2120 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2122 /* this will preserve OOF flag as well */
2123 if (!sipe_strequal(tmp, sip->note)) {
2124 sip->is_oof_note = FALSE;
2125 g_free(sip->note);
2126 sip->note = g_strdup(note);
2127 sip->note_since = time(NULL);
2129 g_free(tmp);
2131 /* schedule 2 sec to capture idle flag */
2132 action_name = g_strdup_printf("<%s>", "+set-status");
2133 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2134 g_free(action_name);
2138 static void
2139 sipe_set_idle(PurpleConnection * gc,
2140 int interval)
2142 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2144 if (gc) {
2145 struct sipe_account_data *sip = gc->proto_data;
2147 if (sip) {
2148 sip->idle_switch = time(NULL);
2149 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2154 static void
2155 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2156 SIPE_UNUSED_PARAMETER const char *alias)
2158 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2159 sipe_group_set_user(sip, name);
2162 static void
2163 sipe_group_buddy(PurpleConnection *gc,
2164 const char *who,
2165 const char *old_group_name,
2166 const char *new_group_name)
2168 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2169 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2170 struct sipe_group * old_group = NULL;
2171 struct sipe_group * new_group;
2173 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2174 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2176 if(!buddy) { // buddy not in roaming list
2177 return;
2180 if (old_group_name) {
2181 old_group = sipe_group_find_by_name(sip, old_group_name);
2183 new_group = sipe_group_find_by_name(sip, new_group_name);
2185 if (old_group) {
2186 buddy->groups = g_slist_remove(buddy->groups, old_group);
2187 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2190 if (!new_group) {
2191 sipe_group_create(sip, new_group_name, who);
2192 } else {
2193 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2194 sipe_group_set_user(sip, who);
2198 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2200 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2202 /* libpurple can call us with undefined buddy or group */
2203 if (buddy && group) {
2204 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2206 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2207 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2208 purple_blist_rename_buddy(buddy, buddy_name);
2209 g_free(buddy_name);
2211 /* Prepend sip: if needed */
2212 if (!g_str_has_prefix(buddy->name, "sip:")) {
2213 gchar *buf = sip_uri_from_name(buddy->name);
2214 purple_blist_rename_buddy(buddy, buf);
2215 g_free(buf);
2218 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2219 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2220 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2221 b->name = g_strdup(buddy->name);
2222 b->just_added = TRUE;
2223 g_hash_table_insert(sip->buddies, b->name, b);
2224 sipe_group_buddy(gc, b->name, NULL, group->name);
2225 /* @TODO should go to callback */
2226 sipe_subscribe_presence_single(sip, b->name);
2227 } else {
2228 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2233 static void sipe_free_buddy(struct sipe_buddy *buddy)
2235 #ifndef _WIN32
2237 * We are calling g_hash_table_foreach_steal(). That means that no
2238 * key/value deallocation functions are called. Therefore the glib
2239 * hash code does not touch the key (buddy->name) or value (buddy)
2240 * of the to-be-deleted hash node at all. It follows that we
2242 * - MUST free the memory for the key ourselves and
2243 * - ARE allowed to do it in this function
2245 * Conclusion: glib must be broken on the Windows platform if sipe
2246 * crashes with SIGTRAP when closing. You'll have to live
2247 * with the memory leak until this is fixed.
2249 g_free(buddy->name);
2250 #endif
2251 g_free(buddy->activity);
2252 g_free(buddy->meeting_subject);
2253 g_free(buddy->meeting_location);
2254 g_free(buddy->note);
2256 g_free(buddy->cal_start_time);
2257 g_free(buddy->cal_free_busy_base64);
2258 g_free(buddy->cal_free_busy);
2259 g_free(buddy->last_non_cal_activity);
2261 sipe_cal_free_working_hours(buddy->cal_working_hours);
2263 g_free(buddy->device_name);
2264 g_slist_free(buddy->groups);
2265 g_free(buddy);
2269 * Unassociates buddy from group first.
2270 * Then see if no groups left, removes buddy completely.
2271 * Otherwise updates buddy groups on server.
2273 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2275 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2276 struct sipe_buddy *b;
2277 struct sipe_group *g = NULL;
2279 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2280 if (!buddy) return;
2282 b = g_hash_table_lookup(sip->buddies, buddy->name);
2283 if (!b) return;
2285 if (group) {
2286 g = sipe_group_find_by_name(sip, group->name);
2289 if (g) {
2290 b->groups = g_slist_remove(b->groups, g);
2291 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2294 if (g_slist_length(b->groups) < 1) {
2295 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2296 sipe_cancel_scheduled_action(sip, action_name);
2297 g_free(action_name);
2299 g_hash_table_remove(sip->buddies, buddy->name);
2301 if (b->name) {
2302 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2303 send_soap_request(sip, body);
2304 g_free(body);
2307 sipe_free_buddy(b);
2308 } else {
2309 //updates groups on server
2310 sipe_group_set_user(sip, b->name);
2315 static void
2316 sipe_rename_group(PurpleConnection *gc,
2317 const char *old_name,
2318 PurpleGroup *group,
2319 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2321 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2322 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2323 if (s_group) {
2324 sipe_group_rename(sip, s_group, group->name);
2325 } else {
2326 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2330 static void
2331 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2333 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2334 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2335 if (s_group) {
2336 gchar *body;
2337 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2338 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2339 send_soap_request(sip, body);
2340 g_free(body);
2342 sip->groups = g_slist_remove(sip->groups, s_group);
2343 g_free(s_group->name);
2344 g_free(s_group);
2345 } else {
2346 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2350 /** All statuses need message attribute to pass Note */
2351 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2353 PurpleStatusType *type;
2354 GList *types = NULL;
2356 /* Macros to reduce code repetition.
2357 Translators: noun */
2358 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2359 prim, id, name, \
2360 TRUE, user, FALSE, \
2361 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2362 NULL); \
2363 types = g_list_append(types, type);
2365 /* Online */
2366 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2367 NULL,
2368 NULL,
2369 TRUE);
2371 /* Busy */
2372 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2373 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2374 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2375 TRUE);
2377 /* Do Not Disturb */
2378 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2379 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2380 NULL,
2381 TRUE);
2383 /* Away */
2384 /* Goes first in the list as
2385 * purple picks the first status with the AWAY type
2386 * for idle.
2388 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2389 NULL,
2390 NULL,
2391 TRUE);
2393 /* Be Right Back */
2394 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2395 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2396 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2397 TRUE);
2399 /* Appear Offline */
2400 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2401 NULL,
2402 NULL,
2403 TRUE);
2405 /* Offline */
2406 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2407 NULL,
2408 NULL,
2409 TRUE);
2410 types = g_list_append(types, type);
2412 return types;
2416 * A callback for g_hash_table_foreach
2418 static void
2419 sipe_buddy_subscribe_cb(char *buddy_name,
2420 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2421 struct sipe_account_data *sip)
2423 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2424 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2425 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2426 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2428 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2429 g_free(action_name);
2433 * Removes entries from purple buddy list
2434 * that does not correspond ones in the roaming contact list.
2436 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2437 GSList *buddies = purple_find_buddies(sip->account, NULL);
2438 GSList *entry = buddies;
2439 struct sipe_buddy *buddy;
2440 PurpleBuddy *b;
2441 PurpleGroup *g;
2443 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2444 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2445 while (entry) {
2446 b = entry->data;
2447 g = purple_buddy_get_group(b);
2448 buddy = g_hash_table_lookup(sip->buddies, b->name);
2449 if(buddy) {
2450 gboolean in_sipe_groups = FALSE;
2451 GSList *entry2 = buddy->groups;
2452 while (entry2) {
2453 struct sipe_group *group = entry2->data;
2454 if (sipe_strequal(group->name, g->name)) {
2455 in_sipe_groups = TRUE;
2456 break;
2458 entry2 = entry2->next;
2460 if(!in_sipe_groups) {
2461 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2462 purple_blist_remove_buddy(b);
2464 } else {
2465 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2466 purple_blist_remove_buddy(b);
2468 entry = entry->next;
2470 g_slist_free(buddies);
2473 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2475 int len = msg->bodylen;
2477 const gchar *tmp = sipmsg_find_header(msg, "Event");
2478 xmlnode *item;
2479 xmlnode *isc;
2480 const gchar *contacts_delta;
2481 xmlnode *group_node;
2482 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2483 return FALSE;
2486 /* Convert the contact from XML to Purple Buddies */
2487 isc = xmlnode_from_str(msg->body, len);
2488 if (!isc) {
2489 return FALSE;
2492 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2493 if (contacts_delta) {
2494 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2497 if (sipe_strequal(isc->name, "contactList")) {
2499 /* Parse groups */
2500 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2501 struct sipe_group * group = g_new0(struct sipe_group, 1);
2502 const char *name = xmlnode_get_attrib(group_node, "name");
2504 if (g_str_has_prefix(name, "~")) {
2505 name = _("Other Contacts");
2507 group->name = g_strdup(name);
2508 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2510 sipe_group_add(sip, group);
2513 // Make sure we have at least one group
2514 if (g_slist_length(sip->groups) == 0) {
2515 struct sipe_group * group = g_new0(struct sipe_group, 1);
2516 PurpleGroup *purple_group;
2517 group->name = g_strdup(_("Other Contacts"));
2518 group->id = 1;
2519 purple_group = purple_group_new(group->name);
2520 purple_blist_add_group(purple_group, NULL);
2521 sip->groups = g_slist_append(sip->groups, group);
2524 /* Parse contacts */
2525 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2526 const gchar *uri = xmlnode_get_attrib(item, "uri");
2527 const gchar *name = xmlnode_get_attrib(item, "name");
2528 gchar *buddy_name;
2529 struct sipe_buddy *buddy = NULL;
2530 gchar *tmp;
2531 gchar **item_groups;
2532 int i = 0;
2534 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2535 tmp = sip_uri_from_name(uri);
2536 buddy_name = g_ascii_strdown(tmp, -1);
2537 g_free(tmp);
2539 /* assign to group Other Contacts if nothing else received */
2540 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2541 if(is_empty(tmp)) {
2542 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2543 g_free(tmp);
2544 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2546 item_groups = g_strsplit(tmp, " ", 0);
2547 g_free(tmp);
2549 while (item_groups[i]) {
2550 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2552 // If couldn't find the right group for this contact, just put them in the first group we have
2553 if (group == NULL && g_slist_length(sip->groups) > 0) {
2554 group = sip->groups->data;
2557 if (group != NULL) {
2558 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2559 if (!b){
2560 b = purple_buddy_new(sip->account, buddy_name, uri);
2561 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2563 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2566 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2567 if (name != NULL && strlen(name) != 0) {
2568 purple_blist_alias_buddy(b, name);
2570 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2574 if (!buddy) {
2575 buddy = g_new0(struct sipe_buddy, 1);
2576 buddy->name = g_strdup(b->name);
2577 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2580 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2582 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2583 } else {
2584 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2585 name);
2588 i++;
2589 } // while, contact groups
2590 g_strfreev(item_groups);
2591 g_free(buddy_name);
2593 } // for, contacts
2595 sipe_cleanup_local_blist(sip);
2597 /* Add self-contact if not there yet. 2005 systems. */
2598 /* This will resemble subscription to roaming_self in 2007 systems */
2599 if (!sip->ocs2007) {
2600 gchar *self_uri = sip_uri_self(sip);
2601 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2603 if (!buddy) {
2604 buddy = g_new0(struct sipe_buddy, 1);
2605 buddy->name = g_strdup(self_uri);
2606 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2608 g_free(self_uri);
2611 xmlnode_free(isc);
2613 /* subscribe to buddies */
2614 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2615 if (sip->batched_support) {
2616 sipe_subscribe_presence_batched(sip, NULL);
2617 } else {
2618 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2620 sip->subscribed_buddies = TRUE;
2622 /* for 2005 systems schedule contacts' status update
2623 * based on their calendar information
2625 if (!sip->ocs2007) {
2626 sipe_sched_calendar_status_update(sip, time(NULL));
2629 return 0;
2633 * Subscribe roaming contacts
2635 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2637 gchar *to = sip_uri_self(sip);
2638 gchar *tmp = get_contact(sip);
2639 gchar *hdr = g_strdup_printf(
2640 "Event: vnd-microsoft-roaming-contacts\r\n"
2641 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2642 "Supported: com.microsoft.autoextend\r\n"
2643 "Supported: ms-benotify\r\n"
2644 "Proxy-Require: ms-benotify\r\n"
2645 "Supported: ms-piggyback-first-notify\r\n"
2646 "Contact: %s\r\n", tmp);
2647 g_free(tmp);
2649 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2650 g_free(to);
2651 g_free(hdr);
2654 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2655 SIPE_UNUSED_PARAMETER void *unused)
2657 gchar *key;
2658 struct sip_dialog *dialog;
2659 gchar *to = sip_uri_self(sip);
2660 gchar *tmp = get_contact(sip);
2661 gchar *hdr = g_strdup_printf(
2662 "Event: presence.wpending\r\n"
2663 "Accept: text/xml+msrtc.wpending\r\n"
2664 "Supported: com.microsoft.autoextend\r\n"
2665 "Supported: ms-benotify\r\n"
2666 "Proxy-Require: ms-benotify\r\n"
2667 "Supported: ms-piggyback-first-notify\r\n"
2668 "Contact: %s\r\n", tmp);
2669 g_free(tmp);
2671 /* Subscription is identified by <event> key */
2672 key = g_strdup_printf("<%s>", "presence.wpending");
2673 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2674 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2676 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2678 g_free(to);
2679 g_free(hdr);
2680 g_free(key);
2684 * Fires on deregistration event initiated by server.
2685 * [MS-SIPREGE] SIP extension.
2688 // 2007 Example
2690 // Content-Type: text/registration-event
2691 // subscription-state: terminated;expires=0
2692 // ms-diagnostics-public: 4141;reason="User disabled"
2694 // deregistered;event=rejected
2696 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2698 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2699 gchar *event = NULL;
2700 gchar *reason = NULL;
2701 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2702 gchar *warning;
2704 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2705 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2707 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2708 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2709 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2710 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2711 } else {
2712 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2713 return;
2716 if (diagnostics != NULL) {
2717 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2718 } else { // for LCS2005
2719 int error_id = 0;
2720 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2721 error_id = 4140; // [MS-SIPREGE]
2722 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2723 reason = g_strdup(_("you are already signed in at another location"));
2724 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2725 error_id = 4141;
2726 reason = g_strdup(_("user disabled")); // [MS-OCER]
2727 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2728 error_id = 4142;
2729 reason = g_strdup(_("user moved")); // [MS-OCER]
2732 g_free(event);
2733 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2734 g_free(reason);
2736 sip->gc->wants_to_die = TRUE;
2737 purple_connection_error(sip->gc, warning);
2738 g_free(warning);
2742 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2744 xmlnode *xn_provision_group_list;
2745 xmlnode *node;
2747 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2749 /* provisionGroup */
2750 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2751 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2752 g_free(sip->focus_factory_uri);
2753 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2754 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2755 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2756 break;
2759 xmlnode_free(xn_provision_group_list);
2762 /** for 2005 system */
2763 static void
2764 sipe_process_provisioning(struct sipe_account_data *sip,
2765 struct sipmsg *msg)
2767 xmlnode *xn_provision;
2768 xmlnode *node;
2770 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2771 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2772 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2773 if ((node = xmlnode_get_child(node, "line"))) {
2774 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2775 const gchar *server = xmlnode_get_attrib(node, "server");
2776 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2777 sip_csta_open(sip, line_uri, server);
2780 xmlnode_free(xn_provision);
2783 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2785 const gchar *contacts_delta;
2786 xmlnode *xml;
2788 xml = xmlnode_from_str(msg->body, msg->bodylen);
2789 if (!xml)
2791 return;
2794 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2795 if (contacts_delta)
2797 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2800 xmlnode_free(xml);
2803 static void
2804 free_container(struct sipe_container *container)
2806 GSList *entry;
2808 if (!container) return;
2810 entry = container->members;
2811 while (entry) {
2812 void *data = entry->data;
2813 entry = g_slist_remove(entry, data);
2814 g_free(data);
2816 g_free(container);
2820 * Finds locally stored MS-PRES container member
2822 static struct sipe_container_member *
2823 sipe_find_container_member(struct sipe_container *container,
2824 const gchar *type,
2825 const gchar *value)
2827 struct sipe_container_member *member;
2828 GSList *entry;
2830 if (container == NULL || type == NULL) {
2831 return NULL;
2834 entry = container->members;
2835 while (entry) {
2836 member = entry->data;
2837 if (!g_strcasecmp(member->type, type)
2838 && ((!member->value && !value)
2839 || (value && member->value && !g_strcasecmp(member->value, value)))
2841 return member;
2843 entry = entry->next;
2845 return NULL;
2849 * Finds locally stored MS-PRES container by id
2851 static struct sipe_container *
2852 sipe_find_container(struct sipe_account_data *sip,
2853 guint id)
2855 struct sipe_container *container;
2856 GSList *entry;
2858 if (sip == NULL) {
2859 return NULL;
2862 entry = sip->containers;
2863 while (entry) {
2864 container = entry->data;
2865 if (id == container->id) {
2866 return container;
2868 entry = entry->next;
2870 return NULL;
2874 * Access Levels
2875 * 32000 - Blocked
2876 * 400 - Personal
2877 * 300 - Team
2878 * 200 - Company
2879 * 100 - Public
2881 static int
2882 sipe_find_access_level(struct sipe_account_data *sip,
2883 const gchar *type,
2884 const gchar *value)
2886 guint containers[] = {32000, 400, 300, 200, 100};
2887 int i = 0;
2889 for (i = 0; i < 5; i++) {
2890 struct sipe_container_member *member;
2891 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2892 if (!container) continue;
2894 member = sipe_find_container_member(container, type, value);
2895 if (member) {
2896 return containers[i];
2900 return -1;
2903 static void
2904 sipe_send_set_container_members(struct sipe_account_data *sip,
2905 guint container_id,
2906 guint container_version,
2907 const gchar* action,
2908 const gchar* type,
2909 const gchar* value)
2911 gchar *self = sip_uri_self(sip);
2912 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2913 gchar *contact;
2914 gchar *hdr;
2915 gchar *body = g_strdup_printf(
2916 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2917 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2918 "</setContainerMembers>",
2919 container_id,
2920 container_version,
2921 action,
2922 type,
2923 value_str);
2924 g_free(value_str);
2926 contact = get_contact(sip);
2927 hdr = g_strdup_printf("Contact: %s\r\n"
2928 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2929 g_free(contact);
2931 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2933 g_free(hdr);
2934 g_free(body);
2935 g_free(self);
2938 static void
2939 free_publication(struct sipe_publication *publication)
2941 g_free(publication->category);
2942 g_free(publication->cal_event_hash);
2943 g_free(publication->note);
2945 g_free(publication->working_hours_xml_str);
2946 g_free(publication->fb_start_str);
2947 g_free(publication->free_busy_base64);
2949 g_free(publication);
2952 /* key is <category><instance><container> */
2953 static gboolean
2954 sipe_is_our_publication(struct sipe_account_data *sip,
2955 const gchar *key)
2957 GSList *entry;
2959 /* filling keys for our publications if not yet cached */
2960 if (!sip->our_publication_keys) {
2961 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2962 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2963 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2964 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2965 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2966 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2967 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2969 purple_debug_info("sipe", "* Our Publication Instances *\n");
2970 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2971 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2972 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2973 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2974 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2975 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2976 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2977 purple_debug_info("sipe", "\tNote : %u\n", 0);
2978 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2980 /* device */
2981 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2982 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2984 /* state:machineState */
2985 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2986 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2987 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2988 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2990 /* state:userState */
2991 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2992 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2993 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2994 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2996 /* state:calendarState */
2997 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2998 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2999 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3000 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3002 /* state:calendarState OOF */
3003 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3004 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3005 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3006 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3008 /* note */
3009 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3010 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3011 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3012 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3013 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3014 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3016 /* note OOF */
3017 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3018 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3019 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3020 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3021 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3022 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3024 /* calendarData:WorkingHours */
3025 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3026 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3027 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3028 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3029 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3030 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3031 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3032 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3033 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3034 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3035 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3036 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3038 /* calendarData:FreeBusy */
3039 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3040 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3041 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3042 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3043 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3044 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3045 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3046 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3047 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3048 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3049 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3050 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3052 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
3053 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3056 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3058 entry = sip->our_publication_keys;
3059 while (entry) {
3060 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3061 if (sipe_strequal(entry->data, key)) {
3062 return TRUE;
3064 entry = entry->next;
3066 return FALSE;
3069 /** Property names to store in blist.xml */
3070 #define ALIAS_PROP "alias"
3071 #define EMAIL_PROP "email"
3072 #define PHONE_PROP "phone"
3073 #define PHONE_DISPLAY_PROP "phone-display"
3074 #define PHONE_MOBILE_PROP "phone-mobile"
3075 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3076 #define PHONE_HOME_PROP "phone-home"
3077 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3078 #define PHONE_OTHER_PROP "phone-other"
3079 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3080 #define PHONE_CUSTOM1_PROP "phone-custom1"
3081 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3082 #define SITE_PROP "site"
3083 #define COMPANY_PROP "company"
3084 #define DEPARTMENT_PROP "department"
3085 #define TITLE_PROP "title"
3086 #define OFFICE_PROP "office"
3087 /** implies work address */
3088 #define ADDRESS_STREET_PROP "address-street"
3089 #define ADDRESS_CITY_PROP "address-city"
3090 #define ADDRESS_STATE_PROP "address-state"
3091 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3092 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3095 * Tries to figure out user first and last name
3096 * based on Display Name and email properties.
3098 * Allocates memory - must be g_free()'d
3100 * Examples to parse:
3101 * First Last
3102 * First Last - Company Name
3103 * Last, First
3104 * Last, First M.
3105 * Last, First (C)(STP) (Company)
3106 * first.last@company.com (preprocessed as "first last")
3107 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3109 * Unusable examples:
3110 * user@company.com (preprocessed as "user")
3111 * first.m.last@company.com (preprocessed as "first m last")
3112 * user.company.com@reuters.net (preprocessed as "user company com")
3114 static void
3115 sipe_get_first_last_names(struct sipe_account_data *sip,
3116 const char *uri,
3117 char **first_name,
3118 char **last_name)
3120 PurpleBuddy *p_buddy;
3121 char *display_name;
3122 const char *email;
3123 const char *first, *last;
3124 char *tmp;
3125 char **parts;
3126 gboolean has_comma = FALSE;
3128 if (!sip || !uri) return;
3130 p_buddy = purple_find_buddy(sip->account, uri);
3132 if (!p_buddy) return;
3134 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3135 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3137 if (!display_name && !email) return;
3139 /* if no display name, make "first last anything_else" out of email */
3140 if (email && !display_name) {
3141 display_name = g_strndup(email, strstr(email, "@") - email);
3142 display_name = purple_strreplace((tmp = display_name), ".", " ");
3143 g_free(tmp);
3146 if (display_name) {
3147 has_comma = (strstr(display_name, ",") != NULL);
3148 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3149 g_free(tmp);
3150 display_name = purple_strreplace((tmp = display_name), ",", " ");
3151 g_free(tmp);
3154 parts = g_strsplit(display_name, " ", 0);
3156 if (!parts[0] || !parts[1]) {
3157 g_free(display_name);
3158 g_strfreev(parts);
3159 return;
3162 if (has_comma) {
3163 last = parts[0];
3164 first = parts[1];
3165 } else {
3166 first = parts[0];
3167 last = parts[1];
3170 if (first_name) {
3171 *first_name = g_strstrip(g_strdup(first));
3174 if (last_name) {
3175 *last_name = g_strstrip(g_strdup(last));
3178 g_free(display_name);
3179 g_strfreev(parts);
3183 * Update user information
3185 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3186 * @param property_name
3187 * @param property_value may be modified to strip white space
3189 static void
3190 sipe_update_user_info(struct sipe_account_data *sip,
3191 const char *uri,
3192 const char *property_name,
3193 char *property_value)
3195 GSList *buddies, *entry;
3197 if (!property_name || strlen(property_name) == 0) return;
3199 if (property_value)
3200 property_value = g_strstrip(property_value);
3202 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3203 while (entry) {
3204 const char *prop_str;
3205 const char *server_alias;
3206 PurpleBuddy *p_buddy = entry->data;
3208 /* for Display Name */
3209 if (sipe_strequal(property_name, ALIAS_PROP)) {
3210 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3211 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3212 purple_blist_alias_buddy(p_buddy, property_value);
3215 server_alias = purple_buddy_get_server_alias(p_buddy);
3216 if (!is_empty(property_value) &&
3217 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3219 purple_blist_server_alias_buddy(p_buddy, property_value);
3222 /* for other properties */
3223 else {
3224 if (!is_empty(property_value)) {
3225 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3226 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3227 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3232 entry = entry->next;
3234 g_slist_free(buddies);
3238 * Update user phone
3239 * Suitable for both 2005 and 2007 systems.
3241 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3242 * @param phone_type
3243 * @param phone may be modified to strip white space
3244 * @param phone_display_string may be modified to strip white space
3246 static void
3247 sipe_update_user_phone(struct sipe_account_data *sip,
3248 const char *uri,
3249 const gchar *phone_type,
3250 gchar *phone,
3251 gchar *phone_display_string)
3253 const char *phone_node = PHONE_PROP; /* work phone by default */
3254 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3256 if(!phone || strlen(phone) == 0) return;
3258 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3259 phone_node = PHONE_MOBILE_PROP;
3260 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3261 } else if (sipe_strequal(phone_type, "home")) {
3262 phone_node = PHONE_HOME_PROP;
3263 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3264 } else if (sipe_strequal(phone_type, "other")) {
3265 phone_node = PHONE_OTHER_PROP;
3266 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3267 } else if (sipe_strequal(phone_type, "custom1")) {
3268 phone_node = PHONE_CUSTOM1_PROP;
3269 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3272 sipe_update_user_info(sip, uri, phone_node, phone);
3273 if (phone_display_string) {
3274 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3278 static void
3279 sipe_update_calendar(struct sipe_account_data *sip)
3281 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3283 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3285 if (sipe_strequal(calendar, "EXCH")) {
3286 sipe_ews_update_calendar(sip);
3289 /* schedule repeat */
3290 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3292 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3296 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3297 * by using standard Purple's means of signals and saved statuses.
3299 * Thus all UI elements get updated: Status Button with Note, docklet.
3300 * This is ablolutely important as both our status and note can come
3301 * inbound (roaming) or be updated programmatically (e.g. based on our
3302 * calendar data).
3304 static void
3305 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3306 const char *status_id,
3307 const char *message,
3308 time_t do_not_publish[])
3310 PurpleStatus *status = purple_account_get_active_status(account);
3311 gboolean changed = TRUE;
3313 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3314 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3316 changed = FALSE;
3319 if (purple_savedstatus_is_idleaway()) {
3320 changed = FALSE;
3323 if (changed) {
3324 PurpleSavedStatus *saved_status;
3325 const PurpleStatusType *acct_status_type =
3326 purple_status_type_find_with_id(account->status_types, status_id);
3327 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3328 sipe_activity activity = sipe_get_activity_by_token(status_id);
3330 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3331 if (saved_status) {
3332 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3335 /* If this type+message is unique then create a new transient saved status
3336 * Ref: gtkstatusbox.c
3338 if (!saved_status) {
3339 GList *tmp;
3340 GList *active_accts = purple_accounts_get_all_active();
3342 saved_status = purple_savedstatus_new(NULL, primitive);
3343 purple_savedstatus_set_message(saved_status, message);
3345 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3346 purple_savedstatus_set_substatus(saved_status,
3347 (PurpleAccount *)tmp->data, acct_status_type, message);
3349 g_list_free(active_accts);
3352 do_not_publish[activity] = time(NULL);
3353 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3354 status_id, (int)do_not_publish[activity]);
3356 /* Set the status for each account */
3357 purple_savedstatus_activate(saved_status);
3361 struct hash_table_delete_payload {
3362 GHashTable *hash_table;
3363 guint container;
3366 static void
3367 sipe_remove_category_container_publications_cb(const char *name,
3368 struct sipe_publication *publication,
3369 struct hash_table_delete_payload *payload)
3371 if (publication->container == payload->container) {
3372 g_hash_table_remove(payload->hash_table, name);
3375 static void
3376 sipe_remove_category_container_publications(GHashTable *our_publications,
3377 const char *category,
3378 guint container)
3380 struct hash_table_delete_payload payload;
3381 payload.hash_table = g_hash_table_lookup(our_publications, category);
3383 if (!payload.hash_table) return;
3385 payload.container = container;
3386 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3389 static void
3390 send_publish_category_initial(struct sipe_account_data *sip);
3393 * When we receive some self (BE) NOTIFY with a new subscriber
3394 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3397 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3399 gchar *contact;
3400 gchar *to;
3401 xmlnode *xml;
3402 xmlnode *node;
3403 xmlnode *node2;
3404 char *display_name = NULL;
3405 char *uri;
3406 GSList *category_names = NULL;
3407 int aggreg_avail = 0;
3408 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3409 gboolean do_update_status = FALSE;
3410 gboolean has_note_cleaned = FALSE;
3412 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3414 xml = xmlnode_from_str(msg->body, msg->bodylen);
3415 if (!xml) return;
3417 contact = get_contact(sip);
3418 to = sip_uri_self(sip);
3421 /* categories */
3422 /* set list of categories participating in this XML */
3423 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3424 const gchar *name = xmlnode_get_attrib(node, "name");
3425 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3427 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3428 category_names ? (int) g_slist_length(category_names) : -1);
3429 /* drop category information */
3430 if (category_names) {
3431 GSList *entry = category_names;
3432 while (entry) {
3433 GHashTable *cat_publications;
3434 const gchar *category = entry->data;
3435 entry = entry->next;
3436 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3437 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3438 if (cat_publications) {
3439 g_hash_table_remove(sip->our_publications, category);
3440 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3444 g_slist_free(category_names);
3445 /* filling our categories reflected in roaming data */
3446 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3447 const char *tmp;
3448 const gchar *name = xmlnode_get_attrib(node, "name");
3449 guint container = xmlnode_get_int_attrib(node, "container", -1);
3450 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3451 guint version = xmlnode_get_int_attrib(node, "version", 0);
3452 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3453 sipe_utils_str_to_time(tmp) : 0;
3454 gchar *key;
3455 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3457 /* Ex. clear note: <category name="note"/> */
3458 if (container == (guint)-1) {
3459 g_free(sip->note);
3460 sip->note = NULL;
3461 do_update_status = TRUE;
3462 continue;
3465 /* Ex. clear note: <category name="note" container="200"/> */
3466 if (instance == (guint)-1) {
3467 if (container == 200) {
3468 g_free(sip->note);
3469 sip->note = NULL;
3470 do_update_status = TRUE;
3472 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3473 sipe_remove_category_container_publications(
3474 sip->our_publications, name, container);
3475 continue;
3478 /* key is <category><instance><container> */
3479 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3480 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3482 /* capture all userState publication for later clean up if required */
3483 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3484 xmlnode *xn_state = xmlnode_get_child(node, "state");
3486 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3487 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3488 publication->category = g_strdup(name);
3489 publication->instance = instance;
3490 publication->container = container;
3491 publication->version = version;
3493 if (!sip->user_state_publications) {
3494 sip->user_state_publications = g_hash_table_new_full(
3495 g_str_hash, g_str_equal,
3496 g_free, (GDestroyNotify)free_publication);
3498 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3499 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3500 key, version);
3504 if (sipe_is_our_publication(sip, key)) {
3505 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3507 publication->category = g_strdup(name);
3508 publication->instance = instance;
3509 publication->container = container;
3510 publication->version = version;
3512 /* filling publication->availability */
3513 if (sipe_strequal(name, "state")) {
3514 xmlnode *xn_state = xmlnode_get_child(node, "state");
3515 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3517 if (xn_avail) {
3518 gchar *avail_str = xmlnode_get_data(xn_avail);
3519 if (avail_str) {
3520 publication->availability = atoi(avail_str);
3522 g_free(avail_str);
3524 /* for calendarState */
3525 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3526 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3527 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3529 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3530 if (xn_activity) {
3531 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3532 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3534 event->is_meeting = TRUE;
3537 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3538 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3540 publication->cal_event_hash = sipe_cal_event_hash(event);
3541 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3542 publication->cal_event_hash);
3543 sipe_cal_event_free(event);
3546 /* filling publication->note */
3547 if (sipe_strequal(name, "note")) {
3548 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3550 if (!has_note_cleaned) {
3551 has_note_cleaned = TRUE;
3553 g_free(sip->note);
3554 sip->note = NULL;
3555 sip->note_since = publish_time;
3557 do_update_status = TRUE;
3560 g_free(publication->note);
3561 publication->note = NULL;
3562 if (xn_body) {
3563 char *tmp;
3565 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3566 g_free(tmp);
3567 if (publish_time >= sip->note_since) {
3568 g_free(sip->note);
3569 sip->note = g_strdup(publication->note);
3570 sip->note_since = publish_time;
3571 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3573 do_update_status = TRUE;
3578 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3579 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3580 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3581 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3582 if (xn_free_busy) {
3583 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3584 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3586 if (xn_working_hours) {
3587 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3591 if (!cat_publications) {
3592 cat_publications = g_hash_table_new_full(
3593 g_str_hash, g_str_equal,
3594 g_free, (GDestroyNotify)free_publication);
3595 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3596 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3598 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3599 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3601 g_free(key);
3603 /* aggregateState (not an our publication) from 2-nd container */
3604 if (sipe_strequal(name, "state") && container == 2) {
3605 xmlnode *xn_state = xmlnode_get_child(node, "state");
3607 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3608 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3609 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3611 if (xn_avail) {
3612 gchar *avail_str = xmlnode_get_data(xn_avail);
3613 if (avail_str) {
3614 aggreg_avail = atoi(avail_str);
3616 g_free(avail_str);
3619 if (xn_activity) {
3620 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3622 aggreg_activity = sipe_get_activity_by_token(activity_token);
3625 do_update_status = TRUE;
3629 /* userProperties published by server from AD */
3630 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3631 xmlnode *line;
3632 /* line, for Remote Call Control (RCC) */
3633 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3634 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3635 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3636 gchar *line_uri;
3638 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3640 line_uri = xmlnode_get_data(line);
3641 if (line_uri) {
3642 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3643 sip_csta_open(sip, line_uri, line_server);
3645 g_free(line_uri);
3647 break;
3651 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3652 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3654 /* containers */
3655 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3656 guint id = xmlnode_get_int_attrib(node, "id", 0);
3657 struct sipe_container *container = sipe_find_container(sip, id);
3659 if (container) {
3660 sip->containers = g_slist_remove(sip->containers, container);
3661 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3662 free_container(container);
3664 container = g_new0(struct sipe_container, 1);
3665 container->id = id;
3666 container->version = xmlnode_get_int_attrib(node, "version", 0);
3667 sip->containers = g_slist_append(sip->containers, container);
3668 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3670 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3671 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3672 member->type = xmlnode_get_attrib(node2, "type");
3673 member->value = xmlnode_get_attrib(node2, "value");
3674 container->members = g_slist_append(container->members, member);
3675 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3676 member->type, member->value ? member->value : "");
3680 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3681 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3682 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3683 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3684 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3685 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3686 /* initial set-up to let counterparties see your status */
3687 if (sameEnterpriseAL < 0) {
3688 struct sipe_container *container = sipe_find_container(sip, 200);
3689 guint version = container ? container->version : 0;
3690 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3692 if (federatedAL < 0) {
3693 struct sipe_container *container = sipe_find_container(sip, 100);
3694 guint version = container ? container->version : 0;
3695 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3697 sip->access_level_set = TRUE;
3700 /* subscribers */
3701 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3702 const char *user;
3703 const char *acknowledged;
3704 gchar *hdr;
3705 gchar *body;
3707 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3708 if (!user) continue;
3709 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3710 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3711 uri = sip_uri_from_name(user);
3713 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3715 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3716 if(!g_ascii_strcasecmp(acknowledged,"false")){
3717 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3718 if (!purple_find_buddy(sip->account, uri)) {
3719 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3722 hdr = g_strdup_printf(
3723 "Contact: %s\r\n"
3724 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3726 body = g_strdup_printf(
3727 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3728 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3729 "</setSubscribers>", user);
3731 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3732 g_free(body);
3733 g_free(hdr);
3735 g_free(display_name);
3736 g_free(uri);
3739 g_free(contact);
3740 xmlnode_free(xml);
3742 /* Publish initial state if not yet.
3743 * Assuming this happens on initial responce to subscription to roaming-self
3744 * so we've already updated our roaming data in full.
3745 * Only for 2007+
3747 if (!sip->initial_state_published) {
3748 send_publish_category_initial(sip);
3749 sip->initial_state_published = TRUE;
3750 /* dalayed run */
3751 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3752 do_update_status = FALSE;
3753 } else if (aggreg_avail) {
3755 g_free(sip->status);
3756 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3757 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3758 } else {
3759 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3763 if (do_update_status) {
3764 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3765 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3768 g_free(to);
3771 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3773 gchar *to = sip_uri_self(sip);
3774 gchar *tmp = get_contact(sip);
3775 gchar *hdr = g_strdup_printf(
3776 "Event: vnd-microsoft-roaming-ACL\r\n"
3777 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3778 "Supported: com.microsoft.autoextend\r\n"
3779 "Supported: ms-benotify\r\n"
3780 "Proxy-Require: ms-benotify\r\n"
3781 "Supported: ms-piggyback-first-notify\r\n"
3782 "Contact: %s\r\n", tmp);
3783 g_free(tmp);
3785 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3786 g_free(to);
3787 g_free(hdr);
3791 * To request for presence information about the user, access level settings that have already been configured by the user
3792 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3793 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3796 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3798 gchar *to = sip_uri_self(sip);
3799 gchar *tmp = get_contact(sip);
3800 gchar *hdr = g_strdup_printf(
3801 "Event: vnd-microsoft-roaming-self\r\n"
3802 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3803 "Supported: ms-benotify\r\n"
3804 "Proxy-Require: ms-benotify\r\n"
3805 "Supported: ms-piggyback-first-notify\r\n"
3806 "Contact: %s\r\n"
3807 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3809 gchar *body=g_strdup(
3810 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3811 "<roaming type=\"categories\"/>"
3812 "<roaming type=\"containers\"/>"
3813 "<roaming type=\"subscribers\"/></roamingList>");
3815 g_free(tmp);
3816 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3817 g_free(body);
3818 g_free(to);
3819 g_free(hdr);
3823 * For 2005 version
3825 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3827 gchar *to = sip_uri_self(sip);
3828 gchar *tmp = get_contact(sip);
3829 gchar *hdr = g_strdup_printf(
3830 "Event: vnd-microsoft-provisioning\r\n"
3831 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3832 "Supported: com.microsoft.autoextend\r\n"
3833 "Supported: ms-benotify\r\n"
3834 "Proxy-Require: ms-benotify\r\n"
3835 "Supported: ms-piggyback-first-notify\r\n"
3836 "Expires: 0\r\n"
3837 "Contact: %s\r\n", tmp);
3839 g_free(tmp);
3840 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3841 g_free(to);
3842 g_free(hdr);
3845 /** Subscription for provisioning information to help with initial
3846 * configuration. This subscription is a one-time query (denoted by the Expires header,
3847 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3848 * configuration, meeting policies, and policy settings that Communicator must enforce.
3849 * TODO: for what we need this information.
3852 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3854 gchar *to = sip_uri_self(sip);
3855 gchar *tmp = get_contact(sip);
3856 gchar *hdr = g_strdup_printf(
3857 "Event: vnd-microsoft-provisioning-v2\r\n"
3858 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3859 "Supported: com.microsoft.autoextend\r\n"
3860 "Supported: ms-benotify\r\n"
3861 "Proxy-Require: ms-benotify\r\n"
3862 "Supported: ms-piggyback-first-notify\r\n"
3863 "Expires: 0\r\n"
3864 "Contact: %s\r\n"
3865 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3866 gchar *body = g_strdup(
3867 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3868 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3869 "<provisioningGroup name=\"ucPolicy\"/>"
3870 "</provisioningGroupList>");
3872 g_free(tmp);
3873 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3874 g_free(body);
3875 g_free(to);
3876 g_free(hdr);
3879 static void
3880 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3881 gpointer value, gpointer user_data)
3883 struct sip_subscription *subscription = value;
3884 struct sip_dialog *dialog = &subscription->dialog;
3885 struct sipe_account_data *sip = user_data;
3886 gchar *tmp = get_contact(sip);
3887 gchar *hdr = g_strdup_printf(
3888 "Event: %s\r\n"
3889 "Expires: 0\r\n"
3890 "Contact: %s\r\n", subscription->event, tmp);
3891 g_free(tmp);
3893 /* Rate limit to max. 25 requests per seconds */
3894 g_usleep(1000000 / 25);
3896 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3897 g_free(hdr);
3900 /* IM Session (INVITE and MESSAGE methods) */
3902 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3903 static gchar *
3904 get_end_points (struct sipe_account_data *sip,
3905 struct sip_session *session)
3907 gchar *res;
3909 if (session == NULL) {
3910 return NULL;
3913 res = g_strdup_printf("<sip:%s>", sip->username);
3915 SIPE_DIALOG_FOREACH {
3916 gchar *tmp = res;
3917 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3918 g_free(tmp);
3920 if (dialog->theirepid) {
3921 tmp = res;
3922 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3923 g_free(tmp);
3925 } SIPE_DIALOG_FOREACH_END;
3927 return res;
3930 static gboolean
3931 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3932 struct sipmsg *msg,
3933 SIPE_UNUSED_PARAMETER struct transaction *trans)
3935 gboolean ret = TRUE;
3937 if (msg->response != 200) {
3938 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3939 return FALSE;
3942 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3944 return ret;
3948 * Asks UA/proxy about its capabilities.
3950 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3952 gchar *to = sip_uri(who);
3953 gchar *contact = get_contact(sip);
3954 gchar *request = g_strdup_printf(
3955 "Accept: application/sdp\r\n"
3956 "Contact: %s\r\n", contact);
3957 g_free(contact);
3959 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3961 g_free(to);
3962 g_free(request);
3965 static void
3966 sipe_notify_user(struct sipe_account_data *sip,
3967 struct sip_session *session,
3968 PurpleMessageFlags flags,
3969 const gchar *message)
3971 PurpleConversation *conv;
3973 if (!session->conv) {
3974 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3975 } else {
3976 conv = session->conv;
3978 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3981 void
3982 sipe_present_info(struct sipe_account_data *sip,
3983 struct sip_session *session,
3984 const gchar *message)
3986 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3989 static void
3990 sipe_present_err(struct sipe_account_data *sip,
3991 struct sip_session *session,
3992 const gchar *message)
3994 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3997 void
3998 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3999 struct sip_session *session,
4000 int sip_error,
4001 int sip_warning,
4002 const gchar *who,
4003 const gchar *message)
4005 char *msg, *msg_tmp, *msg_tmp2;
4006 const char *label;
4008 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
4009 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4010 g_free(msg_tmp);
4011 /* Service unavailable; Server Internal Error; Server Time-out */
4012 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4013 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4014 g_free(msg);
4015 msg = NULL;
4016 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4017 label = _("This message was not delivered to %s because the service is not available");
4018 } else if (sip_error == 486) { /* Busy Here */
4019 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4020 } else if (sip_error == 415) { /* Unsupported media type */
4021 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4022 } else {
4023 label = _("This message was not delivered to %s because one or more recipients are offline");
4026 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4027 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4028 msg ? ":" : "",
4029 msg ? msg : "");
4030 sipe_present_err(sip, session, msg_tmp);
4031 g_free(msg_tmp2);
4032 g_free(msg_tmp);
4033 g_free(msg);
4037 static gboolean
4038 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4039 SIPE_UNUSED_PARAMETER struct transaction *trans)
4041 gboolean ret = TRUE;
4042 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4043 struct sip_session *session = sipe_session_find_im(sip, with);
4044 struct sip_dialog *dialog;
4045 gchar *cseq;
4046 char *key;
4047 struct queued_message *message;
4049 if (!session) {
4050 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
4051 g_free(with);
4052 return FALSE;
4055 dialog = sipe_dialog_find(session, with);
4056 if (!dialog) {
4057 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
4058 g_free(with);
4059 return FALSE;
4062 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4063 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4064 g_free(cseq);
4065 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4067 if (msg->response >= 400) {
4068 PurpleBuddy *pbuddy;
4069 const char *alias = with;
4070 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4071 int warning = -1;
4073 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
4075 if (warn_hdr) {
4076 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4077 if (parts[0]) {
4078 warning = atoi(parts[0]);
4080 g_strfreev(parts);
4083 /* cancel file transfer as rejected by server */
4084 if (msg->response == 606 && /* Not acceptable all. */
4085 warning == 309 && /* Message contents not allowed by policy */
4086 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4088 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4089 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4090 sipe_utils_nameval_free(parsed_body);
4093 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4094 alias = purple_buddy_get_alias(pbuddy);
4097 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4099 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4100 if (msg->response == 408 || /* Request timeout */
4101 msg->response == 480 || /* Temporarily Unavailable */
4102 msg->response == 481) { /* Call/Transaction Does Not Exist */
4103 purple_debug_info("sipe", "process_message_response: assuming dangling IM session, dropping it.\n");
4104 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4107 ret = FALSE;
4108 } else {
4109 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4110 if (message_id) {
4111 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4112 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4113 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4116 g_hash_table_remove(session->unconfirmed_messages, key);
4117 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4118 key, g_hash_table_size(session->unconfirmed_messages));
4121 g_free(key);
4122 g_free(with);
4124 if (ret) sipe_im_process_queue(sip, session);
4125 return ret;
4128 static gboolean
4129 sipe_is_election_finished(struct sip_session *session);
4131 static void
4132 sipe_election_result(struct sipe_account_data *sip,
4133 void *sess);
4135 static gboolean
4136 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4137 SIPE_UNUSED_PARAMETER struct transaction *trans)
4139 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4140 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4141 struct sip_dialog *dialog;
4142 struct sip_session *session;
4144 session = sipe_session_find_chat_by_callid(sip, callid);
4145 if (!session) {
4146 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4147 return FALSE;
4150 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4151 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4152 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4153 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4155 if (xn_request_rm_response) {
4156 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4157 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4159 dialog = sipe_dialog_find(session, with);
4160 if (!dialog) {
4161 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4162 xmlnode_free(xn_action);
4163 return FALSE;
4166 if (allow && !g_strcasecmp(allow, "true")) {
4167 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4168 dialog->election_vote = 1;
4169 } else if (allow && !g_strcasecmp(allow, "false")) {
4170 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4171 dialog->election_vote = -1;
4174 if (sipe_is_election_finished(session)) {
4175 sipe_election_result(sip, session);
4178 } else if (xn_set_rm_response) {
4181 xmlnode_free(xn_action);
4185 return TRUE;
4188 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4190 gchar *hdr;
4191 gchar *tmp;
4192 char *msgtext = NULL;
4193 const gchar *msgr = "";
4194 gchar *tmp2 = NULL;
4196 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4197 char *msgformat;
4198 gchar *msgr_value;
4200 sipe_parse_html(msg, &msgformat, &msgtext);
4201 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4203 msgr_value = sipmsg_get_msgr_string(msgformat);
4204 g_free(msgformat);
4205 if (msgr_value) {
4206 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4207 g_free(msgr_value);
4209 } else {
4210 msgtext = g_strdup(msg);
4213 tmp = get_contact(sip);
4214 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4215 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4216 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4217 if (content_type == NULL)
4218 content_type = "text/plain";
4220 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4221 g_free(tmp);
4222 g_free(tmp2);
4224 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4225 g_free(msgtext);
4226 g_free(hdr);
4230 void
4231 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4233 GSList *entry2 = session->outgoing_message_queue;
4234 while (entry2) {
4235 struct queued_message *msg = entry2->data;
4237 /* for multiparty chat or conference */
4238 if (session->is_multiparty || session->focus_uri) {
4239 gchar *who = sip_uri_self(sip);
4240 serv_got_chat_in(sip->gc, session->chat_id, who,
4241 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4242 g_free(who);
4245 SIPE_DIALOG_FOREACH {
4246 char *key;
4247 struct queued_message *message;
4249 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4251 message = g_new0(struct queued_message,1);
4252 message->body = g_strdup(msg->body);
4253 if (msg->content_type != NULL)
4254 message->content_type = g_strdup(msg->content_type);
4256 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4257 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4258 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4259 key, g_hash_table_size(session->unconfirmed_messages));
4260 g_free(key);
4262 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4263 } SIPE_DIALOG_FOREACH_END;
4265 entry2 = sipe_session_dequeue_message(session);
4269 static void
4270 sipe_refer_notify(struct sipe_account_data *sip,
4271 struct sip_session *session,
4272 const gchar *who,
4273 int status,
4274 const gchar *desc)
4276 gchar *hdr;
4277 gchar *body;
4278 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4280 hdr = g_strdup_printf(
4281 "Event: refer\r\n"
4282 "Subscription-State: %s\r\n"
4283 "Content-Type: message/sipfrag\r\n",
4284 status >= 200 ? "terminated" : "active");
4286 body = g_strdup_printf(
4287 "SIP/2.0 %d %s\r\n",
4288 status, desc);
4290 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4292 g_free(hdr);
4293 g_free(body);
4296 static gboolean
4297 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4299 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4300 struct sip_session *session;
4301 struct sip_dialog *dialog;
4302 char *cseq;
4303 char *key;
4304 struct queued_message *message;
4305 struct sipmsg *request_msg = trans->msg;
4307 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4308 gchar *referred_by;
4310 session = sipe_session_find_chat_by_callid(sip, callid);
4311 if (!session) {
4312 session = sipe_session_find_im(sip, with);
4314 if (!session) {
4315 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4316 g_free(with);
4317 return FALSE;
4320 dialog = sipe_dialog_find(session, with);
4321 if (!dialog) {
4322 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4323 g_free(with);
4324 return FALSE;
4327 sipe_dialog_parse(dialog, msg, TRUE);
4329 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4330 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4331 g_free(cseq);
4332 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4334 if (msg->response != 200) {
4335 PurpleBuddy *pbuddy;
4336 const char *alias = with;
4337 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4338 int warning = -1;
4340 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4342 if (warn_hdr) {
4343 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4344 if (parts[0]) {
4345 warning = atoi(parts[0]);
4347 g_strfreev(parts);
4350 /* cancel file transfer as rejected by server */
4351 if (msg->response == 606 && /* Not acceptable all. */
4352 warning == 309 && /* Message contents not allowed by policy */
4353 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4355 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4356 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4357 sipe_utils_nameval_free(parsed_body);
4360 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4361 alias = purple_buddy_get_alias(pbuddy);
4364 if (message) {
4365 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4366 } else {
4367 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4368 sipe_present_err(sip, session, tmp_msg);
4369 g_free(tmp_msg);
4372 sipe_dialog_remove(session, with);
4374 g_free(key);
4375 g_free(with);
4376 return FALSE;
4379 dialog->cseq = 0;
4380 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4381 dialog->outgoing_invite = NULL;
4382 dialog->is_established = TRUE;
4384 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4385 if (referred_by) {
4386 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4387 g_free(referred_by);
4390 /* add user to chat if it is a multiparty session */
4391 if (session->is_multiparty) {
4392 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4393 with, NULL,
4394 PURPLE_CBFLAGS_NONE, TRUE);
4397 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4398 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4399 sipe_session_dequeue_message(session);
4402 sipe_im_process_queue(sip, session);
4404 g_hash_table_remove(session->unconfirmed_messages, key);
4405 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4406 key, g_hash_table_size(session->unconfirmed_messages));
4408 g_free(key);
4409 g_free(with);
4410 return TRUE;
4414 void
4415 sipe_invite(struct sipe_account_data *sip,
4416 struct sip_session *session,
4417 const gchar *who,
4418 const gchar *msg_body,
4419 const gchar *msg_content_type,
4420 const gchar *referred_by,
4421 const gboolean is_triggered)
4423 gchar *hdr;
4424 gchar *to;
4425 gchar *contact;
4426 gchar *body;
4427 gchar *self;
4428 char *ms_text_format = NULL;
4429 gchar *roster_manager;
4430 gchar *end_points;
4431 gchar *referred_by_str;
4432 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4434 if (dialog && dialog->is_established) {
4435 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4436 return;
4439 if (!dialog) {
4440 dialog = sipe_dialog_add(session);
4441 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4442 dialog->with = g_strdup(who);
4445 if (!(dialog->ourtag)) {
4446 dialog->ourtag = gentag();
4449 to = sip_uri(who);
4451 if (msg_body) {
4452 char *msgtext = NULL;
4453 char *base64_msg;
4454 const gchar *msgr = "";
4455 char *key;
4456 struct queued_message *message;
4457 gchar *tmp = NULL;
4459 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4460 char *msgformat;
4461 gchar *msgr_value;
4463 sipe_parse_html(msg_body, &msgformat, &msgtext);
4464 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4466 msgr_value = sipmsg_get_msgr_string(msgformat);
4467 g_free(msgformat);
4468 if (msgr_value) {
4469 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4470 g_free(msgr_value);
4472 } else {
4473 msgtext = g_strdup(msg_body);
4476 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4477 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4478 msg_content_type ? msg_content_type : "text/plain",
4479 msgr,
4480 base64_msg);
4481 g_free(msgtext);
4482 g_free(tmp);
4483 g_free(base64_msg);
4485 message = g_new0(struct queued_message,1);
4486 message->body = g_strdup(msg_body);
4487 if (msg_content_type != NULL)
4488 message->content_type = g_strdup(msg_content_type);
4490 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4491 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4492 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4493 key, g_hash_table_size(session->unconfirmed_messages));
4494 g_free(key);
4497 contact = get_contact(sip);
4498 end_points = get_end_points(sip, session);
4499 self = sip_uri_self(sip);
4500 roster_manager = g_strdup_printf(
4501 "Roster-Manager: %s\r\n"
4502 "EndPoints: %s\r\n",
4503 self,
4504 end_points);
4505 referred_by_str = referred_by ?
4506 g_strdup_printf(
4507 "Referred-By: %s\r\n",
4508 referred_by)
4509 : g_strdup("");
4510 hdr = g_strdup_printf(
4511 "Supported: ms-sender\r\n"
4512 "%s"
4513 "%s"
4514 "%s"
4515 "%s"
4516 "Contact: %s\r\n%s"
4517 "Content-Type: application/sdp\r\n",
4518 sipe_strequal(session->roster_manager, self) ? roster_manager : "",
4519 referred_by_str,
4520 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4521 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4522 contact,
4523 ms_text_format ? ms_text_format : "");
4524 g_free(ms_text_format);
4525 g_free(self);
4527 body = g_strdup_printf(
4528 "v=0\r\n"
4529 "o=- 0 0 IN IP4 %s\r\n"
4530 "s=session\r\n"
4531 "c=IN IP4 %s\r\n"
4532 "t=0 0\r\n"
4533 "m=%s %d sip null\r\n"
4534 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4535 purple_network_get_my_ip(-1),
4536 purple_network_get_my_ip(-1),
4537 sip->ocs2007 ? "message" : "x-ms-message",
4538 sip->realport);
4540 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4541 to, to, hdr, body, dialog, process_invite_response);
4543 g_free(to);
4544 g_free(roster_manager);
4545 g_free(end_points);
4546 g_free(referred_by_str);
4547 g_free(body);
4548 g_free(hdr);
4549 g_free(contact);
4552 static void
4553 sipe_refer(struct sipe_account_data *sip,
4554 struct sip_session *session,
4555 const gchar *who)
4557 gchar *hdr;
4558 gchar *contact;
4559 gchar *epid = get_epid(sip);
4560 struct sip_dialog *dialog = sipe_dialog_find(session,
4561 session->roster_manager);
4562 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4564 contact = get_contact(sip);
4565 hdr = g_strdup_printf(
4566 "Contact: %s\r\n"
4567 "Refer-to: <%s>\r\n"
4568 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4569 "Require: com.microsoft.rtc-multiparty\r\n",
4570 contact,
4571 who,
4572 sip->username,
4573 ourtag ? ";tag=" : "",
4574 ourtag ? ourtag : "",
4575 epid);
4576 g_free(epid);
4578 send_sip_request(sip->gc, "REFER",
4579 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4581 g_free(hdr);
4582 g_free(contact);
4585 static void
4586 sipe_send_election_request_rm(struct sipe_account_data *sip,
4587 struct sip_dialog *dialog,
4588 int bid)
4590 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4592 gchar *body = g_strdup_printf(
4593 "<?xml version=\"1.0\"?>\r\n"
4594 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4595 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4596 sip->username, bid);
4598 send_sip_request(sip->gc, "INFO",
4599 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4601 g_free(body);
4604 static void
4605 sipe_send_election_set_rm(struct sipe_account_data *sip,
4606 struct sip_dialog *dialog)
4608 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4610 gchar *body = g_strdup_printf(
4611 "<?xml version=\"1.0\"?>\r\n"
4612 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4613 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4614 sip->username);
4616 send_sip_request(sip->gc, "INFO",
4617 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4619 g_free(body);
4622 static void
4623 sipe_session_close(struct sipe_account_data *sip,
4624 struct sip_session * session)
4626 if (session && session->focus_uri) {
4627 sipe_conf_immcu_closed(sip, session);
4628 conf_session_close(sip, session);
4631 if (session) {
4632 SIPE_DIALOG_FOREACH {
4633 /* @TODO slow down BYE message sending rate */
4634 /* @see single subscription code */
4635 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4636 } SIPE_DIALOG_FOREACH_END;
4638 sipe_session_remove(sip, session);
4642 static void
4643 sipe_session_close_all(struct sipe_account_data *sip)
4645 GSList *entry;
4646 while ((entry = sip->sessions) != NULL) {
4647 sipe_session_close(sip, entry->data);
4651 static void
4652 sipe_convo_closed(PurpleConnection * gc, const char *who)
4654 struct sipe_account_data *sip = gc->proto_data;
4656 purple_debug_info("sipe", "conversation with %s closed\n", who);
4657 sipe_session_close(sip, sipe_session_find_im(sip, who));
4660 static void
4661 sipe_chat_leave (PurpleConnection *gc, int id)
4663 struct sipe_account_data *sip = gc->proto_data;
4664 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4666 sipe_session_close(sip, session);
4669 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4670 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4672 struct sipe_account_data *sip = gc->proto_data;
4673 struct sip_session *session;
4674 struct sip_dialog *dialog;
4675 gchar *uri = sip_uri(who);
4677 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4679 session = sipe_session_find_or_add_im(sip, uri);
4680 dialog = sipe_dialog_find(session, uri);
4682 // Queue the message
4683 sipe_session_enqueue_message(session, what, NULL);
4685 if (dialog && !dialog->outgoing_invite) {
4686 sipe_im_process_queue(sip, session);
4687 } else if (!dialog || !dialog->outgoing_invite) {
4688 // Need to send the INVITE to get the outgoing dialog setup
4689 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4692 g_free(uri);
4693 return 1;
4696 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4697 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4699 struct sipe_account_data *sip = gc->proto_data;
4700 struct sip_session *session;
4702 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4704 session = sipe_session_find_chat_by_id(sip, id);
4706 // Queue the message
4707 if (session && session->dialogs) {
4708 sipe_session_enqueue_message(session,what,NULL);
4709 sipe_im_process_queue(sip, session);
4710 } else if (sip) {
4711 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4712 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4714 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4715 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4717 if (sip->ocs2007) {
4718 struct sip_session *session = sipe_session_add_chat(sip);
4720 session->is_multiparty = FALSE;
4721 session->focus_uri = g_strdup(proto_chat_id);
4722 sipe_session_enqueue_message(session, what, NULL);
4723 sipe_invite_conf_focus(sip, session);
4727 return 1;
4730 /* End IM Session (INVITE and MESSAGE methods) */
4732 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4734 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4735 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4736 gchar *from;
4737 struct sip_session *session;
4739 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4741 /* Call Control protocol */
4742 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4744 process_incoming_info_csta(sip, msg);
4745 return;
4748 from = parse_from(sipmsg_find_header(msg, "From"));
4749 session = sipe_session_find_chat_by_callid(sip, callid);
4750 if (!session) {
4751 session = sipe_session_find_im(sip, from);
4753 if (!session) {
4754 g_free(from);
4755 return;
4758 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4760 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4761 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4762 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4764 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4766 if (xn_request_rm) {
4767 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4768 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4769 gchar *body = g_strdup_printf(
4770 "<?xml version=\"1.0\"?>\r\n"
4771 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4772 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4773 sip->username,
4774 session->bid < bid ? "true" : "false");
4775 send_sip_response(sip->gc, msg, 200, "OK", body);
4776 g_free(body);
4777 } else if (xn_set_rm) {
4778 gchar *body;
4779 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4780 g_free(session->roster_manager);
4781 session->roster_manager = g_strdup(rm);
4783 body = g_strdup_printf(
4784 "<?xml version=\"1.0\"?>\r\n"
4785 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4786 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4787 sip->username);
4788 send_sip_response(sip->gc, msg, 200, "OK", body);
4789 g_free(body);
4791 xmlnode_free(xn_action);
4794 else
4796 /* looks like purple lacks typing notification for chat */
4797 if (!session->is_multiparty && !session->focus_uri) {
4798 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4799 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4800 "status");
4801 if (sipe_strequal(status, "type")) {
4802 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4803 } else if (sipe_strequal(status, "idle")) {
4804 serv_got_typing_stopped(sip->gc, from);
4806 xmlnode_free(xn_keyboard_activity);
4809 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4811 g_free(from);
4814 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4816 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4817 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4818 struct sip_session *session;
4819 struct sip_dialog *dialog;
4821 /* collect dialog identification
4822 * we need callid, ourtag and theirtag to unambiguously identify dialog
4824 /* take data before 'msg' will be modified by send_sip_response */
4825 dialog = g_new0(struct sip_dialog, 1);
4826 dialog->callid = g_strdup(callid);
4827 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4828 dialog->with = g_strdup(from);
4829 sipe_dialog_parse(dialog, msg, FALSE);
4831 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4833 session = sipe_session_find_chat_by_callid(sip, callid);
4834 if (!session) {
4835 session = sipe_session_find_im(sip, from);
4837 if (!session) {
4838 sipe_dialog_free(dialog);
4839 g_free(from);
4840 return;
4843 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4844 g_free(session->roster_manager);
4845 session->roster_manager = NULL;
4848 /* This what BYE is essentially for - terminating dialog */
4849 sipe_dialog_remove_3(session, dialog);
4850 sipe_dialog_free(dialog);
4851 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4852 sipe_conf_immcu_closed(sip, session);
4853 } else if (session->is_multiparty) {
4854 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4857 g_free(from);
4860 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4862 gchar *self = sip_uri_self(sip);
4863 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4864 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4865 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4866 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4867 struct sip_session *session;
4868 struct sip_dialog *dialog;
4870 session = sipe_session_find_chat_by_callid(sip, callid);
4871 dialog = sipe_dialog_find(session, from);
4873 if (!session || !dialog || !session->roster_manager || !sipe_strequal(session->roster_manager, self)) {
4874 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4875 } else {
4876 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4878 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4881 g_free(self);
4882 g_free(from);
4883 g_free(refer_to);
4884 g_free(referred_by);
4887 static unsigned int
4888 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4890 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4891 struct sip_session *session;
4892 struct sip_dialog *dialog;
4894 if (state == PURPLE_NOT_TYPING)
4895 return 0;
4897 session = sipe_session_find_im(sip, who);
4898 dialog = sipe_dialog_find(session, who);
4900 if (session && dialog && dialog->is_established) {
4901 send_sip_request(gc, "INFO", who, who,
4902 "Content-Type: application/xml\r\n",
4903 SIPE_SEND_TYPING, dialog, NULL);
4905 return SIPE_TYPING_SEND_TIMEOUT;
4908 static gboolean resend_timeout(struct sipe_account_data *sip)
4910 GSList *tmp = sip->transactions;
4911 time_t currtime = time(NULL);
4912 while (tmp) {
4913 struct transaction *trans = tmp->data;
4914 tmp = tmp->next;
4915 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4916 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4917 /* TODO 408 */
4918 } else {
4919 if ((currtime - trans->time > 2) && trans->retries == 0) {
4920 trans->retries++;
4921 sendout_sipmsg(sip, trans->msg);
4925 return TRUE;
4928 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4929 SIPE_UNUSED_PARAMETER void *unused)
4931 /* register again when security token expires */
4932 /* we have to start a new authentication as the security token
4933 * is almost expired by sending a not signed REGISTER message */
4934 purple_debug_info("sipe", "do a full reauthentication\n");
4935 sipe_auth_free(&sip->registrar);
4936 sipe_auth_free(&sip->proxy);
4937 sip->registerstatus = 0;
4938 do_register(sip);
4939 sip->reauthenticate_set = FALSE;
4942 static gboolean
4943 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4944 struct sipmsg *msg,
4945 GSList *parsed_body)
4947 gboolean found = FALSE;
4949 if (parsed_body) {
4950 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4952 if (sipe_strequal(invitation_command, "INVITE")) {
4953 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4954 found = TRUE;
4955 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4956 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4957 found = TRUE;
4958 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4959 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4960 found = TRUE;
4963 return found;
4966 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4968 gchar *from;
4969 const gchar *contenttype;
4970 gboolean found = FALSE;
4972 from = parse_from(sipmsg_find_header(msg, "From"));
4974 if (!from) return;
4976 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4978 contenttype = sipmsg_find_header(msg, "Content-Type");
4979 if (g_str_has_prefix(contenttype, "text/plain")
4980 || g_str_has_prefix(contenttype, "text/html")
4981 || g_str_has_prefix(contenttype, "multipart/related")
4982 || g_str_has_prefix(contenttype, "multipart/alternative"))
4984 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4985 gchar *html = get_html_message(contenttype, msg->body);
4987 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4988 if (!session) {
4989 session = sipe_session_find_im(sip, from);
4992 if (session && session->focus_uri) { /* a conference */
4993 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4994 gchar *sender = parse_from(tmp);
4995 g_free(tmp);
4996 serv_got_chat_in(sip->gc, session->chat_id, sender,
4997 PURPLE_MESSAGE_RECV, html, time(NULL));
4998 g_free(sender);
4999 } else if (session && session->is_multiparty) { /* a multiparty chat */
5000 serv_got_chat_in(sip->gc, session->chat_id, from,
5001 PURPLE_MESSAGE_RECV, html, time(NULL));
5002 } else {
5003 serv_got_im(sip->gc, from, html, 0, time(NULL));
5005 g_free(html);
5006 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5007 found = TRUE;
5009 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5010 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5011 xmlnode *state;
5012 gchar *statedata;
5014 if (!isc) {
5015 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
5016 g_free(from);
5017 return;
5020 state = xmlnode_get_child(isc, "state");
5022 if (!state) {
5023 purple_debug_info("sipe", "process_incoming_message: no state found\n");
5024 xmlnode_free(isc);
5025 g_free(from);
5026 return;
5029 statedata = xmlnode_get_data(state);
5030 if (statedata) {
5031 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5032 else serv_got_typing_stopped(sip->gc, from);
5034 g_free(statedata);
5036 xmlnode_free(isc);
5037 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5038 found = TRUE;
5039 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5040 GSList *body = sipe_ft_parse_msg_body(msg->body);
5041 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5042 sipe_utils_nameval_free(body);
5043 if (found) {
5044 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5047 if (!found) {
5048 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5049 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5050 if (!session) {
5051 session = sipe_session_find_im(sip, from);
5053 if (session) {
5054 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5055 from);
5056 sipe_present_err(sip, session, errmsg);
5057 g_free(errmsg);
5060 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
5061 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5063 g_free(from);
5066 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5068 gchar *body;
5069 gchar *newTag;
5070 const gchar *oldHeader;
5071 gchar *newHeader;
5072 gboolean is_multiparty = FALSE;
5073 gboolean is_triggered = FALSE;
5074 gboolean was_multiparty = TRUE;
5075 gboolean just_joined = FALSE;
5076 gchar *from;
5077 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5078 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5079 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5080 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5081 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5082 GSList *end_points = NULL;
5083 char *tmp = NULL;
5084 struct sip_session *session;
5085 const gchar *ms_text_format;
5087 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
5088 g_free(tmp);
5090 /* Invitation to join conference */
5091 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5092 process_incoming_invite_conf(sip, msg);
5093 return;
5096 /* Only accept text invitations */
5097 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5098 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5099 return;
5102 // TODO There *must* be a better way to clean up the To header to add a tag...
5103 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
5104 oldHeader = sipmsg_find_header(msg, "To");
5105 newTag = gentag();
5106 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5107 sipmsg_remove_header_now(msg, "To");
5108 sipmsg_add_header_now(msg, "To", newHeader);
5109 g_free(newHeader);
5111 if (end_points_hdr) {
5112 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5114 if (g_slist_length(end_points) > 2) {
5115 is_multiparty = TRUE;
5118 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5119 is_triggered = TRUE;
5120 is_multiparty = TRUE;
5123 session = sipe_session_find_chat_by_callid(sip, callid);
5124 /* Convert to multiparty */
5125 if (session && is_multiparty && !session->is_multiparty) {
5126 g_free(session->with);
5127 session->with = NULL;
5128 was_multiparty = FALSE;
5129 session->is_multiparty = TRUE;
5130 session->chat_id = rand();
5133 if (!session && is_multiparty) {
5134 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5136 /* IM session */
5137 from = parse_from(sipmsg_find_header(msg, "From"));
5138 if (!session) {
5139 session = sipe_session_find_or_add_im(sip, from);
5142 if (session) {
5143 g_free(session->callid);
5144 session->callid = g_strdup(callid);
5146 session->is_multiparty = is_multiparty;
5147 if (roster_manager) {
5148 session->roster_manager = g_strdup(roster_manager);
5152 if (is_multiparty && end_points) {
5153 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5154 GSList *entry = end_points;
5155 while (entry) {
5156 struct sip_dialog *dialog;
5157 struct sipendpoint *end_point = entry->data;
5158 entry = entry->next;
5160 if (!g_strcasecmp(from, end_point->contact) ||
5161 !g_strcasecmp(to, end_point->contact))
5162 continue;
5164 dialog = sipe_dialog_find(session, end_point->contact);
5165 if (dialog) {
5166 g_free(dialog->theirepid);
5167 dialog->theirepid = end_point->epid;
5168 end_point->epid = NULL;
5169 } else {
5170 dialog = sipe_dialog_add(session);
5172 dialog->callid = g_strdup(session->callid);
5173 dialog->with = end_point->contact;
5174 end_point->contact = NULL;
5175 dialog->theirepid = end_point->epid;
5176 end_point->epid = NULL;
5178 just_joined = TRUE;
5180 /* send triggered INVITE */
5181 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5184 g_free(to);
5187 if (end_points) {
5188 GSList *entry = end_points;
5189 while (entry) {
5190 struct sipendpoint *end_point = entry->data;
5191 entry = entry->next;
5192 g_free(end_point->contact);
5193 g_free(end_point->epid);
5194 g_free(end_point);
5196 g_slist_free(end_points);
5199 if (session) {
5200 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5201 if (dialog) {
5202 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5203 sipe_dialog_parse_routes(dialog, msg, FALSE);
5204 } else {
5205 dialog = sipe_dialog_add(session);
5207 dialog->callid = g_strdup(session->callid);
5208 dialog->with = g_strdup(from);
5209 sipe_dialog_parse(dialog, msg, FALSE);
5211 if (!dialog->ourtag) {
5212 dialog->ourtag = newTag;
5213 newTag = NULL;
5216 just_joined = TRUE;
5218 } else {
5219 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5221 g_free(newTag);
5223 if (is_multiparty && !session->conv) {
5224 gchar *chat_title = sipe_chat_get_name(callid);
5225 gchar *self = sip_uri_self(sip);
5226 /* create prpl chat */
5227 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5228 session->chat_title = g_strdup(chat_title);
5229 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5230 /* add self */
5231 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5232 self, NULL,
5233 PURPLE_CBFLAGS_NONE, FALSE);
5234 g_free(chat_title);
5235 g_free(self);
5238 if (is_multiparty && !was_multiparty) {
5239 /* add current IM counterparty to chat */
5240 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5241 sipe_dialog_first(session)->with, NULL,
5242 PURPLE_CBFLAGS_NONE, FALSE);
5245 /* add inviting party to chat */
5246 if (just_joined && session->conv) {
5247 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5248 from, NULL,
5249 PURPLE_CBFLAGS_NONE, TRUE);
5252 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5254 /* This used only in 2005 official client, not 2007 or Reuters.
5255 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5256 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5258 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5259 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5260 if (is_multiparty ||
5261 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5263 if (ms_text_format) {
5264 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5266 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5267 if (tmp) {
5268 gchar *body = (gchar *) purple_base64_decode(tmp, NULL);
5270 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5272 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5273 sipe_utils_nameval_free(parsed_body);
5274 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5276 g_free(tmp);
5278 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5280 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5281 gchar *html = get_html_message(ms_text_format, NULL);
5282 if (html) {
5283 if (is_multiparty) {
5284 serv_got_chat_in(sip->gc, session->chat_id, from,
5285 PURPLE_MESSAGE_RECV, html, time(NULL));
5286 } else {
5287 serv_got_im(sip->gc, from, html, 0, time(NULL));
5289 g_free(html);
5290 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5296 g_free(from);
5298 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5299 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5300 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5302 body = g_strdup_printf(
5303 "v=0\r\n"
5304 "o=- 0 0 IN IP4 %s\r\n"
5305 "s=session\r\n"
5306 "c=IN IP4 %s\r\n"
5307 "t=0 0\r\n"
5308 "m=%s %d sip sip:%s\r\n"
5309 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5310 purple_network_get_my_ip(-1),
5311 purple_network_get_my_ip(-1),
5312 sip->ocs2007 ? "message" : "x-ms-message",
5313 sip->realport,
5314 sip->username);
5315 send_sip_response(sip->gc, msg, 200, "OK", body);
5316 g_free(body);
5319 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5321 gchar *body;
5323 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5324 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5325 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5327 body = g_strdup_printf(
5328 "v=0\r\n"
5329 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5330 "s=session\r\n"
5331 "c=IN IP4 0.0.0.0\r\n"
5332 "t=0 0\r\n"
5333 "m=%s %d sip sip:%s\r\n"
5334 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5335 sip->ocs2007 ? "message" : "x-ms-message",
5336 sip->realport,
5337 sip->username);
5338 send_sip_response(sip->gc, msg, 200, "OK", body);
5339 g_free(body);
5342 static const char*
5343 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5345 const char *res = "NTLM";
5346 #ifdef HAVE_KERBEROS
5347 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5348 res = "Kerberos";
5350 #else
5351 (void) sip; /* make compiler happy */
5352 #endif
5353 return res;
5356 static void sipe_connection_cleanup(struct sipe_account_data *);
5357 static void create_connection(struct sipe_account_data *, gchar *, int);
5359 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5360 SIPE_UNUSED_PARAMETER struct transaction *trans)
5362 gchar *tmp;
5363 const gchar *expires_header;
5364 int expires, i;
5365 GSList *hdr = msg->headers;
5366 struct sipnameval *elem;
5368 expires_header = sipmsg_find_header(msg, "Expires");
5369 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5370 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5372 switch (msg->response) {
5373 case 200:
5374 if (expires == 0) {
5375 sip->registerstatus = 0;
5376 } else {
5377 const gchar *contact_hdr;
5378 gchar *gruu = NULL;
5379 gchar *epid;
5380 gchar *uuid;
5381 gchar *timeout;
5382 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5383 const char *auth_scheme;
5385 if (!sip->reregister_set) {
5386 gchar *action_name = g_strdup_printf("<%s>", "registration");
5387 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5388 g_free(action_name);
5389 sip->reregister_set = TRUE;
5392 sip->registerstatus = 3;
5394 if (server_hdr && !sip->server_version) {
5395 sip->server_version = g_strdup(server_hdr);
5396 g_free(default_ua);
5397 default_ua = NULL;
5400 auth_scheme = sipe_get_auth_scheme_name(sip);
5401 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5403 if (tmp) {
5404 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5405 fill_auth(tmp, &sip->registrar);
5408 if (!sip->reauthenticate_set) {
5409 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5410 guint reauth_timeout;
5411 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5412 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5413 reauth_timeout = sip->registrar.expires - 300;
5414 } else {
5415 /* NTLM: we have to reauthenticate as our security token expires
5416 after eight hours (be five minutes early) */
5417 reauth_timeout = (8 * 3600) - 300;
5419 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5420 g_free(action_name);
5421 sip->reauthenticate_set = TRUE;
5424 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5426 epid = get_epid(sip);
5427 uuid = generateUUIDfromEPID(epid);
5428 g_free(epid);
5430 // There can be multiple Contact headers (one per location where the user is logged in) so
5431 // make sure to only get the one for this uuid
5432 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5433 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5434 if (valid_contact) {
5435 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5436 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5437 g_free(valid_contact);
5438 break;
5439 } else {
5440 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5443 g_free(uuid);
5445 g_free(sip->contact);
5446 if(gruu) {
5447 sip->contact = g_strdup_printf("<%s>", gruu);
5448 g_free(gruu);
5449 } else {
5450 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5451 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);
5453 sip->ocs2007 = FALSE;
5454 sip->batched_support = FALSE;
5456 while(hdr)
5458 elem = hdr->data;
5459 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5460 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5461 /* We interpret this as OCS2007+ indicator */
5462 sip->ocs2007 = TRUE;
5463 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5465 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5466 sip->batched_support = TRUE;
5467 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5470 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5471 gchar **caps = g_strsplit(elem->value,",",0);
5472 i = 0;
5473 while (caps[i]) {
5474 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5475 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5476 i++;
5478 g_strfreev(caps);
5480 hdr = g_slist_next(hdr);
5483 /* rejoin open chats to be able to use them by continue to send messages */
5484 purple_conversation_foreach(sipe_rejoin_chat);
5486 /* subscriptions */
5487 if (!sip->subscribed) { //do it just once, not every re-register
5489 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5490 (GCompareFunc)g_ascii_strcasecmp)) {
5491 sipe_subscribe_roaming_contacts(sip);
5494 /* For 2007+ it does not make sence to subscribe to:
5495 * vnd-microsoft-roaming-ACL
5496 * vnd-microsoft-provisioning (not v2)
5497 * presence.wpending
5498 * These are for backward compatibility.
5500 if (sip->ocs2007)
5502 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5503 (GCompareFunc)g_ascii_strcasecmp)) {
5504 sipe_subscribe_roaming_self(sip);
5506 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5507 (GCompareFunc)g_ascii_strcasecmp)) {
5508 sipe_subscribe_roaming_provisioning_v2(sip);
5511 /* For 2005- servers */
5512 else
5514 //sipe_options_request(sip, sip->sipdomain);
5516 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5517 (GCompareFunc)g_ascii_strcasecmp)) {
5518 sipe_subscribe_roaming_acl(sip);
5520 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5521 (GCompareFunc)g_ascii_strcasecmp)) {
5522 sipe_subscribe_roaming_provisioning(sip);
5524 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5525 (GCompareFunc)g_ascii_strcasecmp)) {
5526 sipe_subscribe_presence_wpending(sip, msg);
5529 /* For 2007+ we publish our initial statuses and calendar data only after
5530 * received our existing publications in sipe_process_roaming_self()
5531 * Only in this case we know versions of current publications made
5532 * on our behalf.
5534 /* For 2005- we publish our initial statuses only after
5535 * received our existing UserInfo data in response to
5536 * self subscription.
5537 * Only in this case we won't override existing UserInfo data
5538 * set earlier or by other client on our behalf.
5542 sip->subscribed = TRUE;
5545 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5546 "timeout=", ";", NULL);
5547 if (timeout != NULL) {
5548 sscanf(timeout, "%u", &sip->keepalive_timeout);
5549 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5550 sip->keepalive_timeout);
5551 g_free(timeout);
5554 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5556 break;
5557 case 301:
5559 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5561 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5562 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5563 gchar **tmp;
5564 gchar *hostname;
5565 int port = 0;
5566 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5567 int i = 1;
5569 tmp = g_strsplit(parts[0], ":", 0);
5570 hostname = g_strdup(tmp[0]);
5571 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5572 g_strfreev(tmp);
5574 while (parts[i]) {
5575 tmp = g_strsplit(parts[i], "=", 0);
5576 if (tmp[1]) {
5577 if (g_strcasecmp("transport", tmp[0]) == 0) {
5578 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5579 transport = SIPE_TRANSPORT_TCP;
5580 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5581 transport = SIPE_TRANSPORT_UDP;
5585 g_strfreev(tmp);
5586 i++;
5588 g_strfreev(parts);
5590 /* Close old connection */
5591 sipe_connection_cleanup(sip);
5593 /* Create new connection */
5594 sip->transport = transport;
5595 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5596 hostname, port, TRANSPORT_DESCRIPTOR);
5597 create_connection(sip, hostname, port);
5599 g_free(redirect);
5601 break;
5602 case 401:
5603 if (sip->registerstatus != 2) {
5604 const char *auth_scheme;
5605 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5606 if (sip->registrar.retries > 3) {
5607 sip->gc->wants_to_die = TRUE;
5608 purple_connection_error(sip->gc, _("Authentication failed"));
5609 return TRUE;
5612 auth_scheme = sipe_get_auth_scheme_name(sip);
5613 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5615 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5616 if (!tmp) {
5617 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5618 sip->gc->wants_to_die = TRUE;
5619 purple_connection_error(sip->gc, tmp2);
5620 g_free(tmp2);
5621 return TRUE;
5623 fill_auth(tmp, &sip->registrar);
5624 sip->registerstatus = 2;
5625 if (sip->account->disconnecting) {
5626 do_register_exp(sip, 0);
5627 } else {
5628 do_register(sip);
5631 break;
5632 case 403:
5634 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5635 gchar **reason = NULL;
5636 gchar *warning;
5637 if (diagnostics != NULL) {
5638 /* Example header:
5639 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5641 reason = g_strsplit(diagnostics, "\"", 0);
5643 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5644 (reason && reason[1]) ? reason[1] : _("no reason given"));
5645 g_strfreev(reason);
5647 sip->gc->wants_to_die = TRUE;
5648 purple_connection_error(sip->gc, warning);
5649 g_free(warning);
5650 return TRUE;
5652 break;
5653 case 404:
5655 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5656 gchar *reason = NULL;
5657 gchar *warning;
5658 if (diagnostics != NULL) {
5659 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5661 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5662 diagnostics ? (reason ? reason : _("no reason given")) :
5663 _("SIP is either not enabled for the destination URI or it does not exist"));
5664 g_free(reason);
5666 sip->gc->wants_to_die = TRUE;
5667 purple_connection_error(sip->gc, warning);
5668 g_free(warning);
5669 return TRUE;
5671 break;
5672 case 503:
5673 case 504: /* Server time-out */
5675 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5676 gchar *reason = NULL;
5677 gchar *warning;
5678 if (diagnostics != NULL) {
5679 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5681 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5682 g_free(reason);
5684 sip->gc->wants_to_die = TRUE;
5685 purple_connection_error(sip->gc, warning);
5686 g_free(warning);
5687 return TRUE;
5689 break;
5691 return TRUE;
5695 * Returns 2005-style activity and Availability.
5697 * @param status Sipe statis id.
5699 static void
5700 sipe_get_act_avail_by_status_2005(const char *status,
5701 int *activity,
5702 int *availability)
5704 int avail = 300; /* online */
5705 int act = 400; /* Available */
5707 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5708 act = 100;
5709 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5710 // act = 150;
5711 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5712 act = 300;
5713 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5714 act = 400;
5715 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5716 // act = 500;
5717 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5718 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5719 act = 600;
5720 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5721 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5722 avail = 0; /* offline */
5723 act = 100;
5724 } else {
5725 act = 400; /* Available */
5728 if (activity) *activity = act;
5729 if (availability) *availability = avail;
5733 * [MS-SIP] 2.2.1
5735 * @param activity 2005 aggregated activity. Ex.: 600
5736 * @param availablity 2005 aggregated availablity. Ex.: 300
5738 static const char *
5739 sipe_get_status_by_act_avail_2005(const int activity,
5740 const int availablity,
5741 char **activity_desc)
5743 const char *status_id = NULL;
5744 const char *act = NULL;
5746 if (activity < 150) {
5747 status_id = SIPE_STATUS_ID_AWAY;
5748 } else if (activity < 200) {
5749 //status_id = SIPE_STATUS_ID_LUNCH;
5750 status_id = SIPE_STATUS_ID_AWAY;
5751 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5752 } else if (activity < 300) {
5753 //status_id = SIPE_STATUS_ID_IDLE;
5754 status_id = SIPE_STATUS_ID_AWAY;
5755 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5756 } else if (activity < 400) {
5757 status_id = SIPE_STATUS_ID_BRB;
5758 } else if (activity < 500) {
5759 status_id = SIPE_STATUS_ID_AVAILABLE;
5760 } else if (activity < 600) {
5761 //status_id = SIPE_STATUS_ID_ON_PHONE;
5762 status_id = SIPE_STATUS_ID_BUSY;
5763 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5764 } else if (activity < 700) {
5765 status_id = SIPE_STATUS_ID_BUSY;
5766 } else if (activity < 800) {
5767 status_id = SIPE_STATUS_ID_AWAY;
5768 } else {
5769 status_id = SIPE_STATUS_ID_AVAILABLE;
5772 if (availablity < 100)
5773 status_id = SIPE_STATUS_ID_OFFLINE;
5775 if (activity_desc && act) {
5776 g_free(*activity_desc);
5777 *activity_desc = g_strdup(act);
5780 return status_id;
5784 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5786 static const char*
5787 sipe_get_status_by_availability(int avail,
5788 char** activity_desc)
5790 const char *status;
5791 const char *act = NULL;
5793 if (avail < 3000) {
5794 status = SIPE_STATUS_ID_OFFLINE;
5795 } else if (avail < 4500) {
5796 status = SIPE_STATUS_ID_AVAILABLE;
5797 } else if (avail < 6000) {
5798 //status = SIPE_STATUS_ID_IDLE;
5799 status = SIPE_STATUS_ID_AVAILABLE;
5800 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5801 } else if (avail < 7500) {
5802 status = SIPE_STATUS_ID_BUSY;
5803 } else if (avail < 9000) {
5804 //status = SIPE_STATUS_ID_BUSYIDLE;
5805 status = SIPE_STATUS_ID_BUSY;
5806 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5807 } else if (avail < 12000) {
5808 status = SIPE_STATUS_ID_DND;
5809 } else if (avail < 15000) {
5810 status = SIPE_STATUS_ID_BRB;
5811 } else if (avail < 18000) {
5812 status = SIPE_STATUS_ID_AWAY;
5813 } else {
5814 status = SIPE_STATUS_ID_OFFLINE;
5817 if (activity_desc && act) {
5818 g_free(*activity_desc);
5819 *activity_desc = g_strdup(act);
5822 return status;
5826 * Returns 2007-style availability value
5828 * @param sipe_status_id (in)
5829 * @param activity_token (out) Must be g_free()'d after use if consumed.
5831 static int
5832 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5834 int availability;
5835 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5837 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5838 availability = 15500;
5839 if (!activity_token || !(*activity_token)) {
5840 activity = SIPE_ACTIVITY_AWAY;
5842 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5843 availability = 12500;
5844 activity = SIPE_ACTIVITY_BRB;
5845 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5846 availability = 9500;
5847 activity = SIPE_ACTIVITY_DND;
5848 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5849 availability = 6500;
5850 if (!activity_token || !(*activity_token)) {
5851 activity = SIPE_ACTIVITY_BUSY;
5853 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5854 availability = 3500;
5855 activity = SIPE_ACTIVITY_ONLINE;
5856 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5857 availability = 0;
5858 } else {
5859 // Offline or invisible
5860 availability = 18500;
5861 activity = SIPE_ACTIVITY_OFFLINE;
5864 if (activity_token) {
5865 *activity_token = g_strdup(sipe_activity_map[activity].token);
5867 return availability;
5870 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5872 const char *uri;
5873 xmlnode *xn_categories;
5874 xmlnode *xn_category;
5875 xmlnode *xn_node;
5876 const char *status = NULL;
5877 gboolean do_update_status = FALSE;
5878 gboolean has_note_cleaned = FALSE;
5879 gboolean has_free_busy_cleaned = FALSE;
5881 xn_categories = xmlnode_from_str(data, len);
5882 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5884 for (xn_category = xmlnode_get_child(xn_categories, "category");
5885 xn_category ;
5886 xn_category = xmlnode_get_next_twin(xn_category) )
5888 const char *tmp;
5889 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5890 time_t publish_time = (tmp = xmlnode_get_attrib(xn_category, "publishTime")) ?
5891 sipe_utils_str_to_time(tmp) : 0;
5893 /* contactCard */
5894 if (sipe_strequal(attrVar, "contactCard"))
5896 xmlnode *node;
5897 /* identity - Display Name and email */
5898 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5899 if (node) {
5900 char* display_name = xmlnode_get_data(
5901 xmlnode_get_descendant(node, "name", "displayName", NULL));
5902 char* email = xmlnode_get_data(
5903 xmlnode_get_child(node, "email"));
5905 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5906 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5908 g_free(display_name);
5909 g_free(email);
5911 /* company */
5912 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5913 if (node) {
5914 char* company = xmlnode_get_data(node);
5915 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5916 g_free(company);
5918 /* department */
5919 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5920 if (node) {
5921 char* department = xmlnode_get_data(node);
5922 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5923 g_free(department);
5925 /* title */
5926 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5927 if (node) {
5928 char* title = xmlnode_get_data(node);
5929 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5930 g_free(title);
5932 /* office */
5933 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5934 if (node) {
5935 char* office = xmlnode_get_data(node);
5936 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5937 g_free(office);
5939 /* site (url) */
5940 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5941 if (node) {
5942 char* site = xmlnode_get_data(node);
5943 sipe_update_user_info(sip, uri, SITE_PROP, site);
5944 g_free(site);
5946 /* phone */
5947 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5948 node;
5949 node = xmlnode_get_next_twin(node))
5951 const char *phone_type = xmlnode_get_attrib(node, "type");
5952 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5953 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5955 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5957 g_free(phone);
5958 g_free(phone_display_string);
5960 /* address */
5961 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5962 node;
5963 node = xmlnode_get_next_twin(node))
5965 if (sipe_strequal(xmlnode_get_attrib(node, "type"), "work")) {
5966 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5967 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5968 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5969 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5970 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5972 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5973 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5974 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5975 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5976 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5978 g_free(street);
5979 g_free(city);
5980 g_free(state);
5981 g_free(zipcode);
5982 g_free(country_code);
5984 break;
5988 /* note */
5989 else if (sipe_strequal(attrVar, "note"))
5991 if (uri) {
5992 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5994 if (!has_note_cleaned) {
5995 has_note_cleaned = TRUE;
5997 g_free(sbuddy->note);
5998 sbuddy->note = NULL;
5999 sbuddy->is_oof_note = FALSE;
6000 sbuddy->note_since = publish_time;
6002 do_update_status = TRUE;
6004 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6005 /* clean up in case no 'note' element is supplied
6006 * which indicate note removal in client
6008 g_free(sbuddy->note);
6009 sbuddy->note = NULL;
6010 sbuddy->is_oof_note = FALSE;
6011 sbuddy->note_since = publish_time;
6013 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
6014 if (xn_node) {
6015 char *tmp;
6016 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
6017 g_free(tmp);
6018 sbuddy->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_node, "type"), "OOF");
6019 sbuddy->note_since = publish_time;
6021 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
6022 uri, sbuddy->note ? sbuddy->note : "");
6024 /* to trigger UI refresh in case no status info is supplied in this update */
6025 do_update_status = TRUE;
6029 /* state */
6030 else if(sipe_strequal(attrVar, "state"))
6032 char *tmp;
6033 int availability;
6034 xmlnode *xn_availability;
6035 xmlnode *xn_activity;
6036 xmlnode *xn_meeting_subject;
6037 xmlnode *xn_meeting_location;
6038 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6040 xn_node = xmlnode_get_child(xn_category, "state");
6041 if (!xn_node) continue;
6042 xn_availability = xmlnode_get_child(xn_node, "availability");
6043 if (!xn_availability) continue;
6044 xn_activity = xmlnode_get_child(xn_node, "activity");
6045 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
6046 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
6048 tmp = xmlnode_get_data(xn_availability);
6049 availability = atoi(tmp);
6050 g_free(tmp);
6052 /* activity, meeting_subject, meeting_location */
6053 if (sbuddy) {
6054 char *tmp = NULL;
6056 /* activity */
6057 g_free(sbuddy->activity);
6058 sbuddy->activity = NULL;
6059 if (xn_activity) {
6060 const char *token = xmlnode_get_attrib(xn_activity, "token");
6061 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
6063 /* from token */
6064 if (!is_empty(token)) {
6065 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6067 /* from custom element */
6068 if (xn_custom) {
6069 char *custom = xmlnode_get_data(xn_custom);
6071 if (!is_empty(custom)) {
6072 sbuddy->activity = custom;
6073 custom = NULL;
6075 g_free(custom);
6078 /* meeting_subject */
6079 g_free(sbuddy->meeting_subject);
6080 sbuddy->meeting_subject = NULL;
6081 if (xn_meeting_subject) {
6082 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
6084 if (!is_empty(meeting_subject)) {
6085 sbuddy->meeting_subject = meeting_subject;
6086 meeting_subject = NULL;
6088 g_free(meeting_subject);
6090 /* meeting_location */
6091 g_free(sbuddy->meeting_location);
6092 sbuddy->meeting_location = NULL;
6093 if (xn_meeting_location) {
6094 char *meeting_location = xmlnode_get_data(xn_meeting_location);
6096 if (!is_empty(meeting_location)) {
6097 sbuddy->meeting_location = meeting_location;
6098 meeting_location = NULL;
6100 g_free(meeting_location);
6103 status = sipe_get_status_by_availability(availability, &tmp);
6104 if (sbuddy->activity && tmp) {
6105 char *tmp2 = sbuddy->activity;
6107 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6108 g_free(tmp);
6109 g_free(tmp2);
6110 } else if (tmp) {
6111 sbuddy->activity = tmp;
6115 do_update_status = TRUE;
6117 /* calendarData */
6118 else if(sipe_strequal(attrVar, "calendarData"))
6120 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6121 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
6122 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
6124 if (sbuddy && xn_free_busy) {
6125 if (!has_free_busy_cleaned) {
6126 has_free_busy_cleaned = TRUE;
6128 g_free(sbuddy->cal_start_time);
6129 sbuddy->cal_start_time = NULL;
6131 g_free(sbuddy->cal_free_busy_base64);
6132 sbuddy->cal_free_busy_base64 = NULL;
6134 g_free(sbuddy->cal_free_busy);
6135 sbuddy->cal_free_busy = NULL;
6137 sbuddy->cal_free_busy_published = publish_time;
6140 if (publish_time >= sbuddy->cal_free_busy_published) {
6141 g_free(sbuddy->cal_start_time);
6142 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
6144 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
6145 15 : 0;
6147 g_free(sbuddy->cal_free_busy_base64);
6148 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
6150 g_free(sbuddy->cal_free_busy);
6151 sbuddy->cal_free_busy = NULL;
6153 sbuddy->cal_free_busy_published = publish_time;
6155 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);
6159 if (sbuddy && xn_working_hours) {
6160 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6165 if (do_update_status) {
6166 if (!status) { /* no status category in this update, using contact's current status */
6167 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6168 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6169 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6170 status = purple_status_get_id(pstatus);
6173 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
6174 sipe_got_user_status(sip, uri, status);
6177 xmlnode_free(xn_categories);
6180 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6182 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6183 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
6184 payload->host = g_strdup(host);
6185 payload->buddies = server;
6186 sipe_subscribe_presence_batched_routed(sip, payload);
6187 sipe_subscribe_presence_batched_routed_free(payload);
6190 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6192 xmlnode *xn_list;
6193 xmlnode *xn_resource;
6194 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6195 g_free, NULL);
6196 GSList *server;
6197 gchar *host;
6199 xn_list = xmlnode_from_str(data, len);
6201 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6202 xn_resource;
6203 xn_resource = xmlnode_get_next_twin(xn_resource) )
6205 const char *uri, *state;
6206 xmlnode *xn_instance;
6208 xn_instance = xmlnode_get_child(xn_resource, "instance");
6209 if (!xn_instance) continue;
6211 uri = xmlnode_get_attrib(xn_resource, "uri");
6212 state = xmlnode_get_attrib(xn_instance, "state");
6213 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6215 if (strstr(state, "resubscribe")) {
6216 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6218 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6219 gchar *user = g_strdup(uri);
6220 host = g_strdup(poolFqdn);
6221 server = g_hash_table_lookup(servers, host);
6222 server = g_slist_append(server, user);
6223 g_hash_table_insert(servers, host, server);
6224 } else {
6225 sipe_subscribe_presence_single(sip, (void *) uri);
6230 /* Send out any deferred poolFqdn subscriptions */
6231 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6232 g_hash_table_destroy(servers);
6234 xmlnode_free(xn_list);
6237 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6239 gchar *uri;
6240 gchar *getbasic;
6241 gchar *activity = NULL;
6242 xmlnode *pidf;
6243 xmlnode *basicstatus = NULL, *tuple, *status;
6244 gboolean isonline = FALSE;
6245 xmlnode *display_name_node;
6247 pidf = xmlnode_from_str(data, len);
6248 if (!pidf) {
6249 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6250 return;
6253 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6255 if ((status = xmlnode_get_child(tuple, "status"))) {
6256 basicstatus = xmlnode_get_child(status, "basic");
6260 if (!basicstatus) {
6261 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6262 xmlnode_free(pidf);
6263 return;
6266 getbasic = xmlnode_get_data(basicstatus);
6267 if (!getbasic) {
6268 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6269 xmlnode_free(pidf);
6270 return;
6273 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6274 if (strstr(getbasic, "open")) {
6275 isonline = TRUE;
6277 g_free(getbasic);
6279 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6281 display_name_node = xmlnode_get_child(pidf, "display-name");
6282 if (display_name_node) {
6283 char * display_name = xmlnode_get_data(display_name_node);
6285 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6286 g_free(display_name);
6289 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6290 if ((status = xmlnode_get_child(tuple, "status"))) {
6291 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6292 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6293 activity = xmlnode_get_data(basicstatus);
6294 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6300 if (isonline) {
6301 const gchar * status_id = NULL;
6302 if (activity) {
6303 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6304 status_id = SIPE_STATUS_ID_BUSY;
6305 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6306 status_id = SIPE_STATUS_ID_AWAY;
6310 if (!status_id) {
6311 status_id = SIPE_STATUS_ID_AVAILABLE;
6314 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6315 sipe_got_user_status(sip, uri, status_id);
6316 } else {
6317 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6320 g_free(activity);
6321 g_free(uri);
6322 xmlnode_free(pidf);
6325 /** 2005 */
6326 static void
6327 sipe_user_info_has_updated(struct sipe_account_data *sip,
6328 xmlnode *xn_userinfo)
6330 xmlnode *xn_states;
6332 g_free(sip->user_states);
6333 sip->user_states = NULL;
6334 if ((xn_states = xmlnode_get_child(xn_userinfo, "states")) != NULL) {
6335 sip->user_states = xmlnode_to_str(xn_states, NULL);
6336 /* this is a hack-around to remove added newline after inner element,
6337 * state in this case, where it shouldn't be.
6338 * After several use of xmlnode_to_str, amount of added newlines
6339 * grows significantly.
6341 purple_str_strip_char(sip->user_states, '\n');
6342 //purple_str_strip_char(sip->user_states, '\r');
6345 /* Publish initial state if not yet.
6346 * Assuming this happens on initial responce to self subscription
6347 * so we've already updated our UserInfo.
6349 if (!sip->initial_state_published) {
6350 send_presence_soap(sip, FALSE);
6351 /* dalayed run */
6352 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6356 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6358 char *activity = NULL;
6359 const char *epid;
6360 const char *status_id = NULL;
6361 const char *name;
6362 char *uri;
6363 char *self_uri = sip_uri_self(sip);
6364 int avl;
6365 int act;
6366 const char *device_name = NULL;
6367 const char *cal_start_time = NULL;
6368 const char *cal_granularity = NULL;
6369 char *cal_free_busy_base64 = NULL;
6370 struct sipe_buddy *sbuddy;
6371 xmlnode *node;
6372 xmlnode *xn_presentity;
6373 xmlnode *xn_availability;
6374 xmlnode *xn_activity;
6375 xmlnode *xn_display_name;
6376 xmlnode *xn_email;
6377 xmlnode *xn_phone_number;
6378 xmlnode *xn_userinfo;
6379 xmlnode *xn_note;
6380 xmlnode *xn_oof;
6381 xmlnode *xn_state;
6382 xmlnode *xn_contact;
6383 char *note;
6384 char *free_activity;
6385 int user_avail;
6386 const char *user_avail_nil;
6387 int res_avail;
6388 time_t user_avail_since = 0;
6389 time_t activity_since = 0;
6391 /* fix for Reuters environment on Linux */
6392 if (data && strstr(data, "encoding=\"utf-16\"")) {
6393 char *tmp_data;
6394 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6395 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6396 g_free(tmp_data);
6397 } else {
6398 xn_presentity = xmlnode_from_str(data, len);
6401 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6402 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6403 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6404 xn_email = xmlnode_get_child(xn_presentity, "email");
6405 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6406 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6407 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6408 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6409 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6410 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6411 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6412 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6413 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6414 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6416 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6417 user_avail = 0;
6418 user_avail_since = 0;
6421 free_activity = NULL;
6423 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6424 uri = sip_uri_from_name(name);
6425 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6426 epid = xmlnode_get_attrib(xn_availability, "epid");
6427 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6429 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6430 res_avail = sipe_get_availability_by_status(status_id, NULL);
6431 if (user_avail > res_avail) {
6432 res_avail = user_avail;
6433 status_id = sipe_get_status_by_availability(user_avail, NULL);
6436 if (xn_display_name) {
6437 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6438 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6439 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6440 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6441 char *tel_uri = sip_to_tel_uri(phone_number);
6443 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6444 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6445 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6446 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6448 g_free(tel_uri);
6449 g_free(phone_label);
6450 g_free(phone_number);
6451 g_free(email);
6452 g_free(display_name);
6455 if (xn_contact) {
6456 /* tel */
6457 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6459 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6460 const char *phone_type = xmlnode_get_attrib(node, "type");
6461 char* phone = xmlnode_get_data(node);
6463 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6465 g_free(phone);
6469 /* devicePresence */
6470 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6471 xmlnode *xn_device_name;
6472 xmlnode *xn_calendar_info;
6473 xmlnode *xn_state;
6474 char *state;
6476 /* deviceName */
6477 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6478 xn_device_name = xmlnode_get_child(node, "deviceName");
6479 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6482 /* calendarInfo */
6483 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6484 if (xn_calendar_info) {
6485 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6487 if (cal_start_time) {
6488 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6489 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6491 if (cal_start_time_t_tmp > cal_start_time_t) {
6492 cal_start_time = cal_start_time_tmp;
6493 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6494 g_free(cal_free_busy_base64);
6495 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6497 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);
6499 } else {
6500 cal_start_time = cal_start_time_tmp;
6501 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6502 g_free(cal_free_busy_base64);
6503 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6505 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);
6509 /* state */
6510 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6511 if (xn_state) {
6512 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6513 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6515 state = xmlnode_get_data(xn_state);
6516 if (dev_avail_since > user_avail_since &&
6517 dev_avail >= res_avail)
6519 res_avail = dev_avail;
6520 if (!is_empty(state))
6522 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6523 g_free(activity);
6524 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6525 } else if (sipe_strequal(state, "presenting")) {
6526 g_free(activity);
6527 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6528 } else {
6529 activity = state;
6530 state = NULL;
6532 activity_since = dev_avail_since;
6534 status_id = sipe_get_status_by_availability(res_avail, &activity);
6536 g_free(state);
6540 /* oof */
6541 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6542 g_free(activity);
6543 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6544 activity_since = 0;
6547 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6548 if (sbuddy)
6550 g_free(sbuddy->activity);
6551 sbuddy->activity = activity;
6552 activity = NULL;
6554 sbuddy->activity_since = activity_since;
6556 sbuddy->user_avail = user_avail;
6557 sbuddy->user_avail_since = user_avail_since;
6559 g_free(sbuddy->note);
6560 sbuddy->note = NULL;
6561 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6563 sbuddy->is_oof_note = (xn_oof != NULL);
6565 g_free(sbuddy->device_name);
6566 sbuddy->device_name = NULL;
6567 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6569 if (!is_empty(cal_free_busy_base64)) {
6570 g_free(sbuddy->cal_start_time);
6571 sbuddy->cal_start_time = g_strdup(cal_start_time);
6573 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6575 g_free(sbuddy->cal_free_busy_base64);
6576 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6577 cal_free_busy_base64 = NULL;
6579 g_free(sbuddy->cal_free_busy);
6580 sbuddy->cal_free_busy = NULL;
6583 sbuddy->last_non_cal_status_id = status_id;
6584 g_free(sbuddy->last_non_cal_activity);
6585 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6587 if (sipe_strequal(sbuddy->name, self_uri)) {
6588 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6590 sip->is_oof_note = sbuddy->is_oof_note;
6592 g_free(sip->note);
6593 sip->note = g_strdup(sbuddy->note);
6595 sip->note_since = time(NULL);
6598 g_free(sip->status);
6599 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6602 g_free(cal_free_busy_base64);
6603 g_free(activity);
6605 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6606 sipe_got_user_status(sip, uri, status_id);
6608 if (!sip->ocs2007 && sipe_strequal(self_uri, uri)) {
6609 sipe_user_info_has_updated(sip, xn_userinfo);
6612 g_free(note);
6613 xmlnode_free(xn_presentity);
6614 g_free(uri);
6615 g_free(self_uri);
6618 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6620 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6622 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6624 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6625 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6627 const char *content = msg->body;
6628 unsigned length = msg->bodylen;
6629 PurpleMimeDocument *mime = NULL;
6631 if (strstr(ctype, "multipart"))
6633 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6634 const char *content_type;
6635 GList* parts;
6636 mime = purple_mime_document_parse(doc);
6637 parts = purple_mime_document_get_parts(mime);
6638 while(parts) {
6639 content = purple_mime_part_get_data(parts->data);
6640 length = purple_mime_part_get_length(parts->data);
6641 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6642 if(content_type && strstr(content_type,"application/rlmi+xml"))
6644 process_incoming_notify_rlmi_resub(sip, content, length);
6646 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6648 process_incoming_notify_msrtc(sip, content, length);
6650 else
6652 process_incoming_notify_rlmi(sip, content, length);
6654 parts = parts->next;
6656 g_free(doc);
6658 if (mime)
6660 purple_mime_document_free(mime);
6663 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6665 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6667 else if(strstr(ctype, "application/rlmi+xml"))
6669 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6672 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6674 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6676 else
6678 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6682 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6684 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6685 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6687 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6689 if (ctype &&
6690 strstr(ctype, "multipart") &&
6691 (strstr(ctype, "application/rlmi+xml") ||
6692 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6693 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6694 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6695 GList *parts = purple_mime_document_get_parts(mime);
6696 GSList *buddies = NULL;
6697 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6699 while (parts) {
6700 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6701 purple_mime_part_get_length(parts->data));
6703 if (xml && !sipe_strequal(xml->name, "list")) {
6704 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6706 buddies = g_slist_append(buddies, uri);
6708 xmlnode_free(xml);
6710 parts = parts->next;
6712 g_free(doc);
6713 if (mime) purple_mime_document_free(mime);
6715 payload->host = g_strdup(who);
6716 payload->buddies = buddies;
6717 sipe_schedule_action(action_name, timeout,
6718 sipe_subscribe_presence_batched_routed,
6719 sipe_subscribe_presence_batched_routed_free,
6720 sip, payload);
6721 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6723 } else {
6724 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6725 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6727 g_free(action_name);
6731 * Dispatcher for all incoming subscription information
6732 * whether it comes from NOTIFY, BENOTIFY requests or
6733 * piggy-backed to subscription's OK responce.
6735 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6736 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6738 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6740 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6741 const gchar *event = sipmsg_find_header(msg, "Event");
6742 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6743 char *tmp;
6745 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6746 event ? event : "",
6747 tmp = fix_newlines(msg->body));
6748 g_free(tmp);
6749 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6751 /* implicit subscriptions */
6752 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6753 sipe_process_imdn(sip, msg);
6756 if (event) {
6757 /* for one off subscriptions (send with Expire: 0) */
6758 if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6760 sipe_process_provisioning_v2(sip, msg);
6762 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6764 sipe_process_provisioning(sip, msg);
6766 else if (!g_ascii_strcasecmp(event, "presence"))
6768 sipe_process_presence(sip, msg);
6770 else if (!g_ascii_strcasecmp(event, "registration-notify"))
6772 sipe_process_registration_notify(sip, msg);
6775 if (!subscription_state || strstr(subscription_state, "active"))
6777 if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6779 sipe_process_roaming_contacts(sip, msg);
6781 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6783 sipe_process_roaming_self(sip, msg);
6785 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6787 sipe_process_roaming_acl(sip, msg);
6789 else if (!g_ascii_strcasecmp(event, "presence.wpending"))
6791 sipe_process_presence_wpending(sip, msg);
6793 else if (!g_ascii_strcasecmp(event, "conference"))
6795 sipe_process_conference(sip, msg);
6800 /* The server sends status 'terminated' */
6801 if (subscription_state && strstr(subscription_state, "terminated") ) {
6802 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6803 gchar *key = sipe_get_subscription_key(event, who);
6805 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6806 g_free(who);
6808 if (g_hash_table_lookup(sip->subscriptions, key)) {
6809 g_hash_table_remove(sip->subscriptions, key);
6810 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6813 g_free(key);
6816 if (!request && event) {
6817 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6818 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6819 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6821 if (timeout) {
6822 /* 2 min ahead of expiration */
6823 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6825 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6826 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6828 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6829 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6830 g_free(action_name);
6832 else if (!g_ascii_strcasecmp(event, "presence") &&
6833 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6835 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6836 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6838 if (sip->batched_support) {
6839 sipe_process_presence_timeout(sip, msg, who, timeout);
6841 else {
6842 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6843 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6845 g_free(action_name);
6846 g_free(who);
6851 /* The client responses on received a NOTIFY message */
6852 if (request && !benotify)
6854 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6859 * Whether user manually changed status or
6860 * it was changed automatically due to user
6861 * became inactive/active again
6863 static gboolean
6864 sipe_is_user_state(struct sipe_account_data *sip)
6866 gboolean res;
6867 time_t now = time(NULL);
6869 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6870 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6872 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6874 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6875 return res;
6878 static void
6879 send_presence_soap0(struct sipe_account_data *sip,
6880 gboolean do_publish_calendar,
6881 gboolean do_reset_status)
6883 struct sipe_ews* ews = sip->ews;
6884 int availability = 0;
6885 int activity = 0;
6886 gchar *body;
6887 gchar *tmp;
6888 gchar *tmp2 = NULL;
6889 gchar *res_note = NULL;
6890 gchar *res_oof = NULL;
6891 const gchar *note_pub = NULL;
6892 gchar *states = NULL;
6893 gchar *calendar_data = NULL;
6894 gchar *epid = get_epid(sip);
6895 time_t now = time(NULL);
6896 gchar *since_time_str = sipe_utils_time_to_str(now);
6897 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6898 const char *user_input;
6899 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6901 if (oof_note && sip->note) {
6902 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6903 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6906 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6908 if (!sip->initial_state_published ||
6909 do_reset_status)
6911 g_free(sip->status);
6912 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6915 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6917 /* Note */
6918 if (pub_oof) {
6919 note_pub = oof_note;
6920 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6921 ews->published = TRUE;
6922 } else if (sip->note) {
6923 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6924 g_free(sip->note);
6925 sip->note = NULL;
6926 sip->is_oof_note = FALSE;
6927 sip->note_since = 0;
6928 } else {
6929 note_pub = sip->note;
6930 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6934 if (note_pub)
6936 /* to protocol internal plain text format */
6937 tmp = purple_markup_strip_html(note_pub);
6938 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6939 g_free(tmp);
6942 /* User State */
6943 if (!do_reset_status) {
6944 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6946 gchar *activity_token = NULL;
6947 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6949 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6950 avail_2007,
6951 since_time_str,
6952 epid,
6953 activity_token);
6954 g_free(activity_token);
6956 else /* preserve existing publication */
6958 if (sip->user_states) {
6959 states = g_strdup(sip->user_states);
6962 } else {
6963 /* do nothing - then User state will be erased */
6965 sip->initial_state_published = TRUE;
6967 /* CalendarInfo */
6968 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6970 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6971 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6972 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6973 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6974 fb_start_str,
6975 free_busy_base64);
6976 g_free(fb_start_str);
6977 g_free(free_busy_base64);
6980 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6982 /* forming resulting XML */
6983 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6984 sip->username,
6985 availability,
6986 activity,
6987 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6988 res_note ? res_note : "",
6989 res_oof ? res_oof : "",
6990 states ? states : "",
6991 calendar_data ? calendar_data : "",
6992 epid,
6993 since_time_str,
6994 since_time_str,
6995 user_input);
6996 g_free(tmp);
6997 g_free(tmp2);
6998 g_free(res_note);
6999 g_free(states);
7000 g_free(calendar_data);
7002 send_soap_request(sip, body);
7004 g_free(body);
7005 g_free(since_time_str);
7006 g_free(epid);
7009 void
7010 send_presence_soap(struct sipe_account_data *sip,
7011 gboolean do_publish_calendar)
7013 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7017 static gboolean
7018 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7019 struct sipmsg *msg,
7020 struct transaction *trans)
7022 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7024 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7025 xmlnode *xml;
7026 xmlnode *node;
7027 gchar *fault_code;
7028 GHashTable *faults;
7029 int index_our;
7030 gboolean has_device_publication = FALSE;
7032 xml = xmlnode_from_str(msg->body, msg->bodylen);
7034 /* test if version mismatch fault */
7035 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7036 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7037 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
7038 g_free(fault_code);
7039 xmlnode_free(xml);
7040 return TRUE;
7042 g_free(fault_code);
7044 /* accumulating information about faulty versions */
7045 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7046 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7047 node;
7048 node = xmlnode_get_next_twin(node))
7050 const gchar *index = xmlnode_get_attrib(node, "index");
7051 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7053 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7054 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
7056 xmlnode_free(xml);
7058 /* here we are parsing own request to figure out what publication
7059 * referensed here only by index went wrong
7061 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7063 /* publication */
7064 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7065 index_our = 1; /* starts with 1 - our first publication */
7066 node;
7067 node = xmlnode_get_next_twin(node), index_our++)
7069 gchar *idx = g_strdup_printf("%d", index_our);
7070 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7071 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7072 g_free(idx);
7074 if (sipe_strequal("device", categoryName)) {
7075 has_device_publication = TRUE;
7078 if (curVersion) { /* fault exist on this index */
7079 const gchar *container = xmlnode_get_attrib(node, "container");
7080 const gchar *instance = xmlnode_get_attrib(node, "instance");
7081 /* key is <category><instance><container> */
7082 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7083 struct sipe_publication *publication =
7084 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
7086 purple_debug_info("sipe", "key is %s\n", key);
7088 if (publication) {
7089 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
7090 key, curVersion, publication->version);
7091 /* updating publication's version to the correct one */
7092 publication->version = atoi(curVersion);
7094 g_free(key);
7097 xmlnode_free(xml);
7098 g_hash_table_destroy(faults);
7100 /* rebublishing with right versions */
7101 if (has_device_publication) {
7102 send_publish_category_initial(sip);
7103 } else {
7104 send_presence_status(sip);
7107 return TRUE;
7111 * Returns 'device' XML part for publication.
7112 * Must be g_free'd after use.
7114 static gchar *
7115 sipe_publish_get_category_device(struct sipe_account_data *sip)
7117 gchar *uri;
7118 gchar *doc;
7119 gchar *epid = get_epid(sip);
7120 gchar *uuid = generateUUIDfromEPID(epid);
7121 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7122 /* key is <category><instance><container> */
7123 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7124 struct sipe_publication *publication =
7125 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7127 g_free(key);
7128 g_free(epid);
7130 uri = sip_uri_self(sip);
7131 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7132 device_instance,
7133 publication ? publication->version : 0,
7134 uuid,
7135 uri,
7136 "00:00:00+01:00", /* @TODO make timezone real*/
7137 sipe_get_host_name()
7140 g_free(uri);
7141 g_free(uuid);
7143 return doc;
7147 * A service method - use
7148 * - send_publish_get_category_state_machine and
7149 * - send_publish_get_category_state_user instead.
7150 * Must be g_free'd after use.
7152 static gchar *
7153 sipe_publish_get_category_state(struct sipe_account_data *sip,
7154 gboolean is_user_state)
7156 int availability = sipe_get_availability_by_status(sip->status, NULL);
7157 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7158 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7159 /* key is <category><instance><container> */
7160 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7161 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7162 struct sipe_publication *publication_2 =
7163 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7164 struct sipe_publication *publication_3 =
7165 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7167 g_free(key_2);
7168 g_free(key_3);
7170 if (publication_2 && (publication_2->availability == availability))
7172 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
7173 return NULL; /* nothing to update */
7176 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7177 instance,
7178 publication_2 ? publication_2->version : 0,
7179 availability,
7180 instance,
7181 publication_3 ? publication_3->version : 0,
7182 availability);
7186 * Only Busy and OOF calendar event are published.
7187 * Different instances are used for that.
7189 * Must be g_free'd after use.
7191 static gchar *
7192 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7193 struct sipe_cal_event *event,
7194 const char *uri,
7195 int cal_satus)
7197 gchar *start_time_str;
7198 int availability = 0;
7199 gchar *res;
7200 gchar *tmp = NULL;
7201 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7202 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7203 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7205 /* key is <category><instance><container> */
7206 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7207 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7208 struct sipe_publication *publication_2 =
7209 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7210 struct sipe_publication *publication_3 =
7211 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7213 g_free(key_2);
7214 g_free(key_3);
7216 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7217 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7218 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7219 return NULL;
7222 if (event &&
7223 publication_3 &&
7224 (publication_3->availability == availability) &&
7225 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7227 g_free(tmp);
7228 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7229 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7230 return NULL; /* nothing to update */
7232 g_free(tmp);
7234 if (event &&
7235 (event->cal_status == SIPE_CAL_BUSY ||
7236 event->cal_status == SIPE_CAL_OOF))
7238 gchar *availability_xml_str = NULL;
7239 gchar *activity_xml_str = NULL;
7241 if (event->cal_status == SIPE_CAL_BUSY) {
7242 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7245 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7246 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7247 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7248 "minAvailability=\"6500\"",
7249 "maxAvailability=\"8999\"");
7250 } else if (event->cal_status == SIPE_CAL_OOF) {
7251 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7252 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7253 "minAvailability=\"12000\"",
7254 "");
7256 start_time_str = sipe_utils_time_to_str(event->start_time);
7258 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7259 instance,
7260 publication_2 ? publication_2->version : 0,
7261 uri,
7262 start_time_str,
7263 availability_xml_str ? availability_xml_str : "",
7264 activity_xml_str ? activity_xml_str : "",
7265 event->subject ? event->subject : "",
7266 event->location ? event->location : "",
7268 instance,
7269 publication_3 ? publication_3->version : 0,
7270 uri,
7271 start_time_str,
7272 availability_xml_str ? availability_xml_str : "",
7273 activity_xml_str ? activity_xml_str : "",
7274 event->subject ? event->subject : "",
7275 event->location ? event->location : ""
7277 g_free(start_time_str);
7278 g_free(availability_xml_str);
7279 g_free(activity_xml_str);
7282 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7284 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7285 instance,
7286 publication_2 ? publication_2->version : 0,
7288 instance,
7289 publication_3 ? publication_3->version : 0
7293 return res;
7297 * Returns 'machineState' XML part for publication.
7298 * Must be g_free'd after use.
7300 static gchar *
7301 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7303 return sipe_publish_get_category_state(sip, FALSE);
7307 * Returns 'userState' XML part for publication.
7308 * Must be g_free'd after use.
7310 static gchar *
7311 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7313 return sipe_publish_get_category_state(sip, TRUE);
7317 * Returns 'note' XML part for publication.
7318 * Must be g_free'd after use.
7320 * Protocol format for Note is plain text.
7322 * @param note a note in Sipe internal HTML format
7323 * @param note_type either personal or OOF
7325 static gchar *
7326 sipe_publish_get_category_note(struct sipe_account_data *sip,
7327 const char *note, /* html */
7328 const char *note_type,
7329 time_t note_start,
7330 time_t note_end)
7332 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7333 /* key is <category><instance><container> */
7334 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7335 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7336 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7338 struct sipe_publication *publication_note_200 =
7339 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7340 struct sipe_publication *publication_note_300 =
7341 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7342 struct sipe_publication *publication_note_400 =
7343 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7345 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7346 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7347 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7348 char *res, *tmp1, *tmp2, *tmp3;
7349 char *start_time_attr;
7350 char *end_time_attr;
7352 g_free(tmp);
7353 tmp = NULL;
7354 g_free(key_note_200);
7355 g_free(key_note_300);
7356 g_free(key_note_400);
7358 /* we even need to republish empty note */
7359 if (sipe_strequal(n1, n2))
7361 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7362 g_free(n1);
7363 return NULL; /* nothing to update */
7366 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7367 g_free(tmp);
7368 tmp = NULL;
7369 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7370 g_free(tmp);
7372 if (n1) {
7373 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7374 instance,
7375 200,
7376 publication_note_200 ? publication_note_200->version : 0,
7377 note_type,
7378 start_time_attr ? start_time_attr : "",
7379 end_time_attr ? end_time_attr : "",
7380 n1);
7382 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7383 instance,
7384 300,
7385 publication_note_300 ? publication_note_300->version : 0,
7386 note_type,
7387 start_time_attr ? start_time_attr : "",
7388 end_time_attr ? end_time_attr : "",
7389 n1);
7391 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7392 instance,
7393 400,
7394 publication_note_400 ? publication_note_400->version : 0,
7395 note_type,
7396 start_time_attr ? start_time_attr : "",
7397 end_time_attr ? end_time_attr : "",
7398 n1);
7399 } else {
7400 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7401 "note",
7402 instance,
7403 200,
7404 publication_note_200 ? publication_note_200->version : 0,
7405 "static");
7406 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7407 "note",
7408 instance,
7409 300,
7410 publication_note_200 ? publication_note_200->version : 0,
7411 "static");
7412 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7413 "note",
7414 instance,
7415 400,
7416 publication_note_200 ? publication_note_200->version : 0,
7417 "static");
7419 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7421 g_free(start_time_attr);
7422 g_free(end_time_attr);
7423 g_free(tmp1);
7424 g_free(tmp2);
7425 g_free(tmp3);
7426 g_free(n1);
7428 return res;
7432 * Returns 'calendarData' XML part with WorkingHours for publication.
7433 * Must be g_free'd after use.
7435 static gchar *
7436 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7438 struct sipe_ews* ews = sip->ews;
7440 /* key is <category><instance><container> */
7441 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7442 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7443 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7444 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7445 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7446 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7448 struct sipe_publication *publication_cal_1 =
7449 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7450 struct sipe_publication *publication_cal_100 =
7451 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7452 struct sipe_publication *publication_cal_200 =
7453 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7454 struct sipe_publication *publication_cal_300 =
7455 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7456 struct sipe_publication *publication_cal_400 =
7457 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7458 struct sipe_publication *publication_cal_32000 =
7459 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7461 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7462 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7464 g_free(key_cal_1);
7465 g_free(key_cal_100);
7466 g_free(key_cal_200);
7467 g_free(key_cal_300);
7468 g_free(key_cal_400);
7469 g_free(key_cal_32000);
7471 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7472 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7473 return NULL;
7476 if (sipe_strequal(n1, n2))
7478 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7479 return NULL; /* nothing to update */
7482 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7483 /* 1 */
7484 publication_cal_1 ? publication_cal_1->version : 0,
7485 ews->email,
7486 ews->working_hours_xml_str,
7487 /* 100 - Public */
7488 publication_cal_100 ? publication_cal_100->version : 0,
7489 /* 200 - Company */
7490 publication_cal_200 ? publication_cal_200->version : 0,
7491 ews->email,
7492 ews->working_hours_xml_str,
7493 /* 300 - Team */
7494 publication_cal_300 ? publication_cal_300->version : 0,
7495 ews->email,
7496 ews->working_hours_xml_str,
7497 /* 400 - Personal */
7498 publication_cal_400 ? publication_cal_400->version : 0,
7499 ews->email,
7500 ews->working_hours_xml_str,
7501 /* 32000 - Blocked */
7502 publication_cal_32000 ? publication_cal_32000->version : 0
7507 * Returns 'calendarData' XML part with FreeBusy for publication.
7508 * Must be g_free'd after use.
7510 static gchar *
7511 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7513 struct sipe_ews* ews = sip->ews;
7514 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7515 char *fb_start_str;
7516 char *free_busy_base64;
7517 const char *st;
7518 const char *fb;
7519 char *res;
7521 /* key is <category><instance><container> */
7522 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7523 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7524 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7525 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7526 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7527 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7529 struct sipe_publication *publication_cal_1 =
7530 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7531 struct sipe_publication *publication_cal_100 =
7532 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7533 struct sipe_publication *publication_cal_200 =
7534 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7535 struct sipe_publication *publication_cal_300 =
7536 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7537 struct sipe_publication *publication_cal_400 =
7538 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7539 struct sipe_publication *publication_cal_32000 =
7540 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7542 g_free(key_cal_1);
7543 g_free(key_cal_100);
7544 g_free(key_cal_200);
7545 g_free(key_cal_300);
7546 g_free(key_cal_400);
7547 g_free(key_cal_32000);
7549 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7550 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7551 return NULL;
7554 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7555 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7557 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7558 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7560 /* we will rebuplish the same data to refresh publication time,
7561 * so if data from multiple sources, most recent will be choosen
7563 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7565 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7566 // g_free(fb_start_str);
7567 // g_free(free_busy_base64);
7568 // return NULL; /* nothing to update */
7571 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7572 /* 1 */
7573 cal_data_instance,
7574 publication_cal_1 ? publication_cal_1->version : 0,
7575 /* 100 - Public */
7576 cal_data_instance,
7577 publication_cal_100 ? publication_cal_100->version : 0,
7578 /* 200 - Company */
7579 cal_data_instance,
7580 publication_cal_200 ? publication_cal_200->version : 0,
7581 ews->email,
7582 fb_start_str,
7583 free_busy_base64,
7584 /* 300 - Team */
7585 cal_data_instance,
7586 publication_cal_300 ? publication_cal_300->version : 0,
7587 ews->email,
7588 fb_start_str,
7589 free_busy_base64,
7590 /* 400 - Personal */
7591 cal_data_instance,
7592 publication_cal_400 ? publication_cal_400->version : 0,
7593 ews->email,
7594 fb_start_str,
7595 free_busy_base64,
7596 /* 32000 - Blocked */
7597 cal_data_instance,
7598 publication_cal_32000 ? publication_cal_32000->version : 0
7601 g_free(fb_start_str);
7602 g_free(free_busy_base64);
7603 return res;
7606 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7608 gchar *uri;
7609 gchar *doc;
7610 gchar *tmp;
7611 gchar *hdr;
7613 uri = sip_uri_self(sip);
7614 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7615 uri,
7616 publications);
7618 tmp = get_contact(sip);
7619 hdr = g_strdup_printf("Contact: %s\r\n"
7620 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7622 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7624 g_free(tmp);
7625 g_free(hdr);
7626 g_free(uri);
7627 g_free(doc);
7630 static void
7631 send_publish_category_initial(struct sipe_account_data *sip)
7633 gchar *pub_device = sipe_publish_get_category_device(sip);
7634 gchar *pub_machine;
7635 gchar *publications;
7637 g_free(sip->status);
7638 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7640 pub_machine = sipe_publish_get_category_state_machine(sip);
7641 publications = g_strdup_printf("%s%s",
7642 pub_device,
7643 pub_machine ? pub_machine : "");
7644 g_free(pub_device);
7645 g_free(pub_machine);
7647 send_presence_publish(sip, publications);
7648 g_free(publications);
7651 static void
7652 send_presence_category_publish(struct sipe_account_data *sip)
7654 gchar *pub_state = sipe_is_user_state(sip) ?
7655 sipe_publish_get_category_state_user(sip) :
7656 sipe_publish_get_category_state_machine(sip);
7657 gchar *pub_note = sipe_publish_get_category_note(sip,
7658 sip->note,
7659 sip->is_oof_note ? "OOF" : "personal",
7662 gchar *publications;
7664 if (!pub_state && !pub_note) {
7665 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7666 return;
7669 publications = g_strdup_printf("%s%s",
7670 pub_state ? pub_state : "",
7671 pub_note ? pub_note : "");
7673 g_free(pub_state);
7674 g_free(pub_note);
7676 send_presence_publish(sip, publications);
7677 g_free(publications);
7681 * Publishes self status
7682 * based on own calendar information.
7684 * For 2007+
7686 void
7687 publish_calendar_status_self(struct sipe_account_data *sip)
7689 struct sipe_cal_event* event = NULL;
7690 gchar *pub_cal_working_hours = NULL;
7691 gchar *pub_cal_free_busy = NULL;
7692 gchar *pub_calendar = NULL;
7693 gchar *pub_calendar2 = NULL;
7694 gchar *pub_oof_note = NULL;
7695 const gchar *oof_note;
7696 time_t oof_start = 0;
7697 time_t oof_end = 0;
7699 if (!sip->ews) {
7700 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7701 return;
7704 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7705 if (sip->ews->cal_events) {
7706 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7709 if (!event) {
7710 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7711 } else {
7712 char *desc = sipe_cal_event_describe(event);
7713 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7714 g_free(desc);
7717 /* Logic
7718 if OOF
7719 OOF publish, Busy clean
7720 ilse if Busy
7721 OOF clean, Busy publish
7722 else
7723 OOF clean, Busy clean
7725 if (event && event->cal_status == SIPE_CAL_OOF) {
7726 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7727 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7728 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7729 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7730 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7731 } else {
7732 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7733 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7736 oof_note = sipe_ews_get_oof_note(sip->ews);
7737 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7738 oof_start = sip->ews->oof_start;
7739 oof_end = sip->ews->oof_end;
7741 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7743 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7744 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7746 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7747 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7748 } else {
7749 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7750 pub_cal_working_hours ? pub_cal_working_hours : "",
7751 pub_cal_free_busy ? pub_cal_free_busy : "",
7752 pub_calendar ? pub_calendar : "",
7753 pub_calendar2 ? pub_calendar2 : "",
7754 pub_oof_note ? pub_oof_note : "");
7756 send_presence_publish(sip, publications);
7757 g_free(publications);
7760 g_free(pub_cal_working_hours);
7761 g_free(pub_cal_free_busy);
7762 g_free(pub_calendar);
7763 g_free(pub_calendar2);
7764 g_free(pub_oof_note);
7766 /* repeat scheduling */
7767 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7770 static void send_presence_status(struct sipe_account_data *sip)
7772 PurpleStatus * status = purple_account_get_active_status(sip->account);
7774 if (!status) return;
7776 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7777 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7778 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7780 if (sip->ocs2007) {
7781 send_presence_category_publish(sip);
7782 } else {
7783 send_presence_soap(sip, FALSE);
7787 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7789 gboolean found = FALSE;
7790 const char *method = msg->method ? msg->method : "NOT FOUND";
7791 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7792 if (msg->response == 0) { /* request */
7793 if (sipe_strequal(method, "MESSAGE")) {
7794 process_incoming_message(sip, msg);
7795 found = TRUE;
7796 } else if (sipe_strequal(method, "NOTIFY")) {
7797 purple_debug_info("sipe","send->process_incoming_notify\n");
7798 process_incoming_notify(sip, msg, TRUE, FALSE);
7799 found = TRUE;
7800 } else if (sipe_strequal(method, "BENOTIFY")) {
7801 purple_debug_info("sipe","send->process_incoming_benotify\n");
7802 process_incoming_notify(sip, msg, TRUE, TRUE);
7803 found = TRUE;
7804 } else if (sipe_strequal(method, "INVITE")) {
7805 process_incoming_invite(sip, msg);
7806 found = TRUE;
7807 } else if (sipe_strequal(method, "REFER")) {
7808 process_incoming_refer(sip, msg);
7809 found = TRUE;
7810 } else if (sipe_strequal(method, "OPTIONS")) {
7811 process_incoming_options(sip, msg);
7812 found = TRUE;
7813 } else if (sipe_strequal(method, "INFO")) {
7814 process_incoming_info(sip, msg);
7815 found = TRUE;
7816 } else if (sipe_strequal(method, "ACK")) {
7817 // ACK's don't need any response
7818 found = TRUE;
7819 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7820 // LCS 2005 sends us these - just respond 200 OK
7821 found = TRUE;
7822 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7823 } else if (sipe_strequal(method, "BYE")) {
7824 process_incoming_bye(sip, msg);
7825 found = TRUE;
7826 } else {
7827 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7829 } else { /* response */
7830 struct transaction *trans = transactions_find(sip, msg);
7831 if (trans) {
7832 if (msg->response == 407) {
7833 gchar *resend, *auth;
7834 const gchar *ptmp;
7836 if (sip->proxy.retries > 30) return;
7837 sip->proxy.retries++;
7838 /* do proxy authentication */
7840 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7842 fill_auth(ptmp, &sip->proxy);
7843 auth = auth_header(sip, &sip->proxy, trans->msg);
7844 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7845 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7846 g_free(auth);
7847 resend = sipmsg_to_string(trans->msg);
7848 /* resend request */
7849 sendout_pkt(sip->gc, resend);
7850 g_free(resend);
7851 } else {
7852 if (msg->response < 200) {
7853 /* ignore provisional response */
7854 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7855 } else {
7856 sip->proxy.retries = 0;
7857 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7858 if (msg->response == 401)
7860 sip->registrar.retries++;
7862 else
7864 sip->registrar.retries = 0;
7866 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7867 } else {
7868 if (msg->response == 401) {
7869 gchar *resend, *auth, *ptmp;
7870 const char* auth_scheme;
7872 if (sip->registrar.retries > 4) return;
7873 sip->registrar.retries++;
7875 auth_scheme = sipe_get_auth_scheme_name(sip);
7876 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7878 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7879 if (!ptmp) {
7880 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7881 sip->gc->wants_to_die = TRUE;
7882 purple_connection_error(sip->gc, tmp2);
7883 g_free(tmp2);
7884 return;
7887 fill_auth(ptmp, &sip->registrar);
7888 auth = auth_header(sip, &sip->registrar, trans->msg);
7889 sipmsg_remove_header_now(trans->msg, "Authorization");
7890 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7891 g_free(auth);
7892 resend = sipmsg_to_string(trans->msg);
7893 /* resend request */
7894 sendout_pkt(sip->gc, resend);
7895 g_free(resend);
7899 if (trans->callback) {
7900 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7901 /* call the callback to process response*/
7902 (trans->callback)(sip, msg, trans);
7905 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7906 transactions_remove(sip, trans);
7910 found = TRUE;
7911 } else {
7912 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7915 if (!found) {
7916 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7920 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7922 char *cur;
7923 char *dummy;
7924 char *tmp;
7925 struct sipmsg *msg;
7926 int restlen;
7927 cur = conn->inbuf;
7929 /* according to the RFC remove CRLF at the beginning */
7930 while (*cur == '\r' || *cur == '\n') {
7931 cur++;
7933 if (cur != conn->inbuf) {
7934 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7935 conn->inbufused = strlen(conn->inbuf);
7938 /* Received a full Header? */
7939 sip->processing_input = TRUE;
7940 while (sip->processing_input &&
7941 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7942 time_t currtime = time(NULL);
7943 cur += 2;
7944 cur[0] = '\0';
7945 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7946 g_free(tmp);
7947 msg = sipmsg_parse_header(conn->inbuf);
7948 cur[0] = '\r';
7949 cur += 2;
7950 restlen = conn->inbufused - (cur - conn->inbuf);
7951 if (msg && restlen >= msg->bodylen) {
7952 dummy = g_malloc(msg->bodylen + 1);
7953 memcpy(dummy, cur, msg->bodylen);
7954 dummy[msg->bodylen] = '\0';
7955 msg->body = dummy;
7956 cur += msg->bodylen;
7957 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7958 conn->inbufused = strlen(conn->inbuf);
7959 } else {
7960 if (msg){
7961 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7962 sipmsg_free(msg);
7964 return;
7967 /*if (msg->body) {
7968 purple_debug_info("sipe", "body:\n%s", msg->body);
7971 // Verify the signature before processing it
7972 if (sip->registrar.gssapi_context) {
7973 struct sipmsg_breakdown msgbd;
7974 gchar *signature_input_str;
7975 gchar *rspauth;
7976 msgbd.msg = msg;
7977 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7978 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
7980 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7982 if (rspauth != NULL) {
7983 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7984 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7985 process_input_message(sip, msg);
7986 } else {
7987 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7988 purple_connection_error(sip->gc, _("Invalid message signature received"));
7989 sip->gc->wants_to_die = TRUE;
7991 } else if (msg->response == 401) {
7992 purple_connection_error(sip->gc, _("Authentication failed"));
7993 sip->gc->wants_to_die = TRUE;
7995 g_free(signature_input_str);
7997 g_free(rspauth);
7998 sipmsg_breakdown_free(&msgbd);
7999 } else {
8000 process_input_message(sip, msg);
8003 sipmsg_free(msg);
8007 static void sipe_udp_process(gpointer data, gint source,
8008 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8010 PurpleConnection *gc = data;
8011 struct sipe_account_data *sip = gc->proto_data;
8012 int len;
8014 static char buffer[65536];
8015 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8016 time_t currtime = time(NULL);
8017 struct sipmsg *msg;
8018 buffer[len] = '\0';
8019 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
8020 msg = sipmsg_parse_msg(buffer);
8021 if (msg) process_input_message(sip, msg);
8025 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8027 struct sipe_account_data *sip = gc->proto_data;
8028 PurpleSslConnection *gsc = sip->gsc;
8030 purple_debug_error("sipe", "%s",debug);
8031 purple_connection_error(gc, msg);
8033 /* Invalidate this connection. Next send will open a new one */
8034 if (gsc) {
8035 connection_remove(sip, gsc->fd);
8036 purple_ssl_close(gsc);
8038 sip->gsc = NULL;
8039 sip->fd = -1;
8042 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8043 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8045 PurpleConnection *gc = data;
8046 struct sipe_account_data *sip;
8047 struct sip_connection *conn;
8048 int readlen, len;
8049 gboolean firstread = TRUE;
8051 /* NOTE: This check *IS* necessary */
8052 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8053 purple_ssl_close(gsc);
8054 return;
8057 sip = gc->proto_data;
8058 conn = connection_find(sip, gsc->fd);
8059 if (conn == NULL) {
8060 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
8061 gc->wants_to_die = TRUE;
8062 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8063 return;
8066 /* Read all available data from the SSL connection */
8067 do {
8068 /* Increase input buffer size as needed */
8069 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8070 conn->inbuflen += SIMPLE_BUF_INC;
8071 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8072 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
8075 /* Try to read as much as there is space left in the buffer */
8076 readlen = conn->inbuflen - conn->inbufused - 1;
8077 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8079 if (len < 0 && errno == EAGAIN) {
8080 /* Try again later */
8081 return;
8082 } else if (len < 0) {
8083 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8084 return;
8085 } else if (firstread && (len == 0)) {
8086 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8087 return;
8090 conn->inbufused += len;
8091 firstread = FALSE;
8093 /* Equivalence indicates that there is possibly more data to read */
8094 } while (len == readlen);
8096 conn->inbuf[conn->inbufused] = '\0';
8097 process_input(sip, conn);
8101 static void sipe_input_cb(gpointer data, gint source,
8102 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8104 PurpleConnection *gc = data;
8105 struct sipe_account_data *sip = gc->proto_data;
8106 int len;
8107 struct sip_connection *conn = connection_find(sip, source);
8108 if (!conn) {
8109 purple_debug_error("sipe", "Connection not found!\n");
8110 return;
8113 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8114 conn->inbuflen += SIMPLE_BUF_INC;
8115 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8118 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8120 if (len < 0 && errno == EAGAIN)
8121 return;
8122 else if (len <= 0) {
8123 purple_debug_info("sipe", "sipe_input_cb: read error\n");
8124 connection_remove(sip, source);
8125 if (sip->fd == source) sip->fd = -1;
8126 return;
8129 conn->inbufused += len;
8130 conn->inbuf[conn->inbufused] = '\0';
8132 process_input(sip, conn);
8135 /* Callback for new connections on incoming TCP port */
8136 static void sipe_newconn_cb(gpointer data, gint source,
8137 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8139 PurpleConnection *gc = data;
8140 struct sipe_account_data *sip = gc->proto_data;
8141 struct sip_connection *conn;
8143 int newfd = accept(source, NULL, NULL);
8145 conn = connection_create(sip, newfd);
8147 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8150 static void login_cb(gpointer data, gint source,
8151 SIPE_UNUSED_PARAMETER const gchar *error_message)
8153 PurpleConnection *gc = data;
8154 struct sipe_account_data *sip;
8155 struct sip_connection *conn;
8157 if (!PURPLE_CONNECTION_IS_VALID(gc))
8159 if (source >= 0)
8160 close(source);
8161 return;
8164 if (source < 0) {
8165 purple_connection_error(gc, _("Could not connect"));
8166 return;
8169 sip = gc->proto_data;
8170 sip->fd = source;
8171 sip->last_keepalive = time(NULL);
8173 conn = connection_create(sip, source);
8175 do_register(sip);
8177 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8180 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8181 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8183 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8184 if (sip == NULL) return;
8186 do_register(sip);
8189 static guint sipe_ht_hash_nick(const char *nick)
8191 char *lc = g_utf8_strdown(nick, -1);
8192 guint bucket = g_str_hash(lc);
8193 g_free(lc);
8195 return bucket;
8198 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8200 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8203 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8205 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8207 sip->listen_data = NULL;
8209 if (listenfd == -1) {
8210 purple_connection_error(sip->gc, _("Could not create listen socket"));
8211 return;
8214 sip->fd = listenfd;
8216 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8217 sip->listenfd = sip->fd;
8219 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8221 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8222 do_register(sip);
8225 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8226 SIPE_UNUSED_PARAMETER const char *error_message)
8228 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8230 sip->query_data = NULL;
8232 if (!hosts || !hosts->data) {
8233 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8234 return;
8237 hosts = g_slist_remove(hosts, hosts->data);
8238 g_free(sip->serveraddr);
8239 sip->serveraddr = hosts->data;
8240 hosts = g_slist_remove(hosts, hosts->data);
8241 while (hosts) {
8242 void *tmp = hosts->data;
8243 hosts = g_slist_remove(hosts, tmp);
8244 hosts = g_slist_remove(hosts, tmp);
8245 g_free(tmp);
8248 /* create socket for incoming connections */
8249 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8250 sipe_udp_host_resolved_listen_cb, sip);
8251 if (sip->listen_data == NULL) {
8252 purple_connection_error(sip->gc, _("Could not create listen socket"));
8253 return;
8257 static const struct sipe_service_data *current_service = NULL;
8259 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8260 PurpleSslErrorType error,
8261 gpointer data)
8263 PurpleConnection *gc = data;
8264 struct sipe_account_data *sip;
8266 /* If the connection is already disconnected, we don't need to do anything else */
8267 if (!PURPLE_CONNECTION_IS_VALID(gc))
8268 return;
8270 sip = gc->proto_data;
8271 current_service = sip->service_data;
8272 if (current_service) {
8273 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8274 current_service->transport ? current_service->transport : "NULL",
8275 current_service->service ? current_service->service : "NULL");
8278 sip->fd = -1;
8279 sip->gsc = NULL;
8281 switch(error) {
8282 case PURPLE_SSL_CONNECT_FAILED:
8283 purple_connection_error(gc, _("Connection failed"));
8284 break;
8285 case PURPLE_SSL_HANDSHAKE_FAILED:
8286 purple_connection_error(gc, _("SSL handshake failed"));
8287 break;
8288 case PURPLE_SSL_CERTIFICATE_INVALID:
8289 purple_connection_error(gc, _("SSL certificate invalid"));
8290 break;
8294 static void
8295 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8297 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8298 PurpleProxyConnectData *connect_data;
8300 sip->listen_data = NULL;
8302 sip->listenfd = listenfd;
8303 if (sip->listenfd == -1) {
8304 purple_connection_error(sip->gc, _("Could not create listen socket"));
8305 return;
8308 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8309 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8310 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8311 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8312 sipe_newconn_cb, sip->gc);
8313 purple_debug_info("sipe", "connecting to %s port %d\n",
8314 sip->realhostname, sip->realport);
8315 /* open tcp connection to the server */
8316 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8317 sip->realport, login_cb, sip->gc);
8319 if (connect_data == NULL) {
8320 purple_connection_error(sip->gc, _("Could not create socket"));
8324 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8326 PurpleAccount *account = sip->account;
8327 PurpleConnection *gc = sip->gc;
8329 if (port == 0) {
8330 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8333 sip->realhostname = hostname;
8334 sip->realport = port;
8336 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8337 hostname, port);
8339 /* TODO: is there a good default grow size? */
8340 if (sip->transport != SIPE_TRANSPORT_UDP)
8341 sip->txbuf = purple_circ_buffer_new(0);
8343 if (sip->transport == SIPE_TRANSPORT_TLS) {
8344 /* SSL case */
8345 if (!purple_ssl_is_supported()) {
8346 gc->wants_to_die = TRUE;
8347 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8348 return;
8351 purple_debug_info("sipe", "using SSL\n");
8353 sip->gsc = purple_ssl_connect(account, hostname, port,
8354 login_cb_ssl, sipe_ssl_connect_failure, gc);
8355 if (sip->gsc == NULL) {
8356 purple_connection_error(gc, _("Could not create SSL context"));
8357 return;
8359 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8360 /* UDP case */
8361 purple_debug_info("sipe", "using UDP\n");
8363 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8364 if (sip->query_data == NULL) {
8365 purple_connection_error(gc, _("Could not resolve hostname"));
8367 } else {
8368 /* TCP case */
8369 purple_debug_info("sipe", "using TCP\n");
8370 /* create socket for incoming connections */
8371 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8372 sipe_tcp_connect_listen_cb, sip);
8373 if (sip->listen_data == NULL) {
8374 purple_connection_error(gc, _("Could not create listen socket"));
8375 return;
8380 /* Service list for autodection */
8381 static const struct sipe_service_data service_autodetect[] = {
8382 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8383 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8384 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8385 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8386 { NULL, NULL, 0 }
8389 /* Service list for SSL/TLS */
8390 static const struct sipe_service_data service_tls[] = {
8391 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8392 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8393 { NULL, NULL, 0 }
8396 /* Service list for TCP */
8397 static const struct sipe_service_data service_tcp[] = {
8398 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8399 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8400 { NULL, NULL, 0 }
8403 /* Service list for UDP */
8404 static const struct sipe_service_data service_udp[] = {
8405 { "sip", "udp", SIPE_TRANSPORT_UDP },
8406 { NULL, NULL, 0 }
8409 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8410 static void resolve_next_service(struct sipe_account_data *sip,
8411 const struct sipe_service_data *start)
8413 if (start) {
8414 sip->service_data = start;
8415 } else {
8416 sip->service_data++;
8417 if (sip->service_data->service == NULL) {
8418 gchar *hostname;
8419 /* Try connecting to the SIP hostname directly */
8420 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8421 if (sip->auto_transport) {
8422 // If SSL is supported, default to using it; OCS servers aren't configured
8423 // by default to accept TCP
8424 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8425 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8426 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8429 hostname = g_strdup(sip->sipdomain);
8430 create_connection(sip, hostname, 0);
8431 return;
8435 /* Try to resolve next service */
8436 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8437 sip->service_data->transport,
8438 sip->sipdomain,
8439 srvresolved, sip);
8442 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8444 struct sipe_account_data *sip = data;
8446 sip->srv_query_data = NULL;
8448 /* find the host to connect to */
8449 if (results) {
8450 gchar *hostname = g_strdup(resp->hostname);
8451 int port = resp->port;
8452 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8453 hostname, port);
8454 g_free(resp);
8456 sip->transport = sip->service_data->type;
8458 create_connection(sip, hostname, port);
8459 } else {
8460 resolve_next_service(sip, NULL);
8464 static void sipe_login(PurpleAccount *account)
8466 PurpleConnection *gc;
8467 struct sipe_account_data *sip;
8468 gchar **signinname_login, **userserver;
8469 const char *transport;
8470 const char *email;
8472 const char *username = purple_account_get_username(account);
8473 gc = purple_account_get_connection(account);
8475 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8477 if (strpbrk(username, "\t\v\r\n") != NULL) {
8478 gc->wants_to_die = TRUE;
8479 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8480 return;
8483 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8484 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8485 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8486 sip->gc = gc;
8487 sip->account = account;
8488 sip->reregister_set = FALSE;
8489 sip->reauthenticate_set = FALSE;
8490 sip->subscribed = FALSE;
8491 sip->subscribed_buddies = FALSE;
8492 sip->initial_state_published = FALSE;
8494 /* username format: <username>,[<optional login>] */
8495 signinname_login = g_strsplit(username, ",", 2);
8496 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8498 /* ensure that username format is name@domain */
8499 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8500 g_strfreev(signinname_login);
8501 gc->wants_to_die = TRUE;
8502 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8503 return;
8505 sip->username = g_strdup(signinname_login[0]);
8507 /* ensure that email format is name@domain if provided */
8508 email = purple_account_get_string(sip->account, "email", NULL);
8509 if (!is_empty(email) &&
8510 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8512 gc->wants_to_die = TRUE;
8513 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8514 return;
8516 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8518 /* login name specified? */
8519 if (signinname_login[1] && strlen(signinname_login[1])) {
8520 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8521 gboolean has_domain = domain_user[1] != NULL;
8522 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8523 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8524 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8525 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8526 sip->authdomain ? sip->authdomain : "", sip->authuser);
8527 g_strfreev(domain_user);
8530 userserver = g_strsplit(signinname_login[0], "@", 2);
8531 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8532 purple_connection_set_display_name(gc, userserver[0]);
8533 sip->sipdomain = g_strdup(userserver[1]);
8534 g_strfreev(userserver);
8535 g_strfreev(signinname_login);
8537 if (strchr(sip->username, ' ') != NULL) {
8538 gc->wants_to_die = TRUE;
8539 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8540 return;
8543 sip->password = g_strdup(purple_connection_get_password(gc));
8545 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8546 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8547 g_free, (GDestroyNotify)g_hash_table_destroy);
8548 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8549 g_free, (GDestroyNotify)sipe_subscription_free);
8551 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8553 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8555 g_free(sip->status);
8556 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8558 sip->auto_transport = FALSE;
8559 transport = purple_account_get_string(account, "transport", "auto");
8560 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8561 if (userserver[0]) {
8562 /* Use user specified server[:port] */
8563 int port = 0;
8565 if (userserver[1])
8566 port = atoi(userserver[1]);
8568 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8569 userserver[0], port);
8571 if (sipe_strequal(transport, "auto")) {
8572 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8573 } else if (sipe_strequal(transport, "tls")) {
8574 sip->transport = SIPE_TRANSPORT_TLS;
8575 } else if (sipe_strequal(transport, "tcp")) {
8576 sip->transport = SIPE_TRANSPORT_TCP;
8577 } else {
8578 sip->transport = SIPE_TRANSPORT_UDP;
8581 create_connection(sip, g_strdup(userserver[0]), port);
8582 } else {
8583 /* Server auto-discovery */
8584 if (sipe_strequal(transport, "auto")) {
8585 sip->auto_transport = TRUE;
8586 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8587 current_service++;
8588 resolve_next_service(sip, current_service);
8589 } else {
8590 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8592 } else if (sipe_strequal(transport, "tls")) {
8593 resolve_next_service(sip, service_tls);
8594 } else if (sipe_strequal(transport, "tcp")) {
8595 resolve_next_service(sip, service_tcp);
8596 } else {
8597 resolve_next_service(sip, service_udp);
8600 g_strfreev(userserver);
8603 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8605 connection_free_all(sip);
8607 g_free(sip->epid);
8608 sip->epid = NULL;
8610 if (sip->query_data != NULL)
8611 purple_dnsquery_destroy(sip->query_data);
8612 sip->query_data = NULL;
8614 if (sip->srv_query_data != NULL)
8615 purple_srv_cancel(sip->srv_query_data);
8616 sip->srv_query_data = NULL;
8618 if (sip->listen_data != NULL)
8619 purple_network_listen_cancel(sip->listen_data);
8620 sip->listen_data = NULL;
8622 if (sip->gsc != NULL)
8623 purple_ssl_close(sip->gsc);
8624 sip->gsc = NULL;
8626 sipe_auth_free(&sip->registrar);
8627 sipe_auth_free(&sip->proxy);
8629 if (sip->txbuf)
8630 purple_circ_buffer_destroy(sip->txbuf);
8631 sip->txbuf = NULL;
8633 g_free(sip->realhostname);
8634 sip->realhostname = NULL;
8636 g_free(sip->server_version);
8637 sip->server_version = NULL;
8639 if (sip->listenpa)
8640 purple_input_remove(sip->listenpa);
8641 sip->listenpa = 0;
8642 if (sip->tx_handler)
8643 purple_input_remove(sip->tx_handler);
8644 sip->tx_handler = 0;
8645 if (sip->resendtimeout)
8646 purple_timeout_remove(sip->resendtimeout);
8647 sip->resendtimeout = 0;
8648 if (sip->timeouts) {
8649 GSList *entry = sip->timeouts;
8650 while (entry) {
8651 struct scheduled_action *sched_action = entry->data;
8652 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8653 purple_timeout_remove(sched_action->timeout_handler);
8654 if (sched_action->destroy) {
8655 (*sched_action->destroy)(sched_action->payload);
8657 g_free(sched_action->name);
8658 g_free(sched_action);
8659 entry = entry->next;
8662 g_slist_free(sip->timeouts);
8664 if (sip->allow_events) {
8665 GSList *entry = sip->allow_events;
8666 while (entry) {
8667 g_free(entry->data);
8668 entry = entry->next;
8671 g_slist_free(sip->allow_events);
8673 if (sip->containers) {
8674 GSList *entry = sip->containers;
8675 while (entry) {
8676 free_container((struct sipe_container *)entry->data);
8677 entry = entry->next;
8680 g_slist_free(sip->containers);
8682 if (sip->contact)
8683 g_free(sip->contact);
8684 sip->contact = NULL;
8685 if (sip->regcallid)
8686 g_free(sip->regcallid);
8687 sip->regcallid = NULL;
8689 if (sip->serveraddr)
8690 g_free(sip->serveraddr);
8691 sip->serveraddr = NULL;
8693 if (sip->focus_factory_uri)
8694 g_free(sip->focus_factory_uri);
8695 sip->focus_factory_uri = NULL;
8697 sip->fd = -1;
8698 sip->processing_input = FALSE;
8700 if (sip->ews) {
8701 sipe_ews_free(sip->ews);
8703 sip->ews = NULL;
8707 * A callback for g_hash_table_foreach_remove
8709 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8710 SIPE_UNUSED_PARAMETER gpointer user_data)
8712 sipe_free_buddy((struct sipe_buddy *) buddy);
8714 /* We must return TRUE as the key/value have already been deleted */
8715 return(TRUE);
8718 static void sipe_close(PurpleConnection *gc)
8720 struct sipe_account_data *sip = gc->proto_data;
8722 if (sip) {
8723 /* leave all conversations */
8724 sipe_session_close_all(sip);
8725 sipe_session_remove_all(sip);
8727 if (sip->csta) {
8728 sip_csta_close(sip);
8731 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8732 /* unsubscribe all */
8733 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8735 /* unregister */
8736 do_register_exp(sip, 0);
8739 sipe_connection_cleanup(sip);
8740 g_free(sip->sipdomain);
8741 g_free(sip->username);
8742 g_free(sip->email);
8743 g_free(sip->password);
8744 g_free(sip->authdomain);
8745 g_free(sip->authuser);
8746 g_free(sip->status);
8747 g_free(sip->note);
8748 g_free(sip->user_states);
8750 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8751 g_hash_table_destroy(sip->buddies);
8752 g_hash_table_destroy(sip->our_publications);
8753 g_hash_table_destroy(sip->user_state_publications);
8754 g_hash_table_destroy(sip->subscriptions);
8755 g_hash_table_destroy(sip->filetransfers);
8757 if (sip->groups) {
8758 GSList *entry = sip->groups;
8759 while (entry) {
8760 struct sipe_group *group = entry->data;
8761 g_free(group->name);
8762 g_free(group);
8763 entry = entry->next;
8766 g_slist_free(sip->groups);
8768 if (sip->our_publication_keys) {
8769 GSList *entry = sip->our_publication_keys;
8770 while (entry) {
8771 g_free(entry->data);
8772 entry = entry->next;
8775 g_slist_free(sip->our_publication_keys);
8777 while (sip->transactions)
8778 transactions_remove(sip, sip->transactions->data);
8780 g_free(gc->proto_data);
8781 gc->proto_data = NULL;
8784 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8785 SIPE_UNUSED_PARAMETER void *user_data)
8787 PurpleAccount *acct = purple_connection_get_account(gc);
8788 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8789 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8790 if (conv == NULL)
8791 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8792 purple_conversation_present(conv);
8793 g_free(id);
8796 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8797 SIPE_UNUSED_PARAMETER void *user_data)
8800 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8801 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8804 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8805 SIPE_UNUSED_PARAMETER struct transaction *trans)
8807 PurpleNotifySearchResults *results;
8808 PurpleNotifySearchColumn *column;
8809 xmlnode *searchResults;
8810 xmlnode *mrow;
8811 int match_count = 0;
8812 gboolean more = FALSE;
8813 gchar *secondary;
8815 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8817 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8818 if (!searchResults) {
8819 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8820 return FALSE;
8823 results = purple_notify_searchresults_new();
8825 if (results == NULL) {
8826 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8827 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8829 xmlnode_free(searchResults);
8830 return FALSE;
8833 column = purple_notify_searchresults_column_new(_("User name"));
8834 purple_notify_searchresults_column_add(results, column);
8836 column = purple_notify_searchresults_column_new(_("Name"));
8837 purple_notify_searchresults_column_add(results, column);
8839 column = purple_notify_searchresults_column_new(_("Company"));
8840 purple_notify_searchresults_column_add(results, column);
8842 column = purple_notify_searchresults_column_new(_("Country"));
8843 purple_notify_searchresults_column_add(results, column);
8845 column = purple_notify_searchresults_column_new(_("Email"));
8846 purple_notify_searchresults_column_add(results, column);
8848 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8849 GList *row = NULL;
8851 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8852 row = g_list_append(row, g_strdup(uri_parts[1]));
8853 g_strfreev(uri_parts);
8855 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8856 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8857 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8858 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8860 purple_notify_searchresults_row_add(results, row);
8861 match_count++;
8864 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8865 char *data = xmlnode_get_data(mrow);
8866 more = (g_strcasecmp(data, "true") == 0);
8867 g_free(data);
8870 secondary = g_strdup_printf(
8871 dngettext(PACKAGE_NAME,
8872 "Found %d contact%s:",
8873 "Found %d contacts%s:", match_count),
8874 match_count, more ? _(" (more matched your query)") : "");
8876 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8877 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8878 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8880 g_free(secondary);
8881 xmlnode_free(searchResults);
8882 return TRUE;
8885 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8887 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8888 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8889 unsigned i = 0;
8891 if (!attrs) return;
8893 do {
8894 PurpleRequestField *field = entries->data;
8895 const char *id = purple_request_field_get_id(field);
8896 const char *value = purple_request_field_string_get_value(field);
8898 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8900 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8901 } while ((entries = g_list_next(entries)) != NULL);
8902 attrs[i] = NULL;
8904 if (i > 0) {
8905 struct sipe_account_data *sip = gc->proto_data;
8906 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8907 gchar *query = g_strjoinv(NULL, attrs);
8908 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8909 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8910 send_soap_request_with_cb(sip, domain_uri, body,
8911 (TransCallback) process_search_contact_response, NULL);
8912 g_free(domain_uri);
8913 g_free(body);
8914 g_free(query);
8917 g_strfreev(attrs);
8920 static void sipe_show_find_contact(PurplePluginAction *action)
8922 PurpleConnection *gc = (PurpleConnection *) action->context;
8923 PurpleRequestFields *fields;
8924 PurpleRequestFieldGroup *group;
8925 PurpleRequestField *field;
8927 fields = purple_request_fields_new();
8928 group = purple_request_field_group_new(NULL);
8929 purple_request_fields_add_group(fields, group);
8931 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8932 purple_request_field_group_add_field(group, field);
8933 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8934 purple_request_field_group_add_field(group, field);
8935 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8936 purple_request_field_group_add_field(group, field);
8937 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8938 purple_request_field_group_add_field(group, field);
8940 purple_request_fields(gc,
8941 _("Search"),
8942 _("Search for a contact"),
8943 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8944 fields,
8945 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8946 _("_Cancel"), NULL,
8947 purple_connection_get_account(gc), NULL, NULL, gc);
8950 static void sipe_show_about_plugin(PurplePluginAction *action)
8952 PurpleConnection *gc = (PurpleConnection *) action->context;
8953 char *tmp = g_strdup_printf(
8955 * Non-translatable parts, like markup, are hard-coded
8956 * into the format string. This requires more translatable
8957 * texts but it makes the translations less error prone.
8959 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
8960 "<br/>"
8961 /* 1 */ "%s:<br/>"
8962 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8963 "<li> - MS Office Communications Server 2007</li><br/>"
8964 "<li> - MS Live Communications Server 2005</li><br/>"
8965 "<li> - MS Live Communications Server 2003</li><br/>"
8966 "<li> - Reuters Messaging</li><br/>"
8967 "<br/>"
8968 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
8969 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8970 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
8971 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8972 /* 8 */ "%s: GPLv2+<br/>"
8973 "<br/>"
8974 /* 9 */ "%s:<br/>"
8975 " - CERN<br/>"
8976 " - Reuters Messaging network<br/>"
8977 " - Deutsche Bank<br/>"
8978 " - Merrill Lynch<br/>"
8979 " - Wachovia<br/>"
8980 " - Intel<br/>"
8981 " - Nokia<br/>"
8982 " - HP<br/>"
8983 " - Symantec<br/>"
8984 " - Accenture<br/>"
8985 " - Capgemini<br/>"
8986 " - Siemens<br/>"
8987 " - Alcatel-Lucent<br/>"
8988 " - BT<br/>"
8989 "<br/>"
8990 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
8991 "<br/>"
8992 /* 12 */ "<b>%s:</b><br/>"
8993 " - Anibal Avelar<br/>"
8994 " - Gabriel Burt<br/>"
8995 " - Stefan Becker<br/>"
8996 " - pier11<br/>"
8997 " - Jakub Adam<br/>"
8998 " - Tomáš Hrabčík<br/>"
8999 "<br/>"
9000 /* 13 */ "%s<br/>"
9002 /* The next 13 texts make up the SIPE about note text */
9003 /* About note, part 1/13: introduction */
9004 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9005 /* About note, part 2/13: home page URL (label) */
9006 _("Home"),
9007 /* About note, part 3/13: support forum URL (label) */
9008 _("Support"),
9009 /* About note, part 4/13: support forum name (hyperlink text) */
9010 _("Help Forum"),
9011 /* About note, part 5/13: bug tracker URL (label) */
9012 _("Report Problems"),
9013 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9014 _("Bug Tracker"),
9015 /* About note, part 7/13: translation service URL (label) */
9016 _("Translations"),
9017 /* About note, part 8/13: license type (label) */
9018 _("License"),
9019 /* About note, part 9/13: known users */
9020 _("We support users in such organizations as"),
9021 /* About note, part 10/13: translation request, text before Transifex.net URL */
9022 /* append a space if text is not empty */
9023 _("Please help us to translate SIPE to your native language here at "),
9024 /* About note, part 11/13: translation request, text after Transifex.net URL */
9025 /* start with a space if text is not empty */
9026 _(" using convenient web interface"),
9027 /* About note, part 12/13: author list (header) */
9028 _("Authors"),
9029 /* About note, part 13/13: Localization credit */
9030 /* PLEASE NOTE: do *NOT* simply translate the english original */
9031 /* but write something similar to the following sentence: */
9032 /* "Localization for <language name> (<language code>): <name>" */
9033 _("Original texts in English (en): SIPE developers")
9035 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9036 g_free(tmp);
9039 static void sipe_republish_calendar(PurplePluginAction *action)
9041 PurpleConnection *gc = (PurpleConnection *) action->context;
9042 struct sipe_account_data *sip = gc->proto_data;
9044 sipe_update_calendar(sip);
9047 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9048 gpointer value,
9049 GString* str)
9051 struct sipe_publication *publication = value;
9053 g_string_append_printf( str,
9054 SIPE_PUB_XML_PUBLICATION_CLEAR,
9055 publication->category,
9056 publication->instance,
9057 publication->container,
9058 publication->version,
9059 "static");
9062 static void sipe_reset_status(PurplePluginAction *action)
9064 PurpleConnection *gc = (PurpleConnection *) action->context;
9065 struct sipe_account_data *sip = gc->proto_data;
9067 if (sip->ocs2007) /* 2007+ */
9069 GString* str = g_string_new(NULL);
9070 gchar *publications;
9072 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9073 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
9074 return;
9077 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9078 publications = g_string_free(str, FALSE);
9080 send_presence_publish(sip, publications);
9081 g_free(publications);
9083 else /* 2005 */
9085 send_presence_soap0(sip, FALSE, TRUE);
9089 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9090 gpointer context)
9092 PurpleConnection *gc = (PurpleConnection *)context;
9093 struct sipe_account_data *sip = gc->proto_data;
9094 GList *menu = NULL;
9095 PurplePluginAction *act;
9096 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9098 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9099 menu = g_list_prepend(menu, act);
9101 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9102 menu = g_list_prepend(menu, act);
9104 if (sipe_strequal(calendar, "EXCH")) {
9105 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9106 menu = g_list_prepend(menu, act);
9109 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9110 menu = g_list_prepend(menu, act);
9112 menu = g_list_reverse(menu);
9114 return menu;
9117 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9121 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9123 return TRUE;
9127 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9129 return TRUE;
9133 static char *sipe_status_text(PurpleBuddy *buddy)
9135 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9136 const PurpleStatus *status = purple_presence_get_active_status(presence);
9137 const char *status_id = purple_status_get_id(status);
9138 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9139 struct sipe_buddy *sbuddy;
9140 char *text = NULL;
9142 if (!sip) return NULL; /* happens on pidgin exit */
9144 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9145 if (sbuddy) {
9146 const char *activity_str = sbuddy->activity ?
9147 sbuddy->activity :
9148 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9149 purple_status_get_name(status) : NULL;
9151 if (activity_str && sbuddy->note)
9153 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9155 else if (activity_str)
9157 text = g_strdup(activity_str);
9159 else if (sbuddy->note)
9161 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9165 return text;
9168 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9170 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9171 const PurpleStatus *status = purple_presence_get_active_status(presence);
9172 struct sipe_account_data *sip;
9173 struct sipe_buddy *sbuddy;
9174 char *note = NULL;
9175 gboolean is_oof_note = FALSE;
9176 char *activity = NULL;
9177 char *calendar = NULL;
9178 char *meeting_subject = NULL;
9179 char *meeting_location = NULL;
9181 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9182 if (sip) //happens on pidgin exit
9184 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9185 if (sbuddy)
9187 note = sbuddy->note;
9188 is_oof_note = sbuddy->is_oof_note;
9189 activity = sbuddy->activity;
9190 calendar = sipe_cal_get_description(sbuddy);
9191 meeting_subject = sbuddy->meeting_subject;
9192 meeting_location = sbuddy->meeting_location;
9196 //Layout
9197 if (purple_presence_is_online(presence))
9199 const char *status_str = activity ? activity : purple_status_get_name(status);
9201 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9203 if (purple_presence_is_online(presence) &&
9204 !is_empty(calendar))
9206 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9208 g_free(calendar);
9209 if (!is_empty(meeting_location))
9211 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9213 if (!is_empty(meeting_subject))
9215 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9218 if (note)
9220 char *tmp = g_strdup_printf("<i>%s</i>", note);
9221 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9223 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9224 g_free(tmp);
9229 #if PURPLE_VERSION_CHECK(2,5,0)
9230 static GHashTable *
9231 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9233 GHashTable *table;
9234 table = g_hash_table_new(g_str_hash, g_str_equal);
9235 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9236 return table;
9238 #endif
9240 static PurpleBuddy *
9241 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9243 PurpleBuddy *clone;
9244 const gchar *server_alias, *email;
9245 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9247 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9249 purple_blist_add_buddy(clone, NULL, group, NULL);
9251 server_alias = purple_buddy_get_server_alias(buddy);
9252 if (server_alias) {
9253 purple_blist_server_alias_buddy(clone, server_alias);
9256 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9257 if (email) {
9258 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9261 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9262 //for UI to update;
9263 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9264 return clone;
9267 static void
9268 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9270 PurpleBuddy *buddy, *b;
9271 PurpleConnection *gc;
9272 PurpleGroup * group = purple_find_group(group_name);
9274 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9276 buddy = (PurpleBuddy *)node;
9278 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9279 gc = purple_account_get_connection(buddy->account);
9281 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9282 if (!b){
9283 purple_blist_add_buddy_clone(group, buddy);
9286 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9289 static void
9290 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9292 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9294 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9296 /* 2007+ conference */
9297 if (sip->ocs2007)
9299 sipe_conf_add(sip, buddy->name);
9301 else /* 2005- multiparty chat */
9303 gchar *self = sip_uri_self(sip);
9304 struct sip_session *session;
9306 session = sipe_session_add_chat(sip);
9307 session->chat_title = sipe_chat_get_name(session->callid);
9308 session->roster_manager = g_strdup(self);
9310 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9311 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9312 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9313 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9315 g_free(self);
9319 static gboolean
9320 sipe_is_election_finished(struct sip_session *session)
9322 gboolean res = TRUE;
9324 SIPE_DIALOG_FOREACH {
9325 if (dialog->election_vote == 0) {
9326 res = FALSE;
9327 break;
9329 } SIPE_DIALOG_FOREACH_END;
9331 if (res) {
9332 session->is_voting_in_progress = FALSE;
9334 return res;
9337 static void
9338 sipe_election_start(struct sipe_account_data *sip,
9339 struct sip_session *session)
9341 int election_timeout;
9343 if (session->is_voting_in_progress) {
9344 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9345 return;
9346 } else {
9347 session->is_voting_in_progress = TRUE;
9349 session->bid = rand();
9351 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9353 SIPE_DIALOG_FOREACH {
9354 /* reset election_vote for each chat participant */
9355 dialog->election_vote = 0;
9357 /* send RequestRM to each chat participant*/
9358 sipe_send_election_request_rm(sip, dialog, session->bid);
9359 } SIPE_DIALOG_FOREACH_END;
9361 election_timeout = 15; /* sec */
9362 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9366 * @param who a URI to whom to invite to chat
9368 void
9369 sipe_invite_to_chat(struct sipe_account_data *sip,
9370 struct sip_session *session,
9371 const gchar *who)
9373 /* a conference */
9374 if (session->focus_uri)
9376 sipe_invite_conf(sip, session, who);
9378 else /* a multi-party chat */
9380 gchar *self = sip_uri_self(sip);
9381 if (session->roster_manager) {
9382 if (sipe_strequal(session->roster_manager, self)) {
9383 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9384 } else {
9385 sipe_refer(sip, session, who);
9387 } else {
9388 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9390 session->pending_invite_queue = slist_insert_unique_sorted(
9391 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9393 sipe_election_start(sip, session);
9395 g_free(self);
9399 void
9400 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9401 struct sip_session *session)
9403 gchar *invitee;
9404 GSList *entry = session->pending_invite_queue;
9406 while (entry) {
9407 invitee = entry->data;
9408 sipe_invite_to_chat(sip, session, invitee);
9409 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9410 g_free(invitee);
9414 static void
9415 sipe_election_result(struct sipe_account_data *sip,
9416 void *sess)
9418 struct sip_session *session = (struct sip_session *)sess;
9419 gchar *rival;
9420 gboolean has_won = TRUE;
9422 if (session->roster_manager) {
9423 purple_debug_info("sipe",
9424 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9425 return;
9428 session->is_voting_in_progress = FALSE;
9430 SIPE_DIALOG_FOREACH {
9431 if (dialog->election_vote < 0) {
9432 has_won = FALSE;
9433 rival = dialog->with;
9434 break;
9436 } SIPE_DIALOG_FOREACH_END;
9438 if (has_won) {
9439 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9441 session->roster_manager = sip_uri_self(sip);
9443 SIPE_DIALOG_FOREACH {
9444 /* send SetRM to each chat participant*/
9445 sipe_send_election_set_rm(sip, dialog);
9446 } SIPE_DIALOG_FOREACH_END;
9447 } else {
9448 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9450 session->bid = 0;
9452 sipe_process_pending_invite_queue(sip, session);
9456 * For 2007+ conference only.
9458 static void
9459 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9461 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9462 struct sip_session *session;
9464 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9465 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9467 session = sipe_session_find_chat_by_title(sip, chat_title);
9469 sipe_conf_modify_user_role(sip, session, buddy->name);
9473 * For 2007+ conference only.
9475 static void
9476 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9478 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9479 struct sip_session *session;
9481 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9482 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9484 session = sipe_session_find_chat_by_title(sip, chat_title);
9486 sipe_conf_delete_user(sip, session, buddy->name);
9489 static void
9490 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9492 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9493 struct sip_session *session;
9495 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9496 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9498 session = sipe_session_find_chat_by_title(sip, chat_title);
9500 sipe_invite_to_chat(sip, session, buddy->name);
9503 static void
9504 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9506 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9508 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9509 if (phone) {
9510 char *tel_uri = sip_to_tel_uri(phone);
9512 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9513 sip_csta_make_call(sip, tel_uri);
9515 g_free(tel_uri);
9519 static void
9520 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9522 const gchar *email;
9523 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9525 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9526 if (email)
9528 char *mailto = g_strdup_printf("mailto:%s", email);
9529 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9530 #ifndef _WIN32
9532 pid_t pid;
9533 char *const parmList[] = {"xdg-email", mailto, NULL};
9534 if ((pid = fork()) == -1)
9536 purple_debug_info("sipe", "fork() error\n");
9538 else if (pid == 0)
9540 execvp(parmList[0], parmList);
9541 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9544 #else
9546 BOOL ret;
9547 _flushall();
9548 errno = 0;
9549 //@TODO resolve env variable %WINDIR% first
9550 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9551 if (errno)
9553 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9556 #endif
9558 g_free(mailto);
9560 else
9562 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9567 * A menu which appear when right-clicking on buddy in contact list.
9569 static GList *
9570 sipe_buddy_menu(PurpleBuddy *buddy)
9572 PurpleBlistNode *g_node;
9573 PurpleGroup *group, *gr_parent;
9574 PurpleMenuAction *act;
9575 GList *menu = NULL;
9576 GList *menu_groups = NULL;
9577 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9578 const char *email;
9579 const char *phone;
9580 const char *phone_disp_str;
9581 gchar *self = sip_uri_self(sip);
9583 SIPE_SESSION_FOREACH {
9584 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9586 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9588 PurpleConvChatBuddyFlags flags;
9589 PurpleConvChatBuddyFlags flags_us;
9591 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9592 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9593 if (session->focus_uri
9594 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9595 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9597 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9598 act = purple_menu_action_new(label,
9599 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9600 session->chat_title, NULL);
9601 g_free(label);
9602 menu = g_list_prepend(menu, act);
9605 if (session->focus_uri
9606 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9608 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9609 act = purple_menu_action_new(label,
9610 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9611 session->chat_title, NULL);
9612 g_free(label);
9613 menu = g_list_prepend(menu, act);
9616 else
9618 if (!session->focus_uri
9619 || (session->focus_uri && !session->locked))
9621 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9622 act = purple_menu_action_new(label,
9623 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9624 session->chat_title, NULL);
9625 g_free(label);
9626 menu = g_list_prepend(menu, act);
9630 } SIPE_SESSION_FOREACH_END;
9632 act = purple_menu_action_new(_("New chat"),
9633 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9634 NULL, NULL);
9635 menu = g_list_prepend(menu, act);
9637 if (sip->csta && !sip->csta->line_status) {
9638 gchar *tmp = NULL;
9639 /* work phone */
9640 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9641 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9642 if (phone) {
9643 gchar *label = g_strdup_printf(_("Work %s"),
9644 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9645 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9646 g_free(tmp);
9647 tmp = NULL;
9648 g_free(label);
9649 menu = g_list_prepend(menu, act);
9652 /* mobile phone */
9653 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9654 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9655 if (phone) {
9656 gchar *label = g_strdup_printf(_("Mobile %s"),
9657 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9658 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9659 g_free(tmp);
9660 tmp = NULL;
9661 g_free(label);
9662 menu = g_list_prepend(menu, act);
9665 /* home phone */
9666 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9667 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9668 if (phone) {
9669 gchar *label = g_strdup_printf(_("Home %s"),
9670 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9671 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9672 g_free(tmp);
9673 tmp = NULL;
9674 g_free(label);
9675 menu = g_list_prepend(menu, act);
9678 /* other phone */
9679 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9680 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9681 if (phone) {
9682 gchar *label = g_strdup_printf(_("Other %s"),
9683 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9684 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9685 g_free(tmp);
9686 tmp = NULL;
9687 g_free(label);
9688 menu = g_list_prepend(menu, act);
9691 /* custom1 phone */
9692 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9693 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9694 if (phone) {
9695 gchar *label = g_strdup_printf(_("Custom1 %s"),
9696 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9697 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9698 g_free(tmp);
9699 tmp = NULL;
9700 g_free(label);
9701 menu = g_list_prepend(menu, act);
9705 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9706 if (email) {
9707 act = purple_menu_action_new(_("Send email..."),
9708 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9709 NULL, NULL);
9710 menu = g_list_prepend(menu, act);
9713 gr_parent = purple_buddy_get_group(buddy);
9714 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9715 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9716 continue;
9718 group = (PurpleGroup *)g_node;
9719 if (group == gr_parent)
9720 continue;
9722 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9723 continue;
9725 act = purple_menu_action_new(purple_group_get_name(group),
9726 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9727 group->name, NULL);
9728 menu_groups = g_list_prepend(menu_groups, act);
9730 menu_groups = g_list_reverse(menu_groups);
9732 act = purple_menu_action_new(_("Copy to"),
9733 NULL,
9734 NULL, menu_groups);
9735 menu = g_list_prepend(menu, act);
9736 menu = g_list_reverse(menu);
9738 g_free(self);
9739 return menu;
9742 static void
9743 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9745 struct sipe_account_data *sip = chat->account->gc->proto_data;
9746 struct sip_session *session;
9748 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9749 sipe_conf_modify_conference_lock(sip, session, locked);
9752 static void
9753 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9755 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9756 sipe_conf_modify_lock(chat, FALSE);
9759 static void
9760 sipe_chat_menu_lock_cb(PurpleChat *chat)
9762 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9763 sipe_conf_modify_lock(chat, TRUE);
9766 static GList *
9767 sipe_chat_menu(PurpleChat *chat)
9769 PurpleMenuAction *act;
9770 PurpleConvChatBuddyFlags flags_us;
9771 GList *menu = NULL;
9772 struct sipe_account_data *sip = chat->account->gc->proto_data;
9773 struct sip_session *session;
9774 gchar *self;
9776 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9777 if (!session) return NULL;
9779 self = sip_uri_self(sip);
9780 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9782 if (session->focus_uri
9783 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9785 if (session->locked) {
9786 act = purple_menu_action_new(_("Unlock"),
9787 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9788 NULL, NULL);
9789 menu = g_list_prepend(menu, act);
9790 } else {
9791 act = purple_menu_action_new(_("Lock"),
9792 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9793 NULL, NULL);
9794 menu = g_list_prepend(menu, act);
9798 menu = g_list_reverse(menu);
9800 g_free(self);
9801 return menu;
9804 static GList *
9805 sipe_blist_node_menu(PurpleBlistNode *node)
9807 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9808 return sipe_buddy_menu((PurpleBuddy *) node);
9809 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9810 return sipe_chat_menu((PurpleChat *)node);
9811 } else {
9812 return NULL;
9816 static gboolean
9817 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9819 char *uri = trans->payload->data;
9821 PurpleNotifyUserInfo *info;
9822 PurpleBuddy *pbuddy = NULL;
9823 struct sipe_buddy *sbuddy;
9824 const char *alias = NULL;
9825 char *device_name = NULL;
9826 char *server_alias = NULL;
9827 char *phone_number = NULL;
9828 char *email = NULL;
9829 const char *site;
9830 char *first_name = NULL;
9831 char *last_name = NULL;
9833 if (!sip) return FALSE;
9835 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9837 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9838 alias = purple_buddy_get_local_alias(pbuddy);
9840 //will query buddy UA's capabilities and send answer to log
9841 sipe_options_request(sip, uri);
9843 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9844 if (sbuddy) {
9845 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9848 info = purple_notify_user_info_new();
9850 if (msg->response != 200) {
9851 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9852 } else {
9853 xmlnode *searchResults;
9854 xmlnode *mrow;
9856 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9857 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9858 if (!searchResults) {
9859 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9860 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9861 const char *value;
9862 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9863 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9864 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9866 /* For 2007 system we will take this from ContactCard -
9867 * it has cleaner tel: URIs at least
9869 if (!sip->ocs2007) {
9870 char *tel_uri = sip_to_tel_uri(phone_number);
9871 /* trims its parameters, so call first */
9872 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9873 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9874 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9875 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9876 g_free(tel_uri);
9879 if (server_alias && strlen(server_alias) > 0) {
9880 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9882 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9883 purple_notify_user_info_add_pair(info, _("Job title"), value);
9885 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9886 purple_notify_user_info_add_pair(info, _("Office"), value);
9888 if (phone_number && strlen(phone_number) > 0) {
9889 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9891 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9892 purple_notify_user_info_add_pair(info, _("Company"), value);
9894 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9895 purple_notify_user_info_add_pair(info, _("City"), value);
9897 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9898 purple_notify_user_info_add_pair(info, _("State"), value);
9900 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9901 purple_notify_user_info_add_pair(info, _("Country"), value);
9903 if (email && strlen(email) > 0) {
9904 purple_notify_user_info_add_pair(info, _("Email address"), email);
9908 xmlnode_free(searchResults);
9911 purple_notify_user_info_add_section_break(info);
9913 if (is_empty(server_alias)) {
9914 g_free(server_alias);
9915 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9916 if (server_alias) {
9917 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9921 /* present alias if it differs from server alias */
9922 if (alias && !sipe_strequal(alias, server_alias))
9924 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9927 if (is_empty(email)) {
9928 g_free(email);
9929 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9930 if (email) {
9931 purple_notify_user_info_add_pair(info, _("Email address"), email);
9935 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9936 if (site) {
9937 purple_notify_user_info_add_pair(info, _("Site"), site);
9940 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9941 if (first_name && last_name) {
9942 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9944 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9945 g_free(link);
9947 g_free(first_name);
9948 g_free(last_name);
9950 if (device_name) {
9951 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9954 /* show a buddy's user info in a nice dialog box */
9955 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9956 uri, /* buddy's URI */
9957 info, /* body */
9958 NULL, /* callback called when dialog closed */
9959 NULL); /* userdata for callback */
9961 g_free(phone_number);
9962 g_free(server_alias);
9963 g_free(email);
9964 g_free(device_name);
9966 return TRUE;
9970 * AD search first, LDAP based
9972 static void sipe_get_info(PurpleConnection *gc, const char *username)
9974 struct sipe_account_data *sip = gc->proto_data;
9975 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9976 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9977 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9978 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9980 payload->destroy = g_free;
9981 payload->data = g_strdup(username);
9983 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9984 send_soap_request_with_cb(sip, domain_uri, body,
9985 (TransCallback) process_get_info_response, payload);
9986 g_free(domain_uri);
9987 g_free(body);
9988 g_free(row);
9991 PurplePluginProtocolInfo prpl_info =
9993 OPT_PROTO_CHAT_TOPIC,
9994 NULL, /* user_splits */
9995 NULL, /* protocol_options */
9996 NO_BUDDY_ICONS, /* icon_spec */
9997 sipe_list_icon, /* list_icon */
9998 NULL, /* list_emblems */
9999 sipe_status_text, /* status_text */
10000 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10001 sipe_status_types, /* away_states */
10002 sipe_blist_node_menu, /* blist_node_menu */
10003 NULL, /* chat_info */
10004 NULL, /* chat_info_defaults */
10005 sipe_login, /* login */
10006 sipe_close, /* close */
10007 sipe_im_send, /* send_im */
10008 NULL, /* set_info */ // TODO maybe
10009 sipe_send_typing, /* send_typing */
10010 sipe_get_info, /* get_info */
10011 sipe_set_status, /* set_status */
10012 sipe_set_idle, /* set_idle */
10013 NULL, /* change_passwd */
10014 sipe_add_buddy, /* add_buddy */
10015 NULL, /* add_buddies */
10016 sipe_remove_buddy, /* remove_buddy */
10017 NULL, /* remove_buddies */
10018 sipe_add_permit, /* add_permit */
10019 sipe_add_deny, /* add_deny */
10020 sipe_add_deny, /* rem_permit */
10021 sipe_add_permit, /* rem_deny */
10022 dummy_permit_deny, /* set_permit_deny */
10023 NULL, /* join_chat */
10024 NULL, /* reject_chat */
10025 NULL, /* get_chat_name */
10026 sipe_chat_invite, /* chat_invite */
10027 sipe_chat_leave, /* chat_leave */
10028 NULL, /* chat_whisper */
10029 sipe_chat_send, /* chat_send */
10030 sipe_keep_alive, /* keepalive */
10031 NULL, /* register_user */
10032 NULL, /* get_cb_info */ // deprecated
10033 NULL, /* get_cb_away */ // deprecated
10034 sipe_alias_buddy, /* alias_buddy */
10035 sipe_group_buddy, /* group_buddy */
10036 sipe_rename_group, /* rename_group */
10037 NULL, /* buddy_free */
10038 sipe_convo_closed, /* convo_closed */
10039 purple_normalize_nocase, /* normalize */
10040 NULL, /* set_buddy_icon */
10041 sipe_remove_group, /* remove_group */
10042 NULL, /* get_cb_real_name */ // TODO?
10043 NULL, /* set_chat_topic */
10044 NULL, /* find_blist_chat */
10045 NULL, /* roomlist_get_list */
10046 NULL, /* roomlist_cancel */
10047 NULL, /* roomlist_expand_category */
10048 NULL, /* can_receive_file */
10049 sipe_ft_send_file, /* send_file */
10050 sipe_ft_new_xfer, /* new_xfer */
10051 NULL, /* offline_message */
10052 NULL, /* whiteboard_prpl_ops */
10053 sipe_send_raw, /* send_raw */
10054 NULL, /* roomlist_room_serialize */
10055 NULL, /* unregister_user */
10056 NULL, /* send_attention */
10057 NULL, /* get_attention_types */
10058 #if !PURPLE_VERSION_CHECK(2,5,0)
10059 /* Backward compatibility when compiling against 2.4.x API */
10060 (void (*)(void)) /* _purple_reserved4 */
10061 #endif
10062 sizeof(PurplePluginProtocolInfo), /* struct_size */
10063 #if PURPLE_VERSION_CHECK(2,5,0)
10064 sipe_get_account_text_table, /* get_account_text_table */
10065 #if PURPLE_VERSION_CHECK(2,6,0)
10066 NULL, /* initiate_media */
10067 NULL, /* get_media_caps */
10068 #endif
10069 #endif
10073 PurplePluginInfo info = {
10074 PURPLE_PLUGIN_MAGIC,
10075 PURPLE_MAJOR_VERSION,
10076 PURPLE_MINOR_VERSION,
10077 PURPLE_PLUGIN_PROTOCOL, /**< type */
10078 NULL, /**< ui_requirement */
10079 0, /**< flags */
10080 NULL, /**< dependencies */
10081 PURPLE_PRIORITY_DEFAULT, /**< priority */
10082 "prpl-sipe", /**< id */
10083 "Office Communicator", /**< name */
10084 PACKAGE_VERSION, /**< version */
10085 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10086 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10087 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10088 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10089 "Gabriel Burt <gburt@novell.com>, " /**< author */
10090 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10091 "pier11 <pier11@operamail.com>", /**< author */
10092 PACKAGE_URL, /**< homepage */
10093 sipe_plugin_load, /**< load */
10094 sipe_plugin_unload, /**< unload */
10095 sipe_plugin_destroy, /**< destroy */
10096 NULL, /**< ui_info */
10097 &prpl_info, /**< extra_info */
10098 NULL,
10099 sipe_actions,
10100 NULL,
10101 NULL,
10102 NULL,
10103 NULL
10107 Local Variables:
10108 mode: c
10109 c-file-style: "bsd"
10110 indent-tabs-mode: t
10111 tab-width: 8
10112 End: