core cleanup: sipe-csta module is purple free
[siplcs.git] / src / core / sipe.c
blob120b7afcade7dc1a9b897eee3033e33dbee46398
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>
60 #include <glib.h>
62 #include "account.h"
63 #include "blist.h"
64 #include "connection.h"
65 #include "conversation.h"
66 #include "core.h"
67 #include "cipher.h"
68 #include "circbuffer.h"
69 #include "debug.h"
70 #include "dnsquery.h"
71 #include "dnssrv.h"
72 #include "ft.h"
73 #include "mime.h"
74 #include "network.h"
75 #include "notify.h"
76 #include "plugin.h"
77 #include "privacy.h"
78 #include "request.h"
79 #include "savedstatuses.h"
80 #include "sslconn.h"
81 #include "util.h"
82 #include "version.h"
83 #include "xmlnode.h"
85 #include "core-depurple.h" /* Temporary for the core de-purple transition */
87 #include "sipe-common.h"
88 #include "sipmsg.h"
89 #include "sip-csta.h"
90 #include "sip-sec.h"
91 #include "sipe-cal.h"
92 #include "sipe-chat.h"
93 #include "sipe-conf.h"
94 #include "sipe-dialog.h"
95 #include "sipe-ews.h"
96 #include "sipe-ft.h"
97 #include "sipe-nls.h"
98 #include "sipe-session.h"
99 #include "sipe-sign.h"
100 #include "sipe-utils.h"
101 #include "sipe-xml.h"
102 #include "http-conn.h"
103 #include "uuid.h"
104 #include "sipe.h"
106 /* Backward compatibility when compiling against 2.4.x API */
107 #if !PURPLE_VERSION_CHECK(2,5,0)
108 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
109 #endif
111 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
113 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
114 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
116 /* Keep in sync with sipe_transport_type! */
117 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
118 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
120 /* Status identifiers (see also: sipe_status_types()) */
121 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
122 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
123 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
124 /* PURPLE_STATUS_UNAVAILABLE: */
125 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
126 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
127 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
128 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
129 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
130 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
131 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
132 /* PURPLE_STATUS_AWAY: */
133 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
134 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
135 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
136 /** Reuters status (user settable) */
137 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
138 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
139 /* ??? PURPLE_STATUS_MOBILE */
140 /* ??? PURPLE_STATUS_TUNE */
142 /* Status attributes (see also sipe_status_types() */
143 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
145 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
147 static struct sipe_activity_map_struct
149 sipe_activity type;
150 const char *token;
151 const char *desc;
152 const char *status_id;
154 } const sipe_activity_map[] =
156 /* This has nothing to do with Availability numbers, like 3500 (online).
157 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
159 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
160 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
161 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
162 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
163 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
164 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
165 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
166 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
167 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
168 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
169 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
170 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
171 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
172 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
173 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
175 /** @param x is sipe_activity */
176 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
179 /* Action name templates */
180 #define ACTION_NAME_PRESENCE "<presence><%s>"
182 static sipe_activity
183 sipe_get_activity_by_token(const char *token)
185 int i;
187 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
189 if (sipe_strequal(token, sipe_activity_map[i].token))
190 return sipe_activity_map[i].type;
193 return sipe_activity_map[0].type;
196 static const char *
197 sipe_get_activity_desc_by_token(const char *token)
199 if (!token) return NULL;
201 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
204 /** Allows to send typed messages from chat window again after account reinstantiation. */
205 static void
206 sipe_rejoin_chat(PurpleConversation *conv)
208 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
209 PURPLE_CONV_CHAT(conv)->left)
211 PURPLE_CONV_CHAT(conv)->left = FALSE;
212 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
216 static char *genbranch()
218 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
219 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
220 rand() & 0xFFFF, rand() & 0xFFFF);
224 static char *default_ua = NULL;
225 static const char*
226 sipe_get_useragent(struct sipe_account_data *sip)
228 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
229 if (is_empty(useragent)) {
230 if (!default_ua) {
231 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
232 /* ref: lzodefs.h */
233 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
234 #define SIPE_TARGET_PLATFORM "linux"
235 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
236 #define SIPE_TARGET_PLATFORM "bsd"
237 #elif defined(__APPLE__) || defined(__MACOS__)
238 #define SIPE_TARGET_PLATFORM "macosx"
239 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
240 #define SIPE_TARGET_PLATFORM "aix"
241 #elif defined(__solaris__) || defined(__sun)
242 #define SIPE_TARGET_PLATFORM "sun"
243 #elif defined(_WIN32)
244 #define SIPE_TARGET_PLATFORM "win"
245 #elif defined(__CYGWIN__)
246 #define SIPE_TARGET_PLATFORM "cygwin"
247 #elif defined(__hpux__)
248 #define SIPE_TARGET_PLATFORM "hpux"
249 #elif defined(__sgi__)
250 #define SIPE_TARGET_PLATFORM "irix"
251 #else
252 #define SIPE_TARGET_PLATFORM "unknown"
253 #endif
255 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
256 #define SIPE_TARGET_ARCH "x86_64"
257 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
258 #define SIPE_TARGET_ARCH "i386"
259 #elif defined(__ppc64__)
260 #define SIPE_TARGET_ARCH "ppc64"
261 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
262 #define SIPE_TARGET_ARCH "ppc"
263 #elif defined(__hppa__) || defined(__hppa)
264 #define SIPE_TARGET_ARCH "hppa"
265 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
266 #define SIPE_TARGET_ARCH "mips"
267 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
268 #define SIPE_TARGET_ARCH "s390"
269 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
270 #define SIPE_TARGET_ARCH "sparc"
271 #elif defined(__arm__)
272 #define SIPE_TARGET_ARCH "arm"
273 #else
274 #define SIPE_TARGET_ARCH "other"
275 #endif
277 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
278 purple_core_get_version(),
279 sip->server_version ? sip->server_version : "");
281 useragent = default_ua;
283 return useragent;
286 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
287 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
289 return "sipe";
292 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
294 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
295 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
296 gpointer data);
298 static void sipe_close(PurpleConnection *gc);
300 static void send_presence_status(struct sipe_account_data *sip);
302 static void sendout_pkt(PurpleConnection *gc, const char *buf);
304 static void sipe_keep_alive(PurpleConnection *gc)
306 struct sipe_account_data *sip = gc->proto_data;
307 if (sip->transport == SIPE_TRANSPORT_UDP) {
308 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
309 gchar buf[2] = {0, 0};
310 purple_debug_info("sipe", "sending keep alive\n");
311 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
312 } else {
313 time_t now = time(NULL);
314 if ((sip->keepalive_timeout > 0) &&
315 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
316 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
318 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
319 sendout_pkt(gc, "\r\n\r\n");
320 sip->last_keepalive = now;
325 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
327 struct sip_connection *ret = NULL;
328 GSList *entry = sip->openconns;
329 while (entry) {
330 ret = entry->data;
331 if (ret->fd == fd) return ret;
332 entry = entry->next;
334 return NULL;
337 static void sipe_auth_free(struct sip_auth *auth)
339 g_free(auth->opaque);
340 auth->opaque = NULL;
341 g_free(auth->realm);
342 auth->realm = NULL;
343 g_free(auth->target);
344 auth->target = NULL;
345 auth->version = 0;
346 auth->type = AUTH_TYPE_UNSET;
347 auth->retries = 0;
348 auth->expires = 0;
349 g_free(auth->gssapi_data);
350 auth->gssapi_data = NULL;
351 sip_sec_destroy_context(auth->gssapi_context);
352 auth->gssapi_context = NULL;
355 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
357 struct sip_connection *ret = g_new0(struct sip_connection, 1);
358 ret->fd = fd;
359 sip->openconns = g_slist_append(sip->openconns, ret);
360 return ret;
363 static void connection_remove(struct sipe_account_data *sip, int fd)
365 struct sip_connection *conn = connection_find(sip, fd);
366 if (conn) {
367 sip->openconns = g_slist_remove(sip->openconns, conn);
368 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
369 g_free(conn->inbuf);
370 g_free(conn);
374 static void connection_free_all(struct sipe_account_data *sip)
376 struct sip_connection *ret = NULL;
377 GSList *entry = sip->openconns;
378 while (entry) {
379 ret = entry->data;
380 connection_remove(sip, ret->fd);
381 entry = sip->openconns;
385 static void
386 sipe_make_signature(struct sipe_account_data *sip,
387 struct sipmsg *msg);
389 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
391 gchar noncecount[9];
392 const char *authuser = sip->authuser;
393 gchar *response;
394 gchar *ret;
396 if (!authuser || strlen(authuser) < 1) {
397 authuser = sip->username;
400 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
401 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
402 gchar *version_str;
404 // If we have a signature for the message, include that
405 if (msg->signature) {
406 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);
409 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
410 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
411 gchar *gssapi_data;
412 gchar *opaque;
413 gchar *sign_str = NULL;
415 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
416 &(auth->expires),
417 auth->type,
418 purple_account_get_bool(sip->account, "sso", TRUE),
419 sip->authdomain ? sip->authdomain : "",
420 authuser,
421 sip->password,
422 auth->target,
423 auth->gssapi_data);
424 if (!gssapi_data || !auth->gssapi_context) {
425 sip->gc->wants_to_die = TRUE;
426 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
427 return NULL;
430 if (auth->version > 3) {
431 sipe_make_signature(sip, msg);
432 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
433 msg->rand, msg->num, msg->signature);
434 } else {
435 sign_str = g_strdup("");
438 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
439 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
440 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);
441 g_free(opaque);
442 g_free(gssapi_data);
443 g_free(version_str);
444 g_free(sign_str);
445 return ret;
448 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
449 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
450 g_free(version_str);
451 return ret;
453 } else { /* Digest */
455 /* Calculate new session key */
456 if (!auth->opaque) {
457 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
458 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
459 authuser, auth->realm, sip->password,
460 auth->gssapi_data, NULL);
463 sprintf(noncecount, "%08d", auth->nc++);
464 response = purple_cipher_http_digest_calculate_response("md5",
465 msg->method, msg->target, NULL, NULL,
466 auth->gssapi_data, noncecount, NULL,
467 auth->opaque);
468 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
470 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);
471 g_free(response);
472 return ret;
476 static char *parse_attribute(const char *attrname, const char *source)
478 const char *tmp, *tmp2;
479 char *retval = NULL;
480 int len = strlen(attrname);
482 if (g_str_has_prefix(source, attrname)) {
483 tmp = source + len;
484 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
485 if (tmp2)
486 retval = g_strndup(tmp, tmp2 - tmp);
487 else
488 retval = g_strdup(tmp);
491 return retval;
494 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
496 int i;
497 gchar **parts;
499 if (!hdr) {
500 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
501 return;
504 if (!g_strncasecmp(hdr, "NTLM", 4)) {
505 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
506 auth->type = AUTH_TYPE_NTLM;
507 hdr += 5;
508 auth->nc = 1;
509 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
510 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
511 auth->type = AUTH_TYPE_KERBEROS;
512 hdr += 9;
513 auth->nc = 3;
514 } else {
515 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
516 auth->type = AUTH_TYPE_DIGEST;
517 hdr += 7;
520 parts = g_strsplit(hdr, "\", ", 0);
521 for (i = 0; parts[i]; i++) {
522 char *tmp;
524 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
526 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
527 g_free(auth->gssapi_data);
528 auth->gssapi_data = tmp;
530 if (auth->type == AUTH_TYPE_NTLM) {
531 /* NTLM module extracts nonce from gssapi-data */
532 auth->nc = 3;
535 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
536 /* Only used with AUTH_TYPE_DIGEST */
537 g_free(auth->gssapi_data);
538 auth->gssapi_data = tmp;
539 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
540 g_free(auth->opaque);
541 auth->opaque = tmp;
542 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
543 g_free(auth->realm);
544 auth->realm = tmp;
546 if (auth->type == AUTH_TYPE_DIGEST) {
547 /* Throw away old session key */
548 g_free(auth->opaque);
549 auth->opaque = NULL;
550 auth->nc = 1;
552 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
553 g_free(auth->target);
554 auth->target = tmp;
555 } else if ((tmp = parse_attribute("version=", parts[i]))) {
556 auth->version = atoi(tmp);
557 g_free(tmp);
559 // uncomment to revert to previous functionality if version 3+ does not work.
560 // auth->version = 2;
562 g_strfreev(parts);
564 return;
567 static void sipe_canwrite_cb(gpointer data,
568 SIPE_UNUSED_PARAMETER gint source,
569 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
571 PurpleConnection *gc = data;
572 struct sipe_account_data *sip = gc->proto_data;
573 gsize max_write;
574 gssize written;
576 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
578 if (max_write == 0) {
579 if (sip->tx_handler != 0){
580 purple_input_remove(sip->tx_handler);
581 sip->tx_handler = 0;
583 return;
586 written = write(sip->fd, sip->txbuf->outptr, max_write);
588 if (written < 0 && errno == EAGAIN)
589 written = 0;
590 else if (written <= 0) {
591 /*TODO: do we really want to disconnect on a failure to write?*/
592 purple_connection_error(gc, _("Could not write"));
593 return;
596 purple_circ_buffer_mark_read(sip->txbuf, written);
599 static void sipe_canwrite_cb_ssl(gpointer data,
600 SIPE_UNUSED_PARAMETER gint src,
601 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
603 PurpleConnection *gc = data;
604 struct sipe_account_data *sip = gc->proto_data;
605 gsize max_write;
606 gssize written;
608 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
610 if (max_write == 0) {
611 if (sip->tx_handler != 0) {
612 purple_input_remove(sip->tx_handler);
613 sip->tx_handler = 0;
614 return;
618 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
620 if (written < 0 && errno == EAGAIN)
621 written = 0;
622 else if (written <= 0) {
623 /*TODO: do we really want to disconnect on a failure to write?*/
624 purple_connection_error(gc, _("Could not write"));
625 return;
628 purple_circ_buffer_mark_read(sip->txbuf, written);
631 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
633 static void send_later_cb(gpointer data, gint source,
634 SIPE_UNUSED_PARAMETER const gchar *error)
636 PurpleConnection *gc = data;
637 struct sipe_account_data *sip;
638 struct sip_connection *conn;
640 if (!PURPLE_CONNECTION_IS_VALID(gc))
642 if (source >= 0)
643 close(source);
644 return;
647 if (source < 0) {
648 purple_connection_error(gc, _("Could not connect"));
649 return;
652 sip = gc->proto_data;
653 sip->fd = source;
654 sip->connecting = FALSE;
655 sip->last_keepalive = time(NULL);
657 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
659 /* If there is more to write now, we need to register a handler */
660 if (sip->txbuf->bufused > 0)
661 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
663 conn = connection_create(sip, source);
664 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
667 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
669 struct sipe_account_data *sip;
671 if (!PURPLE_CONNECTION_IS_VALID(gc))
673 if (gsc) purple_ssl_close(gsc);
674 return NULL;
677 sip = gc->proto_data;
678 sip->fd = gsc->fd;
679 sip->gsc = gsc;
680 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
681 sip->connecting = FALSE;
682 sip->last_keepalive = time(NULL);
684 connection_create(sip, gsc->fd);
686 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
688 return sip;
691 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
692 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
694 PurpleConnection *gc = data;
695 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
696 if (sip == NULL) return;
698 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
700 /* If there is more to write now */
701 if (sip->txbuf->bufused > 0) {
702 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
707 static void sendlater(PurpleConnection *gc, const char *buf)
709 struct sipe_account_data *sip = gc->proto_data;
711 if (!sip->connecting) {
712 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
713 if (sip->transport == SIPE_TRANSPORT_TLS){
714 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
715 } else {
716 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
717 purple_connection_error(gc, _("Could not create socket"));
720 sip->connecting = TRUE;
723 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
724 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
726 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
729 static void sendout_pkt(PurpleConnection *gc, const char *buf)
731 struct sipe_account_data *sip = gc->proto_data;
732 time_t currtime = time(NULL);
733 int writelen = strlen(buf);
734 char *tmp;
736 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
737 g_free(tmp);
738 if (sip->transport == SIPE_TRANSPORT_UDP) {
739 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
740 purple_debug_info("sipe", "could not send packet\n");
742 } else {
743 int ret;
744 if (sip->fd < 0) {
745 sendlater(gc, buf);
746 return;
749 if (sip->tx_handler) {
750 ret = -1;
751 errno = EAGAIN;
752 } else{
753 if (sip->gsc){
754 ret = purple_ssl_write(sip->gsc, buf, writelen);
755 }else{
756 ret = write(sip->fd, buf, writelen);
760 if (ret < 0 && errno == EAGAIN)
761 ret = 0;
762 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
763 sendlater(gc, buf);
764 return;
767 if (ret < writelen) {
768 if (!sip->tx_handler){
769 if (sip->gsc){
770 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
772 else{
773 sip->tx_handler = purple_input_add(sip->fd,
774 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
775 gc);
779 /* XXX: is it OK to do this? You might get part of a request sent
780 with part of another. */
781 if (sip->txbuf->bufused > 0)
782 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
784 purple_circ_buffer_append(sip->txbuf, buf + ret,
785 writelen - ret);
790 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
792 sendout_pkt(gc, buf);
793 return len;
796 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
798 GSList *tmp = msg->headers;
799 gchar *name;
800 gchar *value;
801 GString *outstr = g_string_new("");
802 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
803 while (tmp) {
804 name = ((struct sipnameval*) (tmp->data))->name;
805 value = ((struct sipnameval*) (tmp->data))->value;
806 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
807 tmp = g_slist_next(tmp);
809 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
810 sendout_pkt(sip->gc, outstr->str);
811 g_string_free(outstr, TRUE);
814 static void
815 sipe_make_signature(struct sipe_account_data *sip,
816 struct sipmsg *msg)
818 if (sip->registrar.gssapi_context) {
819 struct sipmsg_breakdown msgbd;
820 gchar *signature_input_str;
821 msgbd.msg = msg;
822 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
823 msgbd.rand = g_strdup_printf("%08x", g_random_int());
824 sip->registrar.ntlm_num++;
825 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
826 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
827 if (signature_input_str != NULL) {
828 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
829 msg->signature = signature_hex;
830 msg->rand = g_strdup(msgbd.rand);
831 msg->num = g_strdup(msgbd.num);
832 g_free(signature_input_str);
834 sipmsg_breakdown_free(&msgbd);
838 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
840 gchar * buf;
842 if (sip->registrar.type == AUTH_TYPE_UNSET) {
843 return;
846 sipe_make_signature(sip, msg);
848 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
849 buf = auth_header(sip, &sip->registrar, msg);
850 if (buf) {
851 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
853 g_free(buf);
854 } 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")) {
855 sip->registrar.nc = 3;
856 sip->registrar.type = AUTH_TYPE_NTLM;
857 #ifdef HAVE_KERBEROS
858 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
859 sip->registrar.type = AUTH_TYPE_KERBEROS;
861 #endif
864 buf = auth_header(sip, &sip->registrar, msg);
865 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
866 g_free(buf);
867 } else {
868 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
872 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
873 const char *text, const char *body)
875 gchar *name;
876 gchar *value;
877 GString *outstr = g_string_new("");
878 struct sipe_account_data *sip = gc->proto_data;
879 gchar *contact;
880 GSList *tmp;
881 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
883 /* Can return NULL! */
884 contact = get_contact(sip);
885 if (contact) {
886 sipmsg_add_header(msg, "Contact", contact);
887 g_free(contact);
890 if (body) {
891 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
892 sipmsg_add_header(msg, "Content-Length", len);
893 g_free(len);
894 } else {
895 sipmsg_add_header(msg, "Content-Length", "0");
898 msg->response = code;
900 sipmsg_strip_headers(msg, keepers);
901 sipmsg_merge_new_headers(msg);
902 sign_outgoing_message(msg, sip, msg->method);
904 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
905 tmp = msg->headers;
906 while (tmp) {
907 name = ((struct sipnameval*) (tmp->data))->name;
908 value = ((struct sipnameval*) (tmp->data))->value;
910 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
911 tmp = g_slist_next(tmp);
913 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
914 sendout_pkt(gc, outstr->str);
915 g_string_free(outstr, TRUE);
918 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
920 if (sip->transactions) {
921 sip->transactions = g_slist_remove(sip->transactions, trans);
922 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
924 if (trans->msg) sipmsg_free(trans->msg);
925 if (trans->payload) {
926 (*trans->payload->destroy)(trans->payload->data);
927 g_free(trans->payload);
929 g_free(trans->key);
930 g_free(trans);
934 static struct transaction *
935 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
937 const gchar *call_id;
938 const gchar *cseq;
939 struct transaction *trans = g_new0(struct transaction, 1);
941 trans->time = time(NULL);
942 trans->msg = (struct sipmsg *)msg;
943 call_id = sipmsg_find_header(trans->msg, "Call-ID");
944 cseq = sipmsg_find_header(trans->msg, "CSeq");
945 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
946 trans->callback = callback;
947 sip->transactions = g_slist_append(sip->transactions, trans);
948 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
949 return trans;
952 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
954 struct transaction *trans;
955 GSList *transactions = sip->transactions;
956 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
957 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
958 gchar *key;
960 if (!call_id || !cseq) {
961 purple_debug(PURPLE_DEBUG_ERROR, "sipe", "transaction_find: no Call-ID or CSeq!\n");
962 return NULL;
965 key = g_strdup_printf("<%s><%s>", call_id, cseq);
966 while (transactions) {
967 trans = transactions->data;
968 if (!g_strcasecmp(trans->key, key)) {
969 g_free(key);
970 return trans;
972 transactions = transactions->next;
975 g_free(key);
976 return NULL;
979 struct transaction *
980 send_sip_request(PurpleConnection *gc, const gchar *method,
981 const gchar *url, const gchar *to, const gchar *addheaders,
982 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
984 struct sipe_account_data *sip = gc->proto_data;
985 const char *addh = "";
986 char *buf;
987 struct sipmsg *msg;
988 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
989 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
990 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
991 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
992 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
993 gchar *route = g_strdup("");
994 gchar *epid = get_epid(sip);
995 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
996 struct transaction *trans = NULL;
998 if (dialog && dialog->routes)
1000 GSList *iter = dialog->routes;
1002 while(iter)
1004 char *tmp = route;
1005 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1006 g_free(tmp);
1007 iter = g_slist_next(iter);
1011 if (!ourtag && !dialog) {
1012 ourtag = gentag();
1015 if (sipe_strequal(method, "REGISTER")) {
1016 if (sip->regcallid) {
1017 g_free(callid);
1018 callid = g_strdup(sip->regcallid);
1019 } else {
1020 sip->regcallid = g_strdup(callid);
1022 cseq = ++sip->cseq;
1025 if (addheaders) addh = addheaders;
1027 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1028 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1029 "From: <sip:%s>%s%s;epid=%s\r\n"
1030 "To: <%s>%s%s%s%s\r\n"
1031 "Max-Forwards: 70\r\n"
1032 "CSeq: %d %s\r\n"
1033 "User-Agent: %s\r\n"
1034 "Call-ID: %s\r\n"
1035 "%s%s"
1036 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1037 method,
1038 dialog && dialog->request ? dialog->request : url,
1039 TRANSPORT_DESCRIPTOR,
1040 purple_network_get_my_ip(-1),
1041 sip->listenport,
1042 branch ? ";branch=" : "",
1043 branch ? branch : "",
1044 sip->username,
1045 ourtag ? ";tag=" : "",
1046 ourtag ? ourtag : "",
1047 epid,
1049 theirtag ? ";tag=" : "",
1050 theirtag ? theirtag : "",
1051 theirepid ? ";epid=" : "",
1052 theirepid ? theirepid : "",
1053 cseq,
1054 method,
1055 sipe_get_useragent(sip),
1056 callid,
1057 route,
1058 addh,
1059 body ? (gsize) strlen(body) : 0,
1060 body ? body : "");
1063 //printf ("parsing msg buf:\n%s\n\n", buf);
1064 msg = sipmsg_parse_msg(buf);
1066 g_free(buf);
1067 g_free(ourtag);
1068 g_free(theirtag);
1069 g_free(theirepid);
1070 g_free(branch);
1071 g_free(callid);
1072 g_free(route);
1073 g_free(epid);
1075 sign_outgoing_message (msg, sip, method);
1077 buf = sipmsg_to_string (msg);
1079 /* add to ongoing transactions */
1080 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1081 if (!sipe_strequal(method, "ACK")) {
1082 trans = transactions_add_buf(sip, msg, tc);
1083 } else {
1084 sipmsg_free(msg);
1086 sendout_pkt(gc, buf);
1087 g_free(buf);
1089 return trans;
1093 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1095 static void
1096 send_soap_request_with_cb(struct sipe_account_data *sip,
1097 gchar *from0,
1098 gchar *body,
1099 TransCallback callback,
1100 struct transaction_payload *payload)
1102 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1103 gchar *contact = get_contact(sip);
1104 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1105 "Content-Type: application/SOAP+xml\r\n",contact);
1107 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1108 trans->payload = payload;
1110 g_free(from);
1111 g_free(contact);
1112 g_free(hdr);
1115 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1117 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1120 static char *get_contact_register(struct sipe_account_data *sip)
1122 char *epid = get_epid(sip);
1123 char *uuid = generateUUIDfromEPID(epid);
1124 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);
1125 g_free(uuid);
1126 g_free(epid);
1127 return(buf);
1130 static void do_register_exp(struct sipe_account_data *sip, int expire)
1132 char *uri;
1133 char *expires;
1134 char *to;
1135 char *contact;
1136 char *hdr;
1138 if (!sip->sipdomain) return;
1140 uri = sip_uri_from_name(sip->sipdomain);
1141 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1142 to = sip_uri_self(sip);
1143 contact = get_contact_register(sip);
1144 hdr = g_strdup_printf("Contact: %s\r\n"
1145 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1146 "Event: registration\r\n"
1147 "Allow-Events: presence\r\n"
1148 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1149 "%s", contact, expires);
1150 g_free(contact);
1151 g_free(expires);
1153 sip->registerstatus = 1;
1155 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1156 process_register_response);
1158 g_free(hdr);
1159 g_free(uri);
1160 g_free(to);
1163 static void do_register_cb(struct sipe_account_data *sip,
1164 SIPE_UNUSED_PARAMETER void *unused)
1166 do_register_exp(sip, -1);
1167 sip->reregister_set = FALSE;
1170 static void do_register(struct sipe_account_data *sip)
1172 do_register_exp(sip, -1);
1175 static void
1176 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1178 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1179 send_soap_request(sip, body);
1180 g_free(body);
1183 static void
1184 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1186 if (allow) {
1187 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1188 } else {
1189 purple_debug_info("sipe", "Blocking contact %s\n", who);
1192 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1195 static
1196 void sipe_auth_user_cb(void * data)
1198 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1199 if (!job) return;
1201 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1202 g_free(job);
1205 static
1206 void sipe_deny_user_cb(void * data)
1208 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1209 if (!job) return;
1211 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1212 g_free(job);
1215 static void
1216 sipe_add_permit(PurpleConnection *gc, const char *name)
1218 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1219 sipe_contact_allow_deny(sip, name, TRUE);
1222 static void
1223 sipe_add_deny(PurpleConnection *gc, const char *name)
1225 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1226 sipe_contact_allow_deny(sip, name, FALSE);
1229 /*static void
1230 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1232 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1233 sipe_contact_set_acl(sip, name, "");
1236 static void
1237 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1239 xmlnode *watchers;
1240 xmlnode *watcher;
1241 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1242 if (msg->response != 0 && msg->response != 200) return;
1244 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1246 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1247 if (!watchers) return;
1249 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1250 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1251 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1252 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1254 // TODO pull out optional displayName to pass as alias
1255 if (remote_user) {
1256 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1257 job->who = remote_user;
1258 job->sip = sip;
1259 purple_account_request_authorization(
1260 sip->account,
1261 remote_user,
1262 _("you"), /* id */
1263 alias,
1264 NULL, /* message */
1265 on_list,
1266 sipe_auth_user_cb,
1267 sipe_deny_user_cb,
1268 (void *) job);
1273 xmlnode_free(watchers);
1274 return;
1277 static void
1278 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1280 PurpleGroup * purple_group = purple_find_group(group->name);
1281 if (!purple_group) {
1282 purple_group = purple_group_new(group->name);
1283 purple_blist_add_group(purple_group, NULL);
1286 if (purple_group) {
1287 group->purple_group = purple_group;
1288 sip->groups = g_slist_append(sip->groups, group);
1289 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1290 } else {
1291 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1295 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1297 struct sipe_group *group;
1298 GSList *entry;
1299 if (sip == NULL) {
1300 return NULL;
1303 entry = sip->groups;
1304 while (entry) {
1305 group = entry->data;
1306 if (group->id == id) {
1307 return group;
1309 entry = entry->next;
1311 return NULL;
1314 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1316 struct sipe_group *group;
1317 GSList *entry;
1318 if (!sip || !name) {
1319 return NULL;
1322 entry = sip->groups;
1323 while (entry) {
1324 group = entry->data;
1325 if (sipe_strequal(group->name, name)) {
1326 return group;
1328 entry = entry->next;
1330 return NULL;
1333 static void
1334 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1336 gchar *body;
1337 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1338 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1339 send_soap_request(sip, body);
1340 g_free(body);
1341 g_free(group->name);
1342 group->name = g_strdup(name);
1346 * Only appends if no such value already stored.
1347 * Like Set in Java.
1349 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1350 GSList * res = list;
1351 if (!g_slist_find_custom(list, data, func)) {
1352 res = g_slist_insert_sorted(list, data, func);
1354 return res;
1357 static int
1358 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1359 return group1->id - group2->id;
1363 * Returns string like "2 4 7 8" - group ids buddy belong to.
1365 static gchar *
1366 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1367 int i = 0;
1368 gchar *res;
1369 //creating array from GList, converting int to gchar*
1370 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1371 GSList *entry = buddy->groups;
1373 if (!ids_arr) return NULL;
1375 while (entry) {
1376 struct sipe_group * group = entry->data;
1377 ids_arr[i] = g_strdup_printf("%d", group->id);
1378 entry = entry->next;
1379 i++;
1381 ids_arr[i] = NULL;
1382 res = g_strjoinv(" ", ids_arr);
1383 g_strfreev(ids_arr);
1384 return res;
1388 * Sends buddy update to server
1390 static void
1391 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1393 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1394 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1396 if (buddy && purple_buddy) {
1397 const char *alias = purple_buddy_get_alias(purple_buddy);
1398 gchar *groups = sipe_get_buddy_groups_string(buddy);
1399 if (groups) {
1400 gchar *body;
1401 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1403 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1404 alias, groups, "true", buddy->name, sip->contacts_delta++
1406 send_soap_request(sip, body);
1407 g_free(groups);
1408 g_free(body);
1413 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1415 if (msg->response == 200) {
1416 struct sipe_group *group;
1417 struct group_user_context *ctx = trans->payload->data;
1418 xmlnode *xml;
1419 xmlnode *node;
1420 char *group_id;
1421 struct sipe_buddy *buddy;
1423 xml = xmlnode_from_str(msg->body, msg->bodylen);
1424 if (!xml) {
1425 return FALSE;
1428 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1429 if (!node) {
1430 xmlnode_free(xml);
1431 return FALSE;
1434 group_id = xmlnode_get_data(node);
1435 if (!group_id) {
1436 xmlnode_free(xml);
1437 return FALSE;
1440 group = g_new0(struct sipe_group, 1);
1441 group->id = (int)g_ascii_strtod(group_id, NULL);
1442 g_free(group_id);
1443 group->name = g_strdup(ctx->group_name);
1445 sipe_group_add(sip, group);
1447 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1448 if (buddy) {
1449 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1452 sipe_group_set_user(sip, ctx->user_name);
1454 xmlnode_free(xml);
1455 return TRUE;
1457 return FALSE;
1460 static void sipe_group_context_destroy(gpointer data)
1462 struct group_user_context *ctx = data;
1463 g_free(ctx->group_name);
1464 g_free(ctx->user_name);
1465 g_free(ctx);
1468 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1470 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1471 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1472 gchar *body;
1473 ctx->group_name = g_strdup(name);
1474 ctx->user_name = g_strdup(who);
1475 payload->destroy = sipe_group_context_destroy;
1476 payload->data = ctx;
1478 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1479 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1480 g_free(body);
1484 * Data structure for scheduled actions
1487 struct scheduled_action {
1489 * Name of action.
1490 * Format is <Event>[<Data>...]
1491 * Example: <presence><sip:user@domain.com> or <registration>
1493 gchar *name;
1494 guint timeout_handler;
1495 gboolean repetitive;
1496 Action action;
1497 GDestroyNotify destroy;
1498 struct sipe_account_data *sip;
1499 void *payload;
1503 * A timer callback
1504 * Should return FALSE if repetitive action is not needed
1506 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1508 gboolean ret;
1509 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1510 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1511 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1512 (sched_action->action)(sched_action->sip, sched_action->payload);
1513 ret = sched_action->repetitive;
1514 if (sched_action->destroy) {
1515 (*sched_action->destroy)(sched_action->payload);
1517 g_free(sched_action->name);
1518 g_free(sched_action);
1519 return ret;
1523 * Kills action timer effectively cancelling
1524 * scheduled action
1526 * @param name of action
1528 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1530 GSList *entry;
1532 if (!sip->timeouts || !name) return;
1534 entry = sip->timeouts;
1535 while (entry) {
1536 struct scheduled_action *sched_action = entry->data;
1537 if(sipe_strequal(sched_action->name, name)) {
1538 GSList *to_delete = entry;
1539 entry = entry->next;
1540 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1541 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1542 purple_timeout_remove(sched_action->timeout_handler);
1543 if (sched_action->destroy) {
1544 (*sched_action->destroy)(sched_action->payload);
1546 g_free(sched_action->name);
1547 g_free(sched_action);
1548 } else {
1549 entry = entry->next;
1554 static void
1555 sipe_schedule_action0(const gchar *name,
1556 int timeout,
1557 gboolean isSeconds,
1558 Action action,
1559 GDestroyNotify destroy,
1560 struct sipe_account_data *sip,
1561 void *payload)
1563 struct scheduled_action *sched_action;
1565 /* Make sure each action only exists once */
1566 sipe_cancel_scheduled_action(sip, name);
1568 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1569 sched_action = g_new0(struct scheduled_action, 1);
1570 sched_action->repetitive = FALSE;
1571 sched_action->name = g_strdup(name);
1572 sched_action->action = action;
1573 sched_action->destroy = destroy;
1574 sched_action->sip = sip;
1575 sched_action->payload = payload;
1576 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1577 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1578 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1579 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1582 void
1583 sipe_schedule_action(const gchar *name,
1584 int timeout,
1585 Action action,
1586 GDestroyNotify destroy,
1587 struct sipe_account_data *sip,
1588 void *payload)
1590 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1594 * Same as sipe_schedule_action() but timeout is in milliseconds.
1596 static void
1597 sipe_schedule_action_msec(const gchar *name,
1598 int timeout,
1599 Action action,
1600 GDestroyNotify destroy,
1601 struct sipe_account_data *sip,
1602 void *payload)
1604 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1607 static void
1608 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1609 time_t calculate_from);
1611 static int
1612 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1614 static const char*
1615 sipe_get_status_by_availability(int avail,
1616 char** activity);
1618 static void
1619 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1620 const char *status_id,
1621 const char *message,
1622 time_t do_not_publish[]);
1624 static void
1625 sipe_apply_calendar_status(struct sipe_account_data *sip,
1626 struct sipe_buddy *sbuddy,
1627 const char *status_id)
1629 time_t cal_avail_since;
1630 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1631 int avail;
1632 gchar *self_uri;
1634 if (!sbuddy) return;
1636 if (cal_status < SIPE_CAL_NO_DATA) {
1637 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_status : %d for %s\n", cal_status, sbuddy->name);
1638 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1641 /* scheduled Cal update call */
1642 if (!status_id) {
1643 status_id = sbuddy->last_non_cal_status_id;
1644 g_free(sbuddy->activity);
1645 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1648 if (!status_id) {
1649 purple_debug_info("sipe", "sipe_apply_calendar_status: status_id is NULL for %s, exiting.\n",
1650 sbuddy->name ? sbuddy->name : "" );
1651 return;
1654 /* adjust to calendar status */
1655 if (cal_status != SIPE_CAL_NO_DATA) {
1656 purple_debug_info("sipe", "sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1658 if (cal_status == SIPE_CAL_BUSY
1659 && cal_avail_since > sbuddy->user_avail_since
1660 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1662 status_id = SIPE_STATUS_ID_BUSY;
1663 g_free(sbuddy->activity);
1664 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1666 avail = sipe_get_availability_by_status(status_id, NULL);
1668 purple_debug_info("sipe", "sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1669 if (cal_avail_since > sbuddy->activity_since) {
1670 if (cal_status == SIPE_CAL_OOF
1671 && avail >= 15000) /* 12000 in 2007 */
1673 g_free(sbuddy->activity);
1674 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1679 /* then set status_id actually */
1680 purple_debug_info("sipe", "sipe_apply_calendar_status: to %s for %s\n", status_id, sbuddy->name ? sbuddy->name : "" );
1681 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1683 /* set our account state to the one in roaming (including calendar info) */
1684 self_uri = sip_uri_self(sip);
1685 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1686 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1687 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1690 purple_debug_info("sipe", "sipe_apply_calendar_status: switch to '%s' for the account\n", sip->status);
1691 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1693 g_free(self_uri);
1696 static void
1697 sipe_got_user_status(struct sipe_account_data *sip,
1698 const char* uri,
1699 const char *status_id)
1701 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1703 if (!sbuddy) return;
1705 /* Check if on 2005 system contact's calendar,
1706 * then set/preserve it.
1708 if (!sip->ocs2007) {
1709 sipe_apply_calendar_status(sip, sbuddy, status_id);
1710 } else {
1711 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1715 static void
1716 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1717 struct sipe_buddy *sbuddy,
1718 struct sipe_account_data *sip)
1720 sipe_apply_calendar_status(sip, sbuddy, NULL);
1724 * Updates contact's status
1725 * based on their calendar information.
1727 * Applicability: 2005 systems
1729 static void
1730 update_calendar_status(struct sipe_account_data *sip)
1732 purple_debug_info("sipe", "update_calendar_status() started.\n");
1733 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1735 /* repeat scheduling */
1736 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1740 * Schedules process of contacts' status update
1741 * based on their calendar information.
1742 * Should be scheduled to the beginning of every
1743 * 15 min interval, like:
1744 * 13:00, 13:15, 13:30, 13:45, etc.
1746 * Applicability: 2005 systems
1748 static void
1749 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1750 time_t calculate_from)
1752 int interval = 15*60;
1753 /** start of the beginning of closest 15 min interval. */
1754 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1756 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1757 asctime(localtime(&calculate_from)));
1758 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1759 asctime(localtime(&next_start)));
1761 sipe_schedule_action("<+2005-cal-status>",
1762 (int)(next_start - time(NULL)),
1763 (Action)update_calendar_status,
1764 NULL,
1765 sip,
1766 NULL);
1770 * Schedules process of self status publish
1771 * based on own calendar information.
1772 * Should be scheduled to the beginning of every
1773 * 15 min interval, like:
1774 * 13:00, 13:15, 13:30, 13:45, etc.
1776 * Applicability: 2007+ systems
1778 static void
1779 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1780 time_t calculate_from)
1782 int interval = 5*60;
1783 /** start of the beginning of closest 5 min interval. */
1784 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1786 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1787 asctime(localtime(&calculate_from)));
1788 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1789 asctime(localtime(&next_start)));
1791 sipe_schedule_action("<+2007-cal-status>",
1792 (int)(next_start - time(NULL)),
1793 (Action)publish_calendar_status_self,
1794 NULL,
1795 sip,
1796 NULL);
1799 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1801 /** Should be g_free()'d
1803 static gchar *
1804 sipe_get_subscription_key(const gchar *event,
1805 const gchar *with)
1807 gchar *key = NULL;
1809 if (is_empty(event)) return NULL;
1811 if (event && sipe_strcase_equal(event, "presence")) {
1812 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1813 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1815 /* @TODO drop participated buddies' just_added flag */
1816 } else if (event) {
1817 /* Subscription is identified by <event> key */
1818 key = g_strdup_printf("<%s>", event);
1821 return key;
1824 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1825 SIPE_UNUSED_PARAMETER struct transaction *trans)
1827 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1828 const gchar *event = sipmsg_find_header(msg, "Event");
1829 gchar *key;
1831 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1832 if (!event) {
1833 struct sipmsg *request_msg = trans->msg;
1834 event = sipmsg_find_header(request_msg, "Event");
1837 key = sipe_get_subscription_key(event, with);
1839 /* 200 OK; 481 Call Leg Does Not Exist */
1840 if (key && (msg->response == 200 || msg->response == 481)) {
1841 if (g_hash_table_lookup(sip->subscriptions, key)) {
1842 g_hash_table_remove(sip->subscriptions, key);
1843 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1847 /* create/store subscription dialog if not yet */
1848 if (msg->response == 200) {
1849 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1850 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1852 if (key) {
1853 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1854 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1856 subscription->dialog.callid = g_strdup(callid);
1857 subscription->dialog.cseq = atoi(cseq);
1858 subscription->dialog.with = g_strdup(with);
1859 subscription->event = g_strdup(event);
1860 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1862 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1865 g_free(cseq);
1868 g_free(key);
1869 g_free(with);
1871 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1873 process_incoming_notify(sip, msg, FALSE, FALSE);
1875 return TRUE;
1878 static void sipe_subscribe_resource_uri(const char *name,
1879 SIPE_UNUSED_PARAMETER gpointer value,
1880 gchar **resources_uri)
1882 gchar *tmp = *resources_uri;
1883 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1884 g_free(tmp);
1887 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1889 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1890 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1891 gchar *tmp = *resources_uri;
1893 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1895 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1896 g_free(tmp);
1900 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1901 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1902 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1903 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1904 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1907 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1909 gchar *key;
1910 gchar *contact = get_contact(sip);
1911 gchar *request;
1912 gchar *content;
1913 gchar *require = "";
1914 gchar *accept = "";
1915 gchar *autoextend = "";
1916 gchar *content_type;
1917 struct sip_dialog *dialog;
1919 if (sip->ocs2007) {
1920 require = ", categoryList";
1921 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1922 content_type = "application/msrtc-adrl-categorylist+xml";
1923 content = g_strdup_printf(
1924 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1925 "<action name=\"subscribe\" id=\"63792024\">\n"
1926 "<adhocList>\n%s</adhocList>\n"
1927 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1928 "<category name=\"calendarData\"/>\n"
1929 "<category name=\"contactCard\"/>\n"
1930 "<category name=\"note\"/>\n"
1931 "<category name=\"state\"/>\n"
1932 "</categoryList>\n"
1933 "</action>\n"
1934 "</batchSub>", sip->username, resources_uri);
1935 } else {
1936 autoextend = "Supported: com.microsoft.autoextend\r\n";
1937 content_type = "application/adrl+xml";
1938 content = g_strdup_printf(
1939 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1940 "<create xmlns=\"\">\n%s</create>\n"
1941 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1943 g_free(resources_uri);
1945 request = g_strdup_printf(
1946 "Require: adhoclist%s\r\n"
1947 "Supported: eventlist\r\n"
1948 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1949 "Supported: ms-piggyback-first-notify\r\n"
1950 "%sSupported: ms-benotify\r\n"
1951 "Proxy-Require: ms-benotify\r\n"
1952 "Event: presence\r\n"
1953 "Content-Type: %s\r\n"
1954 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1955 g_free(contact);
1957 /* subscribe to buddy presence */
1958 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1959 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1960 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1961 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1963 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1965 g_free(content);
1966 g_free(to);
1967 g_free(request);
1968 g_free(key);
1971 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1972 SIPE_UNUSED_PARAMETER void *unused)
1974 gchar *to = sip_uri_self(sip);
1975 gchar *resources_uri = g_strdup("");
1976 if (sip->ocs2007) {
1977 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1978 } else {
1979 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1982 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1985 struct presence_batched_routed {
1986 gchar *host;
1987 GSList *buddies;
1990 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1992 struct presence_batched_routed *data = payload;
1993 GSList *buddies = data->buddies;
1994 while (buddies) {
1995 g_free(buddies->data);
1996 buddies = buddies->next;
1998 g_slist_free(data->buddies);
1999 g_free(data->host);
2000 g_free(payload);
2003 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
2005 struct presence_batched_routed *data = payload;
2006 GSList *buddies = data->buddies;
2007 gchar *resources_uri = g_strdup("");
2008 while (buddies) {
2009 gchar *tmp = resources_uri;
2010 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2011 g_free(tmp);
2012 buddies = buddies->next;
2014 sipe_subscribe_presence_batched_to(sip, resources_uri,
2015 g_strdup(data->host));
2019 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2020 * The user sends a single SUBSCRIBE request to the subscribed contact.
2021 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2025 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2028 gchar *key;
2029 gchar *to = sip_uri((char *)buddy_name);
2030 gchar *tmp = get_contact(sip);
2031 gchar *request;
2032 gchar *content = NULL;
2033 gchar *autoextend = "";
2034 gchar *content_type = "";
2035 struct sip_dialog *dialog;
2036 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2037 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2039 if (sbuddy) sbuddy->just_added = FALSE;
2041 if (sip->ocs2007) {
2042 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2043 } else {
2044 autoextend = "Supported: com.microsoft.autoextend\r\n";
2047 request = g_strdup_printf(
2048 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2049 "Supported: ms-piggyback-first-notify\r\n"
2050 "%s%sSupported: ms-benotify\r\n"
2051 "Proxy-Require: ms-benotify\r\n"
2052 "Event: presence\r\n"
2053 "Contact: %s\r\n", autoextend, content_type, tmp);
2055 if (sip->ocs2007) {
2056 content = g_strdup_printf(
2057 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2058 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2059 "<resource uri=\"%s\"%s\n"
2060 "</adhocList>\n"
2061 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2062 "<category name=\"calendarData\"/>\n"
2063 "<category name=\"contactCard\"/>\n"
2064 "<category name=\"note\"/>\n"
2065 "<category name=\"state\"/>\n"
2066 "</categoryList>\n"
2067 "</action>\n"
2068 "</batchSub>", sip->username, to, context);
2071 g_free(tmp);
2073 /* subscribe to buddy presence */
2074 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2075 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2076 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2077 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2079 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2081 g_free(content);
2082 g_free(to);
2083 g_free(request);
2084 g_free(key);
2087 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2089 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2091 if (!purple_status_is_active(status))
2092 return;
2094 if (account->gc) {
2095 struct sipe_account_data *sip = account->gc->proto_data;
2097 if (sip) {
2098 gchar *action_name;
2099 gchar *tmp;
2100 time_t now = time(NULL);
2101 const char *status_id = purple_status_get_id(status);
2102 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2103 sipe_activity activity = sipe_get_activity_by_token(status_id);
2104 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2106 /* when other point of presence clears note, but we are keeping
2107 * state if OOF note.
2109 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2110 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2111 do_not_publish = FALSE;
2114 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2115 status_id, (int)sip->do_not_publish[activity], (int)now);
2117 sip->do_not_publish[activity] = 0;
2118 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2119 status_id, (int)sip->do_not_publish[activity]);
2121 if (do_not_publish)
2123 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2124 return;
2127 g_free(sip->status);
2128 sip->status = g_strdup(status_id);
2130 /* hack to escape apostrof before comparison */
2131 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2133 /* this will preserve OOF flag as well */
2134 if (!sipe_strequal(tmp, sip->note)) {
2135 sip->is_oof_note = FALSE;
2136 g_free(sip->note);
2137 sip->note = g_strdup(note);
2138 sip->note_since = time(NULL);
2140 g_free(tmp);
2142 /* schedule 2 sec to capture idle flag */
2143 action_name = g_strdup_printf("<%s>", "+set-status");
2144 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2145 g_free(action_name);
2149 static void
2150 sipe_set_idle(PurpleConnection * gc,
2151 int interval)
2153 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2155 if (gc) {
2156 struct sipe_account_data *sip = gc->proto_data;
2158 if (sip) {
2159 sip->idle_switch = time(NULL);
2160 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2165 static void
2166 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2167 SIPE_UNUSED_PARAMETER const char *alias)
2169 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2170 sipe_group_set_user(sip, name);
2173 static void
2174 sipe_group_buddy(PurpleConnection *gc,
2175 const char *who,
2176 const char *old_group_name,
2177 const char *new_group_name)
2179 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2180 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2181 struct sipe_group * old_group = NULL;
2182 struct sipe_group * new_group;
2184 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2185 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2187 if(!buddy) { // buddy not in roaming list
2188 return;
2191 if (old_group_name) {
2192 old_group = sipe_group_find_by_name(sip, old_group_name);
2194 new_group = sipe_group_find_by_name(sip, new_group_name);
2196 if (old_group) {
2197 buddy->groups = g_slist_remove(buddy->groups, old_group);
2198 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2201 if (!new_group) {
2202 sipe_group_create(sip, new_group_name, who);
2203 } else {
2204 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2205 sipe_group_set_user(sip, who);
2209 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2211 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2213 /* libpurple can call us with undefined buddy or group */
2214 if (buddy && group) {
2215 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2217 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2218 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2219 purple_blist_rename_buddy(buddy, buddy_name);
2220 g_free(buddy_name);
2222 /* Prepend sip: if needed */
2223 if (!g_str_has_prefix(buddy->name, "sip:")) {
2224 gchar *buf = sip_uri_from_name(buddy->name);
2225 purple_blist_rename_buddy(buddy, buf);
2226 g_free(buf);
2229 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2230 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2231 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2232 b->name = g_strdup(buddy->name);
2233 b->just_added = TRUE;
2234 g_hash_table_insert(sip->buddies, b->name, b);
2235 sipe_group_buddy(gc, b->name, NULL, group->name);
2236 /* @TODO should go to callback */
2237 sipe_subscribe_presence_single(sip, b->name);
2238 } else {
2239 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2244 static void sipe_free_buddy(struct sipe_buddy *buddy)
2246 #ifndef _WIN32
2248 * We are calling g_hash_table_foreach_steal(). That means that no
2249 * key/value deallocation functions are called. Therefore the glib
2250 * hash code does not touch the key (buddy->name) or value (buddy)
2251 * of the to-be-deleted hash node at all. It follows that we
2253 * - MUST free the memory for the key ourselves and
2254 * - ARE allowed to do it in this function
2256 * Conclusion: glib must be broken on the Windows platform if sipe
2257 * crashes with SIGTRAP when closing. You'll have to live
2258 * with the memory leak until this is fixed.
2260 g_free(buddy->name);
2261 #endif
2262 g_free(buddy->activity);
2263 g_free(buddy->meeting_subject);
2264 g_free(buddy->meeting_location);
2265 g_free(buddy->note);
2267 g_free(buddy->cal_start_time);
2268 g_free(buddy->cal_free_busy_base64);
2269 g_free(buddy->cal_free_busy);
2270 g_free(buddy->last_non_cal_activity);
2272 sipe_cal_free_working_hours(buddy->cal_working_hours);
2274 g_free(buddy->device_name);
2275 g_slist_free(buddy->groups);
2276 g_free(buddy);
2280 * Unassociates buddy from group first.
2281 * Then see if no groups left, removes buddy completely.
2282 * Otherwise updates buddy groups on server.
2284 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2286 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2287 struct sipe_buddy *b;
2288 struct sipe_group *g = NULL;
2290 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2291 if (!buddy) return;
2293 b = g_hash_table_lookup(sip->buddies, buddy->name);
2294 if (!b) return;
2296 if (group) {
2297 g = sipe_group_find_by_name(sip, group->name);
2300 if (g) {
2301 b->groups = g_slist_remove(b->groups, g);
2302 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2305 if (g_slist_length(b->groups) < 1) {
2306 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2307 sipe_cancel_scheduled_action(sip, action_name);
2308 g_free(action_name);
2310 g_hash_table_remove(sip->buddies, buddy->name);
2312 if (b->name) {
2313 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2314 send_soap_request(sip, body);
2315 g_free(body);
2318 sipe_free_buddy(b);
2319 } else {
2320 //updates groups on server
2321 sipe_group_set_user(sip, b->name);
2326 static void
2327 sipe_rename_group(PurpleConnection *gc,
2328 const char *old_name,
2329 PurpleGroup *group,
2330 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2332 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2333 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2334 if (s_group) {
2335 sipe_group_rename(sip, s_group, group->name);
2336 } else {
2337 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2341 static void
2342 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2344 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2345 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2346 if (s_group) {
2347 gchar *body;
2348 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2349 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2350 send_soap_request(sip, body);
2351 g_free(body);
2353 sip->groups = g_slist_remove(sip->groups, s_group);
2354 g_free(s_group->name);
2355 g_free(s_group);
2356 } else {
2357 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2361 /** All statuses need message attribute to pass Note */
2362 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2364 PurpleStatusType *type;
2365 GList *types = NULL;
2367 /* Macros to reduce code repetition.
2368 Translators: noun */
2369 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2370 prim, id, name, \
2371 TRUE, user, FALSE, \
2372 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2373 NULL); \
2374 types = g_list_append(types, type);
2376 /* Online */
2377 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2378 NULL,
2379 NULL,
2380 TRUE);
2382 /* Busy */
2383 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2384 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2385 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2386 TRUE);
2388 /* Do Not Disturb */
2389 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2390 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2391 NULL,
2392 TRUE);
2394 /* Away */
2395 /* Goes first in the list as
2396 * purple picks the first status with the AWAY type
2397 * for idle.
2399 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2400 NULL,
2401 NULL,
2402 TRUE);
2404 /* Be Right Back */
2405 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2406 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2407 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2408 TRUE);
2410 /* Appear Offline */
2411 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2412 NULL,
2413 NULL,
2414 TRUE);
2416 /* Offline */
2417 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2418 NULL,
2419 NULL,
2420 TRUE);
2421 types = g_list_append(types, type);
2423 return types;
2427 * A callback for g_hash_table_foreach
2429 static void
2430 sipe_buddy_subscribe_cb(char *buddy_name,
2431 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2432 struct sipe_account_data *sip)
2434 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2435 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2436 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2437 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2439 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2440 g_free(action_name);
2444 * Removes entries from purple buddy list
2445 * that does not correspond ones in the roaming contact list.
2447 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2448 GSList *buddies = purple_find_buddies(sip->account, NULL);
2449 GSList *entry = buddies;
2450 struct sipe_buddy *buddy;
2451 PurpleBuddy *b;
2452 PurpleGroup *g;
2454 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2455 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2456 while (entry) {
2457 b = entry->data;
2458 g = purple_buddy_get_group(b);
2459 buddy = g_hash_table_lookup(sip->buddies, b->name);
2460 if(buddy) {
2461 gboolean in_sipe_groups = FALSE;
2462 GSList *entry2 = buddy->groups;
2463 while (entry2) {
2464 struct sipe_group *group = entry2->data;
2465 if (sipe_strequal(group->name, g->name)) {
2466 in_sipe_groups = TRUE;
2467 break;
2469 entry2 = entry2->next;
2471 if(!in_sipe_groups) {
2472 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2473 purple_blist_remove_buddy(b);
2475 } else {
2476 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2477 purple_blist_remove_buddy(b);
2479 entry = entry->next;
2481 g_slist_free(buddies);
2484 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2486 int len = msg->bodylen;
2488 const gchar *tmp = sipmsg_find_header(msg, "Event");
2489 xmlnode *item;
2490 xmlnode *isc;
2491 const gchar *contacts_delta;
2492 xmlnode *group_node;
2493 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2494 return FALSE;
2497 /* Convert the contact from XML to Purple Buddies */
2498 isc = xmlnode_from_str(msg->body, len);
2499 if (!isc) {
2500 return FALSE;
2503 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2504 if (contacts_delta) {
2505 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2508 if (sipe_strequal(isc->name, "contactList")) {
2510 /* Parse groups */
2511 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2512 struct sipe_group * group = g_new0(struct sipe_group, 1);
2513 const char *name = xmlnode_get_attrib(group_node, "name");
2515 if (g_str_has_prefix(name, "~")) {
2516 name = _("Other Contacts");
2518 group->name = g_strdup(name);
2519 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2521 sipe_group_add(sip, group);
2524 // Make sure we have at least one group
2525 if (g_slist_length(sip->groups) == 0) {
2526 struct sipe_group * group = g_new0(struct sipe_group, 1);
2527 PurpleGroup *purple_group;
2528 group->name = g_strdup(_("Other Contacts"));
2529 group->id = 1;
2530 purple_group = purple_group_new(group->name);
2531 purple_blist_add_group(purple_group, NULL);
2532 sip->groups = g_slist_append(sip->groups, group);
2535 /* Parse contacts */
2536 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2537 const gchar *uri = xmlnode_get_attrib(item, "uri");
2538 const gchar *name = xmlnode_get_attrib(item, "name");
2539 gchar *buddy_name;
2540 struct sipe_buddy *buddy = NULL;
2541 gchar *tmp;
2542 gchar **item_groups;
2543 int i = 0;
2545 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2546 tmp = sip_uri_from_name(uri);
2547 buddy_name = g_ascii_strdown(tmp, -1);
2548 g_free(tmp);
2550 /* assign to group Other Contacts if nothing else received */
2551 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2552 if(is_empty(tmp)) {
2553 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2554 g_free(tmp);
2555 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2557 item_groups = g_strsplit(tmp, " ", 0);
2558 g_free(tmp);
2560 while (item_groups[i]) {
2561 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2563 // If couldn't find the right group for this contact, just put them in the first group we have
2564 if (group == NULL && g_slist_length(sip->groups) > 0) {
2565 group = sip->groups->data;
2568 if (group != NULL) {
2569 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2570 if (!b){
2571 b = purple_buddy_new(sip->account, buddy_name, uri);
2572 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2574 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2577 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2578 if (name != NULL && strlen(name) != 0) {
2579 purple_blist_alias_buddy(b, name);
2581 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2585 if (!buddy) {
2586 buddy = g_new0(struct sipe_buddy, 1);
2587 buddy->name = g_strdup(b->name);
2588 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2591 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2593 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2594 } else {
2595 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2596 name);
2599 i++;
2600 } // while, contact groups
2601 g_strfreev(item_groups);
2602 g_free(buddy_name);
2604 } // for, contacts
2606 sipe_cleanup_local_blist(sip);
2608 /* Add self-contact if not there yet. 2005 systems. */
2609 /* This will resemble subscription to roaming_self in 2007 systems */
2610 if (!sip->ocs2007) {
2611 gchar *self_uri = sip_uri_self(sip);
2612 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2614 if (!buddy) {
2615 buddy = g_new0(struct sipe_buddy, 1);
2616 buddy->name = g_strdup(self_uri);
2617 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2619 g_free(self_uri);
2622 xmlnode_free(isc);
2624 /* subscribe to buddies */
2625 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2626 if (sip->batched_support) {
2627 sipe_subscribe_presence_batched(sip, NULL);
2628 } else {
2629 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2631 sip->subscribed_buddies = TRUE;
2633 /* for 2005 systems schedule contacts' status update
2634 * based on their calendar information
2636 if (!sip->ocs2007) {
2637 sipe_sched_calendar_status_update(sip, time(NULL));
2640 return 0;
2644 * Subscribe roaming contacts
2646 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2648 gchar *to = sip_uri_self(sip);
2649 gchar *tmp = get_contact(sip);
2650 gchar *hdr = g_strdup_printf(
2651 "Event: vnd-microsoft-roaming-contacts\r\n"
2652 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2653 "Supported: com.microsoft.autoextend\r\n"
2654 "Supported: ms-benotify\r\n"
2655 "Proxy-Require: ms-benotify\r\n"
2656 "Supported: ms-piggyback-first-notify\r\n"
2657 "Contact: %s\r\n", tmp);
2658 g_free(tmp);
2660 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2661 g_free(to);
2662 g_free(hdr);
2665 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2666 SIPE_UNUSED_PARAMETER void *unused)
2668 gchar *key;
2669 struct sip_dialog *dialog;
2670 gchar *to = sip_uri_self(sip);
2671 gchar *tmp = get_contact(sip);
2672 gchar *hdr = g_strdup_printf(
2673 "Event: presence.wpending\r\n"
2674 "Accept: text/xml+msrtc.wpending\r\n"
2675 "Supported: com.microsoft.autoextend\r\n"
2676 "Supported: ms-benotify\r\n"
2677 "Proxy-Require: ms-benotify\r\n"
2678 "Supported: ms-piggyback-first-notify\r\n"
2679 "Contact: %s\r\n", tmp);
2680 g_free(tmp);
2682 /* Subscription is identified by <event> key */
2683 key = g_strdup_printf("<%s>", "presence.wpending");
2684 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2685 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2687 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2689 g_free(to);
2690 g_free(hdr);
2691 g_free(key);
2695 * Fires on deregistration event initiated by server.
2696 * [MS-SIPREGE] SIP extension.
2699 // 2007 Example
2701 // Content-Type: text/registration-event
2702 // subscription-state: terminated;expires=0
2703 // ms-diagnostics-public: 4141;reason="User disabled"
2705 // deregistered;event=rejected
2707 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2709 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2710 gchar *event = NULL;
2711 gchar *reason = NULL;
2712 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2713 gchar *warning;
2715 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2716 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2718 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2719 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2720 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2721 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2722 } else {
2723 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2724 return;
2727 if (diagnostics != NULL) {
2728 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2729 } else { // for LCS2005
2730 int error_id = 0;
2731 if (event && sipe_strcase_equal(event, "unregistered")) {
2732 error_id = 4140; // [MS-SIPREGE]
2733 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2734 reason = g_strdup(_("you are already signed in at another location"));
2735 } else if (event && sipe_strcase_equal(event, "rejected")) {
2736 error_id = 4141;
2737 reason = g_strdup(_("user disabled")); // [MS-OCER]
2738 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2739 error_id = 4142;
2740 reason = g_strdup(_("user moved")); // [MS-OCER]
2743 g_free(event);
2744 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2745 g_free(reason);
2747 sip->gc->wants_to_die = TRUE;
2748 purple_connection_error(sip->gc, warning);
2749 g_free(warning);
2753 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2755 xmlnode *xn_provision_group_list;
2756 xmlnode *node;
2758 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2760 /* provisionGroup */
2761 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2762 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2763 g_free(sip->focus_factory_uri);
2764 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2765 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2766 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2767 break;
2770 xmlnode_free(xn_provision_group_list);
2773 /** for 2005 system */
2774 static void
2775 sipe_process_provisioning(struct sipe_account_data *sip,
2776 struct sipmsg *msg)
2778 xmlnode *xn_provision;
2779 xmlnode *node;
2781 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2782 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2783 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2784 if ((node = xmlnode_get_child(node, "line"))) {
2785 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2786 const gchar *server = xmlnode_get_attrib(node, "server");
2787 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2788 sip_csta_open(sip, line_uri, server);
2791 xmlnode_free(xn_provision);
2794 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2796 const gchar *contacts_delta;
2797 xmlnode *xml;
2799 xml = xmlnode_from_str(msg->body, msg->bodylen);
2800 if (!xml)
2802 return;
2805 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2806 if (contacts_delta)
2808 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2811 xmlnode_free(xml);
2814 static void
2815 free_container(struct sipe_container *container)
2817 GSList *entry;
2819 if (!container) return;
2821 entry = container->members;
2822 while (entry) {
2823 void *data = entry->data;
2824 entry = g_slist_remove(entry, data);
2825 g_free(data);
2827 g_free(container);
2831 * Finds locally stored MS-PRES container member
2833 static struct sipe_container_member *
2834 sipe_find_container_member(struct sipe_container *container,
2835 const gchar *type,
2836 const gchar *value)
2838 struct sipe_container_member *member;
2839 GSList *entry;
2841 if (container == NULL || type == NULL) {
2842 return NULL;
2845 entry = container->members;
2846 while (entry) {
2847 member = entry->data;
2848 if (!g_strcasecmp(member->type, type)
2849 && ((!member->value && !value)
2850 || (value && member->value && !g_strcasecmp(member->value, value)))
2852 return member;
2854 entry = entry->next;
2856 return NULL;
2860 * Finds locally stored MS-PRES container by id
2862 static struct sipe_container *
2863 sipe_find_container(struct sipe_account_data *sip,
2864 guint id)
2866 struct sipe_container *container;
2867 GSList *entry;
2869 if (sip == NULL) {
2870 return NULL;
2873 entry = sip->containers;
2874 while (entry) {
2875 container = entry->data;
2876 if (id == container->id) {
2877 return container;
2879 entry = entry->next;
2881 return NULL;
2885 * Access Levels
2886 * 32000 - Blocked
2887 * 400 - Personal
2888 * 300 - Team
2889 * 200 - Company
2890 * 100 - Public
2892 static int
2893 sipe_find_access_level(struct sipe_account_data *sip,
2894 const gchar *type,
2895 const gchar *value)
2897 guint containers[] = {32000, 400, 300, 200, 100};
2898 int i = 0;
2900 for (i = 0; i < 5; i++) {
2901 struct sipe_container_member *member;
2902 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2903 if (!container) continue;
2905 member = sipe_find_container_member(container, type, value);
2906 if (member) {
2907 return containers[i];
2911 return -1;
2914 static void
2915 sipe_send_set_container_members(struct sipe_account_data *sip,
2916 guint container_id,
2917 guint container_version,
2918 const gchar* action,
2919 const gchar* type,
2920 const gchar* value)
2922 gchar *self = sip_uri_self(sip);
2923 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2924 gchar *contact;
2925 gchar *hdr;
2926 gchar *body = g_strdup_printf(
2927 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2928 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2929 "</setContainerMembers>",
2930 container_id,
2931 container_version,
2932 action,
2933 type,
2934 value_str);
2935 g_free(value_str);
2937 contact = get_contact(sip);
2938 hdr = g_strdup_printf("Contact: %s\r\n"
2939 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2940 g_free(contact);
2942 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2944 g_free(hdr);
2945 g_free(body);
2946 g_free(self);
2949 static void
2950 free_publication(struct sipe_publication *publication)
2952 g_free(publication->category);
2953 g_free(publication->cal_event_hash);
2954 g_free(publication->note);
2956 g_free(publication->working_hours_xml_str);
2957 g_free(publication->fb_start_str);
2958 g_free(publication->free_busy_base64);
2960 g_free(publication);
2963 /* key is <category><instance><container> */
2964 static gboolean
2965 sipe_is_our_publication(struct sipe_account_data *sip,
2966 const gchar *key)
2968 GSList *entry;
2970 /* filling keys for our publications if not yet cached */
2971 if (!sip->our_publication_keys) {
2972 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2973 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2974 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2975 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2976 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2977 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2978 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2980 purple_debug_info("sipe", "* Our Publication Instances *\n");
2981 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2982 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2983 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2984 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2985 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2986 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2987 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2988 purple_debug_info("sipe", "\tNote : %u\n", 0);
2989 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2991 /* device */
2992 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2993 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2995 /* state:machineState */
2996 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2997 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2998 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2999 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3001 /* state:userState */
3002 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3003 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3004 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3005 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3007 /* state:calendarState */
3008 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3009 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3010 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3011 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3013 /* state:calendarState OOF */
3014 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3015 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3016 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3017 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3019 /* note */
3020 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3021 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3022 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3023 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3024 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3025 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3027 /* note OOF */
3028 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3029 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3030 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3031 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3032 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3033 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3035 /* calendarData:WorkingHours */
3036 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3037 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3038 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3039 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3040 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3041 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3042 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3043 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3044 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3045 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3046 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3047 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3049 /* calendarData:FreeBusy */
3050 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3051 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3052 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3053 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3054 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3055 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3056 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3057 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3058 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3059 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3060 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3061 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3063 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
3064 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3067 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3069 entry = sip->our_publication_keys;
3070 while (entry) {
3071 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3072 if (sipe_strequal(entry->data, key)) {
3073 return TRUE;
3075 entry = entry->next;
3077 return FALSE;
3080 /** Property names to store in blist.xml */
3081 #define ALIAS_PROP "alias"
3082 #define EMAIL_PROP "email"
3083 #define PHONE_PROP "phone"
3084 #define PHONE_DISPLAY_PROP "phone-display"
3085 #define PHONE_MOBILE_PROP "phone-mobile"
3086 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3087 #define PHONE_HOME_PROP "phone-home"
3088 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3089 #define PHONE_OTHER_PROP "phone-other"
3090 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3091 #define PHONE_CUSTOM1_PROP "phone-custom1"
3092 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3093 #define SITE_PROP "site"
3094 #define COMPANY_PROP "company"
3095 #define DEPARTMENT_PROP "department"
3096 #define TITLE_PROP "title"
3097 #define OFFICE_PROP "office"
3098 /** implies work address */
3099 #define ADDRESS_STREET_PROP "address-street"
3100 #define ADDRESS_CITY_PROP "address-city"
3101 #define ADDRESS_STATE_PROP "address-state"
3102 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3103 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3106 * Tries to figure out user first and last name
3107 * based on Display Name and email properties.
3109 * Allocates memory - must be g_free()'d
3111 * Examples to parse:
3112 * First Last
3113 * First Last - Company Name
3114 * Last, First
3115 * Last, First M.
3116 * Last, First (C)(STP) (Company)
3117 * first.last@company.com (preprocessed as "first last")
3118 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3120 * Unusable examples:
3121 * user@company.com (preprocessed as "user")
3122 * first.m.last@company.com (preprocessed as "first m last")
3123 * user.company.com@reuters.net (preprocessed as "user company com")
3125 static void
3126 sipe_get_first_last_names(struct sipe_account_data *sip,
3127 const char *uri,
3128 char **first_name,
3129 char **last_name)
3131 PurpleBuddy *p_buddy;
3132 char *display_name;
3133 const char *email;
3134 const char *first, *last;
3135 char *tmp;
3136 char **parts;
3137 gboolean has_comma = FALSE;
3139 if (!sip || !uri) return;
3141 p_buddy = purple_find_buddy(sip->account, uri);
3143 if (!p_buddy) return;
3145 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3146 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3148 if (!display_name && !email) return;
3150 /* if no display name, make "first last anything_else" out of email */
3151 if (email && !display_name) {
3152 display_name = g_strndup(email, strstr(email, "@") - email);
3153 display_name = purple_strreplace((tmp = display_name), ".", " ");
3154 g_free(tmp);
3157 if (display_name) {
3158 has_comma = (strstr(display_name, ",") != NULL);
3159 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3160 g_free(tmp);
3161 display_name = purple_strreplace((tmp = display_name), ",", " ");
3162 g_free(tmp);
3165 parts = g_strsplit(display_name, " ", 0);
3167 if (!parts[0] || !parts[1]) {
3168 g_free(display_name);
3169 g_strfreev(parts);
3170 return;
3173 if (has_comma) {
3174 last = parts[0];
3175 first = parts[1];
3176 } else {
3177 first = parts[0];
3178 last = parts[1];
3181 if (first_name) {
3182 *first_name = g_strstrip(g_strdup(first));
3185 if (last_name) {
3186 *last_name = g_strstrip(g_strdup(last));
3189 g_free(display_name);
3190 g_strfreev(parts);
3194 * Update user information
3196 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3197 * @param property_name
3198 * @param property_value may be modified to strip white space
3200 static void
3201 sipe_update_user_info(struct sipe_account_data *sip,
3202 const char *uri,
3203 const char *property_name,
3204 char *property_value)
3206 GSList *buddies, *entry;
3208 if (!property_name || strlen(property_name) == 0) return;
3210 if (property_value)
3211 property_value = g_strstrip(property_value);
3213 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3214 while (entry) {
3215 const char *prop_str;
3216 const char *server_alias;
3217 PurpleBuddy *p_buddy = entry->data;
3219 /* for Display Name */
3220 if (sipe_strequal(property_name, ALIAS_PROP)) {
3221 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3222 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3223 purple_blist_alias_buddy(p_buddy, property_value);
3226 server_alias = purple_buddy_get_server_alias(p_buddy);
3227 if (!is_empty(property_value) &&
3228 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3230 purple_blist_server_alias_buddy(p_buddy, property_value);
3233 /* for other properties */
3234 else {
3235 if (!is_empty(property_value)) {
3236 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3237 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3238 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3243 entry = entry->next;
3245 g_slist_free(buddies);
3249 * Update user phone
3250 * Suitable for both 2005 and 2007 systems.
3252 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3253 * @param phone_type
3254 * @param phone may be modified to strip white space
3255 * @param phone_display_string may be modified to strip white space
3257 static void
3258 sipe_update_user_phone(struct sipe_account_data *sip,
3259 const char *uri,
3260 const gchar *phone_type,
3261 gchar *phone,
3262 gchar *phone_display_string)
3264 const char *phone_node = PHONE_PROP; /* work phone by default */
3265 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3267 if(!phone || strlen(phone) == 0) return;
3269 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3270 phone_node = PHONE_MOBILE_PROP;
3271 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3272 } else if (sipe_strequal(phone_type, "home")) {
3273 phone_node = PHONE_HOME_PROP;
3274 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3275 } else if (sipe_strequal(phone_type, "other")) {
3276 phone_node = PHONE_OTHER_PROP;
3277 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3278 } else if (sipe_strequal(phone_type, "custom1")) {
3279 phone_node = PHONE_CUSTOM1_PROP;
3280 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3283 sipe_update_user_info(sip, uri, phone_node, phone);
3284 if (phone_display_string) {
3285 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3289 static void
3290 sipe_update_calendar(struct sipe_account_data *sip)
3292 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3294 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3296 if (sipe_strequal(calendar, "EXCH")) {
3297 sipe_ews_update_calendar(sip);
3300 /* schedule repeat */
3301 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3303 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3307 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3308 * by using standard Purple's means of signals and saved statuses.
3310 * Thus all UI elements get updated: Status Button with Note, docklet.
3311 * This is ablolutely important as both our status and note can come
3312 * inbound (roaming) or be updated programmatically (e.g. based on our
3313 * calendar data).
3315 static void
3316 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3317 const char *status_id,
3318 const char *message,
3319 time_t do_not_publish[])
3321 PurpleStatus *status = purple_account_get_active_status(account);
3322 gboolean changed = TRUE;
3324 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3325 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3327 changed = FALSE;
3330 if (purple_savedstatus_is_idleaway()) {
3331 changed = FALSE;
3334 if (changed) {
3335 PurpleSavedStatus *saved_status;
3336 const PurpleStatusType *acct_status_type =
3337 purple_status_type_find_with_id(account->status_types, status_id);
3338 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3339 sipe_activity activity = sipe_get_activity_by_token(status_id);
3341 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3342 if (saved_status) {
3343 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3346 /* If this type+message is unique then create a new transient saved status
3347 * Ref: gtkstatusbox.c
3349 if (!saved_status) {
3350 GList *tmp;
3351 GList *active_accts = purple_accounts_get_all_active();
3353 saved_status = purple_savedstatus_new(NULL, primitive);
3354 purple_savedstatus_set_message(saved_status, message);
3356 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3357 purple_savedstatus_set_substatus(saved_status,
3358 (PurpleAccount *)tmp->data, acct_status_type, message);
3360 g_list_free(active_accts);
3363 do_not_publish[activity] = time(NULL);
3364 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3365 status_id, (int)do_not_publish[activity]);
3367 /* Set the status for each account */
3368 purple_savedstatus_activate(saved_status);
3372 struct hash_table_delete_payload {
3373 GHashTable *hash_table;
3374 guint container;
3377 static void
3378 sipe_remove_category_container_publications_cb(const char *name,
3379 struct sipe_publication *publication,
3380 struct hash_table_delete_payload *payload)
3382 if (publication->container == payload->container) {
3383 g_hash_table_remove(payload->hash_table, name);
3386 static void
3387 sipe_remove_category_container_publications(GHashTable *our_publications,
3388 const char *category,
3389 guint container)
3391 struct hash_table_delete_payload payload;
3392 payload.hash_table = g_hash_table_lookup(our_publications, category);
3394 if (!payload.hash_table) return;
3396 payload.container = container;
3397 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3400 static void
3401 send_publish_category_initial(struct sipe_account_data *sip);
3404 * When we receive some self (BE) NOTIFY with a new subscriber
3405 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3408 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3410 gchar *contact;
3411 gchar *to;
3412 xmlnode *xml;
3413 xmlnode *node;
3414 xmlnode *node2;
3415 char *display_name = NULL;
3416 char *uri;
3417 GSList *category_names = NULL;
3418 int aggreg_avail = 0;
3419 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3420 gboolean do_update_status = FALSE;
3421 gboolean has_note_cleaned = FALSE;
3423 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3425 xml = xmlnode_from_str(msg->body, msg->bodylen);
3426 if (!xml) return;
3428 contact = get_contact(sip);
3429 to = sip_uri_self(sip);
3432 /* categories */
3433 /* set list of categories participating in this XML */
3434 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3435 const gchar *name = xmlnode_get_attrib(node, "name");
3436 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3438 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3439 category_names ? (int) g_slist_length(category_names) : -1);
3440 /* drop category information */
3441 if (category_names) {
3442 GSList *entry = category_names;
3443 while (entry) {
3444 GHashTable *cat_publications;
3445 const gchar *category = entry->data;
3446 entry = entry->next;
3447 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3448 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3449 if (cat_publications) {
3450 g_hash_table_remove(sip->our_publications, category);
3451 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3455 g_slist_free(category_names);
3456 /* filling our categories reflected in roaming data */
3457 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3458 const char *tmp;
3459 const gchar *name = xmlnode_get_attrib(node, "name");
3460 guint container = xmlnode_get_int_attrib(node, "container", -1);
3461 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3462 guint version = xmlnode_get_int_attrib(node, "version", 0);
3463 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3464 sipe_utils_str_to_time(tmp) : 0;
3465 gchar *key;
3466 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3468 /* Ex. clear note: <category name="note"/> */
3469 if (container == (guint)-1) {
3470 g_free(sip->note);
3471 sip->note = NULL;
3472 do_update_status = TRUE;
3473 continue;
3476 /* Ex. clear note: <category name="note" container="200"/> */
3477 if (instance == (guint)-1) {
3478 if (container == 200) {
3479 g_free(sip->note);
3480 sip->note = NULL;
3481 do_update_status = TRUE;
3483 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3484 sipe_remove_category_container_publications(
3485 sip->our_publications, name, container);
3486 continue;
3489 /* key is <category><instance><container> */
3490 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3491 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3493 /* capture all userState publication for later clean up if required */
3494 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3495 xmlnode *xn_state = xmlnode_get_child(node, "state");
3497 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3498 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3499 publication->category = g_strdup(name);
3500 publication->instance = instance;
3501 publication->container = container;
3502 publication->version = version;
3504 if (!sip->user_state_publications) {
3505 sip->user_state_publications = g_hash_table_new_full(
3506 g_str_hash, g_str_equal,
3507 g_free, (GDestroyNotify)free_publication);
3509 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3510 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3511 key, version);
3515 if (sipe_is_our_publication(sip, key)) {
3516 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3518 publication->category = g_strdup(name);
3519 publication->instance = instance;
3520 publication->container = container;
3521 publication->version = version;
3523 /* filling publication->availability */
3524 if (sipe_strequal(name, "state")) {
3525 xmlnode *xn_state = xmlnode_get_child(node, "state");
3526 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3528 if (xn_avail) {
3529 gchar *avail_str = xmlnode_get_data(xn_avail);
3530 if (avail_str) {
3531 publication->availability = atoi(avail_str);
3533 g_free(avail_str);
3535 /* for calendarState */
3536 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3537 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3538 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3540 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3541 if (xn_activity) {
3542 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3543 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3545 event->is_meeting = TRUE;
3548 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3549 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3551 publication->cal_event_hash = sipe_cal_event_hash(event);
3552 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3553 publication->cal_event_hash);
3554 sipe_cal_event_free(event);
3557 /* filling publication->note */
3558 if (sipe_strequal(name, "note")) {
3559 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3561 if (!has_note_cleaned) {
3562 has_note_cleaned = TRUE;
3564 g_free(sip->note);
3565 sip->note = NULL;
3566 sip->note_since = publish_time;
3568 do_update_status = TRUE;
3571 g_free(publication->note);
3572 publication->note = NULL;
3573 if (xn_body) {
3574 char *tmp;
3576 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3577 g_free(tmp);
3578 if (publish_time >= sip->note_since) {
3579 g_free(sip->note);
3580 sip->note = g_strdup(publication->note);
3581 sip->note_since = publish_time;
3582 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3584 do_update_status = TRUE;
3589 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3590 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3591 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3592 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3593 if (xn_free_busy) {
3594 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3595 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3597 if (xn_working_hours) {
3598 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3602 if (!cat_publications) {
3603 cat_publications = g_hash_table_new_full(
3604 g_str_hash, g_str_equal,
3605 g_free, (GDestroyNotify)free_publication);
3606 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3607 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3609 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3610 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3612 g_free(key);
3614 /* aggregateState (not an our publication) from 2-nd container */
3615 if (sipe_strequal(name, "state") && container == 2) {
3616 xmlnode *xn_state = xmlnode_get_child(node, "state");
3618 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3619 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3620 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3622 if (xn_avail) {
3623 gchar *avail_str = xmlnode_get_data(xn_avail);
3624 if (avail_str) {
3625 aggreg_avail = atoi(avail_str);
3627 g_free(avail_str);
3630 if (xn_activity) {
3631 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3633 aggreg_activity = sipe_get_activity_by_token(activity_token);
3636 do_update_status = TRUE;
3640 /* userProperties published by server from AD */
3641 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3642 xmlnode *line;
3643 /* line, for Remote Call Control (RCC) */
3644 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3645 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3646 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3647 gchar *line_uri;
3649 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3651 line_uri = xmlnode_get_data(line);
3652 if (line_uri) {
3653 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3654 sip_csta_open(sip, line_uri, line_server);
3656 g_free(line_uri);
3658 break;
3662 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3663 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3665 /* containers */
3666 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3667 guint id = xmlnode_get_int_attrib(node, "id", 0);
3668 struct sipe_container *container = sipe_find_container(sip, id);
3670 if (container) {
3671 sip->containers = g_slist_remove(sip->containers, container);
3672 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3673 free_container(container);
3675 container = g_new0(struct sipe_container, 1);
3676 container->id = id;
3677 container->version = xmlnode_get_int_attrib(node, "version", 0);
3678 sip->containers = g_slist_append(sip->containers, container);
3679 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3681 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3682 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3683 member->type = xmlnode_get_attrib(node2, "type");
3684 member->value = xmlnode_get_attrib(node2, "value");
3685 container->members = g_slist_append(container->members, member);
3686 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3687 member->type, member->value ? member->value : "");
3691 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3692 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3693 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3694 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3695 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3696 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3697 /* initial set-up to let counterparties see your status */
3698 if (sameEnterpriseAL < 0) {
3699 struct sipe_container *container = sipe_find_container(sip, 200);
3700 guint version = container ? container->version : 0;
3701 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3703 if (federatedAL < 0) {
3704 struct sipe_container *container = sipe_find_container(sip, 100);
3705 guint version = container ? container->version : 0;
3706 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3708 sip->access_level_set = TRUE;
3711 /* subscribers */
3712 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3713 const char *user;
3714 const char *acknowledged;
3715 gchar *hdr;
3716 gchar *body;
3718 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3719 if (!user) continue;
3720 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3721 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3722 uri = sip_uri_from_name(user);
3724 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3726 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3727 if(sipe_strcase_equal(acknowledged,"false")){
3728 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3729 if (!purple_find_buddy(sip->account, uri)) {
3730 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3733 hdr = g_strdup_printf(
3734 "Contact: %s\r\n"
3735 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3737 body = g_strdup_printf(
3738 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3739 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3740 "</setSubscribers>", user);
3742 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3743 g_free(body);
3744 g_free(hdr);
3746 g_free(display_name);
3747 g_free(uri);
3750 g_free(contact);
3751 xmlnode_free(xml);
3753 /* Publish initial state if not yet.
3754 * Assuming this happens on initial responce to subscription to roaming-self
3755 * so we've already updated our roaming data in full.
3756 * Only for 2007+
3758 if (!sip->initial_state_published) {
3759 send_publish_category_initial(sip);
3760 sip->initial_state_published = TRUE;
3761 /* dalayed run */
3762 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3763 do_update_status = FALSE;
3764 } else if (aggreg_avail) {
3766 g_free(sip->status);
3767 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3768 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3769 } else {
3770 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3774 if (do_update_status) {
3775 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3776 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3779 g_free(to);
3782 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3784 gchar *to = sip_uri_self(sip);
3785 gchar *tmp = get_contact(sip);
3786 gchar *hdr = g_strdup_printf(
3787 "Event: vnd-microsoft-roaming-ACL\r\n"
3788 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3789 "Supported: com.microsoft.autoextend\r\n"
3790 "Supported: ms-benotify\r\n"
3791 "Proxy-Require: ms-benotify\r\n"
3792 "Supported: ms-piggyback-first-notify\r\n"
3793 "Contact: %s\r\n", tmp);
3794 g_free(tmp);
3796 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3797 g_free(to);
3798 g_free(hdr);
3802 * To request for presence information about the user, access level settings that have already been configured by the user
3803 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3804 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3807 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3809 gchar *to = sip_uri_self(sip);
3810 gchar *tmp = get_contact(sip);
3811 gchar *hdr = g_strdup_printf(
3812 "Event: vnd-microsoft-roaming-self\r\n"
3813 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3814 "Supported: ms-benotify\r\n"
3815 "Proxy-Require: ms-benotify\r\n"
3816 "Supported: ms-piggyback-first-notify\r\n"
3817 "Contact: %s\r\n"
3818 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3820 gchar *body=g_strdup(
3821 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3822 "<roaming type=\"categories\"/>"
3823 "<roaming type=\"containers\"/>"
3824 "<roaming type=\"subscribers\"/></roamingList>");
3826 g_free(tmp);
3827 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3828 g_free(body);
3829 g_free(to);
3830 g_free(hdr);
3834 * For 2005 version
3836 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3838 gchar *to = sip_uri_self(sip);
3839 gchar *tmp = get_contact(sip);
3840 gchar *hdr = g_strdup_printf(
3841 "Event: vnd-microsoft-provisioning\r\n"
3842 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3843 "Supported: com.microsoft.autoextend\r\n"
3844 "Supported: ms-benotify\r\n"
3845 "Proxy-Require: ms-benotify\r\n"
3846 "Supported: ms-piggyback-first-notify\r\n"
3847 "Expires: 0\r\n"
3848 "Contact: %s\r\n", tmp);
3850 g_free(tmp);
3851 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3852 g_free(to);
3853 g_free(hdr);
3856 /** Subscription for provisioning information to help with initial
3857 * configuration. This subscription is a one-time query (denoted by the Expires header,
3858 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3859 * configuration, meeting policies, and policy settings that Communicator must enforce.
3860 * TODO: for what we need this information.
3863 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3865 gchar *to = sip_uri_self(sip);
3866 gchar *tmp = get_contact(sip);
3867 gchar *hdr = g_strdup_printf(
3868 "Event: vnd-microsoft-provisioning-v2\r\n"
3869 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3870 "Supported: com.microsoft.autoextend\r\n"
3871 "Supported: ms-benotify\r\n"
3872 "Proxy-Require: ms-benotify\r\n"
3873 "Supported: ms-piggyback-first-notify\r\n"
3874 "Expires: 0\r\n"
3875 "Contact: %s\r\n"
3876 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3877 gchar *body = g_strdup(
3878 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3879 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3880 "<provisioningGroup name=\"ucPolicy\"/>"
3881 "</provisioningGroupList>");
3883 g_free(tmp);
3884 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3885 g_free(body);
3886 g_free(to);
3887 g_free(hdr);
3890 static void
3891 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3892 gpointer value, gpointer user_data)
3894 struct sip_subscription *subscription = value;
3895 struct sip_dialog *dialog = &subscription->dialog;
3896 struct sipe_account_data *sip = user_data;
3897 gchar *tmp = get_contact(sip);
3898 gchar *hdr = g_strdup_printf(
3899 "Event: %s\r\n"
3900 "Expires: 0\r\n"
3901 "Contact: %s\r\n", subscription->event, tmp);
3902 g_free(tmp);
3904 /* Rate limit to max. 25 requests per seconds */
3905 g_usleep(1000000 / 25);
3907 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3908 g_free(hdr);
3911 /* IM Session (INVITE and MESSAGE methods) */
3913 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3914 static gchar *
3915 get_end_points (struct sipe_account_data *sip,
3916 struct sip_session *session)
3918 gchar *res;
3920 if (session == NULL) {
3921 return NULL;
3924 res = g_strdup_printf("<sip:%s>", sip->username);
3926 SIPE_DIALOG_FOREACH {
3927 gchar *tmp = res;
3928 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3929 g_free(tmp);
3931 if (dialog->theirepid) {
3932 tmp = res;
3933 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3934 g_free(tmp);
3936 } SIPE_DIALOG_FOREACH_END;
3938 return res;
3941 static gboolean
3942 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3943 struct sipmsg *msg,
3944 SIPE_UNUSED_PARAMETER struct transaction *trans)
3946 gboolean ret = TRUE;
3948 if (msg->response != 200) {
3949 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3950 return FALSE;
3953 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3955 return ret;
3959 * Asks UA/proxy about its capabilities.
3961 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3963 gchar *to = sip_uri(who);
3964 gchar *contact = get_contact(sip);
3965 gchar *request = g_strdup_printf(
3966 "Accept: application/sdp\r\n"
3967 "Contact: %s\r\n", contact);
3968 g_free(contact);
3970 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3972 g_free(to);
3973 g_free(request);
3976 static void
3977 sipe_notify_user(struct sipe_account_data *sip,
3978 struct sip_session *session,
3979 PurpleMessageFlags flags,
3980 const gchar *message)
3982 PurpleConversation *conv;
3984 if (!session->conv) {
3985 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3986 } else {
3987 conv = session->conv;
3989 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3992 void
3993 sipe_present_info(struct sipe_account_data *sip,
3994 struct sip_session *session,
3995 const gchar *message)
3997 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4000 static void
4001 sipe_present_err(struct sipe_account_data *sip,
4002 struct sip_session *session,
4003 const gchar *message)
4005 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4008 void
4009 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4010 struct sip_session *session,
4011 int sip_error,
4012 int sip_warning,
4013 const gchar *who,
4014 const gchar *message)
4016 char *msg, *msg_tmp, *msg_tmp2;
4017 const char *label;
4019 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
4020 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4021 g_free(msg_tmp);
4022 /* Service unavailable; Server Internal Error; Server Time-out */
4023 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4024 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4025 g_free(msg);
4026 msg = NULL;
4027 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4028 label = _("This message was not delivered to %s because the service is not available");
4029 } else if (sip_error == 486) { /* Busy Here */
4030 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4031 } else if (sip_error == 415) { /* Unsupported media type */
4032 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4033 } else {
4034 label = _("This message was not delivered to %s because one or more recipients are offline");
4037 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4038 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4039 msg ? ":" : "",
4040 msg ? msg : "");
4041 sipe_present_err(sip, session, msg_tmp);
4042 g_free(msg_tmp2);
4043 g_free(msg_tmp);
4044 g_free(msg);
4048 static gboolean
4049 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4050 SIPE_UNUSED_PARAMETER struct transaction *trans)
4052 gboolean ret = TRUE;
4053 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4054 struct sip_session *session = sipe_session_find_im(sip, with);
4055 struct sip_dialog *dialog;
4056 gchar *cseq;
4057 char *key;
4058 struct queued_message *message;
4060 if (!session) {
4061 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
4062 g_free(with);
4063 return FALSE;
4066 dialog = sipe_dialog_find(session, with);
4067 if (!dialog) {
4068 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
4069 g_free(with);
4070 return FALSE;
4073 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4074 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4075 g_free(cseq);
4076 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4078 if (msg->response >= 400) {
4079 PurpleBuddy *pbuddy;
4080 const char *alias = with;
4081 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4082 int warning = -1;
4084 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
4086 if (warn_hdr) {
4087 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4088 if (parts[0]) {
4089 warning = atoi(parts[0]);
4091 g_strfreev(parts);
4094 /* cancel file transfer as rejected by server */
4095 if (msg->response == 606 && /* Not acceptable all. */
4096 warning == 309 && /* Message contents not allowed by policy */
4097 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4099 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4100 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4101 sipe_utils_nameval_free(parsed_body);
4104 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4105 alias = purple_buddy_get_alias(pbuddy);
4108 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4110 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4111 if (msg->response == 408 || /* Request timeout */
4112 msg->response == 480 || /* Temporarily Unavailable */
4113 msg->response == 481) { /* Call/Transaction Does Not Exist */
4114 purple_debug_info("sipe", "process_message_response: assuming dangling IM session, dropping it.\n");
4115 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4118 ret = FALSE;
4119 } else {
4120 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4121 if (message_id) {
4122 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4123 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4124 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4127 g_hash_table_remove(session->unconfirmed_messages, key);
4128 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4129 key, g_hash_table_size(session->unconfirmed_messages));
4132 g_free(key);
4133 g_free(with);
4135 if (ret) sipe_im_process_queue(sip, session);
4136 return ret;
4139 static gboolean
4140 sipe_is_election_finished(struct sip_session *session);
4142 static void
4143 sipe_election_result(struct sipe_account_data *sip,
4144 void *sess);
4146 static gboolean
4147 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4148 SIPE_UNUSED_PARAMETER struct transaction *trans)
4150 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4151 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4152 struct sip_dialog *dialog;
4153 struct sip_session *session;
4155 session = sipe_session_find_chat_by_callid(sip, callid);
4156 if (!session) {
4157 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4158 return FALSE;
4161 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4162 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4163 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4164 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4166 if (xn_request_rm_response) {
4167 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4168 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4170 dialog = sipe_dialog_find(session, with);
4171 if (!dialog) {
4172 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4173 xmlnode_free(xn_action);
4174 return FALSE;
4177 if (allow && !g_strcasecmp(allow, "true")) {
4178 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4179 dialog->election_vote = 1;
4180 } else if (allow && !g_strcasecmp(allow, "false")) {
4181 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4182 dialog->election_vote = -1;
4185 if (sipe_is_election_finished(session)) {
4186 sipe_election_result(sip, session);
4189 } else if (xn_set_rm_response) {
4192 xmlnode_free(xn_action);
4196 return TRUE;
4199 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4201 gchar *hdr;
4202 gchar *tmp;
4203 char *msgtext = NULL;
4204 const gchar *msgr = "";
4205 gchar *tmp2 = NULL;
4207 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4208 char *msgformat;
4209 gchar *msgr_value;
4211 sipe_parse_html(msg, &msgformat, &msgtext);
4212 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4214 msgr_value = sipmsg_get_msgr_string(msgformat);
4215 g_free(msgformat);
4216 if (msgr_value) {
4217 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4218 g_free(msgr_value);
4220 } else {
4221 msgtext = g_strdup(msg);
4224 tmp = get_contact(sip);
4225 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4226 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4227 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4228 if (content_type == NULL)
4229 content_type = "text/plain";
4231 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4232 g_free(tmp);
4233 g_free(tmp2);
4235 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4236 g_free(msgtext);
4237 g_free(hdr);
4241 void
4242 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4244 GSList *entry2 = session->outgoing_message_queue;
4245 while (entry2) {
4246 struct queued_message *msg = entry2->data;
4248 /* for multiparty chat or conference */
4249 if (session->is_multiparty || session->focus_uri) {
4250 gchar *who = sip_uri_self(sip);
4251 serv_got_chat_in(sip->gc, session->chat_id, who,
4252 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4253 g_free(who);
4256 SIPE_DIALOG_FOREACH {
4257 char *key;
4258 struct queued_message *message;
4260 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4262 message = g_new0(struct queued_message,1);
4263 message->body = g_strdup(msg->body);
4264 if (msg->content_type != NULL)
4265 message->content_type = g_strdup(msg->content_type);
4267 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4268 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4269 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4270 key, g_hash_table_size(session->unconfirmed_messages));
4271 g_free(key);
4273 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4274 } SIPE_DIALOG_FOREACH_END;
4276 entry2 = sipe_session_dequeue_message(session);
4280 static void
4281 sipe_refer_notify(struct sipe_account_data *sip,
4282 struct sip_session *session,
4283 const gchar *who,
4284 int status,
4285 const gchar *desc)
4287 gchar *hdr;
4288 gchar *body;
4289 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4291 hdr = g_strdup_printf(
4292 "Event: refer\r\n"
4293 "Subscription-State: %s\r\n"
4294 "Content-Type: message/sipfrag\r\n",
4295 status >= 200 ? "terminated" : "active");
4297 body = g_strdup_printf(
4298 "SIP/2.0 %d %s\r\n",
4299 status, desc);
4301 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4303 g_free(hdr);
4304 g_free(body);
4307 static gboolean
4308 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4310 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4311 struct sip_session *session;
4312 struct sip_dialog *dialog;
4313 char *cseq;
4314 char *key;
4315 struct queued_message *message;
4316 struct sipmsg *request_msg = trans->msg;
4318 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4319 gchar *referred_by;
4321 session = sipe_session_find_chat_by_callid(sip, callid);
4322 if (!session) {
4323 session = sipe_session_find_im(sip, with);
4325 if (!session) {
4326 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4327 g_free(with);
4328 return FALSE;
4331 dialog = sipe_dialog_find(session, with);
4332 if (!dialog) {
4333 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4334 g_free(with);
4335 return FALSE;
4338 sipe_dialog_parse(dialog, msg, TRUE);
4340 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4341 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4342 g_free(cseq);
4343 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4345 if (msg->response != 200) {
4346 PurpleBuddy *pbuddy;
4347 const char *alias = with;
4348 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4349 int warning = -1;
4351 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4353 if (warn_hdr) {
4354 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4355 if (parts[0]) {
4356 warning = atoi(parts[0]);
4358 g_strfreev(parts);
4361 /* cancel file transfer as rejected by server */
4362 if (msg->response == 606 && /* Not acceptable all. */
4363 warning == 309 && /* Message contents not allowed by policy */
4364 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4366 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4367 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4368 sipe_utils_nameval_free(parsed_body);
4371 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4372 alias = purple_buddy_get_alias(pbuddy);
4375 if (message) {
4376 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4377 } else {
4378 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4379 sipe_present_err(sip, session, tmp_msg);
4380 g_free(tmp_msg);
4383 sipe_dialog_remove(session, with);
4385 g_free(key);
4386 g_free(with);
4387 return FALSE;
4390 dialog->cseq = 0;
4391 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4392 dialog->outgoing_invite = NULL;
4393 dialog->is_established = TRUE;
4395 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4396 if (referred_by) {
4397 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4398 g_free(referred_by);
4401 /* add user to chat if it is a multiparty session */
4402 if (session->is_multiparty) {
4403 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4404 with, NULL,
4405 PURPLE_CBFLAGS_NONE, TRUE);
4408 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4409 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4410 sipe_session_dequeue_message(session);
4413 sipe_im_process_queue(sip, session);
4415 g_hash_table_remove(session->unconfirmed_messages, key);
4416 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4417 key, g_hash_table_size(session->unconfirmed_messages));
4419 g_free(key);
4420 g_free(with);
4421 return TRUE;
4425 void
4426 sipe_invite(struct sipe_account_data *sip,
4427 struct sip_session *session,
4428 const gchar *who,
4429 const gchar *msg_body,
4430 const gchar *msg_content_type,
4431 const gchar *referred_by,
4432 const gboolean is_triggered)
4434 gchar *hdr;
4435 gchar *to;
4436 gchar *contact;
4437 gchar *body;
4438 gchar *self;
4439 char *ms_text_format = NULL;
4440 gchar *roster_manager;
4441 gchar *end_points;
4442 gchar *referred_by_str;
4443 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4445 if (dialog && dialog->is_established) {
4446 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4447 return;
4450 if (!dialog) {
4451 dialog = sipe_dialog_add(session);
4452 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4453 dialog->with = g_strdup(who);
4456 if (!(dialog->ourtag)) {
4457 dialog->ourtag = gentag();
4460 to = sip_uri(who);
4462 if (msg_body) {
4463 char *msgtext = NULL;
4464 char *base64_msg;
4465 const gchar *msgr = "";
4466 char *key;
4467 struct queued_message *message;
4468 gchar *tmp = NULL;
4470 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4471 char *msgformat;
4472 gchar *msgr_value;
4474 sipe_parse_html(msg_body, &msgformat, &msgtext);
4475 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4477 msgr_value = sipmsg_get_msgr_string(msgformat);
4478 g_free(msgformat);
4479 if (msgr_value) {
4480 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4481 g_free(msgr_value);
4483 } else {
4484 msgtext = g_strdup(msg_body);
4487 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4488 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4489 msg_content_type ? msg_content_type : "text/plain",
4490 msgr,
4491 base64_msg);
4492 g_free(msgtext);
4493 g_free(tmp);
4494 g_free(base64_msg);
4496 message = g_new0(struct queued_message,1);
4497 message->body = g_strdup(msg_body);
4498 if (msg_content_type != NULL)
4499 message->content_type = g_strdup(msg_content_type);
4501 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4502 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4503 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4504 key, g_hash_table_size(session->unconfirmed_messages));
4505 g_free(key);
4508 contact = get_contact(sip);
4509 end_points = get_end_points(sip, session);
4510 self = sip_uri_self(sip);
4511 roster_manager = g_strdup_printf(
4512 "Roster-Manager: %s\r\n"
4513 "EndPoints: %s\r\n",
4514 self,
4515 end_points);
4516 referred_by_str = referred_by ?
4517 g_strdup_printf(
4518 "Referred-By: %s\r\n",
4519 referred_by)
4520 : g_strdup("");
4521 hdr = g_strdup_printf(
4522 "Supported: ms-sender\r\n"
4523 "%s"
4524 "%s"
4525 "%s"
4526 "%s"
4527 "Contact: %s\r\n%s"
4528 "Content-Type: application/sdp\r\n",
4529 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4530 referred_by_str,
4531 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4532 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4533 contact,
4534 ms_text_format ? ms_text_format : "");
4535 g_free(ms_text_format);
4536 g_free(self);
4538 body = g_strdup_printf(
4539 "v=0\r\n"
4540 "o=- 0 0 IN IP4 %s\r\n"
4541 "s=session\r\n"
4542 "c=IN IP4 %s\r\n"
4543 "t=0 0\r\n"
4544 "m=%s %d sip null\r\n"
4545 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4546 purple_network_get_my_ip(-1),
4547 purple_network_get_my_ip(-1),
4548 sip->ocs2007 ? "message" : "x-ms-message",
4549 sip->realport);
4551 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4552 to, to, hdr, body, dialog, process_invite_response);
4554 g_free(to);
4555 g_free(roster_manager);
4556 g_free(end_points);
4557 g_free(referred_by_str);
4558 g_free(body);
4559 g_free(hdr);
4560 g_free(contact);
4563 static void
4564 sipe_refer(struct sipe_account_data *sip,
4565 struct sip_session *session,
4566 const gchar *who)
4568 gchar *hdr;
4569 gchar *contact;
4570 gchar *epid = get_epid(sip);
4571 struct sip_dialog *dialog = sipe_dialog_find(session,
4572 session->roster_manager);
4573 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4575 contact = get_contact(sip);
4576 hdr = g_strdup_printf(
4577 "Contact: %s\r\n"
4578 "Refer-to: <%s>\r\n"
4579 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4580 "Require: com.microsoft.rtc-multiparty\r\n",
4581 contact,
4582 who,
4583 sip->username,
4584 ourtag ? ";tag=" : "",
4585 ourtag ? ourtag : "",
4586 epid);
4587 g_free(epid);
4589 send_sip_request(sip->gc, "REFER",
4590 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4592 g_free(hdr);
4593 g_free(contact);
4596 static void
4597 sipe_send_election_request_rm(struct sipe_account_data *sip,
4598 struct sip_dialog *dialog,
4599 int bid)
4601 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4603 gchar *body = g_strdup_printf(
4604 "<?xml version=\"1.0\"?>\r\n"
4605 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4606 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4607 sip->username, bid);
4609 send_sip_request(sip->gc, "INFO",
4610 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4612 g_free(body);
4615 static void
4616 sipe_send_election_set_rm(struct sipe_account_data *sip,
4617 struct sip_dialog *dialog)
4619 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4621 gchar *body = g_strdup_printf(
4622 "<?xml version=\"1.0\"?>\r\n"
4623 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4624 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4625 sip->username);
4627 send_sip_request(sip->gc, "INFO",
4628 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4630 g_free(body);
4633 static void
4634 sipe_session_close(struct sipe_account_data *sip,
4635 struct sip_session * session)
4637 if (session && session->focus_uri) {
4638 sipe_conf_immcu_closed(sip, session);
4639 conf_session_close(sip, session);
4642 if (session) {
4643 SIPE_DIALOG_FOREACH {
4644 /* @TODO slow down BYE message sending rate */
4645 /* @see single subscription code */
4646 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4647 } SIPE_DIALOG_FOREACH_END;
4649 sipe_session_remove(sip, session);
4653 static void
4654 sipe_session_close_all(struct sipe_account_data *sip)
4656 GSList *entry;
4657 while ((entry = sip->sessions) != NULL) {
4658 sipe_session_close(sip, entry->data);
4662 static void
4663 sipe_convo_closed(PurpleConnection * gc, const char *who)
4665 struct sipe_account_data *sip = gc->proto_data;
4667 purple_debug_info("sipe", "conversation with %s closed\n", who);
4668 sipe_session_close(sip, sipe_session_find_im(sip, who));
4671 static void
4672 sipe_chat_leave (PurpleConnection *gc, int id)
4674 struct sipe_account_data *sip = gc->proto_data;
4675 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4677 sipe_session_close(sip, session);
4680 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4681 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4683 struct sipe_account_data *sip = gc->proto_data;
4684 struct sip_session *session;
4685 struct sip_dialog *dialog;
4686 gchar *uri = sip_uri(who);
4688 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4690 session = sipe_session_find_or_add_im(sip, uri);
4691 dialog = sipe_dialog_find(session, uri);
4693 // Queue the message
4694 sipe_session_enqueue_message(session, what, NULL);
4696 if (dialog && !dialog->outgoing_invite) {
4697 sipe_im_process_queue(sip, session);
4698 } else if (!dialog || !dialog->outgoing_invite) {
4699 // Need to send the INVITE to get the outgoing dialog setup
4700 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4703 g_free(uri);
4704 return 1;
4707 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4708 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4710 struct sipe_account_data *sip = gc->proto_data;
4711 struct sip_session *session;
4713 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4715 session = sipe_session_find_chat_by_id(sip, id);
4717 // Queue the message
4718 if (session && session->dialogs) {
4719 sipe_session_enqueue_message(session,what,NULL);
4720 sipe_im_process_queue(sip, session);
4721 } else if (sip) {
4722 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4723 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4725 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4726 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4728 if (sip->ocs2007) {
4729 struct sip_session *session = sipe_session_add_chat(sip);
4731 session->is_multiparty = FALSE;
4732 session->focus_uri = g_strdup(proto_chat_id);
4733 sipe_session_enqueue_message(session, what, NULL);
4734 sipe_invite_conf_focus(sip, session);
4738 return 1;
4741 /* End IM Session (INVITE and MESSAGE methods) */
4743 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4745 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4746 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4747 gchar *from;
4748 struct sip_session *session;
4750 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4752 /* Call Control protocol */
4753 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4755 process_incoming_info_csta(sip, msg);
4756 return;
4759 from = parse_from(sipmsg_find_header(msg, "From"));
4760 session = sipe_session_find_chat_by_callid(sip, callid);
4761 if (!session) {
4762 session = sipe_session_find_im(sip, from);
4764 if (!session) {
4765 g_free(from);
4766 return;
4769 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4771 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4772 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4773 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4775 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4777 if (xn_request_rm) {
4778 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4779 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4780 gchar *body = g_strdup_printf(
4781 "<?xml version=\"1.0\"?>\r\n"
4782 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4783 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4784 sip->username,
4785 session->bid < bid ? "true" : "false");
4786 send_sip_response(sip->gc, msg, 200, "OK", body);
4787 g_free(body);
4788 } else if (xn_set_rm) {
4789 gchar *body;
4790 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4791 g_free(session->roster_manager);
4792 session->roster_manager = g_strdup(rm);
4794 body = g_strdup_printf(
4795 "<?xml version=\"1.0\"?>\r\n"
4796 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4797 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4798 sip->username);
4799 send_sip_response(sip->gc, msg, 200, "OK", body);
4800 g_free(body);
4802 xmlnode_free(xn_action);
4805 else
4807 /* looks like purple lacks typing notification for chat */
4808 if (!session->is_multiparty && !session->focus_uri) {
4809 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4810 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4811 "status");
4812 if (sipe_strequal(status, "type")) {
4813 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4814 } else if (sipe_strequal(status, "idle")) {
4815 serv_got_typing_stopped(sip->gc, from);
4817 xmlnode_free(xn_keyboard_activity);
4820 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4822 g_free(from);
4825 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4827 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4828 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4829 struct sip_session *session;
4830 struct sip_dialog *dialog;
4832 /* collect dialog identification
4833 * we need callid, ourtag and theirtag to unambiguously identify dialog
4835 /* take data before 'msg' will be modified by send_sip_response */
4836 dialog = g_new0(struct sip_dialog, 1);
4837 dialog->callid = g_strdup(callid);
4838 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4839 dialog->with = g_strdup(from);
4840 sipe_dialog_parse(dialog, msg, FALSE);
4842 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4844 session = sipe_session_find_chat_by_callid(sip, callid);
4845 if (!session) {
4846 session = sipe_session_find_im(sip, from);
4848 if (!session) {
4849 sipe_dialog_free(dialog);
4850 g_free(from);
4851 return;
4854 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4855 g_free(session->roster_manager);
4856 session->roster_manager = NULL;
4859 /* This what BYE is essentially for - terminating dialog */
4860 sipe_dialog_remove_3(session, dialog);
4861 sipe_dialog_free(dialog);
4862 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4863 sipe_conf_immcu_closed(sip, session);
4864 } else if (session->is_multiparty) {
4865 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4868 g_free(from);
4871 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4873 gchar *self = sip_uri_self(sip);
4874 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4875 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4876 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4877 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4878 struct sip_session *session;
4879 struct sip_dialog *dialog;
4881 session = sipe_session_find_chat_by_callid(sip, callid);
4882 dialog = sipe_dialog_find(session, from);
4884 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
4885 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4886 } else {
4887 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4889 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4892 g_free(self);
4893 g_free(from);
4894 g_free(refer_to);
4895 g_free(referred_by);
4898 static unsigned int
4899 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4901 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4902 struct sip_session *session;
4903 struct sip_dialog *dialog;
4905 if (state == PURPLE_NOT_TYPING)
4906 return 0;
4908 session = sipe_session_find_im(sip, who);
4909 dialog = sipe_dialog_find(session, who);
4911 if (session && dialog && dialog->is_established) {
4912 send_sip_request(gc, "INFO", who, who,
4913 "Content-Type: application/xml\r\n",
4914 SIPE_SEND_TYPING, dialog, NULL);
4916 return SIPE_TYPING_SEND_TIMEOUT;
4919 static gboolean resend_timeout(struct sipe_account_data *sip)
4921 GSList *tmp = sip->transactions;
4922 time_t currtime = time(NULL);
4923 while (tmp) {
4924 struct transaction *trans = tmp->data;
4925 tmp = tmp->next;
4926 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4927 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4928 /* TODO 408 */
4929 } else {
4930 if ((currtime - trans->time > 2) && trans->retries == 0) {
4931 trans->retries++;
4932 sendout_sipmsg(sip, trans->msg);
4936 return TRUE;
4939 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4940 SIPE_UNUSED_PARAMETER void *unused)
4942 /* register again when security token expires */
4943 /* we have to start a new authentication as the security token
4944 * is almost expired by sending a not signed REGISTER message */
4945 purple_debug_info("sipe", "do a full reauthentication\n");
4946 sipe_auth_free(&sip->registrar);
4947 sipe_auth_free(&sip->proxy);
4948 sip->registerstatus = 0;
4949 do_register(sip);
4950 sip->reauthenticate_set = FALSE;
4953 static gboolean
4954 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4955 struct sipmsg *msg,
4956 GSList *parsed_body)
4958 gboolean found = FALSE;
4960 if (parsed_body) {
4961 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4963 if (sipe_strequal(invitation_command, "INVITE")) {
4964 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4965 found = TRUE;
4966 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4967 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4968 found = TRUE;
4969 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4970 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4971 found = TRUE;
4974 return found;
4977 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4979 gchar *from;
4980 const gchar *contenttype;
4981 gboolean found = FALSE;
4983 from = parse_from(sipmsg_find_header(msg, "From"));
4985 if (!from) return;
4987 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4989 contenttype = sipmsg_find_header(msg, "Content-Type");
4990 if (g_str_has_prefix(contenttype, "text/plain")
4991 || g_str_has_prefix(contenttype, "text/html")
4992 || g_str_has_prefix(contenttype, "multipart/related")
4993 || g_str_has_prefix(contenttype, "multipart/alternative"))
4995 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4996 gchar *html = get_html_message(contenttype, msg->body);
4998 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4999 if (!session) {
5000 session = sipe_session_find_im(sip, from);
5003 if (session && session->focus_uri) { /* a conference */
5004 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5005 gchar *sender = parse_from(tmp);
5006 g_free(tmp);
5007 serv_got_chat_in(sip->gc, session->chat_id, sender,
5008 PURPLE_MESSAGE_RECV, html, time(NULL));
5009 g_free(sender);
5010 } else if (session && session->is_multiparty) { /* a multiparty chat */
5011 serv_got_chat_in(sip->gc, session->chat_id, from,
5012 PURPLE_MESSAGE_RECV, html, time(NULL));
5013 } else {
5014 serv_got_im(sip->gc, from, html, 0, time(NULL));
5016 g_free(html);
5017 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5018 found = TRUE;
5020 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5021 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5022 xmlnode *state;
5023 gchar *statedata;
5025 if (!isc) {
5026 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
5027 g_free(from);
5028 return;
5031 state = xmlnode_get_child(isc, "state");
5033 if (!state) {
5034 purple_debug_info("sipe", "process_incoming_message: no state found\n");
5035 xmlnode_free(isc);
5036 g_free(from);
5037 return;
5040 statedata = xmlnode_get_data(state);
5041 if (statedata) {
5042 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5043 else serv_got_typing_stopped(sip->gc, from);
5045 g_free(statedata);
5047 xmlnode_free(isc);
5048 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5049 found = TRUE;
5050 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5051 GSList *body = sipe_ft_parse_msg_body(msg->body);
5052 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5053 sipe_utils_nameval_free(body);
5054 if (found) {
5055 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5058 if (!found) {
5059 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5060 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5061 if (!session) {
5062 session = sipe_session_find_im(sip, from);
5064 if (session) {
5065 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5066 from);
5067 sipe_present_err(sip, session, errmsg);
5068 g_free(errmsg);
5071 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
5072 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5074 g_free(from);
5077 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5079 gchar *body;
5080 gchar *newTag;
5081 const gchar *oldHeader;
5082 gchar *newHeader;
5083 gboolean is_multiparty = FALSE;
5084 gboolean is_triggered = FALSE;
5085 gboolean was_multiparty = TRUE;
5086 gboolean just_joined = FALSE;
5087 gchar *from;
5088 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5089 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5090 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5091 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5092 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5093 GSList *end_points = NULL;
5094 char *tmp = NULL;
5095 struct sip_session *session;
5096 const gchar *ms_text_format;
5098 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
5099 g_free(tmp);
5101 /* Invitation to join conference */
5102 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5103 process_incoming_invite_conf(sip, msg);
5104 return;
5107 /* Only accept text invitations */
5108 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5109 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5110 return;
5113 // TODO There *must* be a better way to clean up the To header to add a tag...
5114 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
5115 oldHeader = sipmsg_find_header(msg, "To");
5116 newTag = gentag();
5117 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5118 sipmsg_remove_header_now(msg, "To");
5119 sipmsg_add_header_now(msg, "To", newHeader);
5120 g_free(newHeader);
5122 if (end_points_hdr) {
5123 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5125 if (g_slist_length(end_points) > 2) {
5126 is_multiparty = TRUE;
5129 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5130 is_triggered = TRUE;
5131 is_multiparty = TRUE;
5134 session = sipe_session_find_chat_by_callid(sip, callid);
5135 /* Convert to multiparty */
5136 if (session && is_multiparty && !session->is_multiparty) {
5137 g_free(session->with);
5138 session->with = NULL;
5139 was_multiparty = FALSE;
5140 session->is_multiparty = TRUE;
5141 session->chat_id = rand();
5144 if (!session && is_multiparty) {
5145 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5147 /* IM session */
5148 from = parse_from(sipmsg_find_header(msg, "From"));
5149 if (!session) {
5150 session = sipe_session_find_or_add_im(sip, from);
5153 if (session) {
5154 g_free(session->callid);
5155 session->callid = g_strdup(callid);
5157 session->is_multiparty = is_multiparty;
5158 if (roster_manager) {
5159 session->roster_manager = g_strdup(roster_manager);
5163 if (is_multiparty && end_points) {
5164 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5165 GSList *entry = end_points;
5166 while (entry) {
5167 struct sip_dialog *dialog;
5168 struct sipendpoint *end_point = entry->data;
5169 entry = entry->next;
5171 if (!g_strcasecmp(from, end_point->contact) ||
5172 !g_strcasecmp(to, end_point->contact))
5173 continue;
5175 dialog = sipe_dialog_find(session, end_point->contact);
5176 if (dialog) {
5177 g_free(dialog->theirepid);
5178 dialog->theirepid = end_point->epid;
5179 end_point->epid = NULL;
5180 } else {
5181 dialog = sipe_dialog_add(session);
5183 dialog->callid = g_strdup(session->callid);
5184 dialog->with = end_point->contact;
5185 end_point->contact = NULL;
5186 dialog->theirepid = end_point->epid;
5187 end_point->epid = NULL;
5189 just_joined = TRUE;
5191 /* send triggered INVITE */
5192 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5195 g_free(to);
5198 if (end_points) {
5199 GSList *entry = end_points;
5200 while (entry) {
5201 struct sipendpoint *end_point = entry->data;
5202 entry = entry->next;
5203 g_free(end_point->contact);
5204 g_free(end_point->epid);
5205 g_free(end_point);
5207 g_slist_free(end_points);
5210 if (session) {
5211 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5212 if (dialog) {
5213 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5214 sipe_dialog_parse_routes(dialog, msg, FALSE);
5215 } else {
5216 dialog = sipe_dialog_add(session);
5218 dialog->callid = g_strdup(session->callid);
5219 dialog->with = g_strdup(from);
5220 sipe_dialog_parse(dialog, msg, FALSE);
5222 if (!dialog->ourtag) {
5223 dialog->ourtag = newTag;
5224 newTag = NULL;
5227 just_joined = TRUE;
5229 } else {
5230 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5232 g_free(newTag);
5234 if (is_multiparty && !session->conv) {
5235 gchar *chat_title = sipe_chat_get_name(callid);
5236 gchar *self = sip_uri_self(sip);
5237 /* create prpl chat */
5238 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5239 session->chat_title = g_strdup(chat_title);
5240 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5241 /* add self */
5242 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5243 self, NULL,
5244 PURPLE_CBFLAGS_NONE, FALSE);
5245 g_free(chat_title);
5246 g_free(self);
5249 if (is_multiparty && !was_multiparty) {
5250 /* add current IM counterparty to chat */
5251 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5252 sipe_dialog_first(session)->with, NULL,
5253 PURPLE_CBFLAGS_NONE, FALSE);
5256 /* add inviting party to chat */
5257 if (just_joined && session->conv) {
5258 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5259 from, NULL,
5260 PURPLE_CBFLAGS_NONE, TRUE);
5263 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5265 /* This used only in 2005 official client, not 2007 or Reuters.
5266 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5267 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5269 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5270 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5271 if (is_multiparty ||
5272 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5274 if (ms_text_format) {
5275 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5277 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5278 if (tmp) {
5279 gsize len;
5280 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5282 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5284 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5285 sipe_utils_nameval_free(parsed_body);
5286 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5288 g_free(tmp);
5290 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5292 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5293 gchar *html = get_html_message(ms_text_format, NULL);
5294 if (html) {
5295 if (is_multiparty) {
5296 serv_got_chat_in(sip->gc, session->chat_id, from,
5297 PURPLE_MESSAGE_RECV, html, time(NULL));
5298 } else {
5299 serv_got_im(sip->gc, from, html, 0, time(NULL));
5301 g_free(html);
5302 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5308 g_free(from);
5310 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5311 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5312 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5314 body = g_strdup_printf(
5315 "v=0\r\n"
5316 "o=- 0 0 IN IP4 %s\r\n"
5317 "s=session\r\n"
5318 "c=IN IP4 %s\r\n"
5319 "t=0 0\r\n"
5320 "m=%s %d sip sip:%s\r\n"
5321 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5322 purple_network_get_my_ip(-1),
5323 purple_network_get_my_ip(-1),
5324 sip->ocs2007 ? "message" : "x-ms-message",
5325 sip->realport,
5326 sip->username);
5327 send_sip_response(sip->gc, msg, 200, "OK", body);
5328 g_free(body);
5331 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5333 gchar *body;
5335 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5336 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5337 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5339 body = g_strdup_printf(
5340 "v=0\r\n"
5341 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5342 "s=session\r\n"
5343 "c=IN IP4 0.0.0.0\r\n"
5344 "t=0 0\r\n"
5345 "m=%s %d sip sip:%s\r\n"
5346 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5347 sip->ocs2007 ? "message" : "x-ms-message",
5348 sip->realport,
5349 sip->username);
5350 send_sip_response(sip->gc, msg, 200, "OK", body);
5351 g_free(body);
5354 static const char*
5355 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5357 const char *res = "NTLM";
5358 #ifdef HAVE_KERBEROS
5359 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5360 res = "Kerberos";
5362 #else
5363 (void) sip; /* make compiler happy */
5364 #endif
5365 return res;
5368 static void sipe_connection_cleanup(struct sipe_account_data *);
5369 static void create_connection(struct sipe_account_data *, gchar *, int);
5371 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5372 SIPE_UNUSED_PARAMETER struct transaction *trans)
5374 gchar *tmp;
5375 const gchar *expires_header;
5376 int expires, i;
5377 GSList *hdr = msg->headers;
5378 struct sipnameval *elem;
5380 expires_header = sipmsg_find_header(msg, "Expires");
5381 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5382 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5384 switch (msg->response) {
5385 case 200:
5386 if (expires == 0) {
5387 sip->registerstatus = 0;
5388 } else {
5389 const gchar *contact_hdr;
5390 gchar *gruu = NULL;
5391 gchar *epid;
5392 gchar *uuid;
5393 gchar *timeout;
5394 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5395 const char *auth_scheme;
5397 if (!sip->reregister_set) {
5398 gchar *action_name = g_strdup_printf("<%s>", "registration");
5399 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5400 g_free(action_name);
5401 sip->reregister_set = TRUE;
5404 sip->registerstatus = 3;
5406 if (server_hdr && !sip->server_version) {
5407 sip->server_version = g_strdup(server_hdr);
5408 g_free(default_ua);
5409 default_ua = NULL;
5412 auth_scheme = sipe_get_auth_scheme_name(sip);
5413 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5415 if (tmp) {
5416 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5417 fill_auth(tmp, &sip->registrar);
5420 if (!sip->reauthenticate_set) {
5421 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5422 guint reauth_timeout;
5423 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5424 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5425 reauth_timeout = sip->registrar.expires - 300;
5426 } else {
5427 /* NTLM: we have to reauthenticate as our security token expires
5428 after eight hours (be five minutes early) */
5429 reauth_timeout = (8 * 3600) - 300;
5431 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5432 g_free(action_name);
5433 sip->reauthenticate_set = TRUE;
5436 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5438 epid = get_epid(sip);
5439 uuid = generateUUIDfromEPID(epid);
5440 g_free(epid);
5442 // There can be multiple Contact headers (one per location where the user is logged in) so
5443 // make sure to only get the one for this uuid
5444 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5445 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5446 if (valid_contact) {
5447 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5448 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5449 g_free(valid_contact);
5450 break;
5451 } else {
5452 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5455 g_free(uuid);
5457 g_free(sip->contact);
5458 if(gruu) {
5459 sip->contact = g_strdup_printf("<%s>", gruu);
5460 g_free(gruu);
5461 } else {
5462 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5463 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);
5465 sip->ocs2007 = FALSE;
5466 sip->batched_support = FALSE;
5468 while(hdr)
5470 elem = hdr->data;
5471 if (sipe_strcase_equal(elem->name, "Supported")) {
5472 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5473 /* We interpret this as OCS2007+ indicator */
5474 sip->ocs2007 = TRUE;
5475 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5477 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5478 sip->batched_support = TRUE;
5479 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5482 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5483 gchar **caps = g_strsplit(elem->value,",",0);
5484 i = 0;
5485 while (caps[i]) {
5486 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5487 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5488 i++;
5490 g_strfreev(caps);
5492 hdr = g_slist_next(hdr);
5495 /* rejoin open chats to be able to use them by continue to send messages */
5496 purple_conversation_foreach(sipe_rejoin_chat);
5498 /* subscriptions */
5499 if (!sip->subscribed) { //do it just once, not every re-register
5501 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5502 (GCompareFunc)g_ascii_strcasecmp)) {
5503 sipe_subscribe_roaming_contacts(sip);
5506 /* For 2007+ it does not make sence to subscribe to:
5507 * vnd-microsoft-roaming-ACL
5508 * vnd-microsoft-provisioning (not v2)
5509 * presence.wpending
5510 * These are for backward compatibility.
5512 if (sip->ocs2007)
5514 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5515 (GCompareFunc)g_ascii_strcasecmp)) {
5516 sipe_subscribe_roaming_self(sip);
5518 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5519 (GCompareFunc)g_ascii_strcasecmp)) {
5520 sipe_subscribe_roaming_provisioning_v2(sip);
5523 /* For 2005- servers */
5524 else
5526 //sipe_options_request(sip, sip->sipdomain);
5528 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5529 (GCompareFunc)g_ascii_strcasecmp)) {
5530 sipe_subscribe_roaming_acl(sip);
5532 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5533 (GCompareFunc)g_ascii_strcasecmp)) {
5534 sipe_subscribe_roaming_provisioning(sip);
5536 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5537 (GCompareFunc)g_ascii_strcasecmp)) {
5538 sipe_subscribe_presence_wpending(sip, msg);
5541 /* For 2007+ we publish our initial statuses and calendar data only after
5542 * received our existing publications in sipe_process_roaming_self()
5543 * Only in this case we know versions of current publications made
5544 * on our behalf.
5546 /* For 2005- we publish our initial statuses only after
5547 * received our existing UserInfo data in response to
5548 * self subscription.
5549 * Only in this case we won't override existing UserInfo data
5550 * set earlier or by other client on our behalf.
5554 sip->subscribed = TRUE;
5557 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5558 "timeout=", ";", NULL);
5559 if (timeout != NULL) {
5560 sscanf(timeout, "%u", &sip->keepalive_timeout);
5561 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5562 sip->keepalive_timeout);
5563 g_free(timeout);
5566 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5568 break;
5569 case 301:
5571 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5573 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5574 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5575 gchar **tmp;
5576 gchar *hostname;
5577 int port = 0;
5578 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5579 int i = 1;
5581 tmp = g_strsplit(parts[0], ":", 0);
5582 hostname = g_strdup(tmp[0]);
5583 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5584 g_strfreev(tmp);
5586 while (parts[i]) {
5587 tmp = g_strsplit(parts[i], "=", 0);
5588 if (tmp[1]) {
5589 if (g_strcasecmp("transport", tmp[0]) == 0) {
5590 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5591 transport = SIPE_TRANSPORT_TCP;
5592 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5593 transport = SIPE_TRANSPORT_UDP;
5597 g_strfreev(tmp);
5598 i++;
5600 g_strfreev(parts);
5602 /* Close old connection */
5603 sipe_connection_cleanup(sip);
5605 /* Create new connection */
5606 sip->transport = transport;
5607 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5608 hostname, port, TRANSPORT_DESCRIPTOR);
5609 create_connection(sip, hostname, port);
5611 g_free(redirect);
5613 break;
5614 case 401:
5615 if (sip->registerstatus != 2) {
5616 const char *auth_scheme;
5617 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5618 if (sip->registrar.retries > 3) {
5619 sip->gc->wants_to_die = TRUE;
5620 purple_connection_error(sip->gc, _("Authentication failed"));
5621 return TRUE;
5624 auth_scheme = sipe_get_auth_scheme_name(sip);
5625 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5627 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5628 if (!tmp) {
5629 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5630 sip->gc->wants_to_die = TRUE;
5631 purple_connection_error(sip->gc, tmp2);
5632 g_free(tmp2);
5633 return TRUE;
5635 fill_auth(tmp, &sip->registrar);
5636 sip->registerstatus = 2;
5637 if (sip->account->disconnecting) {
5638 do_register_exp(sip, 0);
5639 } else {
5640 do_register(sip);
5643 break;
5644 case 403:
5646 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5647 gchar **reason = NULL;
5648 gchar *warning;
5649 if (diagnostics != NULL) {
5650 /* Example header:
5651 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5653 reason = g_strsplit(diagnostics, "\"", 0);
5655 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5656 (reason && reason[1]) ? reason[1] : _("no reason given"));
5657 g_strfreev(reason);
5659 sip->gc->wants_to_die = TRUE;
5660 purple_connection_error(sip->gc, warning);
5661 g_free(warning);
5662 return TRUE;
5664 break;
5665 case 404:
5667 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5668 gchar *reason = NULL;
5669 gchar *warning;
5670 if (diagnostics != NULL) {
5671 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5673 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5674 diagnostics ? (reason ? reason : _("no reason given")) :
5675 _("SIP is either not enabled for the destination URI or it does not exist"));
5676 g_free(reason);
5678 sip->gc->wants_to_die = TRUE;
5679 purple_connection_error(sip->gc, warning);
5680 g_free(warning);
5681 return TRUE;
5683 break;
5684 case 503:
5685 case 504: /* Server time-out */
5687 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5688 gchar *reason = NULL;
5689 gchar *warning;
5690 if (diagnostics != NULL) {
5691 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5693 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5694 g_free(reason);
5696 sip->gc->wants_to_die = TRUE;
5697 purple_connection_error(sip->gc, warning);
5698 g_free(warning);
5699 return TRUE;
5701 break;
5703 return TRUE;
5707 * Returns 2005-style activity and Availability.
5709 * @param status Sipe statis id.
5711 static void
5712 sipe_get_act_avail_by_status_2005(const char *status,
5713 int *activity,
5714 int *availability)
5716 int avail = 300; /* online */
5717 int act = 400; /* Available */
5719 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5720 act = 100;
5721 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5722 // act = 150;
5723 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5724 act = 300;
5725 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5726 act = 400;
5727 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5728 // act = 500;
5729 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5730 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5731 act = 600;
5732 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5733 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5734 avail = 0; /* offline */
5735 act = 100;
5736 } else {
5737 act = 400; /* Available */
5740 if (activity) *activity = act;
5741 if (availability) *availability = avail;
5745 * [MS-SIP] 2.2.1
5747 * @param activity 2005 aggregated activity. Ex.: 600
5748 * @param availablity 2005 aggregated availablity. Ex.: 300
5750 static const char *
5751 sipe_get_status_by_act_avail_2005(const int activity,
5752 const int availablity,
5753 char **activity_desc)
5755 const char *status_id = NULL;
5756 const char *act = NULL;
5758 if (activity < 150) {
5759 status_id = SIPE_STATUS_ID_AWAY;
5760 } else if (activity < 200) {
5761 //status_id = SIPE_STATUS_ID_LUNCH;
5762 status_id = SIPE_STATUS_ID_AWAY;
5763 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5764 } else if (activity < 300) {
5765 //status_id = SIPE_STATUS_ID_IDLE;
5766 status_id = SIPE_STATUS_ID_AWAY;
5767 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5768 } else if (activity < 400) {
5769 status_id = SIPE_STATUS_ID_BRB;
5770 } else if (activity < 500) {
5771 status_id = SIPE_STATUS_ID_AVAILABLE;
5772 } else if (activity < 600) {
5773 //status_id = SIPE_STATUS_ID_ON_PHONE;
5774 status_id = SIPE_STATUS_ID_BUSY;
5775 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5776 } else if (activity < 700) {
5777 status_id = SIPE_STATUS_ID_BUSY;
5778 } else if (activity < 800) {
5779 status_id = SIPE_STATUS_ID_AWAY;
5780 } else {
5781 status_id = SIPE_STATUS_ID_AVAILABLE;
5784 if (availablity < 100)
5785 status_id = SIPE_STATUS_ID_OFFLINE;
5787 if (activity_desc && act) {
5788 g_free(*activity_desc);
5789 *activity_desc = g_strdup(act);
5792 return status_id;
5796 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5798 static const char*
5799 sipe_get_status_by_availability(int avail,
5800 char** activity_desc)
5802 const char *status;
5803 const char *act = NULL;
5805 if (avail < 3000) {
5806 status = SIPE_STATUS_ID_OFFLINE;
5807 } else if (avail < 4500) {
5808 status = SIPE_STATUS_ID_AVAILABLE;
5809 } else if (avail < 6000) {
5810 //status = SIPE_STATUS_ID_IDLE;
5811 status = SIPE_STATUS_ID_AVAILABLE;
5812 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5813 } else if (avail < 7500) {
5814 status = SIPE_STATUS_ID_BUSY;
5815 } else if (avail < 9000) {
5816 //status = SIPE_STATUS_ID_BUSYIDLE;
5817 status = SIPE_STATUS_ID_BUSY;
5818 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5819 } else if (avail < 12000) {
5820 status = SIPE_STATUS_ID_DND;
5821 } else if (avail < 15000) {
5822 status = SIPE_STATUS_ID_BRB;
5823 } else if (avail < 18000) {
5824 status = SIPE_STATUS_ID_AWAY;
5825 } else {
5826 status = SIPE_STATUS_ID_OFFLINE;
5829 if (activity_desc && act) {
5830 g_free(*activity_desc);
5831 *activity_desc = g_strdup(act);
5834 return status;
5838 * Returns 2007-style availability value
5840 * @param sipe_status_id (in)
5841 * @param activity_token (out) Must be g_free()'d after use if consumed.
5843 static int
5844 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5846 int availability;
5847 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5849 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5850 availability = 15500;
5851 if (!activity_token || !(*activity_token)) {
5852 activity = SIPE_ACTIVITY_AWAY;
5854 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5855 availability = 12500;
5856 activity = SIPE_ACTIVITY_BRB;
5857 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5858 availability = 9500;
5859 activity = SIPE_ACTIVITY_DND;
5860 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5861 availability = 6500;
5862 if (!activity_token || !(*activity_token)) {
5863 activity = SIPE_ACTIVITY_BUSY;
5865 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5866 availability = 3500;
5867 activity = SIPE_ACTIVITY_ONLINE;
5868 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5869 availability = 0;
5870 } else {
5871 // Offline or invisible
5872 availability = 18500;
5873 activity = SIPE_ACTIVITY_OFFLINE;
5876 if (activity_token) {
5877 *activity_token = g_strdup(sipe_activity_map[activity].token);
5879 return availability;
5882 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5884 const char *uri;
5885 sipe_xml *xn_categories;
5886 const sipe_xml *xn_category;
5887 const char *status = NULL;
5888 gboolean do_update_status = FALSE;
5889 gboolean has_note_cleaned = FALSE;
5890 gboolean has_free_busy_cleaned = FALSE;
5892 xn_categories = sipe_xml_parse(data, len);
5893 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
5895 for (xn_category = sipe_xml_child(xn_categories, "category");
5896 xn_category ;
5897 xn_category = sipe_xml_twin(xn_category) )
5899 const sipe_xml *xn_node;
5900 const char *tmp;
5901 const char *attrVar = sipe_xml_attribute(xn_category, "name");
5902 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
5903 sipe_utils_str_to_time(tmp) : 0;
5905 /* contactCard */
5906 if (sipe_strequal(attrVar, "contactCard"))
5908 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
5910 if (card) {
5911 const sipe_xml *node;
5912 /* identity - Display Name and email */
5913 node = sipe_xml_child(card, "identity");
5914 if (node) {
5915 char* display_name = sipe_xml_data(
5916 sipe_xml_child(node, "name/displayName"));
5917 char* email = sipe_xml_data(
5918 sipe_xml_child(node, "email"));
5920 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5921 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5923 g_free(display_name);
5924 g_free(email);
5926 /* company */
5927 node = sipe_xml_child(card, "company");
5928 if (node) {
5929 char* company = sipe_xml_data(node);
5930 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5931 g_free(company);
5933 /* department */
5934 node = sipe_xml_child(card, "department");
5935 if (node) {
5936 char* department = sipe_xml_data(node);
5937 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5938 g_free(department);
5940 /* title */
5941 node = sipe_xml_child(card, "title");
5942 if (node) {
5943 char* title = sipe_xml_data(node);
5944 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5945 g_free(title);
5947 /* office */
5948 node = sipe_xml_child(card, "office");
5949 if (node) {
5950 char* office = sipe_xml_data(node);
5951 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5952 g_free(office);
5954 /* site (url) */
5955 node = sipe_xml_child(card, "url");
5956 if (node) {
5957 char* site = sipe_xml_data(node);
5958 sipe_update_user_info(sip, uri, SITE_PROP, site);
5959 g_free(site);
5961 /* phone */
5962 for (node = sipe_xml_child(card, "phone");
5963 node;
5964 node = sipe_xml_twin(node))
5966 const char *phone_type = sipe_xml_attribute(node, "type");
5967 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
5968 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
5970 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5972 g_free(phone);
5973 g_free(phone_display_string);
5975 /* address */
5976 for (node = sipe_xml_child(card, "address");
5977 node;
5978 node = sipe_xml_twin(node))
5980 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
5981 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
5982 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
5983 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
5984 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
5985 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
5987 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5988 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5989 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5990 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5991 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5993 g_free(street);
5994 g_free(city);
5995 g_free(state);
5996 g_free(zipcode);
5997 g_free(country_code);
5999 break;
6004 /* note */
6005 else if (sipe_strequal(attrVar, "note"))
6007 if (uri) {
6008 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6010 if (!has_note_cleaned) {
6011 has_note_cleaned = TRUE;
6013 g_free(sbuddy->note);
6014 sbuddy->note = NULL;
6015 sbuddy->is_oof_note = FALSE;
6016 sbuddy->note_since = publish_time;
6018 do_update_status = TRUE;
6020 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6021 /* clean up in case no 'note' element is supplied
6022 * which indicate note removal in client
6024 g_free(sbuddy->note);
6025 sbuddy->note = NULL;
6026 sbuddy->is_oof_note = FALSE;
6027 sbuddy->note_since = publish_time;
6029 xn_node = sipe_xml_child(xn_category, "note/body");
6030 if (xn_node) {
6031 char *tmp;
6032 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6033 g_free(tmp);
6034 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6035 sbuddy->note_since = publish_time;
6037 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
6038 uri, sbuddy->note ? sbuddy->note : "");
6040 /* to trigger UI refresh in case no status info is supplied in this update */
6041 do_update_status = TRUE;
6045 /* state */
6046 else if(sipe_strequal(attrVar, "state"))
6048 char *tmp;
6049 int availability;
6050 const sipe_xml *xn_availability;
6051 const sipe_xml *xn_activity;
6052 const sipe_xml *xn_meeting_subject;
6053 const sipe_xml *xn_meeting_location;
6054 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6056 xn_node = sipe_xml_child(xn_category, "state");
6057 if (!xn_node) continue;
6058 xn_availability = sipe_xml_child(xn_node, "availability");
6059 if (!xn_availability) continue;
6060 xn_activity = sipe_xml_child(xn_node, "activity");
6061 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6062 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6064 tmp = sipe_xml_data(xn_availability);
6065 availability = atoi(tmp);
6066 g_free(tmp);
6068 /* activity, meeting_subject, meeting_location */
6069 if (sbuddy) {
6070 char *tmp = NULL;
6072 /* activity */
6073 g_free(sbuddy->activity);
6074 sbuddy->activity = NULL;
6075 if (xn_activity) {
6076 const char *token = sipe_xml_attribute(xn_activity, "token");
6077 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6079 /* from token */
6080 if (!is_empty(token)) {
6081 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6083 /* from custom element */
6084 if (xn_custom) {
6085 char *custom = sipe_xml_data(xn_custom);
6087 if (!is_empty(custom)) {
6088 sbuddy->activity = custom;
6089 custom = NULL;
6091 g_free(custom);
6094 /* meeting_subject */
6095 g_free(sbuddy->meeting_subject);
6096 sbuddy->meeting_subject = NULL;
6097 if (xn_meeting_subject) {
6098 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6100 if (!is_empty(meeting_subject)) {
6101 sbuddy->meeting_subject = meeting_subject;
6102 meeting_subject = NULL;
6104 g_free(meeting_subject);
6106 /* meeting_location */
6107 g_free(sbuddy->meeting_location);
6108 sbuddy->meeting_location = NULL;
6109 if (xn_meeting_location) {
6110 char *meeting_location = sipe_xml_data(xn_meeting_location);
6112 if (!is_empty(meeting_location)) {
6113 sbuddy->meeting_location = meeting_location;
6114 meeting_location = NULL;
6116 g_free(meeting_location);
6119 status = sipe_get_status_by_availability(availability, &tmp);
6120 if (sbuddy->activity && tmp) {
6121 char *tmp2 = sbuddy->activity;
6123 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6124 g_free(tmp);
6125 g_free(tmp2);
6126 } else if (tmp) {
6127 sbuddy->activity = tmp;
6131 do_update_status = TRUE;
6133 /* calendarData */
6134 else if(sipe_strequal(attrVar, "calendarData"))
6136 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6137 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6138 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6140 if (sbuddy && xn_free_busy) {
6141 if (!has_free_busy_cleaned) {
6142 has_free_busy_cleaned = TRUE;
6144 g_free(sbuddy->cal_start_time);
6145 sbuddy->cal_start_time = NULL;
6147 g_free(sbuddy->cal_free_busy_base64);
6148 sbuddy->cal_free_busy_base64 = NULL;
6150 g_free(sbuddy->cal_free_busy);
6151 sbuddy->cal_free_busy = NULL;
6153 sbuddy->cal_free_busy_published = publish_time;
6156 if (publish_time >= sbuddy->cal_free_busy_published) {
6157 g_free(sbuddy->cal_start_time);
6158 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6160 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6161 15 : 0;
6163 g_free(sbuddy->cal_free_busy_base64);
6164 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6166 g_free(sbuddy->cal_free_busy);
6167 sbuddy->cal_free_busy = NULL;
6169 sbuddy->cal_free_busy_published = publish_time;
6171 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);
6175 if (sbuddy && xn_working_hours) {
6176 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6181 if (do_update_status) {
6182 if (!status) { /* no status category in this update, using contact's current status */
6183 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6184 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6185 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6186 status = purple_status_get_id(pstatus);
6189 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
6190 sipe_got_user_status(sip, uri, status);
6193 sipe_xml_free(xn_categories);
6196 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6198 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6199 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
6200 payload->host = g_strdup(host);
6201 payload->buddies = server;
6202 sipe_subscribe_presence_batched_routed(sip, payload);
6203 sipe_subscribe_presence_batched_routed_free(payload);
6206 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6208 xmlnode *xn_list;
6209 xmlnode *xn_resource;
6210 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6211 g_free, NULL);
6212 GSList *server;
6213 gchar *host;
6215 xn_list = xmlnode_from_str(data, len);
6217 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6218 xn_resource;
6219 xn_resource = xmlnode_get_next_twin(xn_resource) )
6221 const char *uri, *state;
6222 xmlnode *xn_instance;
6224 xn_instance = xmlnode_get_child(xn_resource, "instance");
6225 if (!xn_instance) continue;
6227 uri = xmlnode_get_attrib(xn_resource, "uri");
6228 state = xmlnode_get_attrib(xn_instance, "state");
6229 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6231 if (strstr(state, "resubscribe")) {
6232 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6234 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6235 gchar *user = g_strdup(uri);
6236 host = g_strdup(poolFqdn);
6237 server = g_hash_table_lookup(servers, host);
6238 server = g_slist_append(server, user);
6239 g_hash_table_insert(servers, host, server);
6240 } else {
6241 sipe_subscribe_presence_single(sip, (void *) uri);
6246 /* Send out any deferred poolFqdn subscriptions */
6247 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6248 g_hash_table_destroy(servers);
6250 xmlnode_free(xn_list);
6253 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6255 gchar *uri;
6256 gchar *getbasic;
6257 gchar *activity = NULL;
6258 xmlnode *pidf;
6259 xmlnode *basicstatus = NULL, *tuple, *status;
6260 gboolean isonline = FALSE;
6261 xmlnode *display_name_node;
6263 pidf = xmlnode_from_str(data, len);
6264 if (!pidf) {
6265 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6266 return;
6269 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6271 if ((status = xmlnode_get_child(tuple, "status"))) {
6272 basicstatus = xmlnode_get_child(status, "basic");
6276 if (!basicstatus) {
6277 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6278 xmlnode_free(pidf);
6279 return;
6282 getbasic = xmlnode_get_data(basicstatus);
6283 if (!getbasic) {
6284 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6285 xmlnode_free(pidf);
6286 return;
6289 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6290 if (strstr(getbasic, "open")) {
6291 isonline = TRUE;
6293 g_free(getbasic);
6295 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6297 display_name_node = xmlnode_get_child(pidf, "display-name");
6298 if (display_name_node) {
6299 char * display_name = xmlnode_get_data(display_name_node);
6301 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6302 g_free(display_name);
6305 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6306 if ((status = xmlnode_get_child(tuple, "status"))) {
6307 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6308 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6309 activity = xmlnode_get_data(basicstatus);
6310 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6316 if (isonline) {
6317 const gchar * status_id = NULL;
6318 if (activity) {
6319 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6320 status_id = SIPE_STATUS_ID_BUSY;
6321 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6322 status_id = SIPE_STATUS_ID_AWAY;
6326 if (!status_id) {
6327 status_id = SIPE_STATUS_ID_AVAILABLE;
6330 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6331 sipe_got_user_status(sip, uri, status_id);
6332 } else {
6333 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6336 g_free(activity);
6337 g_free(uri);
6338 xmlnode_free(pidf);
6341 /** 2005 */
6342 static void
6343 sipe_user_info_has_updated(struct sipe_account_data *sip,
6344 xmlnode *xn_userinfo)
6346 xmlnode *xn_states;
6348 g_free(sip->user_states);
6349 sip->user_states = NULL;
6350 if ((xn_states = xmlnode_get_child(xn_userinfo, "states")) != NULL) {
6351 sip->user_states = xmlnode_to_str(xn_states, NULL);
6352 /* this is a hack-around to remove added newline after inner element,
6353 * state in this case, where it shouldn't be.
6354 * After several use of xmlnode_to_str, amount of added newlines
6355 * grows significantly.
6357 purple_str_strip_char(sip->user_states, '\n');
6358 //purple_str_strip_char(sip->user_states, '\r');
6361 /* Publish initial state if not yet.
6362 * Assuming this happens on initial responce to self subscription
6363 * so we've already updated our UserInfo.
6365 if (!sip->initial_state_published) {
6366 send_presence_soap(sip, FALSE);
6367 /* dalayed run */
6368 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6372 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6374 char *activity = NULL;
6375 const char *epid;
6376 const char *status_id = NULL;
6377 const char *name;
6378 char *uri;
6379 char *self_uri = sip_uri_self(sip);
6380 int avl;
6381 int act;
6382 const char *device_name = NULL;
6383 const char *cal_start_time = NULL;
6384 const char *cal_granularity = NULL;
6385 char *cal_free_busy_base64 = NULL;
6386 struct sipe_buddy *sbuddy;
6387 xmlnode *node;
6388 xmlnode *xn_presentity;
6389 xmlnode *xn_availability;
6390 xmlnode *xn_activity;
6391 xmlnode *xn_display_name;
6392 xmlnode *xn_email;
6393 xmlnode *xn_phone_number;
6394 xmlnode *xn_userinfo;
6395 xmlnode *xn_note;
6396 xmlnode *xn_oof;
6397 xmlnode *xn_state;
6398 xmlnode *xn_contact;
6399 char *note;
6400 char *free_activity;
6401 int user_avail;
6402 const char *user_avail_nil;
6403 int res_avail;
6404 time_t user_avail_since = 0;
6405 time_t activity_since = 0;
6407 /* fix for Reuters environment on Linux */
6408 if (data && strstr(data, "encoding=\"utf-16\"")) {
6409 char *tmp_data;
6410 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6411 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6412 g_free(tmp_data);
6413 } else {
6414 xn_presentity = xmlnode_from_str(data, len);
6417 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6418 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6419 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6420 xn_email = xmlnode_get_child(xn_presentity, "email");
6421 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6422 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6423 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6424 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6425 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6426 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6427 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6428 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6429 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6430 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6432 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6433 user_avail = 0;
6434 user_avail_since = 0;
6437 free_activity = NULL;
6439 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6440 uri = sip_uri_from_name(name);
6441 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6442 epid = xmlnode_get_attrib(xn_availability, "epid");
6443 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6445 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6446 res_avail = sipe_get_availability_by_status(status_id, NULL);
6447 if (user_avail > res_avail) {
6448 res_avail = user_avail;
6449 status_id = sipe_get_status_by_availability(user_avail, NULL);
6452 if (xn_display_name) {
6453 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6454 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6455 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6456 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6457 char *tel_uri = sip_to_tel_uri(phone_number);
6459 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6460 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6461 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6462 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6464 g_free(tel_uri);
6465 g_free(phone_label);
6466 g_free(phone_number);
6467 g_free(email);
6468 g_free(display_name);
6471 if (xn_contact) {
6472 /* tel */
6473 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6475 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6476 const char *phone_type = xmlnode_get_attrib(node, "type");
6477 char* phone = xmlnode_get_data(node);
6479 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6481 g_free(phone);
6485 /* devicePresence */
6486 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6487 xmlnode *xn_device_name;
6488 xmlnode *xn_calendar_info;
6489 xmlnode *xn_state;
6490 char *state;
6492 /* deviceName */
6493 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6494 xn_device_name = xmlnode_get_child(node, "deviceName");
6495 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6498 /* calendarInfo */
6499 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6500 if (xn_calendar_info) {
6501 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6503 if (cal_start_time) {
6504 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6505 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6507 if (cal_start_time_t_tmp > cal_start_time_t) {
6508 cal_start_time = cal_start_time_tmp;
6509 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6510 g_free(cal_free_busy_base64);
6511 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6513 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);
6515 } else {
6516 cal_start_time = cal_start_time_tmp;
6517 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6518 g_free(cal_free_busy_base64);
6519 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6521 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);
6525 /* state */
6526 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6527 if (xn_state) {
6528 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6529 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6531 state = xmlnode_get_data(xn_state);
6532 if (dev_avail_since > user_avail_since &&
6533 dev_avail >= res_avail)
6535 res_avail = dev_avail;
6536 if (!is_empty(state))
6538 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6539 g_free(activity);
6540 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6541 } else if (sipe_strequal(state, "presenting")) {
6542 g_free(activity);
6543 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6544 } else {
6545 activity = state;
6546 state = NULL;
6548 activity_since = dev_avail_since;
6550 status_id = sipe_get_status_by_availability(res_avail, &activity);
6552 g_free(state);
6556 /* oof */
6557 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6558 g_free(activity);
6559 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6560 activity_since = 0;
6563 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6564 if (sbuddy)
6566 g_free(sbuddy->activity);
6567 sbuddy->activity = activity;
6568 activity = NULL;
6570 sbuddy->activity_since = activity_since;
6572 sbuddy->user_avail = user_avail;
6573 sbuddy->user_avail_since = user_avail_since;
6575 g_free(sbuddy->note);
6576 sbuddy->note = NULL;
6577 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6579 sbuddy->is_oof_note = (xn_oof != NULL);
6581 g_free(sbuddy->device_name);
6582 sbuddy->device_name = NULL;
6583 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6585 if (!is_empty(cal_free_busy_base64)) {
6586 g_free(sbuddy->cal_start_time);
6587 sbuddy->cal_start_time = g_strdup(cal_start_time);
6589 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6591 g_free(sbuddy->cal_free_busy_base64);
6592 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6593 cal_free_busy_base64 = NULL;
6595 g_free(sbuddy->cal_free_busy);
6596 sbuddy->cal_free_busy = NULL;
6599 sbuddy->last_non_cal_status_id = status_id;
6600 g_free(sbuddy->last_non_cal_activity);
6601 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6603 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6604 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6606 sip->is_oof_note = sbuddy->is_oof_note;
6608 g_free(sip->note);
6609 sip->note = g_strdup(sbuddy->note);
6611 sip->note_since = time(NULL);
6614 g_free(sip->status);
6615 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6618 g_free(cal_free_busy_base64);
6619 g_free(activity);
6621 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6622 sipe_got_user_status(sip, uri, status_id);
6624 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6625 sipe_user_info_has_updated(sip, xn_userinfo);
6628 g_free(note);
6629 xmlnode_free(xn_presentity);
6630 g_free(uri);
6631 g_free(self_uri);
6634 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6636 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6638 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6640 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6641 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6643 const char *content = msg->body;
6644 unsigned length = msg->bodylen;
6645 PurpleMimeDocument *mime = NULL;
6647 if (strstr(ctype, "multipart"))
6649 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6650 const char *content_type;
6651 GList* parts;
6652 mime = purple_mime_document_parse(doc);
6653 parts = purple_mime_document_get_parts(mime);
6654 while(parts) {
6655 content = purple_mime_part_get_data(parts->data);
6656 length = purple_mime_part_get_length(parts->data);
6657 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6658 if(content_type && strstr(content_type,"application/rlmi+xml"))
6660 process_incoming_notify_rlmi_resub(sip, content, length);
6662 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6664 process_incoming_notify_msrtc(sip, content, length);
6666 else
6668 process_incoming_notify_rlmi(sip, content, length);
6670 parts = parts->next;
6672 g_free(doc);
6674 if (mime)
6676 purple_mime_document_free(mime);
6679 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6681 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6683 else if(strstr(ctype, "application/rlmi+xml"))
6685 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6688 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6690 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6692 else
6694 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6698 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6700 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6701 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6703 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6705 if (ctype &&
6706 strstr(ctype, "multipart") &&
6707 (strstr(ctype, "application/rlmi+xml") ||
6708 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6709 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6710 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6711 GList *parts = purple_mime_document_get_parts(mime);
6712 GSList *buddies = NULL;
6713 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6715 while (parts) {
6716 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6717 purple_mime_part_get_length(parts->data));
6719 if (xml && !sipe_strequal(xml->name, "list")) {
6720 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6722 buddies = g_slist_append(buddies, uri);
6724 xmlnode_free(xml);
6726 parts = parts->next;
6728 g_free(doc);
6729 if (mime) purple_mime_document_free(mime);
6731 payload->host = g_strdup(who);
6732 payload->buddies = buddies;
6733 sipe_schedule_action(action_name, timeout,
6734 sipe_subscribe_presence_batched_routed,
6735 sipe_subscribe_presence_batched_routed_free,
6736 sip, payload);
6737 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6739 } else {
6740 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6741 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6743 g_free(action_name);
6747 * Dispatcher for all incoming subscription information
6748 * whether it comes from NOTIFY, BENOTIFY requests or
6749 * piggy-backed to subscription's OK responce.
6751 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6752 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6754 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6756 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6757 const gchar *event = sipmsg_find_header(msg, "Event");
6758 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6759 char *tmp;
6761 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6762 event ? event : "",
6763 tmp = fix_newlines(msg->body));
6764 g_free(tmp);
6765 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6767 /* implicit subscriptions */
6768 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6769 sipe_process_imdn(sip, msg);
6772 if (event) {
6773 /* for one off subscriptions (send with Expire: 0) */
6774 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
6776 sipe_process_provisioning_v2(sip, msg);
6778 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
6780 sipe_process_provisioning(sip, msg);
6782 else if (sipe_strcase_equal(event, "presence"))
6784 sipe_process_presence(sip, msg);
6786 else if (sipe_strcase_equal(event, "registration-notify"))
6788 sipe_process_registration_notify(sip, msg);
6791 if (!subscription_state || strstr(subscription_state, "active"))
6793 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
6795 sipe_process_roaming_contacts(sip, msg);
6797 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
6799 sipe_process_roaming_self(sip, msg);
6801 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
6803 sipe_process_roaming_acl(sip, msg);
6805 else if (sipe_strcase_equal(event, "presence.wpending"))
6807 sipe_process_presence_wpending(sip, msg);
6809 else if (sipe_strcase_equal(event, "conference"))
6811 sipe_process_conference(sip, msg);
6816 /* The server sends status 'terminated' */
6817 if (subscription_state && strstr(subscription_state, "terminated") ) {
6818 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6819 gchar *key = sipe_get_subscription_key(event, who);
6821 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6822 g_free(who);
6824 if (g_hash_table_lookup(sip->subscriptions, key)) {
6825 g_hash_table_remove(sip->subscriptions, key);
6826 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6829 g_free(key);
6832 if (!request && event) {
6833 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6834 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6835 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6837 if (timeout) {
6838 /* 2 min ahead of expiration */
6839 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6841 if (sipe_strcase_equal(event, "presence.wpending") &&
6842 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6844 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6845 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6846 g_free(action_name);
6848 else if (sipe_strcase_equal(event, "presence") &&
6849 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6851 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6852 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6854 if (sip->batched_support) {
6855 sipe_process_presence_timeout(sip, msg, who, timeout);
6857 else {
6858 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6859 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6861 g_free(action_name);
6862 g_free(who);
6867 /* The client responses on received a NOTIFY message */
6868 if (request && !benotify)
6870 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6875 * Whether user manually changed status or
6876 * it was changed automatically due to user
6877 * became inactive/active again
6879 static gboolean
6880 sipe_is_user_state(struct sipe_account_data *sip)
6882 gboolean res;
6883 time_t now = time(NULL);
6885 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6886 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6888 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6890 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6891 return res;
6894 static void
6895 send_presence_soap0(struct sipe_account_data *sip,
6896 gboolean do_publish_calendar,
6897 gboolean do_reset_status)
6899 struct sipe_ews* ews = sip->ews;
6900 int availability = 0;
6901 int activity = 0;
6902 gchar *body;
6903 gchar *tmp;
6904 gchar *tmp2 = NULL;
6905 gchar *res_note = NULL;
6906 gchar *res_oof = NULL;
6907 const gchar *note_pub = NULL;
6908 gchar *states = NULL;
6909 gchar *calendar_data = NULL;
6910 gchar *epid = get_epid(sip);
6911 time_t now = time(NULL);
6912 gchar *since_time_str = sipe_utils_time_to_str(now);
6913 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6914 const char *user_input;
6915 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6917 if (oof_note && sip->note) {
6918 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6919 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6922 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6924 if (!sip->initial_state_published ||
6925 do_reset_status)
6927 g_free(sip->status);
6928 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6931 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6933 /* Note */
6934 if (pub_oof) {
6935 note_pub = oof_note;
6936 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6937 ews->published = TRUE;
6938 } else if (sip->note) {
6939 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6940 g_free(sip->note);
6941 sip->note = NULL;
6942 sip->is_oof_note = FALSE;
6943 sip->note_since = 0;
6944 } else {
6945 note_pub = sip->note;
6946 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6950 if (note_pub)
6952 /* to protocol internal plain text format */
6953 tmp = purple_markup_strip_html(note_pub);
6954 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6955 g_free(tmp);
6958 /* User State */
6959 if (!do_reset_status) {
6960 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6962 gchar *activity_token = NULL;
6963 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6965 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6966 avail_2007,
6967 since_time_str,
6968 epid,
6969 activity_token);
6970 g_free(activity_token);
6972 else /* preserve existing publication */
6974 if (sip->user_states) {
6975 states = g_strdup(sip->user_states);
6978 } else {
6979 /* do nothing - then User state will be erased */
6981 sip->initial_state_published = TRUE;
6983 /* CalendarInfo */
6984 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6986 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6987 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6988 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6989 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6990 fb_start_str,
6991 free_busy_base64);
6992 g_free(fb_start_str);
6993 g_free(free_busy_base64);
6996 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6998 /* forming resulting XML */
6999 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7000 sip->username,
7001 availability,
7002 activity,
7003 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7004 res_note ? res_note : "",
7005 res_oof ? res_oof : "",
7006 states ? states : "",
7007 calendar_data ? calendar_data : "",
7008 epid,
7009 since_time_str,
7010 since_time_str,
7011 user_input);
7012 g_free(tmp);
7013 g_free(tmp2);
7014 g_free(res_note);
7015 g_free(states);
7016 g_free(calendar_data);
7018 send_soap_request(sip, body);
7020 g_free(body);
7021 g_free(since_time_str);
7022 g_free(epid);
7025 void
7026 send_presence_soap(struct sipe_account_data *sip,
7027 gboolean do_publish_calendar)
7029 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7033 static gboolean
7034 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7035 struct sipmsg *msg,
7036 struct transaction *trans)
7038 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7040 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7041 xmlnode *xml;
7042 xmlnode *node;
7043 gchar *fault_code;
7044 GHashTable *faults;
7045 int index_our;
7046 gboolean has_device_publication = FALSE;
7048 xml = xmlnode_from_str(msg->body, msg->bodylen);
7050 /* test if version mismatch fault */
7051 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7052 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7053 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
7054 g_free(fault_code);
7055 xmlnode_free(xml);
7056 return TRUE;
7058 g_free(fault_code);
7060 /* accumulating information about faulty versions */
7061 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7062 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7063 node;
7064 node = xmlnode_get_next_twin(node))
7066 const gchar *index = xmlnode_get_attrib(node, "index");
7067 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7069 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7070 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
7072 xmlnode_free(xml);
7074 /* here we are parsing own request to figure out what publication
7075 * referensed here only by index went wrong
7077 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7079 /* publication */
7080 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7081 index_our = 1; /* starts with 1 - our first publication */
7082 node;
7083 node = xmlnode_get_next_twin(node), index_our++)
7085 gchar *idx = g_strdup_printf("%d", index_our);
7086 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7087 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7088 g_free(idx);
7090 if (sipe_strequal("device", categoryName)) {
7091 has_device_publication = TRUE;
7094 if (curVersion) { /* fault exist on this index */
7095 const gchar *container = xmlnode_get_attrib(node, "container");
7096 const gchar *instance = xmlnode_get_attrib(node, "instance");
7097 /* key is <category><instance><container> */
7098 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7099 struct sipe_publication *publication =
7100 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
7102 purple_debug_info("sipe", "key is %s\n", key);
7104 if (publication) {
7105 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
7106 key, curVersion, publication->version);
7107 /* updating publication's version to the correct one */
7108 publication->version = atoi(curVersion);
7110 g_free(key);
7113 xmlnode_free(xml);
7114 g_hash_table_destroy(faults);
7116 /* rebublishing with right versions */
7117 if (has_device_publication) {
7118 send_publish_category_initial(sip);
7119 } else {
7120 send_presence_status(sip);
7123 return TRUE;
7127 * Returns 'device' XML part for publication.
7128 * Must be g_free'd after use.
7130 static gchar *
7131 sipe_publish_get_category_device(struct sipe_account_data *sip)
7133 gchar *uri;
7134 gchar *doc;
7135 gchar *epid = get_epid(sip);
7136 gchar *uuid = generateUUIDfromEPID(epid);
7137 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7138 /* key is <category><instance><container> */
7139 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7140 struct sipe_publication *publication =
7141 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7143 g_free(key);
7144 g_free(epid);
7146 uri = sip_uri_self(sip);
7147 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7148 device_instance,
7149 publication ? publication->version : 0,
7150 uuid,
7151 uri,
7152 "00:00:00+01:00", /* @TODO make timezone real*/
7153 g_get_host_name()
7156 g_free(uri);
7157 g_free(uuid);
7159 return doc;
7163 * A service method - use
7164 * - send_publish_get_category_state_machine and
7165 * - send_publish_get_category_state_user instead.
7166 * Must be g_free'd after use.
7168 static gchar *
7169 sipe_publish_get_category_state(struct sipe_account_data *sip,
7170 gboolean is_user_state)
7172 int availability = sipe_get_availability_by_status(sip->status, NULL);
7173 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7174 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7175 /* key is <category><instance><container> */
7176 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7177 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7178 struct sipe_publication *publication_2 =
7179 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7180 struct sipe_publication *publication_3 =
7181 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7183 g_free(key_2);
7184 g_free(key_3);
7186 if (publication_2 && (publication_2->availability == availability))
7188 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
7189 return NULL; /* nothing to update */
7192 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7193 instance,
7194 publication_2 ? publication_2->version : 0,
7195 availability,
7196 instance,
7197 publication_3 ? publication_3->version : 0,
7198 availability);
7202 * Only Busy and OOF calendar event are published.
7203 * Different instances are used for that.
7205 * Must be g_free'd after use.
7207 static gchar *
7208 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7209 struct sipe_cal_event *event,
7210 const char *uri,
7211 int cal_satus)
7213 gchar *start_time_str;
7214 int availability = 0;
7215 gchar *res;
7216 gchar *tmp = NULL;
7217 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7218 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7219 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7221 /* key is <category><instance><container> */
7222 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7223 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7224 struct sipe_publication *publication_2 =
7225 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7226 struct sipe_publication *publication_3 =
7227 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7229 g_free(key_2);
7230 g_free(key_3);
7232 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7233 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7234 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7235 return NULL;
7238 if (event &&
7239 publication_3 &&
7240 (publication_3->availability == availability) &&
7241 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7243 g_free(tmp);
7244 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7245 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7246 return NULL; /* nothing to update */
7248 g_free(tmp);
7250 if (event &&
7251 (event->cal_status == SIPE_CAL_BUSY ||
7252 event->cal_status == SIPE_CAL_OOF))
7254 gchar *availability_xml_str = NULL;
7255 gchar *activity_xml_str = NULL;
7257 if (event->cal_status == SIPE_CAL_BUSY) {
7258 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7261 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7262 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7263 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7264 "minAvailability=\"6500\"",
7265 "maxAvailability=\"8999\"");
7266 } else if (event->cal_status == SIPE_CAL_OOF) {
7267 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7268 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7269 "minAvailability=\"12000\"",
7270 "");
7272 start_time_str = sipe_utils_time_to_str(event->start_time);
7274 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7275 instance,
7276 publication_2 ? publication_2->version : 0,
7277 uri,
7278 start_time_str,
7279 availability_xml_str ? availability_xml_str : "",
7280 activity_xml_str ? activity_xml_str : "",
7281 event->subject ? event->subject : "",
7282 event->location ? event->location : "",
7284 instance,
7285 publication_3 ? publication_3->version : 0,
7286 uri,
7287 start_time_str,
7288 availability_xml_str ? availability_xml_str : "",
7289 activity_xml_str ? activity_xml_str : "",
7290 event->subject ? event->subject : "",
7291 event->location ? event->location : ""
7293 g_free(start_time_str);
7294 g_free(availability_xml_str);
7295 g_free(activity_xml_str);
7298 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7300 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7301 instance,
7302 publication_2 ? publication_2->version : 0,
7304 instance,
7305 publication_3 ? publication_3->version : 0
7309 return res;
7313 * Returns 'machineState' XML part for publication.
7314 * Must be g_free'd after use.
7316 static gchar *
7317 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7319 return sipe_publish_get_category_state(sip, FALSE);
7323 * Returns 'userState' XML part for publication.
7324 * Must be g_free'd after use.
7326 static gchar *
7327 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7329 return sipe_publish_get_category_state(sip, TRUE);
7333 * Returns 'note' XML part for publication.
7334 * Must be g_free'd after use.
7336 * Protocol format for Note is plain text.
7338 * @param note a note in Sipe internal HTML format
7339 * @param note_type either personal or OOF
7341 static gchar *
7342 sipe_publish_get_category_note(struct sipe_account_data *sip,
7343 const char *note, /* html */
7344 const char *note_type,
7345 time_t note_start,
7346 time_t note_end)
7348 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7349 /* key is <category><instance><container> */
7350 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7351 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7352 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7354 struct sipe_publication *publication_note_200 =
7355 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7356 struct sipe_publication *publication_note_300 =
7357 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7358 struct sipe_publication *publication_note_400 =
7359 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7361 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7362 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7363 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7364 char *res, *tmp1, *tmp2, *tmp3;
7365 char *start_time_attr;
7366 char *end_time_attr;
7368 g_free(tmp);
7369 tmp = NULL;
7370 g_free(key_note_200);
7371 g_free(key_note_300);
7372 g_free(key_note_400);
7374 /* we even need to republish empty note */
7375 if (sipe_strequal(n1, n2))
7377 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7378 g_free(n1);
7379 return NULL; /* nothing to update */
7382 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7383 g_free(tmp);
7384 tmp = NULL;
7385 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7386 g_free(tmp);
7388 if (n1) {
7389 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7390 instance,
7391 200,
7392 publication_note_200 ? publication_note_200->version : 0,
7393 note_type,
7394 start_time_attr ? start_time_attr : "",
7395 end_time_attr ? end_time_attr : "",
7396 n1);
7398 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7399 instance,
7400 300,
7401 publication_note_300 ? publication_note_300->version : 0,
7402 note_type,
7403 start_time_attr ? start_time_attr : "",
7404 end_time_attr ? end_time_attr : "",
7405 n1);
7407 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7408 instance,
7409 400,
7410 publication_note_400 ? publication_note_400->version : 0,
7411 note_type,
7412 start_time_attr ? start_time_attr : "",
7413 end_time_attr ? end_time_attr : "",
7414 n1);
7415 } else {
7416 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7417 "note",
7418 instance,
7419 200,
7420 publication_note_200 ? publication_note_200->version : 0,
7421 "static");
7422 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7423 "note",
7424 instance,
7425 300,
7426 publication_note_200 ? publication_note_200->version : 0,
7427 "static");
7428 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7429 "note",
7430 instance,
7431 400,
7432 publication_note_200 ? publication_note_200->version : 0,
7433 "static");
7435 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7437 g_free(start_time_attr);
7438 g_free(end_time_attr);
7439 g_free(tmp1);
7440 g_free(tmp2);
7441 g_free(tmp3);
7442 g_free(n1);
7444 return res;
7448 * Returns 'calendarData' XML part with WorkingHours for publication.
7449 * Must be g_free'd after use.
7451 static gchar *
7452 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7454 struct sipe_ews* ews = sip->ews;
7456 /* key is <category><instance><container> */
7457 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7458 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7459 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7460 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7461 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7462 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7464 struct sipe_publication *publication_cal_1 =
7465 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7466 struct sipe_publication *publication_cal_100 =
7467 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7468 struct sipe_publication *publication_cal_200 =
7469 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7470 struct sipe_publication *publication_cal_300 =
7471 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7472 struct sipe_publication *publication_cal_400 =
7473 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7474 struct sipe_publication *publication_cal_32000 =
7475 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7477 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7478 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7480 g_free(key_cal_1);
7481 g_free(key_cal_100);
7482 g_free(key_cal_200);
7483 g_free(key_cal_300);
7484 g_free(key_cal_400);
7485 g_free(key_cal_32000);
7487 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7488 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7489 return NULL;
7492 if (sipe_strequal(n1, n2))
7494 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7495 return NULL; /* nothing to update */
7498 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7499 /* 1 */
7500 publication_cal_1 ? publication_cal_1->version : 0,
7501 ews->email,
7502 ews->working_hours_xml_str,
7503 /* 100 - Public */
7504 publication_cal_100 ? publication_cal_100->version : 0,
7505 /* 200 - Company */
7506 publication_cal_200 ? publication_cal_200->version : 0,
7507 ews->email,
7508 ews->working_hours_xml_str,
7509 /* 300 - Team */
7510 publication_cal_300 ? publication_cal_300->version : 0,
7511 ews->email,
7512 ews->working_hours_xml_str,
7513 /* 400 - Personal */
7514 publication_cal_400 ? publication_cal_400->version : 0,
7515 ews->email,
7516 ews->working_hours_xml_str,
7517 /* 32000 - Blocked */
7518 publication_cal_32000 ? publication_cal_32000->version : 0
7523 * Returns 'calendarData' XML part with FreeBusy for publication.
7524 * Must be g_free'd after use.
7526 static gchar *
7527 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7529 struct sipe_ews* ews = sip->ews;
7530 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7531 char *fb_start_str;
7532 char *free_busy_base64;
7533 const char *st;
7534 const char *fb;
7535 char *res;
7537 /* key is <category><instance><container> */
7538 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7539 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7540 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7541 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7542 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7543 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7545 struct sipe_publication *publication_cal_1 =
7546 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7547 struct sipe_publication *publication_cal_100 =
7548 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7549 struct sipe_publication *publication_cal_200 =
7550 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7551 struct sipe_publication *publication_cal_300 =
7552 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7553 struct sipe_publication *publication_cal_400 =
7554 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7555 struct sipe_publication *publication_cal_32000 =
7556 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7558 g_free(key_cal_1);
7559 g_free(key_cal_100);
7560 g_free(key_cal_200);
7561 g_free(key_cal_300);
7562 g_free(key_cal_400);
7563 g_free(key_cal_32000);
7565 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7566 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7567 return NULL;
7570 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7571 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7573 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7574 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7576 /* we will rebuplish the same data to refresh publication time,
7577 * so if data from multiple sources, most recent will be choosen
7579 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7581 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7582 // g_free(fb_start_str);
7583 // g_free(free_busy_base64);
7584 // return NULL; /* nothing to update */
7587 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7588 /* 1 */
7589 cal_data_instance,
7590 publication_cal_1 ? publication_cal_1->version : 0,
7591 /* 100 - Public */
7592 cal_data_instance,
7593 publication_cal_100 ? publication_cal_100->version : 0,
7594 /* 200 - Company */
7595 cal_data_instance,
7596 publication_cal_200 ? publication_cal_200->version : 0,
7597 ews->email,
7598 fb_start_str,
7599 free_busy_base64,
7600 /* 300 - Team */
7601 cal_data_instance,
7602 publication_cal_300 ? publication_cal_300->version : 0,
7603 ews->email,
7604 fb_start_str,
7605 free_busy_base64,
7606 /* 400 - Personal */
7607 cal_data_instance,
7608 publication_cal_400 ? publication_cal_400->version : 0,
7609 ews->email,
7610 fb_start_str,
7611 free_busy_base64,
7612 /* 32000 - Blocked */
7613 cal_data_instance,
7614 publication_cal_32000 ? publication_cal_32000->version : 0
7617 g_free(fb_start_str);
7618 g_free(free_busy_base64);
7619 return res;
7622 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7624 gchar *uri;
7625 gchar *doc;
7626 gchar *tmp;
7627 gchar *hdr;
7629 uri = sip_uri_self(sip);
7630 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7631 uri,
7632 publications);
7634 tmp = get_contact(sip);
7635 hdr = g_strdup_printf("Contact: %s\r\n"
7636 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7638 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7640 g_free(tmp);
7641 g_free(hdr);
7642 g_free(uri);
7643 g_free(doc);
7646 static void
7647 send_publish_category_initial(struct sipe_account_data *sip)
7649 gchar *pub_device = sipe_publish_get_category_device(sip);
7650 gchar *pub_machine;
7651 gchar *publications;
7653 g_free(sip->status);
7654 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7656 pub_machine = sipe_publish_get_category_state_machine(sip);
7657 publications = g_strdup_printf("%s%s",
7658 pub_device,
7659 pub_machine ? pub_machine : "");
7660 g_free(pub_device);
7661 g_free(pub_machine);
7663 send_presence_publish(sip, publications);
7664 g_free(publications);
7667 static void
7668 send_presence_category_publish(struct sipe_account_data *sip)
7670 gchar *pub_state = sipe_is_user_state(sip) ?
7671 sipe_publish_get_category_state_user(sip) :
7672 sipe_publish_get_category_state_machine(sip);
7673 gchar *pub_note = sipe_publish_get_category_note(sip,
7674 sip->note,
7675 sip->is_oof_note ? "OOF" : "personal",
7678 gchar *publications;
7680 if (!pub_state && !pub_note) {
7681 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7682 return;
7685 publications = g_strdup_printf("%s%s",
7686 pub_state ? pub_state : "",
7687 pub_note ? pub_note : "");
7689 g_free(pub_state);
7690 g_free(pub_note);
7692 send_presence_publish(sip, publications);
7693 g_free(publications);
7697 * Publishes self status
7698 * based on own calendar information.
7700 * For 2007+
7702 void
7703 publish_calendar_status_self(struct sipe_account_data *sip)
7705 struct sipe_cal_event* event = NULL;
7706 gchar *pub_cal_working_hours = NULL;
7707 gchar *pub_cal_free_busy = NULL;
7708 gchar *pub_calendar = NULL;
7709 gchar *pub_calendar2 = NULL;
7710 gchar *pub_oof_note = NULL;
7711 const gchar *oof_note;
7712 time_t oof_start = 0;
7713 time_t oof_end = 0;
7715 if (!sip->ews) {
7716 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7717 return;
7720 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7721 if (sip->ews->cal_events) {
7722 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7725 if (!event) {
7726 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7727 } else {
7728 char *desc = sipe_cal_event_describe(event);
7729 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7730 g_free(desc);
7733 /* Logic
7734 if OOF
7735 OOF publish, Busy clean
7736 ilse if Busy
7737 OOF clean, Busy publish
7738 else
7739 OOF clean, Busy clean
7741 if (event && event->cal_status == SIPE_CAL_OOF) {
7742 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7743 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7744 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7745 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7746 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7747 } else {
7748 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7749 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7752 oof_note = sipe_ews_get_oof_note(sip->ews);
7753 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7754 oof_start = sip->ews->oof_start;
7755 oof_end = sip->ews->oof_end;
7757 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7759 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7760 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7762 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7763 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7764 } else {
7765 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7766 pub_cal_working_hours ? pub_cal_working_hours : "",
7767 pub_cal_free_busy ? pub_cal_free_busy : "",
7768 pub_calendar ? pub_calendar : "",
7769 pub_calendar2 ? pub_calendar2 : "",
7770 pub_oof_note ? pub_oof_note : "");
7772 send_presence_publish(sip, publications);
7773 g_free(publications);
7776 g_free(pub_cal_working_hours);
7777 g_free(pub_cal_free_busy);
7778 g_free(pub_calendar);
7779 g_free(pub_calendar2);
7780 g_free(pub_oof_note);
7782 /* repeat scheduling */
7783 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7786 static void send_presence_status(struct sipe_account_data *sip)
7788 PurpleStatus * status = purple_account_get_active_status(sip->account);
7790 if (!status) return;
7792 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7793 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7794 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7796 if (sip->ocs2007) {
7797 send_presence_category_publish(sip);
7798 } else {
7799 send_presence_soap(sip, FALSE);
7803 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7805 gboolean found = FALSE;
7806 const char *method = msg->method ? msg->method : "NOT FOUND";
7807 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7808 if (msg->response == 0) { /* request */
7809 if (sipe_strequal(method, "MESSAGE")) {
7810 process_incoming_message(sip, msg);
7811 found = TRUE;
7812 } else if (sipe_strequal(method, "NOTIFY")) {
7813 purple_debug_info("sipe","send->process_incoming_notify\n");
7814 process_incoming_notify(sip, msg, TRUE, FALSE);
7815 found = TRUE;
7816 } else if (sipe_strequal(method, "BENOTIFY")) {
7817 purple_debug_info("sipe","send->process_incoming_benotify\n");
7818 process_incoming_notify(sip, msg, TRUE, TRUE);
7819 found = TRUE;
7820 } else if (sipe_strequal(method, "INVITE")) {
7821 process_incoming_invite(sip, msg);
7822 found = TRUE;
7823 } else if (sipe_strequal(method, "REFER")) {
7824 process_incoming_refer(sip, msg);
7825 found = TRUE;
7826 } else if (sipe_strequal(method, "OPTIONS")) {
7827 process_incoming_options(sip, msg);
7828 found = TRUE;
7829 } else if (sipe_strequal(method, "INFO")) {
7830 process_incoming_info(sip, msg);
7831 found = TRUE;
7832 } else if (sipe_strequal(method, "ACK")) {
7833 // ACK's don't need any response
7834 found = TRUE;
7835 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7836 // LCS 2005 sends us these - just respond 200 OK
7837 found = TRUE;
7838 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7839 } else if (sipe_strequal(method, "BYE")) {
7840 process_incoming_bye(sip, msg);
7841 found = TRUE;
7842 } else {
7843 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7845 } else { /* response */
7846 struct transaction *trans = transactions_find(sip, msg);
7847 if (trans) {
7848 if (msg->response == 407) {
7849 gchar *resend, *auth;
7850 const gchar *ptmp;
7852 if (sip->proxy.retries > 30) return;
7853 sip->proxy.retries++;
7854 /* do proxy authentication */
7856 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7858 fill_auth(ptmp, &sip->proxy);
7859 auth = auth_header(sip, &sip->proxy, trans->msg);
7860 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7861 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7862 g_free(auth);
7863 resend = sipmsg_to_string(trans->msg);
7864 /* resend request */
7865 sendout_pkt(sip->gc, resend);
7866 g_free(resend);
7867 } else {
7868 if (msg->response < 200) {
7869 /* ignore provisional response */
7870 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7871 } else {
7872 sip->proxy.retries = 0;
7873 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7874 if (msg->response == 401)
7876 sip->registrar.retries++;
7878 else
7880 sip->registrar.retries = 0;
7882 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7883 } else {
7884 if (msg->response == 401) {
7885 gchar *resend, *auth, *ptmp;
7886 const char* auth_scheme;
7888 if (sip->registrar.retries > 4) return;
7889 sip->registrar.retries++;
7891 auth_scheme = sipe_get_auth_scheme_name(sip);
7892 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7894 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7895 if (!ptmp) {
7896 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7897 sip->gc->wants_to_die = TRUE;
7898 purple_connection_error(sip->gc, tmp2);
7899 g_free(tmp2);
7900 return;
7903 fill_auth(ptmp, &sip->registrar);
7904 auth = auth_header(sip, &sip->registrar, trans->msg);
7905 sipmsg_remove_header_now(trans->msg, "Authorization");
7906 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7907 g_free(auth);
7908 resend = sipmsg_to_string(trans->msg);
7909 /* resend request */
7910 sendout_pkt(sip->gc, resend);
7911 g_free(resend);
7915 if (trans->callback) {
7916 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7917 /* call the callback to process response*/
7918 (trans->callback)(sip, msg, trans);
7921 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7922 transactions_remove(sip, trans);
7926 found = TRUE;
7927 } else {
7928 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7931 if (!found) {
7932 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7936 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7938 char *cur;
7939 char *dummy;
7940 char *tmp;
7941 struct sipmsg *msg;
7942 int restlen;
7943 cur = conn->inbuf;
7945 /* according to the RFC remove CRLF at the beginning */
7946 while (*cur == '\r' || *cur == '\n') {
7947 cur++;
7949 if (cur != conn->inbuf) {
7950 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7951 conn->inbufused = strlen(conn->inbuf);
7954 /* Received a full Header? */
7955 sip->processing_input = TRUE;
7956 while (sip->processing_input &&
7957 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7958 time_t currtime = time(NULL);
7959 cur += 2;
7960 cur[0] = '\0';
7961 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7962 g_free(tmp);
7963 msg = sipmsg_parse_header(conn->inbuf);
7964 cur[0] = '\r';
7965 cur += 2;
7966 restlen = conn->inbufused - (cur - conn->inbuf);
7967 if (msg && restlen >= msg->bodylen) {
7968 dummy = g_malloc(msg->bodylen + 1);
7969 memcpy(dummy, cur, msg->bodylen);
7970 dummy[msg->bodylen] = '\0';
7971 msg->body = dummy;
7972 cur += msg->bodylen;
7973 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7974 conn->inbufused = strlen(conn->inbuf);
7975 } else {
7976 if (msg){
7977 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7978 sipmsg_free(msg);
7980 return;
7983 /*if (msg->body) {
7984 purple_debug_info("sipe", "body:\n%s", msg->body);
7987 // Verify the signature before processing it
7988 if (sip->registrar.gssapi_context) {
7989 struct sipmsg_breakdown msgbd;
7990 gchar *signature_input_str;
7991 gchar *rspauth;
7992 msgbd.msg = msg;
7993 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7994 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
7996 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7998 if (rspauth != NULL) {
7999 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8000 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
8001 process_input_message(sip, msg);
8002 } else {
8003 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
8004 purple_connection_error(sip->gc, _("Invalid message signature received"));
8005 sip->gc->wants_to_die = TRUE;
8007 } else if (msg->response == 401) {
8008 purple_connection_error(sip->gc, _("Authentication failed"));
8009 sip->gc->wants_to_die = TRUE;
8011 g_free(signature_input_str);
8013 g_free(rspauth);
8014 sipmsg_breakdown_free(&msgbd);
8015 } else {
8016 process_input_message(sip, msg);
8019 sipmsg_free(msg);
8023 static void sipe_udp_process(gpointer data, gint source,
8024 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8026 PurpleConnection *gc = data;
8027 struct sipe_account_data *sip = gc->proto_data;
8028 int len;
8030 static char buffer[65536];
8031 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8032 time_t currtime = time(NULL);
8033 struct sipmsg *msg;
8034 buffer[len] = '\0';
8035 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
8036 msg = sipmsg_parse_msg(buffer);
8037 if (msg) process_input_message(sip, msg);
8041 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8043 struct sipe_account_data *sip = gc->proto_data;
8044 PurpleSslConnection *gsc = sip->gsc;
8046 purple_debug_error("sipe", "%s",debug);
8047 purple_connection_error(gc, msg);
8049 /* Invalidate this connection. Next send will open a new one */
8050 if (gsc) {
8051 connection_remove(sip, gsc->fd);
8052 purple_ssl_close(gsc);
8054 sip->gsc = NULL;
8055 sip->fd = -1;
8058 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8059 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8061 PurpleConnection *gc = data;
8062 struct sipe_account_data *sip;
8063 struct sip_connection *conn;
8064 int readlen, len;
8065 gboolean firstread = TRUE;
8067 /* NOTE: This check *IS* necessary */
8068 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8069 purple_ssl_close(gsc);
8070 return;
8073 sip = gc->proto_data;
8074 conn = connection_find(sip, gsc->fd);
8075 if (conn == NULL) {
8076 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
8077 gc->wants_to_die = TRUE;
8078 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8079 return;
8082 /* Read all available data from the SSL connection */
8083 do {
8084 /* Increase input buffer size as needed */
8085 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8086 conn->inbuflen += SIMPLE_BUF_INC;
8087 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8088 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
8091 /* Try to read as much as there is space left in the buffer */
8092 readlen = conn->inbuflen - conn->inbufused - 1;
8093 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8095 if (len < 0 && errno == EAGAIN) {
8096 /* Try again later */
8097 return;
8098 } else if (len < 0) {
8099 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8100 return;
8101 } else if (firstread && (len == 0)) {
8102 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8103 return;
8106 conn->inbufused += len;
8107 firstread = FALSE;
8109 /* Equivalence indicates that there is possibly more data to read */
8110 } while (len == readlen);
8112 conn->inbuf[conn->inbufused] = '\0';
8113 process_input(sip, conn);
8117 static void sipe_input_cb(gpointer data, gint source,
8118 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8120 PurpleConnection *gc = data;
8121 struct sipe_account_data *sip = gc->proto_data;
8122 int len;
8123 struct sip_connection *conn = connection_find(sip, source);
8124 if (!conn) {
8125 purple_debug_error("sipe", "Connection not found!\n");
8126 return;
8129 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8130 conn->inbuflen += SIMPLE_BUF_INC;
8131 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8134 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8136 if (len < 0 && errno == EAGAIN)
8137 return;
8138 else if (len <= 0) {
8139 purple_debug_info("sipe", "sipe_input_cb: read error\n");
8140 connection_remove(sip, source);
8141 if (sip->fd == source) sip->fd = -1;
8142 return;
8145 conn->inbufused += len;
8146 conn->inbuf[conn->inbufused] = '\0';
8148 process_input(sip, conn);
8151 /* Callback for new connections on incoming TCP port */
8152 static void sipe_newconn_cb(gpointer data, gint source,
8153 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8155 PurpleConnection *gc = data;
8156 struct sipe_account_data *sip = gc->proto_data;
8157 struct sip_connection *conn;
8159 int newfd = accept(source, NULL, NULL);
8161 conn = connection_create(sip, newfd);
8163 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8166 static void login_cb(gpointer data, gint source,
8167 SIPE_UNUSED_PARAMETER const gchar *error_message)
8169 PurpleConnection *gc = data;
8170 struct sipe_account_data *sip;
8171 struct sip_connection *conn;
8173 if (!PURPLE_CONNECTION_IS_VALID(gc))
8175 if (source >= 0)
8176 close(source);
8177 return;
8180 if (source < 0) {
8181 purple_connection_error(gc, _("Could not connect"));
8182 return;
8185 sip = gc->proto_data;
8186 sip->fd = source;
8187 sip->last_keepalive = time(NULL);
8189 conn = connection_create(sip, source);
8191 do_register(sip);
8193 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8196 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8197 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8199 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8200 if (sip == NULL) return;
8202 do_register(sip);
8205 static guint sipe_ht_hash_nick(const char *nick)
8207 char *lc = g_utf8_strdown(nick, -1);
8208 guint bucket = g_str_hash(lc);
8209 g_free(lc);
8211 return bucket;
8214 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8216 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8219 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8221 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8223 sip->listen_data = NULL;
8225 if (listenfd == -1) {
8226 purple_connection_error(sip->gc, _("Could not create listen socket"));
8227 return;
8230 sip->fd = listenfd;
8232 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8233 sip->listenfd = sip->fd;
8235 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8237 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8238 do_register(sip);
8241 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8242 SIPE_UNUSED_PARAMETER const char *error_message)
8244 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8246 sip->query_data = NULL;
8248 if (!hosts || !hosts->data) {
8249 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8250 return;
8253 hosts = g_slist_remove(hosts, hosts->data);
8254 g_free(sip->serveraddr);
8255 sip->serveraddr = hosts->data;
8256 hosts = g_slist_remove(hosts, hosts->data);
8257 while (hosts) {
8258 void *tmp = hosts->data;
8259 hosts = g_slist_remove(hosts, tmp);
8260 hosts = g_slist_remove(hosts, tmp);
8261 g_free(tmp);
8264 /* create socket for incoming connections */
8265 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8266 sipe_udp_host_resolved_listen_cb, sip);
8267 if (sip->listen_data == NULL) {
8268 purple_connection_error(sip->gc, _("Could not create listen socket"));
8269 return;
8273 static const struct sipe_service_data *current_service = NULL;
8275 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8276 PurpleSslErrorType error,
8277 gpointer data)
8279 PurpleConnection *gc = data;
8280 struct sipe_account_data *sip;
8282 /* If the connection is already disconnected, we don't need to do anything else */
8283 if (!PURPLE_CONNECTION_IS_VALID(gc))
8284 return;
8286 sip = gc->proto_data;
8287 current_service = sip->service_data;
8288 if (current_service) {
8289 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8290 current_service->transport ? current_service->transport : "NULL",
8291 current_service->service ? current_service->service : "NULL");
8294 sip->fd = -1;
8295 sip->gsc = NULL;
8297 switch(error) {
8298 case PURPLE_SSL_CONNECT_FAILED:
8299 purple_connection_error(gc, _("Connection failed"));
8300 break;
8301 case PURPLE_SSL_HANDSHAKE_FAILED:
8302 purple_connection_error(gc, _("SSL handshake failed"));
8303 break;
8304 case PURPLE_SSL_CERTIFICATE_INVALID:
8305 purple_connection_error(gc, _("SSL certificate invalid"));
8306 break;
8310 static void
8311 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8313 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8314 PurpleProxyConnectData *connect_data;
8316 sip->listen_data = NULL;
8318 sip->listenfd = listenfd;
8319 if (sip->listenfd == -1) {
8320 purple_connection_error(sip->gc, _("Could not create listen socket"));
8321 return;
8324 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8325 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8326 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8327 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8328 sipe_newconn_cb, sip->gc);
8329 purple_debug_info("sipe", "connecting to %s port %d\n",
8330 sip->realhostname, sip->realport);
8331 /* open tcp connection to the server */
8332 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8333 sip->realport, login_cb, sip->gc);
8335 if (connect_data == NULL) {
8336 purple_connection_error(sip->gc, _("Could not create socket"));
8340 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8342 PurpleAccount *account = sip->account;
8343 PurpleConnection *gc = sip->gc;
8345 if (port == 0) {
8346 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8349 sip->realhostname = hostname;
8350 sip->realport = port;
8352 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8353 hostname, port);
8355 /* TODO: is there a good default grow size? */
8356 if (sip->transport != SIPE_TRANSPORT_UDP)
8357 sip->txbuf = purple_circ_buffer_new(0);
8359 if (sip->transport == SIPE_TRANSPORT_TLS) {
8360 /* SSL case */
8361 if (!purple_ssl_is_supported()) {
8362 gc->wants_to_die = TRUE;
8363 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8364 return;
8367 purple_debug_info("sipe", "using SSL\n");
8369 sip->gsc = purple_ssl_connect(account, hostname, port,
8370 login_cb_ssl, sipe_ssl_connect_failure, gc);
8371 if (sip->gsc == NULL) {
8372 purple_connection_error(gc, _("Could not create SSL context"));
8373 return;
8375 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8376 /* UDP case */
8377 purple_debug_info("sipe", "using UDP\n");
8379 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8380 if (sip->query_data == NULL) {
8381 purple_connection_error(gc, _("Could not resolve hostname"));
8383 } else {
8384 /* TCP case */
8385 purple_debug_info("sipe", "using TCP\n");
8386 /* create socket for incoming connections */
8387 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8388 sipe_tcp_connect_listen_cb, sip);
8389 if (sip->listen_data == NULL) {
8390 purple_connection_error(gc, _("Could not create listen socket"));
8391 return;
8396 /* Service list for autodection */
8397 static const struct sipe_service_data service_autodetect[] = {
8398 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8399 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8400 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8401 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8402 { NULL, NULL, 0 }
8405 /* Service list for SSL/TLS */
8406 static const struct sipe_service_data service_tls[] = {
8407 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8408 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8409 { NULL, NULL, 0 }
8412 /* Service list for TCP */
8413 static const struct sipe_service_data service_tcp[] = {
8414 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8415 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8416 { NULL, NULL, 0 }
8419 /* Service list for UDP */
8420 static const struct sipe_service_data service_udp[] = {
8421 { "sip", "udp", SIPE_TRANSPORT_UDP },
8422 { NULL, NULL, 0 }
8425 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8426 static void resolve_next_service(struct sipe_account_data *sip,
8427 const struct sipe_service_data *start)
8429 if (start) {
8430 sip->service_data = start;
8431 } else {
8432 sip->service_data++;
8433 if (sip->service_data->service == NULL) {
8434 gchar *hostname;
8435 /* Try connecting to the SIP hostname directly */
8436 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8437 if (sip->auto_transport) {
8438 // If SSL is supported, default to using it; OCS servers aren't configured
8439 // by default to accept TCP
8440 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8441 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8442 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8445 hostname = g_strdup(sip->sipdomain);
8446 create_connection(sip, hostname, 0);
8447 return;
8451 /* Try to resolve next service */
8452 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8453 sip->service_data->transport,
8454 sip->sipdomain,
8455 srvresolved, sip);
8458 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8460 struct sipe_account_data *sip = data;
8462 sip->srv_query_data = NULL;
8464 /* find the host to connect to */
8465 if (results) {
8466 gchar *hostname = g_strdup(resp->hostname);
8467 int port = resp->port;
8468 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8469 hostname, port);
8470 g_free(resp);
8472 sip->transport = sip->service_data->type;
8474 create_connection(sip, hostname, port);
8475 } else {
8476 resolve_next_service(sip, NULL);
8480 static void sipe_login(PurpleAccount *account)
8482 PurpleConnection *gc;
8483 struct sipe_account_data *sip;
8484 gchar **signinname_login, **userserver;
8485 const char *transport;
8486 const char *email;
8488 const char *username = purple_account_get_username(account);
8489 gc = purple_account_get_connection(account);
8491 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8493 if (strpbrk(username, "\t\v\r\n") != NULL) {
8494 gc->wants_to_die = TRUE;
8495 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8496 return;
8499 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8500 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8501 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8502 sip->gc = gc;
8503 sip->account = account;
8504 sip->reregister_set = FALSE;
8505 sip->reauthenticate_set = FALSE;
8506 sip->subscribed = FALSE;
8507 sip->subscribed_buddies = FALSE;
8508 sip->initial_state_published = FALSE;
8510 /* username format: <username>,[<optional login>] */
8511 signinname_login = g_strsplit(username, ",", 2);
8512 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8514 /* ensure that username format is name@domain */
8515 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8516 g_strfreev(signinname_login);
8517 gc->wants_to_die = TRUE;
8518 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8519 return;
8521 sip->username = g_strdup(signinname_login[0]);
8523 /* ensure that email format is name@domain if provided */
8524 email = purple_account_get_string(sip->account, "email", NULL);
8525 if (!is_empty(email) &&
8526 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8528 gc->wants_to_die = TRUE;
8529 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8530 return;
8532 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8534 /* login name specified? */
8535 if (signinname_login[1] && strlen(signinname_login[1])) {
8536 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8537 gboolean has_domain = domain_user[1] != NULL;
8538 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8539 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8540 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8541 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8542 sip->authdomain ? sip->authdomain : "", sip->authuser);
8543 g_strfreev(domain_user);
8546 userserver = g_strsplit(signinname_login[0], "@", 2);
8547 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8548 purple_connection_set_display_name(gc, userserver[0]);
8549 sip->sipdomain = g_strdup(userserver[1]);
8550 g_strfreev(userserver);
8551 g_strfreev(signinname_login);
8553 if (strchr(sip->username, ' ') != NULL) {
8554 gc->wants_to_die = TRUE;
8555 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8556 return;
8559 sip->password = g_strdup(purple_connection_get_password(gc));
8561 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8562 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8563 g_free, (GDestroyNotify)g_hash_table_destroy);
8564 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8565 g_free, (GDestroyNotify)sipe_subscription_free);
8567 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8569 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8571 g_free(sip->status);
8572 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8574 sip->auto_transport = FALSE;
8575 transport = purple_account_get_string(account, "transport", "auto");
8576 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8577 if (userserver[0]) {
8578 /* Use user specified server[:port] */
8579 int port = 0;
8581 if (userserver[1])
8582 port = atoi(userserver[1]);
8584 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8585 userserver[0], port);
8587 if (sipe_strequal(transport, "auto")) {
8588 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8589 } else if (sipe_strequal(transport, "tls")) {
8590 sip->transport = SIPE_TRANSPORT_TLS;
8591 } else if (sipe_strequal(transport, "tcp")) {
8592 sip->transport = SIPE_TRANSPORT_TCP;
8593 } else {
8594 sip->transport = SIPE_TRANSPORT_UDP;
8597 create_connection(sip, g_strdup(userserver[0]), port);
8598 } else {
8599 /* Server auto-discovery */
8600 if (sipe_strequal(transport, "auto")) {
8601 sip->auto_transport = TRUE;
8602 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8603 current_service++;
8604 resolve_next_service(sip, current_service);
8605 } else {
8606 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8608 } else if (sipe_strequal(transport, "tls")) {
8609 resolve_next_service(sip, service_tls);
8610 } else if (sipe_strequal(transport, "tcp")) {
8611 resolve_next_service(sip, service_tcp);
8612 } else {
8613 resolve_next_service(sip, service_udp);
8616 g_strfreev(userserver);
8619 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8621 connection_free_all(sip);
8623 g_free(sip->epid);
8624 sip->epid = NULL;
8626 if (sip->query_data != NULL)
8627 purple_dnsquery_destroy(sip->query_data);
8628 sip->query_data = NULL;
8630 if (sip->srv_query_data != NULL)
8631 purple_srv_cancel(sip->srv_query_data);
8632 sip->srv_query_data = NULL;
8634 if (sip->listen_data != NULL)
8635 purple_network_listen_cancel(sip->listen_data);
8636 sip->listen_data = NULL;
8638 if (sip->gsc != NULL)
8639 purple_ssl_close(sip->gsc);
8640 sip->gsc = NULL;
8642 sipe_auth_free(&sip->registrar);
8643 sipe_auth_free(&sip->proxy);
8645 if (sip->txbuf)
8646 purple_circ_buffer_destroy(sip->txbuf);
8647 sip->txbuf = NULL;
8649 g_free(sip->realhostname);
8650 sip->realhostname = NULL;
8652 g_free(sip->server_version);
8653 sip->server_version = NULL;
8655 if (sip->listenpa)
8656 purple_input_remove(sip->listenpa);
8657 sip->listenpa = 0;
8658 if (sip->tx_handler)
8659 purple_input_remove(sip->tx_handler);
8660 sip->tx_handler = 0;
8661 if (sip->resendtimeout)
8662 purple_timeout_remove(sip->resendtimeout);
8663 sip->resendtimeout = 0;
8664 if (sip->timeouts) {
8665 GSList *entry = sip->timeouts;
8666 while (entry) {
8667 struct scheduled_action *sched_action = entry->data;
8668 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8669 purple_timeout_remove(sched_action->timeout_handler);
8670 if (sched_action->destroy) {
8671 (*sched_action->destroy)(sched_action->payload);
8673 g_free(sched_action->name);
8674 g_free(sched_action);
8675 entry = entry->next;
8678 g_slist_free(sip->timeouts);
8680 if (sip->allow_events) {
8681 GSList *entry = sip->allow_events;
8682 while (entry) {
8683 g_free(entry->data);
8684 entry = entry->next;
8687 g_slist_free(sip->allow_events);
8689 if (sip->containers) {
8690 GSList *entry = sip->containers;
8691 while (entry) {
8692 free_container((struct sipe_container *)entry->data);
8693 entry = entry->next;
8696 g_slist_free(sip->containers);
8698 if (sip->contact)
8699 g_free(sip->contact);
8700 sip->contact = NULL;
8701 if (sip->regcallid)
8702 g_free(sip->regcallid);
8703 sip->regcallid = NULL;
8705 if (sip->serveraddr)
8706 g_free(sip->serveraddr);
8707 sip->serveraddr = NULL;
8709 if (sip->focus_factory_uri)
8710 g_free(sip->focus_factory_uri);
8711 sip->focus_factory_uri = NULL;
8713 sip->fd = -1;
8714 sip->processing_input = FALSE;
8716 if (sip->ews) {
8717 sipe_ews_free(sip->ews);
8719 sip->ews = NULL;
8723 * A callback for g_hash_table_foreach_remove
8725 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8726 SIPE_UNUSED_PARAMETER gpointer user_data)
8728 sipe_free_buddy((struct sipe_buddy *) buddy);
8730 /* We must return TRUE as the key/value have already been deleted */
8731 return(TRUE);
8734 static void sipe_close(PurpleConnection *gc)
8736 struct sipe_account_data *sip = gc->proto_data;
8738 if (sip) {
8739 /* leave all conversations */
8740 sipe_session_close_all(sip);
8741 sipe_session_remove_all(sip);
8743 if (sip->csta) {
8744 sip_csta_close(sip);
8747 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8748 /* unsubscribe all */
8749 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8751 /* unregister */
8752 do_register_exp(sip, 0);
8755 sipe_connection_cleanup(sip);
8756 g_free(sip->sipdomain);
8757 g_free(sip->username);
8758 g_free(sip->email);
8759 g_free(sip->password);
8760 g_free(sip->authdomain);
8761 g_free(sip->authuser);
8762 g_free(sip->status);
8763 g_free(sip->note);
8764 g_free(sip->user_states);
8766 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8767 g_hash_table_destroy(sip->buddies);
8768 g_hash_table_destroy(sip->our_publications);
8769 g_hash_table_destroy(sip->user_state_publications);
8770 g_hash_table_destroy(sip->subscriptions);
8771 g_hash_table_destroy(sip->filetransfers);
8773 if (sip->groups) {
8774 GSList *entry = sip->groups;
8775 while (entry) {
8776 struct sipe_group *group = entry->data;
8777 g_free(group->name);
8778 g_free(group);
8779 entry = entry->next;
8782 g_slist_free(sip->groups);
8784 if (sip->our_publication_keys) {
8785 GSList *entry = sip->our_publication_keys;
8786 while (entry) {
8787 g_free(entry->data);
8788 entry = entry->next;
8791 g_slist_free(sip->our_publication_keys);
8793 while (sip->transactions)
8794 transactions_remove(sip, sip->transactions->data);
8796 g_free(gc->proto_data);
8797 gc->proto_data = NULL;
8800 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8801 SIPE_UNUSED_PARAMETER void *user_data)
8803 PurpleAccount *acct = purple_connection_get_account(gc);
8804 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8805 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8806 if (conv == NULL)
8807 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8808 purple_conversation_present(conv);
8809 g_free(id);
8812 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8813 SIPE_UNUSED_PARAMETER void *user_data)
8816 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8817 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8820 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8821 SIPE_UNUSED_PARAMETER struct transaction *trans)
8823 PurpleNotifySearchResults *results;
8824 PurpleNotifySearchColumn *column;
8825 xmlnode *searchResults;
8826 xmlnode *mrow;
8827 int match_count = 0;
8828 gboolean more = FALSE;
8829 gchar *secondary;
8831 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8833 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8834 if (!searchResults) {
8835 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8836 return FALSE;
8839 results = purple_notify_searchresults_new();
8841 if (results == NULL) {
8842 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8843 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8845 xmlnode_free(searchResults);
8846 return FALSE;
8849 column = purple_notify_searchresults_column_new(_("User name"));
8850 purple_notify_searchresults_column_add(results, column);
8852 column = purple_notify_searchresults_column_new(_("Name"));
8853 purple_notify_searchresults_column_add(results, column);
8855 column = purple_notify_searchresults_column_new(_("Company"));
8856 purple_notify_searchresults_column_add(results, column);
8858 column = purple_notify_searchresults_column_new(_("Country"));
8859 purple_notify_searchresults_column_add(results, column);
8861 column = purple_notify_searchresults_column_new(_("Email"));
8862 purple_notify_searchresults_column_add(results, column);
8864 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8865 GList *row = NULL;
8867 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8868 row = g_list_append(row, g_strdup(uri_parts[1]));
8869 g_strfreev(uri_parts);
8871 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8872 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8873 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8874 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8876 purple_notify_searchresults_row_add(results, row);
8877 match_count++;
8880 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8881 char *data = xmlnode_get_data(mrow);
8882 more = (g_strcasecmp(data, "true") == 0);
8883 g_free(data);
8886 secondary = g_strdup_printf(
8887 dngettext(PACKAGE_NAME,
8888 "Found %d contact%s:",
8889 "Found %d contacts%s:", match_count),
8890 match_count, more ? _(" (more matched your query)") : "");
8892 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8893 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8894 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8896 g_free(secondary);
8897 xmlnode_free(searchResults);
8898 return TRUE;
8901 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8903 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8904 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8905 unsigned i = 0;
8907 if (!attrs) return;
8909 do {
8910 PurpleRequestField *field = entries->data;
8911 const char *id = purple_request_field_get_id(field);
8912 const char *value = purple_request_field_string_get_value(field);
8914 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8916 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8917 } while ((entries = g_list_next(entries)) != NULL);
8918 attrs[i] = NULL;
8920 if (i > 0) {
8921 struct sipe_account_data *sip = gc->proto_data;
8922 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8923 gchar *query = g_strjoinv(NULL, attrs);
8924 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8925 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8926 send_soap_request_with_cb(sip, domain_uri, body,
8927 (TransCallback) process_search_contact_response, NULL);
8928 g_free(domain_uri);
8929 g_free(body);
8930 g_free(query);
8933 g_strfreev(attrs);
8936 static void sipe_show_find_contact(PurplePluginAction *action)
8938 PurpleConnection *gc = (PurpleConnection *) action->context;
8939 PurpleRequestFields *fields;
8940 PurpleRequestFieldGroup *group;
8941 PurpleRequestField *field;
8943 fields = purple_request_fields_new();
8944 group = purple_request_field_group_new(NULL);
8945 purple_request_fields_add_group(fields, group);
8947 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8948 purple_request_field_group_add_field(group, field);
8949 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8950 purple_request_field_group_add_field(group, field);
8951 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8952 purple_request_field_group_add_field(group, field);
8953 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8954 purple_request_field_group_add_field(group, field);
8956 purple_request_fields(gc,
8957 _("Search"),
8958 _("Search for a contact"),
8959 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8960 fields,
8961 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8962 _("_Cancel"), NULL,
8963 purple_connection_get_account(gc), NULL, NULL, gc);
8966 static void sipe_show_about_plugin(PurplePluginAction *action)
8968 PurpleConnection *gc = (PurpleConnection *) action->context;
8969 char *tmp = g_strdup_printf(
8971 * Non-translatable parts, like markup, are hard-coded
8972 * into the format string. This requires more translatable
8973 * texts but it makes the translations less error prone.
8975 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
8976 "<br/>"
8977 /* 1 */ "%s:<br/>"
8978 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8979 "<li> - MS Office Communications Server 2007</li><br/>"
8980 "<li> - MS Live Communications Server 2005</li><br/>"
8981 "<li> - MS Live Communications Server 2003</li><br/>"
8982 "<li> - Reuters Messaging</li><br/>"
8983 "<br/>"
8984 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
8985 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8986 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
8987 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8988 /* 8 */ "%s: GPLv2+<br/>"
8989 "<br/>"
8990 /* 9 */ "%s:<br/>"
8991 " - CERN<br/>"
8992 " - Reuters Messaging network<br/>"
8993 " - Deutsche Bank<br/>"
8994 " - Merrill Lynch<br/>"
8995 " - Wachovia<br/>"
8996 " - Intel<br/>"
8997 " - Nokia<br/>"
8998 " - HP<br/>"
8999 " - Symantec<br/>"
9000 " - Accenture<br/>"
9001 " - Capgemini<br/>"
9002 " - Siemens<br/>"
9003 " - Alcatel-Lucent<br/>"
9004 " - BT<br/>"
9005 "<br/>"
9006 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9007 "<br/>"
9008 /* 12 */ "<b>%s:</b><br/>"
9009 " - Anibal Avelar<br/>"
9010 " - Gabriel Burt<br/>"
9011 " - Stefan Becker<br/>"
9012 " - pier11<br/>"
9013 " - Jakub Adam<br/>"
9014 " - Tomáš Hrabčík<br/>"
9015 "<br/>"
9016 /* 13 */ "%s<br/>"
9018 /* The next 13 texts make up the SIPE about note text */
9019 /* About note, part 1/13: introduction */
9020 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9021 /* About note, part 2/13: home page URL (label) */
9022 _("Home"),
9023 /* About note, part 3/13: support forum URL (label) */
9024 _("Support"),
9025 /* About note, part 4/13: support forum name (hyperlink text) */
9026 _("Help Forum"),
9027 /* About note, part 5/13: bug tracker URL (label) */
9028 _("Report Problems"),
9029 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9030 _("Bug Tracker"),
9031 /* About note, part 7/13: translation service URL (label) */
9032 _("Translations"),
9033 /* About note, part 8/13: license type (label) */
9034 _("License"),
9035 /* About note, part 9/13: known users */
9036 _("We support users in such organizations as"),
9037 /* About note, part 10/13: translation request, text before Transifex.net URL */
9038 /* append a space if text is not empty */
9039 _("Please help us to translate SIPE to your native language here at "),
9040 /* About note, part 11/13: translation request, text after Transifex.net URL */
9041 /* start with a space if text is not empty */
9042 _(" using convenient web interface"),
9043 /* About note, part 12/13: author list (header) */
9044 _("Authors"),
9045 /* About note, part 13/13: Localization credit */
9046 /* PLEASE NOTE: do *NOT* simply translate the english original */
9047 /* but write something similar to the following sentence: */
9048 /* "Localization for <language name> (<language code>): <name>" */
9049 _("Original texts in English (en): SIPE developers")
9051 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9052 g_free(tmp);
9055 static void sipe_republish_calendar(PurplePluginAction *action)
9057 PurpleConnection *gc = (PurpleConnection *) action->context;
9058 struct sipe_account_data *sip = gc->proto_data;
9060 sipe_update_calendar(sip);
9063 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9064 gpointer value,
9065 GString* str)
9067 struct sipe_publication *publication = value;
9069 g_string_append_printf( str,
9070 SIPE_PUB_XML_PUBLICATION_CLEAR,
9071 publication->category,
9072 publication->instance,
9073 publication->container,
9074 publication->version,
9075 "static");
9078 static void sipe_reset_status(PurplePluginAction *action)
9080 PurpleConnection *gc = (PurpleConnection *) action->context;
9081 struct sipe_account_data *sip = gc->proto_data;
9083 if (sip->ocs2007) /* 2007+ */
9085 GString* str = g_string_new(NULL);
9086 gchar *publications;
9088 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9089 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
9090 return;
9093 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9094 publications = g_string_free(str, FALSE);
9096 send_presence_publish(sip, publications);
9097 g_free(publications);
9099 else /* 2005 */
9101 send_presence_soap0(sip, FALSE, TRUE);
9105 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9106 gpointer context)
9108 PurpleConnection *gc = (PurpleConnection *)context;
9109 struct sipe_account_data *sip = gc->proto_data;
9110 GList *menu = NULL;
9111 PurplePluginAction *act;
9112 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9114 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9115 menu = g_list_prepend(menu, act);
9117 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9118 menu = g_list_prepend(menu, act);
9120 if (sipe_strequal(calendar, "EXCH")) {
9121 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9122 menu = g_list_prepend(menu, act);
9125 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9126 menu = g_list_prepend(menu, act);
9128 menu = g_list_reverse(menu);
9130 return menu;
9133 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9137 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9139 return TRUE;
9143 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9145 return TRUE;
9149 static char *sipe_status_text(PurpleBuddy *buddy)
9151 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9152 const PurpleStatus *status = purple_presence_get_active_status(presence);
9153 const char *status_id = purple_status_get_id(status);
9154 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9155 struct sipe_buddy *sbuddy;
9156 char *text = NULL;
9158 if (!sip) return NULL; /* happens on pidgin exit */
9160 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9161 if (sbuddy) {
9162 const char *activity_str = sbuddy->activity ?
9163 sbuddy->activity :
9164 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9165 purple_status_get_name(status) : NULL;
9167 if (activity_str && sbuddy->note)
9169 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9171 else if (activity_str)
9173 text = g_strdup(activity_str);
9175 else if (sbuddy->note)
9177 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9181 return text;
9184 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9186 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9187 const PurpleStatus *status = purple_presence_get_active_status(presence);
9188 struct sipe_account_data *sip;
9189 struct sipe_buddy *sbuddy;
9190 char *note = NULL;
9191 gboolean is_oof_note = FALSE;
9192 char *activity = NULL;
9193 char *calendar = NULL;
9194 char *meeting_subject = NULL;
9195 char *meeting_location = NULL;
9197 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9198 if (sip) //happens on pidgin exit
9200 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9201 if (sbuddy)
9203 note = sbuddy->note;
9204 is_oof_note = sbuddy->is_oof_note;
9205 activity = sbuddy->activity;
9206 calendar = sipe_cal_get_description(sbuddy);
9207 meeting_subject = sbuddy->meeting_subject;
9208 meeting_location = sbuddy->meeting_location;
9212 //Layout
9213 if (purple_presence_is_online(presence))
9215 const char *status_str = activity ? activity : purple_status_get_name(status);
9217 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9219 if (purple_presence_is_online(presence) &&
9220 !is_empty(calendar))
9222 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9224 g_free(calendar);
9225 if (!is_empty(meeting_location))
9227 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9229 if (!is_empty(meeting_subject))
9231 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9234 if (note)
9236 char *tmp = g_strdup_printf("<i>%s</i>", note);
9237 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9239 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9240 g_free(tmp);
9245 #if PURPLE_VERSION_CHECK(2,5,0)
9246 static GHashTable *
9247 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9249 GHashTable *table;
9250 table = g_hash_table_new(g_str_hash, g_str_equal);
9251 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9252 return table;
9254 #endif
9256 static PurpleBuddy *
9257 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9259 PurpleBuddy *clone;
9260 const gchar *server_alias, *email;
9261 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9263 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9265 purple_blist_add_buddy(clone, NULL, group, NULL);
9267 server_alias = purple_buddy_get_server_alias(buddy);
9268 if (server_alias) {
9269 purple_blist_server_alias_buddy(clone, server_alias);
9272 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9273 if (email) {
9274 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9277 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9278 //for UI to update;
9279 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9280 return clone;
9283 static void
9284 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9286 PurpleBuddy *buddy, *b;
9287 PurpleConnection *gc;
9288 PurpleGroup * group = purple_find_group(group_name);
9290 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9292 buddy = (PurpleBuddy *)node;
9294 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9295 gc = purple_account_get_connection(buddy->account);
9297 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9298 if (!b){
9299 purple_blist_add_buddy_clone(group, buddy);
9302 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9305 static void
9306 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9308 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9310 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9312 /* 2007+ conference */
9313 if (sip->ocs2007)
9315 sipe_conf_add(sip, buddy->name);
9317 else /* 2005- multiparty chat */
9319 gchar *self = sip_uri_self(sip);
9320 struct sip_session *session;
9322 session = sipe_session_add_chat(sip);
9323 session->chat_title = sipe_chat_get_name(session->callid);
9324 session->roster_manager = g_strdup(self);
9326 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9327 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9328 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9329 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9331 g_free(self);
9335 static gboolean
9336 sipe_is_election_finished(struct sip_session *session)
9338 gboolean res = TRUE;
9340 SIPE_DIALOG_FOREACH {
9341 if (dialog->election_vote == 0) {
9342 res = FALSE;
9343 break;
9345 } SIPE_DIALOG_FOREACH_END;
9347 if (res) {
9348 session->is_voting_in_progress = FALSE;
9350 return res;
9353 static void
9354 sipe_election_start(struct sipe_account_data *sip,
9355 struct sip_session *session)
9357 int election_timeout;
9359 if (session->is_voting_in_progress) {
9360 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9361 return;
9362 } else {
9363 session->is_voting_in_progress = TRUE;
9365 session->bid = rand();
9367 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9369 SIPE_DIALOG_FOREACH {
9370 /* reset election_vote for each chat participant */
9371 dialog->election_vote = 0;
9373 /* send RequestRM to each chat participant*/
9374 sipe_send_election_request_rm(sip, dialog, session->bid);
9375 } SIPE_DIALOG_FOREACH_END;
9377 election_timeout = 15; /* sec */
9378 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9382 * @param who a URI to whom to invite to chat
9384 void
9385 sipe_invite_to_chat(struct sipe_account_data *sip,
9386 struct sip_session *session,
9387 const gchar *who)
9389 /* a conference */
9390 if (session->focus_uri)
9392 sipe_invite_conf(sip, session, who);
9394 else /* a multi-party chat */
9396 gchar *self = sip_uri_self(sip);
9397 if (session->roster_manager) {
9398 if (sipe_strcase_equal(session->roster_manager, self)) {
9399 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9400 } else {
9401 sipe_refer(sip, session, who);
9403 } else {
9404 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9406 session->pending_invite_queue = slist_insert_unique_sorted(
9407 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9409 sipe_election_start(sip, session);
9411 g_free(self);
9415 void
9416 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9417 struct sip_session *session)
9419 gchar *invitee;
9420 GSList *entry = session->pending_invite_queue;
9422 while (entry) {
9423 invitee = entry->data;
9424 sipe_invite_to_chat(sip, session, invitee);
9425 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9426 g_free(invitee);
9430 static void
9431 sipe_election_result(struct sipe_account_data *sip,
9432 void *sess)
9434 struct sip_session *session = (struct sip_session *)sess;
9435 gchar *rival;
9436 gboolean has_won = TRUE;
9438 if (session->roster_manager) {
9439 purple_debug_info("sipe",
9440 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9441 return;
9444 session->is_voting_in_progress = FALSE;
9446 SIPE_DIALOG_FOREACH {
9447 if (dialog->election_vote < 0) {
9448 has_won = FALSE;
9449 rival = dialog->with;
9450 break;
9452 } SIPE_DIALOG_FOREACH_END;
9454 if (has_won) {
9455 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9457 session->roster_manager = sip_uri_self(sip);
9459 SIPE_DIALOG_FOREACH {
9460 /* send SetRM to each chat participant*/
9461 sipe_send_election_set_rm(sip, dialog);
9462 } SIPE_DIALOG_FOREACH_END;
9463 } else {
9464 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9466 session->bid = 0;
9468 sipe_process_pending_invite_queue(sip, session);
9472 * For 2007+ conference only.
9474 static void
9475 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9477 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9478 struct sip_session *session;
9480 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9481 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9483 session = sipe_session_find_chat_by_title(sip, chat_title);
9485 sipe_conf_modify_user_role(sip, session, buddy->name);
9489 * For 2007+ conference only.
9491 static void
9492 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9494 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9495 struct sip_session *session;
9497 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9498 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9500 session = sipe_session_find_chat_by_title(sip, chat_title);
9502 sipe_conf_delete_user(sip, session, buddy->name);
9505 static void
9506 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9508 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9509 struct sip_session *session;
9511 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9512 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9514 session = sipe_session_find_chat_by_title(sip, chat_title);
9516 sipe_invite_to_chat(sip, session, buddy->name);
9519 static void
9520 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9522 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9524 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9525 if (phone) {
9526 char *tel_uri = sip_to_tel_uri(phone);
9528 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9529 sip_csta_make_call(sip, tel_uri);
9531 g_free(tel_uri);
9535 static void
9536 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9538 const gchar *email;
9539 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9541 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9542 if (email)
9544 char *mailto = g_strdup_printf("mailto:%s", email);
9545 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9546 #ifndef _WIN32
9548 pid_t pid;
9549 char *const parmList[] = {"xdg-email", mailto, NULL};
9550 if ((pid = fork()) == -1)
9552 purple_debug_info("sipe", "fork() error\n");
9554 else if (pid == 0)
9556 execvp(parmList[0], parmList);
9557 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9560 #else
9562 BOOL ret;
9563 _flushall();
9564 errno = 0;
9565 //@TODO resolve env variable %WINDIR% first
9566 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9567 if (errno)
9569 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9572 #endif
9574 g_free(mailto);
9576 else
9578 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9583 * A menu which appear when right-clicking on buddy in contact list.
9585 static GList *
9586 sipe_buddy_menu(PurpleBuddy *buddy)
9588 PurpleBlistNode *g_node;
9589 PurpleGroup *group, *gr_parent;
9590 PurpleMenuAction *act;
9591 GList *menu = NULL;
9592 GList *menu_groups = NULL;
9593 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9594 const char *email;
9595 const char *phone;
9596 const char *phone_disp_str;
9597 gchar *self = sip_uri_self(sip);
9599 SIPE_SESSION_FOREACH {
9600 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9602 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9604 PurpleConvChatBuddyFlags flags;
9605 PurpleConvChatBuddyFlags flags_us;
9607 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9608 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9609 if (session->focus_uri
9610 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9611 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9613 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9614 act = purple_menu_action_new(label,
9615 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9616 session->chat_title, NULL);
9617 g_free(label);
9618 menu = g_list_prepend(menu, act);
9621 if (session->focus_uri
9622 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9624 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9625 act = purple_menu_action_new(label,
9626 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9627 session->chat_title, NULL);
9628 g_free(label);
9629 menu = g_list_prepend(menu, act);
9632 else
9634 if (!session->focus_uri
9635 || (session->focus_uri && !session->locked))
9637 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9638 act = purple_menu_action_new(label,
9639 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9640 session->chat_title, NULL);
9641 g_free(label);
9642 menu = g_list_prepend(menu, act);
9646 } SIPE_SESSION_FOREACH_END;
9648 act = purple_menu_action_new(_("New chat"),
9649 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9650 NULL, NULL);
9651 menu = g_list_prepend(menu, act);
9653 if (sip->csta && !sip->csta->line_status) {
9654 gchar *tmp = NULL;
9655 /* work phone */
9656 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9657 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9658 if (phone) {
9659 gchar *label = g_strdup_printf(_("Work %s"),
9660 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9661 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9662 g_free(tmp);
9663 tmp = NULL;
9664 g_free(label);
9665 menu = g_list_prepend(menu, act);
9668 /* mobile phone */
9669 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9670 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9671 if (phone) {
9672 gchar *label = g_strdup_printf(_("Mobile %s"),
9673 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9674 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9675 g_free(tmp);
9676 tmp = NULL;
9677 g_free(label);
9678 menu = g_list_prepend(menu, act);
9681 /* home phone */
9682 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9683 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9684 if (phone) {
9685 gchar *label = g_strdup_printf(_("Home %s"),
9686 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9687 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9688 g_free(tmp);
9689 tmp = NULL;
9690 g_free(label);
9691 menu = g_list_prepend(menu, act);
9694 /* other phone */
9695 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9696 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9697 if (phone) {
9698 gchar *label = g_strdup_printf(_("Other %s"),
9699 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9700 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9701 g_free(tmp);
9702 tmp = NULL;
9703 g_free(label);
9704 menu = g_list_prepend(menu, act);
9707 /* custom1 phone */
9708 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9709 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9710 if (phone) {
9711 gchar *label = g_strdup_printf(_("Custom1 %s"),
9712 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9713 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9714 g_free(tmp);
9715 tmp = NULL;
9716 g_free(label);
9717 menu = g_list_prepend(menu, act);
9721 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9722 if (email) {
9723 act = purple_menu_action_new(_("Send email..."),
9724 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9725 NULL, NULL);
9726 menu = g_list_prepend(menu, act);
9729 gr_parent = purple_buddy_get_group(buddy);
9730 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9731 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9732 continue;
9734 group = (PurpleGroup *)g_node;
9735 if (group == gr_parent)
9736 continue;
9738 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9739 continue;
9741 act = purple_menu_action_new(purple_group_get_name(group),
9742 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9743 group->name, NULL);
9744 menu_groups = g_list_prepend(menu_groups, act);
9746 menu_groups = g_list_reverse(menu_groups);
9748 act = purple_menu_action_new(_("Copy to"),
9749 NULL,
9750 NULL, menu_groups);
9751 menu = g_list_prepend(menu, act);
9752 menu = g_list_reverse(menu);
9754 g_free(self);
9755 return menu;
9758 static void
9759 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9761 struct sipe_account_data *sip = chat->account->gc->proto_data;
9762 struct sip_session *session;
9764 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9765 sipe_conf_modify_conference_lock(sip, session, locked);
9768 static void
9769 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9771 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9772 sipe_conf_modify_lock(chat, FALSE);
9775 static void
9776 sipe_chat_menu_lock_cb(PurpleChat *chat)
9778 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9779 sipe_conf_modify_lock(chat, TRUE);
9782 static GList *
9783 sipe_chat_menu(PurpleChat *chat)
9785 PurpleMenuAction *act;
9786 PurpleConvChatBuddyFlags flags_us;
9787 GList *menu = NULL;
9788 struct sipe_account_data *sip = chat->account->gc->proto_data;
9789 struct sip_session *session;
9790 gchar *self;
9792 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9793 if (!session) return NULL;
9795 self = sip_uri_self(sip);
9796 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9798 if (session->focus_uri
9799 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9801 if (session->locked) {
9802 act = purple_menu_action_new(_("Unlock"),
9803 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9804 NULL, NULL);
9805 menu = g_list_prepend(menu, act);
9806 } else {
9807 act = purple_menu_action_new(_("Lock"),
9808 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9809 NULL, NULL);
9810 menu = g_list_prepend(menu, act);
9814 menu = g_list_reverse(menu);
9816 g_free(self);
9817 return menu;
9820 static GList *
9821 sipe_blist_node_menu(PurpleBlistNode *node)
9823 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9824 return sipe_buddy_menu((PurpleBuddy *) node);
9825 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9826 return sipe_chat_menu((PurpleChat *)node);
9827 } else {
9828 return NULL;
9832 static gboolean
9833 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9835 char *uri = trans->payload->data;
9837 PurpleNotifyUserInfo *info;
9838 PurpleBuddy *pbuddy = NULL;
9839 struct sipe_buddy *sbuddy;
9840 const char *alias = NULL;
9841 char *device_name = NULL;
9842 char *server_alias = NULL;
9843 char *phone_number = NULL;
9844 char *email = NULL;
9845 const char *site;
9846 char *first_name = NULL;
9847 char *last_name = NULL;
9849 if (!sip) return FALSE;
9851 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9853 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9854 alias = purple_buddy_get_local_alias(pbuddy);
9856 //will query buddy UA's capabilities and send answer to log
9857 sipe_options_request(sip, uri);
9859 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9860 if (sbuddy) {
9861 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9864 info = purple_notify_user_info_new();
9866 if (msg->response != 200) {
9867 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9868 } else {
9869 xmlnode *searchResults;
9870 xmlnode *mrow;
9872 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9873 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9874 if (!searchResults) {
9875 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9876 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9877 const char *value;
9878 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9879 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9880 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9882 /* For 2007 system we will take this from ContactCard -
9883 * it has cleaner tel: URIs at least
9885 if (!sip->ocs2007) {
9886 char *tel_uri = sip_to_tel_uri(phone_number);
9887 /* trims its parameters, so call first */
9888 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9889 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9890 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9891 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9892 g_free(tel_uri);
9895 if (server_alias && strlen(server_alias) > 0) {
9896 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9898 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9899 purple_notify_user_info_add_pair(info, _("Job title"), value);
9901 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9902 purple_notify_user_info_add_pair(info, _("Office"), value);
9904 if (phone_number && strlen(phone_number) > 0) {
9905 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9907 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9908 purple_notify_user_info_add_pair(info, _("Company"), value);
9910 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9911 purple_notify_user_info_add_pair(info, _("City"), value);
9913 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9914 purple_notify_user_info_add_pair(info, _("State"), value);
9916 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9917 purple_notify_user_info_add_pair(info, _("Country"), value);
9919 if (email && strlen(email) > 0) {
9920 purple_notify_user_info_add_pair(info, _("Email address"), email);
9924 xmlnode_free(searchResults);
9927 purple_notify_user_info_add_section_break(info);
9929 if (is_empty(server_alias)) {
9930 g_free(server_alias);
9931 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9932 if (server_alias) {
9933 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9937 /* present alias if it differs from server alias */
9938 if (alias && !sipe_strequal(alias, server_alias))
9940 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9943 if (is_empty(email)) {
9944 g_free(email);
9945 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9946 if (email) {
9947 purple_notify_user_info_add_pair(info, _("Email address"), email);
9951 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9952 if (site) {
9953 purple_notify_user_info_add_pair(info, _("Site"), site);
9956 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9957 if (first_name && last_name) {
9958 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9960 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9961 g_free(link);
9963 g_free(first_name);
9964 g_free(last_name);
9966 if (device_name) {
9967 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9970 /* show a buddy's user info in a nice dialog box */
9971 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9972 uri, /* buddy's URI */
9973 info, /* body */
9974 NULL, /* callback called when dialog closed */
9975 NULL); /* userdata for callback */
9977 g_free(phone_number);
9978 g_free(server_alias);
9979 g_free(email);
9980 g_free(device_name);
9982 return TRUE;
9986 * AD search first, LDAP based
9988 static void sipe_get_info(PurpleConnection *gc, const char *username)
9990 struct sipe_account_data *sip = gc->proto_data;
9991 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9992 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9993 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9994 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9996 payload->destroy = g_free;
9997 payload->data = g_strdup(username);
9999 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
10000 send_soap_request_with_cb(sip, domain_uri, body,
10001 (TransCallback) process_get_info_response, payload);
10002 g_free(domain_uri);
10003 g_free(body);
10004 g_free(row);
10007 PurplePluginProtocolInfo prpl_info =
10009 OPT_PROTO_CHAT_TOPIC,
10010 NULL, /* user_splits */
10011 NULL, /* protocol_options */
10012 NO_BUDDY_ICONS, /* icon_spec */
10013 sipe_list_icon, /* list_icon */
10014 NULL, /* list_emblems */
10015 sipe_status_text, /* status_text */
10016 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10017 sipe_status_types, /* away_states */
10018 sipe_blist_node_menu, /* blist_node_menu */
10019 NULL, /* chat_info */
10020 NULL, /* chat_info_defaults */
10021 sipe_login, /* login */
10022 sipe_close, /* close */
10023 sipe_im_send, /* send_im */
10024 NULL, /* set_info */ // TODO maybe
10025 sipe_send_typing, /* send_typing */
10026 sipe_get_info, /* get_info */
10027 sipe_set_status, /* set_status */
10028 sipe_set_idle, /* set_idle */
10029 NULL, /* change_passwd */
10030 sipe_add_buddy, /* add_buddy */
10031 NULL, /* add_buddies */
10032 sipe_remove_buddy, /* remove_buddy */
10033 NULL, /* remove_buddies */
10034 sipe_add_permit, /* add_permit */
10035 sipe_add_deny, /* add_deny */
10036 sipe_add_deny, /* rem_permit */
10037 sipe_add_permit, /* rem_deny */
10038 dummy_permit_deny, /* set_permit_deny */
10039 NULL, /* join_chat */
10040 NULL, /* reject_chat */
10041 NULL, /* get_chat_name */
10042 sipe_chat_invite, /* chat_invite */
10043 sipe_chat_leave, /* chat_leave */
10044 NULL, /* chat_whisper */
10045 sipe_chat_send, /* chat_send */
10046 sipe_keep_alive, /* keepalive */
10047 NULL, /* register_user */
10048 NULL, /* get_cb_info */ // deprecated
10049 NULL, /* get_cb_away */ // deprecated
10050 sipe_alias_buddy, /* alias_buddy */
10051 sipe_group_buddy, /* group_buddy */
10052 sipe_rename_group, /* rename_group */
10053 NULL, /* buddy_free */
10054 sipe_convo_closed, /* convo_closed */
10055 purple_normalize_nocase, /* normalize */
10056 NULL, /* set_buddy_icon */
10057 sipe_remove_group, /* remove_group */
10058 NULL, /* get_cb_real_name */ // TODO?
10059 NULL, /* set_chat_topic */
10060 NULL, /* find_blist_chat */
10061 NULL, /* roomlist_get_list */
10062 NULL, /* roomlist_cancel */
10063 NULL, /* roomlist_expand_category */
10064 NULL, /* can_receive_file */
10065 sipe_ft_send_file, /* send_file */
10066 sipe_ft_new_xfer, /* new_xfer */
10067 NULL, /* offline_message */
10068 NULL, /* whiteboard_prpl_ops */
10069 sipe_send_raw, /* send_raw */
10070 NULL, /* roomlist_room_serialize */
10071 NULL, /* unregister_user */
10072 NULL, /* send_attention */
10073 NULL, /* get_attention_types */
10074 #if !PURPLE_VERSION_CHECK(2,5,0)
10075 /* Backward compatibility when compiling against 2.4.x API */
10076 (void (*)(void)) /* _purple_reserved4 */
10077 #endif
10078 sizeof(PurplePluginProtocolInfo), /* struct_size */
10079 #if PURPLE_VERSION_CHECK(2,5,0)
10080 sipe_get_account_text_table, /* get_account_text_table */
10081 #if PURPLE_VERSION_CHECK(2,6,0)
10082 NULL, /* initiate_media */
10083 NULL, /* get_media_caps */
10084 #endif
10085 #endif
10089 PurplePluginInfo info = {
10090 PURPLE_PLUGIN_MAGIC,
10091 PURPLE_MAJOR_VERSION,
10092 PURPLE_MINOR_VERSION,
10093 PURPLE_PLUGIN_PROTOCOL, /**< type */
10094 NULL, /**< ui_requirement */
10095 0, /**< flags */
10096 NULL, /**< dependencies */
10097 PURPLE_PRIORITY_DEFAULT, /**< priority */
10098 "prpl-sipe", /**< id */
10099 "Office Communicator", /**< name */
10100 PACKAGE_VERSION, /**< version */
10101 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10102 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10103 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10104 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10105 "Gabriel Burt <gburt@novell.com>, " /**< author */
10106 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10107 "pier11 <pier11@operamail.com>", /**< author */
10108 PACKAGE_URL, /**< homepage */
10109 sipe_plugin_load, /**< load */
10110 sipe_plugin_unload, /**< unload */
10111 sipe_plugin_destroy, /**< destroy */
10112 NULL, /**< ui_info */
10113 &prpl_info, /**< extra_info */
10114 NULL,
10115 sipe_actions,
10116 NULL,
10117 NULL,
10118 NULL,
10119 NULL
10123 Local Variables:
10124 mode: c
10125 c-file-style: "bsd"
10126 indent-tabs-mode: t
10127 tab-width: 8
10128 End: