mingw: fixed Windows build
[siplcs.git] / src / core / sipe.c
blobc1742ab5ea0b1e443302b10af860c7652a1e422c
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 #include "internal.h"
47 #else
48 #include <sys/types.h>
49 #include <sys/socket.h>
50 #include <netinet/in.h>
51 #endif /* _WIN32 */
53 #include <time.h>
54 #include <stdio.h>
55 #include <errno.h>
56 #include <string.h>
57 #include <unistd.h>
58 #include <glib.h>
61 #include "blist.h"
62 #include "conversation.h"
63 #include "dnsquery.h"
64 #include "debug.h"
65 #include "notify.h"
66 #include "savedstatuses.h"
67 #include "privacy.h"
68 #include "util.h"
69 #include "version.h"
70 #include "network.h"
71 #include "xmlnode.h"
72 #include "mime.h"
73 #include "core.h"
75 #include "core-depurple.h" /* Temporary for the core de-purple transition */
77 #include "sipe.h"
78 #include "sipe-cal.h"
79 #include "sipe-ews.h"
80 #include "sipe-chat.h"
81 #include "sipe-conf.h"
82 #include "sip-csta.h"
83 #include "sipe-dialog.h"
84 #include "sipe-nls.h"
85 #include "sipe-session.h"
86 #include "sipe-utils.h"
87 #include "sipe-ft.h"
88 #include "sipmsg.h"
89 #include "sipe-sign.h"
90 #include "dnssrv.h"
91 #include "request.h"
93 /* Backward compatibility when compiling against 2.4.x API */
94 #if !PURPLE_VERSION_CHECK(2,5,0)
95 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
96 #endif
98 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
100 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
101 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
103 /* Keep in sync with sipe_transport_type! */
104 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
105 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
107 /* Status identifiers (see also: sipe_status_types()) */
108 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
109 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
110 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
111 /* PURPLE_STATUS_UNAVAILABLE: */
112 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
113 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
114 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
115 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
116 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
117 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
118 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
119 /* PURPLE_STATUS_AWAY: */
120 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
121 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
122 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
123 /** Reuters status (user settable) */
124 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
125 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
126 /* ??? PURPLE_STATUS_MOBILE */
127 /* ??? PURPLE_STATUS_TUNE */
129 /* Status attributes (see also sipe_status_types() */
130 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
132 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
134 static struct sipe_activity_map_struct
136 sipe_activity type;
137 const char *token;
138 const char *desc;
139 const char *status_id;
141 } const sipe_activity_map[] =
143 /* This has nothing to do with Availability numbers, like 3500 (online).
144 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
146 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
147 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
148 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
149 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
150 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
151 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
152 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
153 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
154 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
155 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
156 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
157 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
158 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
159 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
160 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
162 /** @param x is sipe_activity */
163 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
166 /* Action name templates */
167 #define ACTION_NAME_PRESENCE "<presence><%s>"
169 static sipe_activity
170 sipe_get_activity_by_token(const char *token)
172 int i;
174 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
176 if (sipe_strequal(token, sipe_activity_map[i].token))
177 return sipe_activity_map[i].type;
180 return sipe_activity_map[0].type;
183 static const char *
184 sipe_get_activity_desc_by_token(const char *token)
186 if (!token) return NULL;
188 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
191 /** Allows to send typed messages from chat window again after account reinstantiation. */
192 static void
193 sipe_rejoin_chat(PurpleConversation *conv)
195 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
196 PURPLE_CONV_CHAT(conv)->left)
198 PURPLE_CONV_CHAT(conv)->left = FALSE;
199 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
203 static char *genbranch()
205 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
206 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
207 rand() & 0xFFFF, rand() & 0xFFFF);
211 static char *default_ua = NULL;
212 static const char*
213 sipe_get_useragent(struct sipe_account_data *sip)
215 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
216 if (is_empty(useragent)) {
217 if (!default_ua) {
218 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
219 /* ref: lzodefs.h */
220 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
221 #define SIPE_TARGET_PLATFORM "linux"
222 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
223 #define SIPE_TARGET_PLATFORM "bsd"
224 #elif defined(__APPLE__) || defined(__MACOS__)
225 #define SIPE_TARGET_PLATFORM "macosx"
226 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
227 #define SIPE_TARGET_PLATFORM "aix"
228 #elif defined(__solaris__) || defined(__sun)
229 #define SIPE_TARGET_PLATFORM "sun"
230 #elif defined(_WIN32)
231 #define SIPE_TARGET_PLATFORM "win"
232 #elif defined(__CYGWIN__)
233 #define SIPE_TARGET_PLATFORM "cygwin"
234 #elif defined(__hpux__)
235 #define SIPE_TARGET_PLATFORM "hpux"
236 #elif defined(__sgi__)
237 #define SIPE_TARGET_PLATFORM "irix"
238 #else
239 #define SIPE_TARGET_PLATFORM "unknown"
240 #endif
242 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
243 #define SIPE_TARGET_ARCH "x86_64"
244 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
245 #define SIPE_TARGET_ARCH "i386"
246 #elif defined(__ppc64__)
247 #define SIPE_TARGET_ARCH "ppc64"
248 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
249 #define SIPE_TARGET_ARCH "ppc"
250 #elif defined(__hppa__) || defined(__hppa)
251 #define SIPE_TARGET_ARCH "hppa"
252 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
253 #define SIPE_TARGET_ARCH "mips"
254 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
255 #define SIPE_TARGET_ARCH "s390"
256 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
257 #define SIPE_TARGET_ARCH "sparc"
258 #elif defined(__arm__)
259 #define SIPE_TARGET_ARCH "arm"
260 #else
261 #define SIPE_TARGET_ARCH "other"
262 #endif
264 default_ua = g_strdup_printf("Purple/%s Sipe/" SIPE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
265 purple_core_get_version(),
266 sip->server_version ? sip->server_version : "");
268 useragent = default_ua;
270 return useragent;
273 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
274 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
276 return "sipe";
279 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
281 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
282 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
283 gpointer data);
285 static void sipe_close(PurpleConnection *gc);
287 static void send_presence_status(struct sipe_account_data *sip);
289 static void sendout_pkt(PurpleConnection *gc, const char *buf);
291 static void sipe_keep_alive(PurpleConnection *gc)
293 struct sipe_account_data *sip = gc->proto_data;
294 if (sip->transport == SIPE_TRANSPORT_UDP) {
295 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
296 gchar buf[2] = {0, 0};
297 purple_debug_info("sipe", "sending keep alive\n");
298 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
299 } else {
300 time_t now = time(NULL);
301 if ((sip->keepalive_timeout > 0) &&
302 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
303 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
305 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
306 sendout_pkt(gc, "\r\n\r\n");
307 sip->last_keepalive = now;
312 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
314 struct sip_connection *ret = NULL;
315 GSList *entry = sip->openconns;
316 while (entry) {
317 ret = entry->data;
318 if (ret->fd == fd) return ret;
319 entry = entry->next;
321 return NULL;
324 static void sipe_auth_free(struct sip_auth *auth)
326 g_free(auth->opaque);
327 auth->opaque = NULL;
328 g_free(auth->realm);
329 auth->realm = NULL;
330 g_free(auth->target);
331 auth->target = NULL;
332 auth->version = 0;
333 auth->type = AUTH_TYPE_UNSET;
334 auth->retries = 0;
335 auth->expires = 0;
336 g_free(auth->gssapi_data);
337 auth->gssapi_data = NULL;
338 sip_sec_destroy_context(auth->gssapi_context);
339 auth->gssapi_context = NULL;
342 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
344 struct sip_connection *ret = g_new0(struct sip_connection, 1);
345 ret->fd = fd;
346 sip->openconns = g_slist_append(sip->openconns, ret);
347 return ret;
350 static void connection_remove(struct sipe_account_data *sip, int fd)
352 struct sip_connection *conn = connection_find(sip, fd);
353 if (conn) {
354 sip->openconns = g_slist_remove(sip->openconns, conn);
355 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
356 g_free(conn->inbuf);
357 g_free(conn);
361 static void connection_free_all(struct sipe_account_data *sip)
363 struct sip_connection *ret = NULL;
364 GSList *entry = sip->openconns;
365 while (entry) {
366 ret = entry->data;
367 connection_remove(sip, ret->fd);
368 entry = sip->openconns;
372 static void
373 sipe_make_signature(struct sipe_account_data *sip,
374 struct sipmsg *msg);
376 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
378 gchar noncecount[9];
379 const char *authuser = sip->authuser;
380 gchar *response;
381 gchar *ret;
383 if (!authuser || strlen(authuser) < 1) {
384 authuser = sip->username;
387 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
388 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
389 gchar *version_str;
391 // If we have a signature for the message, include that
392 if (msg->signature) {
393 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);
396 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
397 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
398 gchar *gssapi_data;
399 gchar *opaque;
400 gchar *sign_str = NULL;
402 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
403 &(auth->expires),
404 auth->type,
405 purple_account_get_bool(sip->account, "sso", TRUE),
406 sip->authdomain ? sip->authdomain : "",
407 authuser,
408 sip->password,
409 auth->target,
410 auth->gssapi_data);
411 if (!gssapi_data || !auth->gssapi_context) {
412 sip->gc->wants_to_die = TRUE;
413 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
414 return NULL;
417 if (auth->version > 3) {
418 sipe_make_signature(sip, msg);
419 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
420 msg->rand, msg->num, msg->signature);
421 } else {
422 sign_str = g_strdup("");
425 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
426 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
427 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);
428 g_free(opaque);
429 g_free(gssapi_data);
430 g_free(version_str);
431 g_free(sign_str);
432 return ret;
435 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
436 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
437 g_free(version_str);
438 return ret;
440 } else { /* Digest */
442 /* Calculate new session key */
443 if (!auth->opaque) {
444 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
445 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
446 authuser, auth->realm, sip->password,
447 auth->gssapi_data, NULL);
450 sprintf(noncecount, "%08d", auth->nc++);
451 response = purple_cipher_http_digest_calculate_response("md5",
452 msg->method, msg->target, NULL, NULL,
453 auth->gssapi_data, noncecount, NULL,
454 auth->opaque);
455 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
457 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);
458 g_free(response);
459 return ret;
463 static char *parse_attribute(const char *attrname, const char *source)
465 const char *tmp, *tmp2;
466 char *retval = NULL;
467 int len = strlen(attrname);
469 if (g_str_has_prefix(source, attrname)) {
470 tmp = source + len;
471 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
472 if (tmp2)
473 retval = g_strndup(tmp, tmp2 - tmp);
474 else
475 retval = g_strdup(tmp);
478 return retval;
481 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
483 int i;
484 gchar **parts;
486 if (!hdr) {
487 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
488 return;
491 if (!g_strncasecmp(hdr, "NTLM", 4)) {
492 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
493 auth->type = AUTH_TYPE_NTLM;
494 hdr += 5;
495 auth->nc = 1;
496 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
497 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
498 auth->type = AUTH_TYPE_KERBEROS;
499 hdr += 9;
500 auth->nc = 3;
501 } else {
502 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
503 auth->type = AUTH_TYPE_DIGEST;
504 hdr += 7;
507 parts = g_strsplit(hdr, "\", ", 0);
508 for (i = 0; parts[i]; i++) {
509 char *tmp;
511 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
513 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
514 g_free(auth->gssapi_data);
515 auth->gssapi_data = tmp;
517 if (auth->type == AUTH_TYPE_NTLM) {
518 /* NTLM module extracts nonce from gssapi-data */
519 auth->nc = 3;
522 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
523 /* Only used with AUTH_TYPE_DIGEST */
524 g_free(auth->gssapi_data);
525 auth->gssapi_data = tmp;
526 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
527 g_free(auth->opaque);
528 auth->opaque = tmp;
529 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
530 g_free(auth->realm);
531 auth->realm = tmp;
533 if (auth->type == AUTH_TYPE_DIGEST) {
534 /* Throw away old session key */
535 g_free(auth->opaque);
536 auth->opaque = NULL;
537 auth->nc = 1;
539 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
540 g_free(auth->target);
541 auth->target = tmp;
542 } else if ((tmp = parse_attribute("version=", parts[i]))) {
543 auth->version = atoi(tmp);
544 g_free(tmp);
546 // uncomment to revert to previous functionality if version 3+ does not work.
547 // auth->version = 2;
549 g_strfreev(parts);
551 return;
554 static void sipe_canwrite_cb(gpointer data,
555 SIPE_UNUSED_PARAMETER gint source,
556 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
558 PurpleConnection *gc = data;
559 struct sipe_account_data *sip = gc->proto_data;
560 gsize max_write;
561 gssize written;
563 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
565 if (max_write == 0) {
566 if (sip->tx_handler != 0){
567 purple_input_remove(sip->tx_handler);
568 sip->tx_handler = 0;
570 return;
573 written = write(sip->fd, sip->txbuf->outptr, max_write);
575 if (written < 0 && errno == EAGAIN)
576 written = 0;
577 else if (written <= 0) {
578 /*TODO: do we really want to disconnect on a failure to write?*/
579 purple_connection_error(gc, _("Could not write"));
580 return;
583 purple_circ_buffer_mark_read(sip->txbuf, written);
586 static void sipe_canwrite_cb_ssl(gpointer data,
587 SIPE_UNUSED_PARAMETER gint src,
588 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
590 PurpleConnection *gc = data;
591 struct sipe_account_data *sip = gc->proto_data;
592 gsize max_write;
593 gssize written;
595 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
597 if (max_write == 0) {
598 if (sip->tx_handler != 0) {
599 purple_input_remove(sip->tx_handler);
600 sip->tx_handler = 0;
601 return;
605 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
607 if (written < 0 && errno == EAGAIN)
608 written = 0;
609 else if (written <= 0) {
610 /*TODO: do we really want to disconnect on a failure to write?*/
611 purple_connection_error(gc, _("Could not write"));
612 return;
615 purple_circ_buffer_mark_read(sip->txbuf, written);
618 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
620 static void send_later_cb(gpointer data, gint source,
621 SIPE_UNUSED_PARAMETER const gchar *error)
623 PurpleConnection *gc = data;
624 struct sipe_account_data *sip;
625 struct sip_connection *conn;
627 if (!PURPLE_CONNECTION_IS_VALID(gc))
629 if (source >= 0)
630 close(source);
631 return;
634 if (source < 0) {
635 purple_connection_error(gc, _("Could not connect"));
636 return;
639 sip = gc->proto_data;
640 sip->fd = source;
641 sip->connecting = FALSE;
642 sip->last_keepalive = time(NULL);
644 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
646 /* If there is more to write now, we need to register a handler */
647 if (sip->txbuf->bufused > 0)
648 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
650 conn = connection_create(sip, source);
651 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
654 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
656 struct sipe_account_data *sip;
658 if (!PURPLE_CONNECTION_IS_VALID(gc))
660 if (gsc) purple_ssl_close(gsc);
661 return NULL;
664 sip = gc->proto_data;
665 sip->fd = gsc->fd;
666 sip->gsc = gsc;
667 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
668 sip->connecting = FALSE;
669 sip->last_keepalive = time(NULL);
671 connection_create(sip, gsc->fd);
673 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
675 return sip;
678 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
679 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
681 PurpleConnection *gc = data;
682 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
683 if (sip == NULL) return;
685 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
687 /* If there is more to write now */
688 if (sip->txbuf->bufused > 0) {
689 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
694 static void sendlater(PurpleConnection *gc, const char *buf)
696 struct sipe_account_data *sip = gc->proto_data;
698 if (!sip->connecting) {
699 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
700 if (sip->transport == SIPE_TRANSPORT_TLS){
701 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
702 } else {
703 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
704 purple_connection_error(gc, _("Could not create socket"));
707 sip->connecting = TRUE;
710 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
711 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
713 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
716 static void sendout_pkt(PurpleConnection *gc, const char *buf)
718 struct sipe_account_data *sip = gc->proto_data;
719 time_t currtime = time(NULL);
720 int writelen = strlen(buf);
721 char *tmp;
723 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
724 g_free(tmp);
725 if (sip->transport == SIPE_TRANSPORT_UDP) {
726 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
727 purple_debug_info("sipe", "could not send packet\n");
729 } else {
730 int ret;
731 if (sip->fd < 0) {
732 sendlater(gc, buf);
733 return;
736 if (sip->tx_handler) {
737 ret = -1;
738 errno = EAGAIN;
739 } else{
740 if (sip->gsc){
741 ret = purple_ssl_write(sip->gsc, buf, writelen);
742 }else{
743 ret = write(sip->fd, buf, writelen);
747 if (ret < 0 && errno == EAGAIN)
748 ret = 0;
749 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
750 sendlater(gc, buf);
751 return;
754 if (ret < writelen) {
755 if (!sip->tx_handler){
756 if (sip->gsc){
757 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
759 else{
760 sip->tx_handler = purple_input_add(sip->fd,
761 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
762 gc);
766 /* XXX: is it OK to do this? You might get part of a request sent
767 with part of another. */
768 if (sip->txbuf->bufused > 0)
769 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
771 purple_circ_buffer_append(sip->txbuf, buf + ret,
772 writelen - ret);
777 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
779 sendout_pkt(gc, buf);
780 return len;
783 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
785 GSList *tmp = msg->headers;
786 gchar *name;
787 gchar *value;
788 GString *outstr = g_string_new("");
789 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
790 while (tmp) {
791 name = ((struct sipnameval*) (tmp->data))->name;
792 value = ((struct sipnameval*) (tmp->data))->value;
793 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
794 tmp = g_slist_next(tmp);
796 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
797 sendout_pkt(sip->gc, outstr->str);
798 g_string_free(outstr, TRUE);
801 static void
802 sipe_make_signature(struct sipe_account_data *sip,
803 struct sipmsg *msg)
805 if (sip->registrar.gssapi_context) {
806 struct sipmsg_breakdown msgbd;
807 gchar *signature_input_str;
808 msgbd.msg = msg;
809 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
810 msgbd.rand = g_strdup_printf("%08x", g_random_int());
811 sip->registrar.ntlm_num++;
812 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
813 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
814 if (signature_input_str != NULL) {
815 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
816 msg->signature = signature_hex;
817 msg->rand = g_strdup(msgbd.rand);
818 msg->num = g_strdup(msgbd.num);
819 g_free(signature_input_str);
821 sipmsg_breakdown_free(&msgbd);
825 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
827 gchar * buf;
829 if (sip->registrar.type == AUTH_TYPE_UNSET) {
830 return;
833 sipe_make_signature(sip, msg);
835 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
836 buf = auth_header(sip, &sip->registrar, msg);
837 if (buf) {
838 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
840 g_free(buf);
841 } 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")) {
842 sip->registrar.nc = 3;
843 sip->registrar.type = AUTH_TYPE_NTLM;
844 #ifdef USE_KERBEROS
845 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
846 sip->registrar.type = AUTH_TYPE_KERBEROS;
848 #endif
851 buf = auth_header(sip, &sip->registrar, msg);
852 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
853 g_free(buf);
854 } else {
855 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
859 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
860 const char *text, const char *body)
862 gchar *name;
863 gchar *value;
864 GString *outstr = g_string_new("");
865 struct sipe_account_data *sip = gc->proto_data;
866 gchar *contact;
867 GSList *tmp;
868 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
870 /* Can return NULL! */
871 contact = get_contact(sip);
872 if (contact) {
873 sipmsg_add_header(msg, "Contact", contact);
874 g_free(contact);
877 if (body) {
878 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
879 sipmsg_add_header(msg, "Content-Length", len);
880 g_free(len);
881 } else {
882 sipmsg_add_header(msg, "Content-Length", "0");
885 msg->response = code;
887 sipmsg_strip_headers(msg, keepers);
888 sipmsg_merge_new_headers(msg);
889 sign_outgoing_message(msg, sip, msg->method);
891 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
892 tmp = msg->headers;
893 while (tmp) {
894 name = ((struct sipnameval*) (tmp->data))->name;
895 value = ((struct sipnameval*) (tmp->data))->value;
897 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
898 tmp = g_slist_next(tmp);
900 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
901 sendout_pkt(gc, outstr->str);
902 g_string_free(outstr, TRUE);
905 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
907 if (sip->transactions) {
908 sip->transactions = g_slist_remove(sip->transactions, trans);
909 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
911 if (trans->msg) sipmsg_free(trans->msg);
912 if (trans->payload) {
913 (*trans->payload->destroy)(trans->payload->data);
914 g_free(trans->payload);
916 g_free(trans->key);
917 g_free(trans);
921 static struct transaction *
922 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
924 const gchar *call_id;
925 const gchar *cseq;
926 struct transaction *trans = g_new0(struct transaction, 1);
928 trans->time = time(NULL);
929 trans->msg = (struct sipmsg *)msg;
930 call_id = sipmsg_find_header(trans->msg, "Call-ID");
931 cseq = sipmsg_find_header(trans->msg, "CSeq");
932 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
933 trans->callback = callback;
934 sip->transactions = g_slist_append(sip->transactions, trans);
935 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
936 return trans;
939 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
941 struct transaction *trans;
942 GSList *transactions = sip->transactions;
943 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
944 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
945 gchar *key;
947 if (!call_id || !cseq) {
948 purple_debug(PURPLE_DEBUG_ERROR, "sipe", "transaction_find: no Call-ID or CSeq!\n");
949 return NULL;
952 key = g_strdup_printf("<%s><%s>", call_id, cseq);
953 while (transactions) {
954 trans = transactions->data;
955 if (!g_strcasecmp(trans->key, key)) {
956 g_free(key);
957 return trans;
959 transactions = transactions->next;
962 g_free(key);
963 return NULL;
966 struct transaction *
967 send_sip_request(PurpleConnection *gc, const gchar *method,
968 const gchar *url, const gchar *to, const gchar *addheaders,
969 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
971 struct sipe_account_data *sip = gc->proto_data;
972 const char *addh = "";
973 char *buf;
974 struct sipmsg *msg;
975 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
976 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
977 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
978 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
979 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
980 gchar *route = g_strdup("");
981 gchar *epid = get_epid(sip);
982 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
983 struct transaction *trans = NULL;
985 if (dialog && dialog->routes)
987 GSList *iter = dialog->routes;
989 while(iter)
991 char *tmp = route;
992 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
993 g_free(tmp);
994 iter = g_slist_next(iter);
998 if (!ourtag && !dialog) {
999 ourtag = gentag();
1002 if (sipe_strequal(method, "REGISTER")) {
1003 if (sip->regcallid) {
1004 g_free(callid);
1005 callid = g_strdup(sip->regcallid);
1006 } else {
1007 sip->regcallid = g_strdup(callid);
1009 cseq = ++sip->cseq;
1012 if (addheaders) addh = addheaders;
1014 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1015 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1016 "From: <sip:%s>%s%s;epid=%s\r\n"
1017 "To: <%s>%s%s%s%s\r\n"
1018 "Max-Forwards: 70\r\n"
1019 "CSeq: %d %s\r\n"
1020 "User-Agent: %s\r\n"
1021 "Call-ID: %s\r\n"
1022 "%s%s"
1023 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1024 method,
1025 dialog && dialog->request ? dialog->request : url,
1026 TRANSPORT_DESCRIPTOR,
1027 purple_network_get_my_ip(-1),
1028 sip->listenport,
1029 branch ? ";branch=" : "",
1030 branch ? branch : "",
1031 sip->username,
1032 ourtag ? ";tag=" : "",
1033 ourtag ? ourtag : "",
1034 epid,
1036 theirtag ? ";tag=" : "",
1037 theirtag ? theirtag : "",
1038 theirepid ? ";epid=" : "",
1039 theirepid ? theirepid : "",
1040 cseq,
1041 method,
1042 sipe_get_useragent(sip),
1043 callid,
1044 route,
1045 addh,
1046 body ? (gsize) strlen(body) : 0,
1047 body ? body : "");
1050 //printf ("parsing msg buf:\n%s\n\n", buf);
1051 msg = sipmsg_parse_msg(buf);
1053 g_free(buf);
1054 g_free(ourtag);
1055 g_free(theirtag);
1056 g_free(theirepid);
1057 g_free(branch);
1058 g_free(callid);
1059 g_free(route);
1060 g_free(epid);
1062 sign_outgoing_message (msg, sip, method);
1064 buf = sipmsg_to_string (msg);
1066 /* add to ongoing transactions */
1067 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1068 if (!sipe_strequal(method, "ACK")) {
1069 trans = transactions_add_buf(sip, msg, tc);
1070 } else {
1071 sipmsg_free(msg);
1073 sendout_pkt(gc, buf);
1074 g_free(buf);
1076 return trans;
1080 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1082 static void
1083 send_soap_request_with_cb(struct sipe_account_data *sip,
1084 gchar *from0,
1085 gchar *body,
1086 TransCallback callback,
1087 struct transaction_payload *payload)
1089 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1090 gchar *contact = get_contact(sip);
1091 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1092 "Content-Type: application/SOAP+xml\r\n",contact);
1094 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1095 trans->payload = payload;
1097 g_free(from);
1098 g_free(contact);
1099 g_free(hdr);
1102 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1104 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1107 static char *get_contact_register(struct sipe_account_data *sip)
1109 char *epid = get_epid(sip);
1110 char *uuid = generateUUIDfromEPID(epid);
1111 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);
1112 g_free(uuid);
1113 g_free(epid);
1114 return(buf);
1117 static void do_register_exp(struct sipe_account_data *sip, int expire)
1119 char *uri;
1120 char *expires;
1121 char *to;
1122 char *contact;
1123 char *hdr;
1125 if (!sip->sipdomain) return;
1127 uri = sip_uri_from_name(sip->sipdomain);
1128 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1129 to = sip_uri_self(sip);
1130 contact = get_contact_register(sip);
1131 hdr = g_strdup_printf("Contact: %s\r\n"
1132 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1133 "Event: registration\r\n"
1134 "Allow-Events: presence\r\n"
1135 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1136 "%s", contact, expires);
1137 g_free(contact);
1138 g_free(expires);
1140 sip->registerstatus = 1;
1142 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1143 process_register_response);
1145 g_free(hdr);
1146 g_free(uri);
1147 g_free(to);
1150 static void do_register_cb(struct sipe_account_data *sip,
1151 SIPE_UNUSED_PARAMETER void *unused)
1153 do_register_exp(sip, -1);
1154 sip->reregister_set = FALSE;
1157 static void do_register(struct sipe_account_data *sip)
1159 do_register_exp(sip, -1);
1162 static void
1163 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1165 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1166 send_soap_request(sip, body);
1167 g_free(body);
1170 static void
1171 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1173 if (allow) {
1174 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1175 } else {
1176 purple_debug_info("sipe", "Blocking contact %s\n", who);
1179 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1182 static
1183 void sipe_auth_user_cb(void * data)
1185 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1186 if (!job) return;
1188 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1189 g_free(job);
1192 static
1193 void sipe_deny_user_cb(void * data)
1195 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1196 if (!job) return;
1198 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1199 g_free(job);
1202 static void
1203 sipe_add_permit(PurpleConnection *gc, const char *name)
1205 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1206 sipe_contact_allow_deny(sip, name, TRUE);
1209 static void
1210 sipe_add_deny(PurpleConnection *gc, const char *name)
1212 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1213 sipe_contact_allow_deny(sip, name, FALSE);
1216 /*static void
1217 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1219 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1220 sipe_contact_set_acl(sip, name, "");
1223 static void
1224 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1226 xmlnode *watchers;
1227 xmlnode *watcher;
1228 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1229 if (msg->response != 0 && msg->response != 200) return;
1231 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1233 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1234 if (!watchers) return;
1236 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1237 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1238 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1239 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1241 // TODO pull out optional displayName to pass as alias
1242 if (remote_user) {
1243 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1244 job->who = remote_user;
1245 job->sip = sip;
1246 purple_account_request_authorization(
1247 sip->account,
1248 remote_user,
1249 _("you"), /* id */
1250 alias,
1251 NULL, /* message */
1252 on_list,
1253 sipe_auth_user_cb,
1254 sipe_deny_user_cb,
1255 (void *) job);
1260 xmlnode_free(watchers);
1261 return;
1264 static void
1265 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1267 PurpleGroup * purple_group = purple_find_group(group->name);
1268 if (!purple_group) {
1269 purple_group = purple_group_new(group->name);
1270 purple_blist_add_group(purple_group, NULL);
1273 if (purple_group) {
1274 group->purple_group = purple_group;
1275 sip->groups = g_slist_append(sip->groups, group);
1276 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1277 } else {
1278 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1282 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1284 struct sipe_group *group;
1285 GSList *entry;
1286 if (sip == NULL) {
1287 return NULL;
1290 entry = sip->groups;
1291 while (entry) {
1292 group = entry->data;
1293 if (group->id == id) {
1294 return group;
1296 entry = entry->next;
1298 return NULL;
1301 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1303 struct sipe_group *group;
1304 GSList *entry;
1305 if (!sip || !name) {
1306 return NULL;
1309 entry = sip->groups;
1310 while (entry) {
1311 group = entry->data;
1312 if (sipe_strequal(group->name, name)) {
1313 return group;
1315 entry = entry->next;
1317 return NULL;
1320 static void
1321 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1323 gchar *body;
1324 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1325 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1326 send_soap_request(sip, body);
1327 g_free(body);
1328 g_free(group->name);
1329 group->name = g_strdup(name);
1333 * Only appends if no such value already stored.
1334 * Like Set in Java.
1336 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1337 GSList * res = list;
1338 if (!g_slist_find_custom(list, data, func)) {
1339 res = g_slist_insert_sorted(list, data, func);
1341 return res;
1344 static int
1345 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1346 return group1->id - group2->id;
1350 * Returns string like "2 4 7 8" - group ids buddy belong to.
1352 static gchar *
1353 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1354 int i = 0;
1355 gchar *res;
1356 //creating array from GList, converting int to gchar*
1357 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1358 GSList *entry = buddy->groups;
1360 if (!ids_arr) return NULL;
1362 while (entry) {
1363 struct sipe_group * group = entry->data;
1364 ids_arr[i] = g_strdup_printf("%d", group->id);
1365 entry = entry->next;
1366 i++;
1368 ids_arr[i] = NULL;
1369 res = g_strjoinv(" ", ids_arr);
1370 g_strfreev(ids_arr);
1371 return res;
1375 * Sends buddy update to server
1377 static void
1378 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1380 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1381 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1383 if (buddy && purple_buddy) {
1384 const char *alias = purple_buddy_get_alias(purple_buddy);
1385 gchar *groups = sipe_get_buddy_groups_string(buddy);
1386 if (groups) {
1387 gchar *body;
1388 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1390 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1391 alias, groups, "true", buddy->name, sip->contacts_delta++
1393 send_soap_request(sip, body);
1394 g_free(groups);
1395 g_free(body);
1400 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1402 if (msg->response == 200) {
1403 struct sipe_group *group;
1404 struct group_user_context *ctx = trans->payload->data;
1405 xmlnode *xml;
1406 xmlnode *node;
1407 char *group_id;
1408 struct sipe_buddy *buddy;
1410 xml = xmlnode_from_str(msg->body, msg->bodylen);
1411 if (!xml) {
1412 return FALSE;
1415 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1416 if (!node) {
1417 xmlnode_free(xml);
1418 return FALSE;
1421 group_id = xmlnode_get_data(node);
1422 if (!group_id) {
1423 xmlnode_free(xml);
1424 return FALSE;
1427 group = g_new0(struct sipe_group, 1);
1428 group->id = (int)g_ascii_strtod(group_id, NULL);
1429 g_free(group_id);
1430 group->name = g_strdup(ctx->group_name);
1432 sipe_group_add(sip, group);
1434 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1435 if (buddy) {
1436 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1439 sipe_group_set_user(sip, ctx->user_name);
1441 xmlnode_free(xml);
1442 return TRUE;
1444 return FALSE;
1447 static void sipe_group_context_destroy(gpointer data)
1449 struct group_user_context *ctx = data;
1450 g_free(ctx->group_name);
1451 g_free(ctx->user_name);
1452 g_free(ctx);
1455 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1457 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1458 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1459 gchar *body;
1460 ctx->group_name = g_strdup(name);
1461 ctx->user_name = g_strdup(who);
1462 payload->destroy = sipe_group_context_destroy;
1463 payload->data = ctx;
1465 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1466 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1467 g_free(body);
1471 * Data structure for scheduled actions
1474 struct scheduled_action {
1476 * Name of action.
1477 * Format is <Event>[<Data>...]
1478 * Example: <presence><sip:user@domain.com> or <registration>
1480 gchar *name;
1481 guint timeout_handler;
1482 gboolean repetitive;
1483 Action action;
1484 GDestroyNotify destroy;
1485 struct sipe_account_data *sip;
1486 void *payload;
1490 * A timer callback
1491 * Should return FALSE if repetitive action is not needed
1493 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1495 gboolean ret;
1496 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1497 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1498 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1499 (sched_action->action)(sched_action->sip, sched_action->payload);
1500 ret = sched_action->repetitive;
1501 if (sched_action->destroy) {
1502 (*sched_action->destroy)(sched_action->payload);
1504 g_free(sched_action->name);
1505 g_free(sched_action);
1506 return ret;
1510 * Kills action timer effectively cancelling
1511 * scheduled action
1513 * @param name of action
1515 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1517 GSList *entry;
1519 if (!sip->timeouts || !name) return;
1521 entry = sip->timeouts;
1522 while (entry) {
1523 struct scheduled_action *sched_action = entry->data;
1524 if(sipe_strequal(sched_action->name, name)) {
1525 GSList *to_delete = entry;
1526 entry = entry->next;
1527 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1528 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1529 purple_timeout_remove(sched_action->timeout_handler);
1530 if (sched_action->destroy) {
1531 (*sched_action->destroy)(sched_action->payload);
1533 g_free(sched_action->name);
1534 g_free(sched_action);
1535 } else {
1536 entry = entry->next;
1541 static void
1542 sipe_schedule_action0(const gchar *name,
1543 int timeout,
1544 gboolean isSeconds,
1545 Action action,
1546 GDestroyNotify destroy,
1547 struct sipe_account_data *sip,
1548 void *payload)
1550 struct scheduled_action *sched_action;
1552 /* Make sure each action only exists once */
1553 sipe_cancel_scheduled_action(sip, name);
1555 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1556 sched_action = g_new0(struct scheduled_action, 1);
1557 sched_action->repetitive = FALSE;
1558 sched_action->name = g_strdup(name);
1559 sched_action->action = action;
1560 sched_action->destroy = destroy;
1561 sched_action->sip = sip;
1562 sched_action->payload = payload;
1563 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1564 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1565 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1566 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1569 void
1570 sipe_schedule_action(const gchar *name,
1571 int timeout,
1572 Action action,
1573 GDestroyNotify destroy,
1574 struct sipe_account_data *sip,
1575 void *payload)
1577 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1581 * Same as sipe_schedule_action() but timeout is in milliseconds.
1583 static void
1584 sipe_schedule_action_msec(const gchar *name,
1585 int timeout,
1586 Action action,
1587 GDestroyNotify destroy,
1588 struct sipe_account_data *sip,
1589 void *payload)
1591 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1594 static void
1595 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1596 time_t calculate_from);
1598 static int
1599 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1601 static const char*
1602 sipe_get_status_by_availability(int avail,
1603 char** activity);
1605 static void
1606 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1607 const char *status_id,
1608 const char *message,
1609 time_t do_not_publish[]);
1611 static void
1612 sipe_apply_calendar_status(struct sipe_account_data *sip,
1613 struct sipe_buddy *sbuddy,
1614 const char *status_id)
1616 time_t cal_avail_since;
1617 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1618 int avail;
1619 gchar *self_uri;
1621 if (!sbuddy) return;
1623 if (cal_status < SIPE_CAL_NO_DATA) {
1624 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_status : %d for %s\n", cal_status, sbuddy->name);
1625 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1628 /* scheduled Cal update call */
1629 if (!status_id) {
1630 status_id = sbuddy->last_non_cal_status_id;
1631 g_free(sbuddy->activity);
1632 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1635 if (!status_id) {
1636 purple_debug_info("sipe", "sipe_apply_calendar_status: status_id is NULL for %s, exiting.\n",
1637 sbuddy->name ? sbuddy->name : "" );
1638 return;
1641 /* adjust to calendar status */
1642 if (cal_status != SIPE_CAL_NO_DATA) {
1643 purple_debug_info("sipe", "sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1645 if (cal_status == SIPE_CAL_BUSY
1646 && cal_avail_since > sbuddy->user_avail_since
1647 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1649 status_id = SIPE_STATUS_ID_BUSY;
1650 g_free(sbuddy->activity);
1651 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1653 avail = sipe_get_availability_by_status(status_id, NULL);
1655 purple_debug_info("sipe", "sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1656 if (cal_avail_since > sbuddy->activity_since) {
1657 if (cal_status == SIPE_CAL_OOF
1658 && avail >= 15000) /* 12000 in 2007 */
1660 g_free(sbuddy->activity);
1661 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1666 /* then set status_id actually */
1667 purple_debug_info("sipe", "sipe_apply_calendar_status: to %s for %s\n", status_id, sbuddy->name ? sbuddy->name : "" );
1668 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1670 /* set our account state to the one in roaming (including calendar info) */
1671 self_uri = sip_uri_self(sip);
1672 if (sip->initial_state_published && sipe_strequal(sbuddy->name, self_uri)) {
1673 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1674 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1677 purple_debug_info("sipe", "sipe_apply_calendar_status: switch to '%s' for the account\n", sip->status);
1678 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1680 g_free(self_uri);
1683 static void
1684 sipe_got_user_status(struct sipe_account_data *sip,
1685 const char* uri,
1686 const char *status_id)
1688 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1690 if (!sbuddy) return;
1692 /* Check if on 2005 system contact's calendar,
1693 * then set/preserve it.
1695 if (!sip->ocs2007) {
1696 sipe_apply_calendar_status(sip, sbuddy, status_id);
1697 } else {
1698 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1702 static void
1703 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1704 struct sipe_buddy *sbuddy,
1705 struct sipe_account_data *sip)
1707 sipe_apply_calendar_status(sip, sbuddy, NULL);
1711 * Updates contact's status
1712 * based on their calendar information.
1714 * Applicability: 2005 systems
1716 static void
1717 update_calendar_status(struct sipe_account_data *sip)
1719 purple_debug_info("sipe", "update_calendar_status() started.\n");
1720 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1722 /* repeat scheduling */
1723 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1727 * Schedules process of contacts' status update
1728 * based on their calendar information.
1729 * Should be scheduled to the beginning of every
1730 * 15 min interval, like:
1731 * 13:00, 13:15, 13:30, 13:45, etc.
1733 * Applicability: 2005 systems
1735 static void
1736 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1737 time_t calculate_from)
1739 int interval = 15*60;
1740 /** start of the beginning of closest 15 min interval. */
1741 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1743 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1744 asctime(localtime(&calculate_from)));
1745 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1746 asctime(localtime(&next_start)));
1748 sipe_schedule_action("<+2005-cal-status>",
1749 (int)(next_start - time(NULL)),
1750 (Action)update_calendar_status,
1751 NULL,
1752 sip,
1753 NULL);
1757 * Schedules process of self status publish
1758 * based on own calendar information.
1759 * Should be scheduled to the beginning of every
1760 * 15 min interval, like:
1761 * 13:00, 13:15, 13:30, 13:45, etc.
1763 * Applicability: 2007+ systems
1765 static void
1766 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1767 time_t calculate_from)
1769 int interval = 5*60;
1770 /** start of the beginning of closest 5 min interval. */
1771 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1773 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1774 asctime(localtime(&calculate_from)));
1775 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1776 asctime(localtime(&next_start)));
1778 sipe_schedule_action("<+2007-cal-status>",
1779 (int)(next_start - time(NULL)),
1780 (Action)publish_calendar_status_self,
1781 NULL,
1782 sip,
1783 NULL);
1786 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1788 /** Should be g_free()'d
1790 static gchar *
1791 sipe_get_subscription_key(const gchar *event,
1792 const gchar *with)
1794 gchar *key = NULL;
1796 if (is_empty(event)) return NULL;
1798 if (event && !g_ascii_strcasecmp(event, "presence")) {
1799 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1800 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1802 /* @TODO drop participated buddies' just_added flag */
1803 } else if (event) {
1804 /* Subscription is identified by <event> key */
1805 key = g_strdup_printf("<%s>", event);
1808 return key;
1811 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1812 SIPE_UNUSED_PARAMETER struct transaction *trans)
1814 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1815 const gchar *event = sipmsg_find_header(msg, "Event");
1816 gchar *key;
1818 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1819 if (!event) {
1820 struct sipmsg *request_msg = trans->msg;
1821 event = sipmsg_find_header(request_msg, "Event");
1824 key = sipe_get_subscription_key(event, with);
1826 /* 200 OK; 481 Call Leg Does Not Exist */
1827 if (key && (msg->response == 200 || msg->response == 481)) {
1828 if (g_hash_table_lookup(sip->subscriptions, key)) {
1829 g_hash_table_remove(sip->subscriptions, key);
1830 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1834 /* create/store subscription dialog if not yet */
1835 if (msg->response == 200) {
1836 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1837 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1839 if (key) {
1840 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1841 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1843 subscription->dialog.callid = g_strdup(callid);
1844 subscription->dialog.cseq = atoi(cseq);
1845 subscription->dialog.with = g_strdup(with);
1846 subscription->event = g_strdup(event);
1847 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1849 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1852 g_free(cseq);
1855 g_free(key);
1856 g_free(with);
1858 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1860 process_incoming_notify(sip, msg, FALSE, FALSE);
1862 return TRUE;
1865 static void sipe_subscribe_resource_uri(const char *name,
1866 SIPE_UNUSED_PARAMETER gpointer value,
1867 gchar **resources_uri)
1869 gchar *tmp = *resources_uri;
1870 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1871 g_free(tmp);
1874 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1876 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1877 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1878 gchar *tmp = *resources_uri;
1880 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1882 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1883 g_free(tmp);
1887 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1888 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1889 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1890 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1891 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1894 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1896 gchar *key;
1897 gchar *contact = get_contact(sip);
1898 gchar *request;
1899 gchar *content;
1900 gchar *require = "";
1901 gchar *accept = "";
1902 gchar *autoextend = "";
1903 gchar *content_type;
1904 struct sip_dialog *dialog;
1906 if (sip->ocs2007) {
1907 require = ", categoryList";
1908 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1909 content_type = "application/msrtc-adrl-categorylist+xml";
1910 content = g_strdup_printf(
1911 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1912 "<action name=\"subscribe\" id=\"63792024\">\n"
1913 "<adhocList>\n%s</adhocList>\n"
1914 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1915 "<category name=\"calendarData\"/>\n"
1916 "<category name=\"contactCard\"/>\n"
1917 "<category name=\"note\"/>\n"
1918 "<category name=\"state\"/>\n"
1919 "</categoryList>\n"
1920 "</action>\n"
1921 "</batchSub>", sip->username, resources_uri);
1922 } else {
1923 autoextend = "Supported: com.microsoft.autoextend\r\n";
1924 content_type = "application/adrl+xml";
1925 content = g_strdup_printf(
1926 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1927 "<create xmlns=\"\">\n%s</create>\n"
1928 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1930 g_free(resources_uri);
1932 request = g_strdup_printf(
1933 "Require: adhoclist%s\r\n"
1934 "Supported: eventlist\r\n"
1935 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1936 "Supported: ms-piggyback-first-notify\r\n"
1937 "%sSupported: ms-benotify\r\n"
1938 "Proxy-Require: ms-benotify\r\n"
1939 "Event: presence\r\n"
1940 "Content-Type: %s\r\n"
1941 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1942 g_free(contact);
1944 /* subscribe to buddy presence */
1945 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1946 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1947 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1948 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1950 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1952 g_free(content);
1953 g_free(to);
1954 g_free(request);
1955 g_free(key);
1958 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1959 SIPE_UNUSED_PARAMETER void *unused)
1961 gchar *to = sip_uri_self(sip);
1962 gchar *resources_uri = g_strdup("");
1963 if (sip->ocs2007) {
1964 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1965 } else {
1966 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1969 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1972 struct presence_batched_routed {
1973 gchar *host;
1974 GSList *buddies;
1977 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1979 struct presence_batched_routed *data = payload;
1980 GSList *buddies = data->buddies;
1981 while (buddies) {
1982 g_free(buddies->data);
1983 buddies = buddies->next;
1985 g_slist_free(data->buddies);
1986 g_free(data->host);
1987 g_free(payload);
1990 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1992 struct presence_batched_routed *data = payload;
1993 GSList *buddies = data->buddies;
1994 gchar *resources_uri = g_strdup("");
1995 while (buddies) {
1996 gchar *tmp = resources_uri;
1997 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1998 g_free(tmp);
1999 buddies = buddies->next;
2001 sipe_subscribe_presence_batched_to(sip, resources_uri,
2002 g_strdup(data->host));
2006 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2007 * The user sends a single SUBSCRIBE request to the subscribed contact.
2008 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2012 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2015 gchar *key;
2016 gchar *to = sip_uri((char *)buddy_name);
2017 gchar *tmp = get_contact(sip);
2018 gchar *request;
2019 gchar *content = NULL;
2020 gchar *autoextend = "";
2021 gchar *content_type = "";
2022 struct sip_dialog *dialog;
2023 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2024 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2026 if (sbuddy) sbuddy->just_added = FALSE;
2028 if (sip->ocs2007) {
2029 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2030 } else {
2031 autoextend = "Supported: com.microsoft.autoextend\r\n";
2034 request = g_strdup_printf(
2035 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2036 "Supported: ms-piggyback-first-notify\r\n"
2037 "%s%sSupported: ms-benotify\r\n"
2038 "Proxy-Require: ms-benotify\r\n"
2039 "Event: presence\r\n"
2040 "Contact: %s\r\n", autoextend, content_type, tmp);
2042 if (sip->ocs2007) {
2043 content = g_strdup_printf(
2044 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2045 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2046 "<resource uri=\"%s\"%s\n"
2047 "</adhocList>\n"
2048 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2049 "<category name=\"calendarData\"/>\n"
2050 "<category name=\"contactCard\"/>\n"
2051 "<category name=\"note\"/>\n"
2052 "<category name=\"state\"/>\n"
2053 "</categoryList>\n"
2054 "</action>\n"
2055 "</batchSub>", sip->username, to, context);
2058 g_free(tmp);
2060 /* subscribe to buddy presence */
2061 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2062 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2063 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2064 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2066 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2068 g_free(content);
2069 g_free(to);
2070 g_free(request);
2071 g_free(key);
2074 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2076 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2078 if (!purple_status_is_active(status))
2079 return;
2081 if (account->gc) {
2082 struct sipe_account_data *sip = account->gc->proto_data;
2084 if (sip) {
2085 gchar *action_name;
2086 gchar *tmp;
2087 time_t now = time(NULL);
2088 const char *status_id = purple_status_get_id(status);
2089 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2090 sipe_activity activity = sipe_get_activity_by_token(status_id);
2091 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2093 /* when other point of presence clears note, but we are keeping
2094 * state if OOF note.
2096 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2097 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2098 do_not_publish = FALSE;
2101 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2102 status_id, (int)sip->do_not_publish[activity], (int)now);
2104 sip->do_not_publish[activity] = 0;
2105 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2106 status_id, (int)sip->do_not_publish[activity]);
2108 if (do_not_publish)
2110 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2111 return;
2114 g_free(sip->status);
2115 sip->status = g_strdup(status_id);
2117 /* hack to escape apostrof before comparison */
2118 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2120 /* this will preserve OOF flag as well */
2121 if (!sipe_strequal(tmp, sip->note)) {
2122 sip->is_oof_note = FALSE;
2123 g_free(sip->note);
2124 sip->note = g_strdup(note);
2125 sip->note_since = time(NULL);
2127 g_free(tmp);
2129 /* schedule 2 sec to capture idle flag */
2130 action_name = g_strdup_printf("<%s>", "+set-status");
2131 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2132 g_free(action_name);
2136 static void
2137 sipe_set_idle(PurpleConnection * gc,
2138 int interval)
2140 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2142 if (gc) {
2143 struct sipe_account_data *sip = gc->proto_data;
2145 if (sip) {
2146 sip->idle_switch = time(NULL);
2147 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2152 static void
2153 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2154 SIPE_UNUSED_PARAMETER const char *alias)
2156 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2157 sipe_group_set_user(sip, name);
2160 static void
2161 sipe_group_buddy(PurpleConnection *gc,
2162 const char *who,
2163 const char *old_group_name,
2164 const char *new_group_name)
2166 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2167 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2168 struct sipe_group * old_group = NULL;
2169 struct sipe_group * new_group;
2171 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2172 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2174 if(!buddy) { // buddy not in roaming list
2175 return;
2178 if (old_group_name) {
2179 old_group = sipe_group_find_by_name(sip, old_group_name);
2181 new_group = sipe_group_find_by_name(sip, new_group_name);
2183 if (old_group) {
2184 buddy->groups = g_slist_remove(buddy->groups, old_group);
2185 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2188 if (!new_group) {
2189 sipe_group_create(sip, new_group_name, who);
2190 } else {
2191 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2192 sipe_group_set_user(sip, who);
2196 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2198 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2200 /* libpurple can call us with undefined buddy or group */
2201 if (buddy && group) {
2202 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2204 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2205 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2206 purple_blist_rename_buddy(buddy, buddy_name);
2207 g_free(buddy_name);
2209 /* Prepend sip: if needed */
2210 if (!g_str_has_prefix(buddy->name, "sip:")) {
2211 gchar *buf = sip_uri_from_name(buddy->name);
2212 purple_blist_rename_buddy(buddy, buf);
2213 g_free(buf);
2216 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2217 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2218 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2219 b->name = g_strdup(buddy->name);
2220 b->just_added = TRUE;
2221 g_hash_table_insert(sip->buddies, b->name, b);
2222 sipe_group_buddy(gc, b->name, NULL, group->name);
2223 /* @TODO should go to callback */
2224 sipe_subscribe_presence_single(sip, b->name);
2225 } else {
2226 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2231 static void sipe_free_buddy(struct sipe_buddy *buddy)
2233 #ifndef _WIN32
2235 * We are calling g_hash_table_foreach_steal(). That means that no
2236 * key/value deallocation functions are called. Therefore the glib
2237 * hash code does not touch the key (buddy->name) or value (buddy)
2238 * of the to-be-deleted hash node at all. It follows that we
2240 * - MUST free the memory for the key ourselves and
2241 * - ARE allowed to do it in this function
2243 * Conclusion: glib must be broken on the Windows platform if sipe
2244 * crashes with SIGTRAP when closing. You'll have to live
2245 * with the memory leak until this is fixed.
2247 g_free(buddy->name);
2248 #endif
2249 g_free(buddy->activity);
2250 g_free(buddy->meeting_subject);
2251 g_free(buddy->meeting_location);
2252 g_free(buddy->note);
2254 g_free(buddy->cal_start_time);
2255 g_free(buddy->cal_free_busy_base64);
2256 g_free(buddy->cal_free_busy);
2257 g_free(buddy->last_non_cal_activity);
2259 sipe_cal_free_working_hours(buddy->cal_working_hours);
2261 g_free(buddy->device_name);
2262 g_slist_free(buddy->groups);
2263 g_free(buddy);
2267 * Unassociates buddy from group first.
2268 * Then see if no groups left, removes buddy completely.
2269 * Otherwise updates buddy groups on server.
2271 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2273 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2274 struct sipe_buddy *b;
2275 struct sipe_group *g = NULL;
2277 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2278 if (!buddy) return;
2280 b = g_hash_table_lookup(sip->buddies, buddy->name);
2281 if (!b) return;
2283 if (group) {
2284 g = sipe_group_find_by_name(sip, group->name);
2287 if (g) {
2288 b->groups = g_slist_remove(b->groups, g);
2289 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2292 if (g_slist_length(b->groups) < 1) {
2293 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2294 sipe_cancel_scheduled_action(sip, action_name);
2295 g_free(action_name);
2297 g_hash_table_remove(sip->buddies, buddy->name);
2299 if (b->name) {
2300 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2301 send_soap_request(sip, body);
2302 g_free(body);
2305 sipe_free_buddy(b);
2306 } else {
2307 //updates groups on server
2308 sipe_group_set_user(sip, b->name);
2313 static void
2314 sipe_rename_group(PurpleConnection *gc,
2315 const char *old_name,
2316 PurpleGroup *group,
2317 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2319 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2320 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2321 if (s_group) {
2322 sipe_group_rename(sip, s_group, group->name);
2323 } else {
2324 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2328 static void
2329 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2331 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2332 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2333 if (s_group) {
2334 gchar *body;
2335 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2336 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2337 send_soap_request(sip, body);
2338 g_free(body);
2340 sip->groups = g_slist_remove(sip->groups, s_group);
2341 g_free(s_group->name);
2342 g_free(s_group);
2343 } else {
2344 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2348 /** All statuses need message attribute to pass Note */
2349 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2351 PurpleStatusType *type;
2352 GList *types = NULL;
2354 /* Macros to reduce code repetition.
2355 Translators: noun */
2356 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2357 prim, id, name, \
2358 TRUE, user, FALSE, \
2359 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2360 NULL); \
2361 types = g_list_append(types, type);
2363 /* Online */
2364 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2365 NULL,
2366 NULL,
2367 TRUE);
2369 /* Busy */
2370 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2371 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2372 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2373 TRUE);
2375 /* Do Not Disturb */
2376 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2377 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2378 NULL,
2379 TRUE);
2381 /* Away */
2382 /* Goes first in the list as
2383 * purple picks the first status with the AWAY type
2384 * for idle.
2386 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2387 NULL,
2388 NULL,
2389 TRUE);
2391 /* Be Right Back */
2392 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2393 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2394 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2395 TRUE);
2397 /* Appear Offline */
2398 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2399 NULL,
2400 NULL,
2401 TRUE);
2403 /* Offline */
2404 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2405 NULL,
2406 NULL,
2407 TRUE);
2408 types = g_list_append(types, type);
2410 return types;
2414 * A callback for g_hash_table_foreach
2416 static void
2417 sipe_buddy_subscribe_cb(char *buddy_name,
2418 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2419 struct sipe_account_data *sip)
2421 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2422 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2423 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2424 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2426 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2427 g_free(action_name);
2431 * Removes entries from purple buddy list
2432 * that does not correspond ones in the roaming contact list.
2434 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2435 GSList *buddies = purple_find_buddies(sip->account, NULL);
2436 GSList *entry = buddies;
2437 struct sipe_buddy *buddy;
2438 PurpleBuddy *b;
2439 PurpleGroup *g;
2441 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2442 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2443 while (entry) {
2444 b = entry->data;
2445 g = purple_buddy_get_group(b);
2446 buddy = g_hash_table_lookup(sip->buddies, b->name);
2447 if(buddy) {
2448 gboolean in_sipe_groups = FALSE;
2449 GSList *entry2 = buddy->groups;
2450 while (entry2) {
2451 struct sipe_group *group = entry2->data;
2452 if (sipe_strequal(group->name, g->name)) {
2453 in_sipe_groups = TRUE;
2454 break;
2456 entry2 = entry2->next;
2458 if(!in_sipe_groups) {
2459 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2460 purple_blist_remove_buddy(b);
2462 } else {
2463 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2464 purple_blist_remove_buddy(b);
2466 entry = entry->next;
2468 g_slist_free(buddies);
2471 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2473 int len = msg->bodylen;
2475 const gchar *tmp = sipmsg_find_header(msg, "Event");
2476 xmlnode *item;
2477 xmlnode *isc;
2478 const gchar *contacts_delta;
2479 xmlnode *group_node;
2480 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2481 return FALSE;
2484 /* Convert the contact from XML to Purple Buddies */
2485 isc = xmlnode_from_str(msg->body, len);
2486 if (!isc) {
2487 return FALSE;
2490 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2491 if (contacts_delta) {
2492 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2495 if (sipe_strequal(isc->name, "contactList")) {
2497 /* Parse groups */
2498 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2499 struct sipe_group * group = g_new0(struct sipe_group, 1);
2500 const char *name = xmlnode_get_attrib(group_node, "name");
2502 if (g_str_has_prefix(name, "~")) {
2503 name = _("Other Contacts");
2505 group->name = g_strdup(name);
2506 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2508 sipe_group_add(sip, group);
2511 // Make sure we have at least one group
2512 if (g_slist_length(sip->groups) == 0) {
2513 struct sipe_group * group = g_new0(struct sipe_group, 1);
2514 PurpleGroup *purple_group;
2515 group->name = g_strdup(_("Other Contacts"));
2516 group->id = 1;
2517 purple_group = purple_group_new(group->name);
2518 purple_blist_add_group(purple_group, NULL);
2519 sip->groups = g_slist_append(sip->groups, group);
2522 /* Parse contacts */
2523 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2524 const gchar *uri = xmlnode_get_attrib(item, "uri");
2525 const gchar *name = xmlnode_get_attrib(item, "name");
2526 gchar *buddy_name;
2527 struct sipe_buddy *buddy = NULL;
2528 gchar *tmp;
2529 gchar **item_groups;
2530 int i = 0;
2532 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2533 tmp = sip_uri_from_name(uri);
2534 buddy_name = g_ascii_strdown(tmp, -1);
2535 g_free(tmp);
2537 /* assign to group Other Contacts if nothing else received */
2538 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2539 if(is_empty(tmp)) {
2540 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2541 g_free(tmp);
2542 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2544 item_groups = g_strsplit(tmp, " ", 0);
2545 g_free(tmp);
2547 while (item_groups[i]) {
2548 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2550 // If couldn't find the right group for this contact, just put them in the first group we have
2551 if (group == NULL && g_slist_length(sip->groups) > 0) {
2552 group = sip->groups->data;
2555 if (group != NULL) {
2556 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2557 if (!b){
2558 b = purple_buddy_new(sip->account, buddy_name, uri);
2559 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2561 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2564 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2565 if (name != NULL && strlen(name) != 0) {
2566 purple_blist_alias_buddy(b, name);
2568 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2572 if (!buddy) {
2573 buddy = g_new0(struct sipe_buddy, 1);
2574 buddy->name = g_strdup(b->name);
2575 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2578 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2580 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2581 } else {
2582 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2583 name);
2586 i++;
2587 } // while, contact groups
2588 g_strfreev(item_groups);
2589 g_free(buddy_name);
2591 } // for, contacts
2593 sipe_cleanup_local_blist(sip);
2595 /* Add self-contact if not there yet. 2005 systems. */
2596 /* This will resemble subscription to roaming_self in 2007 systems */
2597 if (!sip->ocs2007) {
2598 gchar *self_uri = sip_uri_self(sip);
2599 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2601 if (!buddy) {
2602 buddy = g_new0(struct sipe_buddy, 1);
2603 buddy->name = g_strdup(self_uri);
2604 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2606 g_free(self_uri);
2609 xmlnode_free(isc);
2611 /* subscribe to buddies */
2612 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2613 if (sip->batched_support) {
2614 sipe_subscribe_presence_batched(sip, NULL);
2615 } else {
2616 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2618 sip->subscribed_buddies = TRUE;
2620 /* for 2005 systems schedule contacts' status update
2621 * based on their calendar information
2623 if (!sip->ocs2007) {
2624 sipe_sched_calendar_status_update(sip, time(NULL));
2627 return 0;
2631 * Subscribe roaming contacts
2633 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2635 gchar *to = sip_uri_self(sip);
2636 gchar *tmp = get_contact(sip);
2637 gchar *hdr = g_strdup_printf(
2638 "Event: vnd-microsoft-roaming-contacts\r\n"
2639 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2640 "Supported: com.microsoft.autoextend\r\n"
2641 "Supported: ms-benotify\r\n"
2642 "Proxy-Require: ms-benotify\r\n"
2643 "Supported: ms-piggyback-first-notify\r\n"
2644 "Contact: %s\r\n", tmp);
2645 g_free(tmp);
2647 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2648 g_free(to);
2649 g_free(hdr);
2652 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2653 SIPE_UNUSED_PARAMETER void *unused)
2655 gchar *key;
2656 struct sip_dialog *dialog;
2657 gchar *to = sip_uri_self(sip);
2658 gchar *tmp = get_contact(sip);
2659 gchar *hdr = g_strdup_printf(
2660 "Event: presence.wpending\r\n"
2661 "Accept: text/xml+msrtc.wpending\r\n"
2662 "Supported: com.microsoft.autoextend\r\n"
2663 "Supported: ms-benotify\r\n"
2664 "Proxy-Require: ms-benotify\r\n"
2665 "Supported: ms-piggyback-first-notify\r\n"
2666 "Contact: %s\r\n", tmp);
2667 g_free(tmp);
2669 /* Subscription is identified by <event> key */
2670 key = g_strdup_printf("<%s>", "presence.wpending");
2671 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2672 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2674 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2676 g_free(to);
2677 g_free(hdr);
2678 g_free(key);
2682 * Fires on deregistration event initiated by server.
2683 * [MS-SIPREGE] SIP extension.
2686 // 2007 Example
2688 // Content-Type: text/registration-event
2689 // subscription-state: terminated;expires=0
2690 // ms-diagnostics-public: 4141;reason="User disabled"
2692 // deregistered;event=rejected
2694 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2696 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2697 gchar *event = NULL;
2698 gchar *reason = NULL;
2699 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2700 gchar *warning;
2702 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2703 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2705 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2706 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2707 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2708 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2709 } else {
2710 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2711 return;
2714 if (diagnostics != NULL) {
2715 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2716 } else { // for LCS2005
2717 int error_id = 0;
2718 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2719 error_id = 4140; // [MS-SIPREGE]
2720 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2721 reason = g_strdup(_("you are already signed in at another location"));
2722 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2723 error_id = 4141;
2724 reason = g_strdup(_("user disabled")); // [MS-OCER]
2725 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2726 error_id = 4142;
2727 reason = g_strdup(_("user moved")); // [MS-OCER]
2730 g_free(event);
2731 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2732 g_free(reason);
2734 sip->gc->wants_to_die = TRUE;
2735 purple_connection_error(sip->gc, warning);
2736 g_free(warning);
2740 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2742 xmlnode *xn_provision_group_list;
2743 xmlnode *node;
2745 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2747 /* provisionGroup */
2748 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2749 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2750 g_free(sip->focus_factory_uri);
2751 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2752 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2753 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2754 break;
2757 xmlnode_free(xn_provision_group_list);
2760 /** for 2005 system */
2761 static void
2762 sipe_process_provisioning(struct sipe_account_data *sip,
2763 struct sipmsg *msg)
2765 xmlnode *xn_provision;
2766 xmlnode *node;
2768 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2769 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2770 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2771 if ((node = xmlnode_get_child(node, "line"))) {
2772 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2773 const gchar *server = xmlnode_get_attrib(node, "server");
2774 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2775 sip_csta_open(sip, line_uri, server);
2778 xmlnode_free(xn_provision);
2781 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2783 const gchar *contacts_delta;
2784 xmlnode *xml;
2786 xml = xmlnode_from_str(msg->body, msg->bodylen);
2787 if (!xml)
2789 return;
2792 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2793 if (contacts_delta)
2795 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2798 xmlnode_free(xml);
2801 static void
2802 free_container(struct sipe_container *container)
2804 GSList *entry;
2806 if (!container) return;
2808 entry = container->members;
2809 while (entry) {
2810 void *data = entry->data;
2811 entry = g_slist_remove(entry, data);
2812 g_free(data);
2814 g_free(container);
2818 * Finds locally stored MS-PRES container member
2820 static struct sipe_container_member *
2821 sipe_find_container_member(struct sipe_container *container,
2822 const gchar *type,
2823 const gchar *value)
2825 struct sipe_container_member *member;
2826 GSList *entry;
2828 if (container == NULL || type == NULL) {
2829 return NULL;
2832 entry = container->members;
2833 while (entry) {
2834 member = entry->data;
2835 if (!g_strcasecmp(member->type, type)
2836 && ((!member->value && !value)
2837 || (value && member->value && !g_strcasecmp(member->value, value)))
2839 return member;
2841 entry = entry->next;
2843 return NULL;
2847 * Finds locally stored MS-PRES container by id
2849 static struct sipe_container *
2850 sipe_find_container(struct sipe_account_data *sip,
2851 guint id)
2853 struct sipe_container *container;
2854 GSList *entry;
2856 if (sip == NULL) {
2857 return NULL;
2860 entry = sip->containers;
2861 while (entry) {
2862 container = entry->data;
2863 if (id == container->id) {
2864 return container;
2866 entry = entry->next;
2868 return NULL;
2872 * Access Levels
2873 * 32000 - Blocked
2874 * 400 - Personal
2875 * 300 - Team
2876 * 200 - Company
2877 * 100 - Public
2879 static int
2880 sipe_find_access_level(struct sipe_account_data *sip,
2881 const gchar *type,
2882 const gchar *value)
2884 guint containers[] = {32000, 400, 300, 200, 100};
2885 int i = 0;
2887 for (i = 0; i < 5; i++) {
2888 struct sipe_container_member *member;
2889 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2890 if (!container) continue;
2892 member = sipe_find_container_member(container, type, value);
2893 if (member) {
2894 return containers[i];
2898 return -1;
2901 static void
2902 sipe_send_set_container_members(struct sipe_account_data *sip,
2903 guint container_id,
2904 guint container_version,
2905 const gchar* action,
2906 const gchar* type,
2907 const gchar* value)
2909 gchar *self = sip_uri_self(sip);
2910 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2911 gchar *contact;
2912 gchar *hdr;
2913 gchar *body = g_strdup_printf(
2914 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2915 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2916 "</setContainerMembers>",
2917 container_id,
2918 container_version,
2919 action,
2920 type,
2921 value_str);
2922 g_free(value_str);
2924 contact = get_contact(sip);
2925 hdr = g_strdup_printf("Contact: %s\r\n"
2926 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2927 g_free(contact);
2929 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2931 g_free(hdr);
2932 g_free(body);
2933 g_free(self);
2936 static void
2937 free_publication(struct sipe_publication *publication)
2939 g_free(publication->category);
2940 g_free(publication->cal_event_hash);
2941 g_free(publication->note);
2943 g_free(publication->working_hours_xml_str);
2944 g_free(publication->fb_start_str);
2945 g_free(publication->free_busy_base64);
2947 g_free(publication);
2950 /* key is <category><instance><container> */
2951 static gboolean
2952 sipe_is_our_publication(struct sipe_account_data *sip,
2953 const gchar *key)
2955 GSList *entry;
2957 /* filling keys for our publications if not yet cached */
2958 if (!sip->our_publication_keys) {
2959 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2960 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2961 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2962 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2963 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2964 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2965 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2967 purple_debug_info("sipe", "* Our Publication Instances *\n");
2968 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2969 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2970 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2971 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2972 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2973 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2974 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2975 purple_debug_info("sipe", "\tNote : %u\n", 0);
2976 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2978 /* device */
2979 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2980 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2982 /* state:machineState */
2983 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2984 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2985 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2986 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2988 /* state:userState */
2989 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2990 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2991 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2992 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2994 /* state:calendarState */
2995 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2996 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2997 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2998 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3000 /* state:calendarState OOF */
3001 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3002 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3003 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3004 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3006 /* note */
3007 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3008 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3009 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3010 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3011 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3012 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3014 /* note OOF */
3015 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3016 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3017 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3018 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3019 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3020 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3022 /* calendarData:WorkingHours */
3023 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3024 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3025 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3026 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3027 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3028 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3029 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3030 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3031 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3032 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3033 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3034 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3036 /* calendarData:FreeBusy */
3037 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3038 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3039 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3040 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3041 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3042 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3043 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3044 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3045 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3046 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3047 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3048 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3050 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
3051 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3054 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3056 entry = sip->our_publication_keys;
3057 while (entry) {
3058 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3059 if (sipe_strequal(entry->data, key)) {
3060 return TRUE;
3062 entry = entry->next;
3064 return FALSE;
3067 /** Property names to store in blist.xml */
3068 #define ALIAS_PROP "alias"
3069 #define EMAIL_PROP "email"
3070 #define PHONE_PROP "phone"
3071 #define PHONE_DISPLAY_PROP "phone-display"
3072 #define PHONE_MOBILE_PROP "phone-mobile"
3073 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3074 #define PHONE_HOME_PROP "phone-home"
3075 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3076 #define PHONE_OTHER_PROP "phone-other"
3077 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3078 #define PHONE_CUSTOM1_PROP "phone-custom1"
3079 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3080 #define SITE_PROP "site"
3081 #define COMPANY_PROP "company"
3082 #define DEPARTMENT_PROP "department"
3083 #define TITLE_PROP "title"
3084 #define OFFICE_PROP "office"
3085 /** implies work address */
3086 #define ADDRESS_STREET_PROP "address-street"
3087 #define ADDRESS_CITY_PROP "address-city"
3088 #define ADDRESS_STATE_PROP "address-state"
3089 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3090 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3093 * Tries to figure out user first and last name
3094 * based on Display Name and email properties.
3096 * Allocates memory - must be g_free()'d
3098 * Examples to parse:
3099 * First Last
3100 * First Last - Company Name
3101 * Last, First
3102 * Last, First M.
3103 * Last, First (C)(STP) (Company)
3104 * first.last@company.com (preprocessed as "first last")
3105 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3107 * Unusable examples:
3108 * user@company.com (preprocessed as "user")
3109 * first.m.last@company.com (preprocessed as "first m last")
3110 * user.company.com@reuters.net (preprocessed as "user company com")
3112 static void
3113 sipe_get_first_last_names(struct sipe_account_data *sip,
3114 const char *uri,
3115 char **first_name,
3116 char **last_name)
3118 PurpleBuddy *p_buddy;
3119 char *display_name;
3120 const char *email;
3121 const char *first, *last;
3122 char *tmp;
3123 char **parts;
3124 gboolean has_comma = FALSE;
3126 if (!sip || !uri) return;
3128 p_buddy = purple_find_buddy(sip->account, uri);
3130 if (!p_buddy) return;
3132 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3133 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3135 if (!display_name && !email) return;
3137 /* if no display name, make "first last anything_else" out of email */
3138 if (email && !display_name) {
3139 display_name = g_strndup(email, strstr(email, "@") - email);
3140 display_name = purple_strreplace((tmp = display_name), ".", " ");
3141 g_free(tmp);
3144 if (display_name) {
3145 has_comma = (strstr(display_name, ",") != NULL);
3146 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3147 g_free(tmp);
3148 display_name = purple_strreplace((tmp = display_name), ",", " ");
3149 g_free(tmp);
3152 parts = g_strsplit(display_name, " ", 0);
3154 if (!parts[0] || !parts[1]) {
3155 g_free(display_name);
3156 g_strfreev(parts);
3157 return;
3160 if (has_comma) {
3161 last = parts[0];
3162 first = parts[1];
3163 } else {
3164 first = parts[0];
3165 last = parts[1];
3168 if (first_name) {
3169 *first_name = g_strstrip(g_strdup(first));
3172 if (last_name) {
3173 *last_name = g_strstrip(g_strdup(last));
3176 g_free(display_name);
3177 g_strfreev(parts);
3181 * Update user information
3183 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3184 * @param property_name
3185 * @param property_value may be modified to strip white space
3187 static void
3188 sipe_update_user_info(struct sipe_account_data *sip,
3189 const char *uri,
3190 const char *property_name,
3191 char *property_value)
3193 GSList *buddies, *entry;
3195 if (!property_name || strlen(property_name) == 0) return;
3197 if (property_value)
3198 property_value = g_strstrip(property_value);
3200 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3201 while (entry) {
3202 const char *prop_str;
3203 const char *server_alias;
3204 PurpleBuddy *p_buddy = entry->data;
3206 /* for Display Name */
3207 if (sipe_strequal(property_name, ALIAS_PROP)) {
3208 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3209 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3210 purple_blist_alias_buddy(p_buddy, property_value);
3213 server_alias = purple_buddy_get_server_alias(p_buddy);
3214 if (!is_empty(property_value) &&
3215 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3217 purple_blist_server_alias_buddy(p_buddy, property_value);
3220 /* for other properties */
3221 else {
3222 if (!is_empty(property_value)) {
3223 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3224 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3225 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3230 entry = entry->next;
3232 g_slist_free(buddies);
3236 * Update user phone
3237 * Suitable for both 2005 and 2007 systems.
3239 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3240 * @param phone_type
3241 * @param phone may be modified to strip white space
3242 * @param phone_display_string may be modified to strip white space
3244 static void
3245 sipe_update_user_phone(struct sipe_account_data *sip,
3246 const char *uri,
3247 const gchar *phone_type,
3248 gchar *phone,
3249 gchar *phone_display_string)
3251 const char *phone_node = PHONE_PROP; /* work phone by default */
3252 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3254 if(!phone || strlen(phone) == 0) return;
3256 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3257 phone_node = PHONE_MOBILE_PROP;
3258 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3259 } else if (sipe_strequal(phone_type, "home")) {
3260 phone_node = PHONE_HOME_PROP;
3261 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3262 } else if (sipe_strequal(phone_type, "other")) {
3263 phone_node = PHONE_OTHER_PROP;
3264 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3265 } else if (sipe_strequal(phone_type, "custom1")) {
3266 phone_node = PHONE_CUSTOM1_PROP;
3267 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3270 sipe_update_user_info(sip, uri, phone_node, phone);
3271 if (phone_display_string) {
3272 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3276 static void
3277 sipe_update_calendar(struct sipe_account_data *sip)
3279 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3281 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3283 if (sipe_strequal(calendar, "EXCH")) {
3284 sipe_ews_update_calendar(sip);
3287 /* schedule repeat */
3288 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3290 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3294 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3295 * by using standard Purple's means of signals and saved statuses.
3297 * Thus all UI elements get updated: Status Button with Note, docklet.
3298 * This is ablolutely important as both our status and note can come
3299 * inbound (roaming) or be updated programmatically (e.g. based on our
3300 * calendar data).
3302 static void
3303 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3304 const char *status_id,
3305 const char *message,
3306 time_t do_not_publish[])
3308 PurpleStatus *status = purple_account_get_active_status(account);
3309 gboolean changed = TRUE;
3311 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3312 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3314 changed = FALSE;
3317 if (purple_savedstatus_is_idleaway()) {
3318 changed = FALSE;
3321 if (changed) {
3322 PurpleSavedStatus *saved_status;
3323 const PurpleStatusType *acct_status_type =
3324 purple_status_type_find_with_id(account->status_types, status_id);
3325 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3326 sipe_activity activity = sipe_get_activity_by_token(status_id);
3328 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3329 if (saved_status) {
3330 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3333 /* If this type+message is unique then create a new transient saved status
3334 * Ref: gtkstatusbox.c
3336 if (!saved_status) {
3337 GList *tmp;
3338 GList *active_accts = purple_accounts_get_all_active();
3340 saved_status = purple_savedstatus_new(NULL, primitive);
3341 purple_savedstatus_set_message(saved_status, message);
3343 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3344 purple_savedstatus_set_substatus(saved_status,
3345 (PurpleAccount *)tmp->data, acct_status_type, message);
3347 g_list_free(active_accts);
3350 do_not_publish[activity] = time(NULL);
3351 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3352 status_id, (int)do_not_publish[activity]);
3354 /* Set the status for each account */
3355 purple_savedstatus_activate(saved_status);
3359 struct hash_table_delete_payload {
3360 GHashTable *hash_table;
3361 guint container;
3364 static void
3365 sipe_remove_category_container_publications_cb(const char *name,
3366 struct sipe_publication *publication,
3367 struct hash_table_delete_payload *payload)
3369 if (publication->container == payload->container) {
3370 g_hash_table_remove(payload->hash_table, name);
3373 static void
3374 sipe_remove_category_container_publications(GHashTable *our_publications,
3375 const char *category,
3376 guint container)
3378 struct hash_table_delete_payload payload;
3379 payload.hash_table = g_hash_table_lookup(our_publications, category);
3381 if (!payload.hash_table) return;
3383 payload.container = container;
3384 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3387 static void
3388 send_publish_category_initial(struct sipe_account_data *sip);
3391 * When we receive some self (BE) NOTIFY with a new subscriber
3392 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3395 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3397 gchar *contact;
3398 gchar *to;
3399 xmlnode *xml;
3400 xmlnode *node;
3401 xmlnode *node2;
3402 char *display_name = NULL;
3403 char *uri;
3404 GSList *category_names = NULL;
3405 int aggreg_avail = 0;
3406 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3407 gboolean do_update_status = FALSE;
3408 gboolean has_note_cleaned = FALSE;
3410 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3412 xml = xmlnode_from_str(msg->body, msg->bodylen);
3413 if (!xml) return;
3415 contact = get_contact(sip);
3416 to = sip_uri_self(sip);
3419 /* categories */
3420 /* set list of categories participating in this XML */
3421 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3422 const gchar *name = xmlnode_get_attrib(node, "name");
3423 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3425 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3426 category_names ? (int) g_slist_length(category_names) : -1);
3427 /* drop category information */
3428 if (category_names) {
3429 GSList *entry = category_names;
3430 while (entry) {
3431 GHashTable *cat_publications;
3432 const gchar *category = entry->data;
3433 entry = entry->next;
3434 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3435 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3436 if (cat_publications) {
3437 g_hash_table_remove(sip->our_publications, category);
3438 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3442 g_slist_free(category_names);
3443 /* filling our categories reflected in roaming data */
3444 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3445 const char *tmp;
3446 const gchar *name = xmlnode_get_attrib(node, "name");
3447 guint container = xmlnode_get_int_attrib(node, "container", -1);
3448 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3449 guint version = xmlnode_get_int_attrib(node, "version", 0);
3450 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3451 sipe_utils_str_to_time(tmp) : 0;
3452 gchar *key;
3453 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3455 /* Ex. clear note: <category name="note"/> */
3456 if (container == (guint)-1) {
3457 g_free(sip->note);
3458 sip->note = NULL;
3459 do_update_status = TRUE;
3460 continue;
3463 /* Ex. clear note: <category name="note" container="200"/> */
3464 if (instance == (guint)-1) {
3465 if (container == 200) {
3466 g_free(sip->note);
3467 sip->note = NULL;
3468 do_update_status = TRUE;
3470 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3471 sipe_remove_category_container_publications(
3472 sip->our_publications, name, container);
3473 continue;
3476 /* key is <category><instance><container> */
3477 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3478 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3480 /* capture all userState publication for later clean up if required */
3481 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3482 xmlnode *xn_state = xmlnode_get_child(node, "state");
3484 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3485 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3486 publication->category = g_strdup(name);
3487 publication->instance = instance;
3488 publication->container = container;
3489 publication->version = version;
3491 if (!sip->user_state_publications) {
3492 sip->user_state_publications = g_hash_table_new_full(
3493 g_str_hash, g_str_equal,
3494 g_free, (GDestroyNotify)free_publication);
3496 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3497 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3498 key, version);
3502 if (sipe_is_our_publication(sip, key)) {
3503 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3505 publication->category = g_strdup(name);
3506 publication->instance = instance;
3507 publication->container = container;
3508 publication->version = version;
3510 /* filling publication->availability */
3511 if (sipe_strequal(name, "state")) {
3512 xmlnode *xn_state = xmlnode_get_child(node, "state");
3513 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3515 if (xn_avail) {
3516 gchar *avail_str = xmlnode_get_data(xn_avail);
3517 if (avail_str) {
3518 publication->availability = atoi(avail_str);
3520 g_free(avail_str);
3522 /* for calendarState */
3523 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3524 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3525 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3527 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3528 if (xn_activity) {
3529 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3530 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3532 event->is_meeting = TRUE;
3535 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3536 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3538 publication->cal_event_hash = sipe_cal_event_hash(event);
3539 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3540 publication->cal_event_hash);
3541 sipe_cal_event_free(event);
3544 /* filling publication->note */
3545 if (sipe_strequal(name, "note")) {
3546 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3548 if (!has_note_cleaned) {
3549 has_note_cleaned = TRUE;
3551 g_free(sip->note);
3552 sip->note = NULL;
3553 sip->note_since = publish_time;
3555 do_update_status = TRUE;
3558 g_free(publication->note);
3559 publication->note = NULL;
3560 if (xn_body) {
3561 char *tmp;
3563 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3564 g_free(tmp);
3565 if (publish_time >= sip->note_since) {
3566 g_free(sip->note);
3567 sip->note = g_strdup(publication->note);
3568 sip->note_since = publish_time;
3569 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3571 do_update_status = TRUE;
3576 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3577 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3578 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3579 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3580 if (xn_free_busy) {
3581 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3582 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3584 if (xn_working_hours) {
3585 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3589 if (!cat_publications) {
3590 cat_publications = g_hash_table_new_full(
3591 g_str_hash, g_str_equal,
3592 g_free, (GDestroyNotify)free_publication);
3593 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3594 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3596 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3597 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3599 g_free(key);
3601 /* aggregateState (not an our publication) from 2-nd container */
3602 if (sipe_strequal(name, "state") && container == 2) {
3603 xmlnode *xn_state = xmlnode_get_child(node, "state");
3605 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3606 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3607 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3609 if (xn_avail) {
3610 gchar *avail_str = xmlnode_get_data(xn_avail);
3611 if (avail_str) {
3612 aggreg_avail = atoi(avail_str);
3614 g_free(avail_str);
3617 if (xn_activity) {
3618 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3620 aggreg_activity = sipe_get_activity_by_token(activity_token);
3623 do_update_status = TRUE;
3627 /* userProperties published by server from AD */
3628 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3629 xmlnode *line;
3630 /* line, for Remote Call Control (RCC) */
3631 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3632 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3633 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3634 gchar *line_uri;
3636 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3638 line_uri = xmlnode_get_data(line);
3639 if (line_uri) {
3640 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3641 sip_csta_open(sip, line_uri, line_server);
3643 g_free(line_uri);
3645 break;
3649 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3650 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3652 /* containers */
3653 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3654 guint id = xmlnode_get_int_attrib(node, "id", 0);
3655 struct sipe_container *container = sipe_find_container(sip, id);
3657 if (container) {
3658 sip->containers = g_slist_remove(sip->containers, container);
3659 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3660 free_container(container);
3662 container = g_new0(struct sipe_container, 1);
3663 container->id = id;
3664 container->version = xmlnode_get_int_attrib(node, "version", 0);
3665 sip->containers = g_slist_append(sip->containers, container);
3666 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3668 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3669 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3670 member->type = xmlnode_get_attrib(node2, "type");
3671 member->value = xmlnode_get_attrib(node2, "value");
3672 container->members = g_slist_append(container->members, member);
3673 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3674 member->type, member->value ? member->value : "");
3678 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3679 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3680 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3681 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3682 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3683 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3684 /* initial set-up to let counterparties see your status */
3685 if (sameEnterpriseAL < 0) {
3686 struct sipe_container *container = sipe_find_container(sip, 200);
3687 guint version = container ? container->version : 0;
3688 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3690 if (federatedAL < 0) {
3691 struct sipe_container *container = sipe_find_container(sip, 100);
3692 guint version = container ? container->version : 0;
3693 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3695 sip->access_level_set = TRUE;
3698 /* subscribers */
3699 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3700 const char *user;
3701 const char *acknowledged;
3702 gchar *hdr;
3703 gchar *body;
3705 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3706 if (!user) continue;
3707 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3708 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3709 uri = sip_uri_from_name(user);
3711 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3713 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3714 if(!g_ascii_strcasecmp(acknowledged,"false")){
3715 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3716 if (!purple_find_buddy(sip->account, uri)) {
3717 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3720 hdr = g_strdup_printf(
3721 "Contact: %s\r\n"
3722 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3724 body = g_strdup_printf(
3725 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3726 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3727 "</setSubscribers>", user);
3729 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3730 g_free(body);
3731 g_free(hdr);
3733 g_free(display_name);
3734 g_free(uri);
3737 g_free(contact);
3738 xmlnode_free(xml);
3740 /* Publish initial state if not yet.
3741 * Assuming this happens on initial responce to subscription to roaming-self
3742 * so we've already updated our roaming data in full.
3743 * Only for 2007+
3745 if (!sip->initial_state_published) {
3746 send_publish_category_initial(sip);
3747 sip->initial_state_published = TRUE;
3748 /* dalayed run */
3749 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3750 do_update_status = FALSE;
3751 } else if (aggreg_avail) {
3753 g_free(sip->status);
3754 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3755 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3756 } else {
3757 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3761 if (do_update_status) {
3762 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3763 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3766 g_free(to);
3769 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3771 gchar *to = sip_uri_self(sip);
3772 gchar *tmp = get_contact(sip);
3773 gchar *hdr = g_strdup_printf(
3774 "Event: vnd-microsoft-roaming-ACL\r\n"
3775 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3776 "Supported: com.microsoft.autoextend\r\n"
3777 "Supported: ms-benotify\r\n"
3778 "Proxy-Require: ms-benotify\r\n"
3779 "Supported: ms-piggyback-first-notify\r\n"
3780 "Contact: %s\r\n", tmp);
3781 g_free(tmp);
3783 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3784 g_free(to);
3785 g_free(hdr);
3789 * To request for presence information about the user, access level settings that have already been configured by the user
3790 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3791 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3794 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3796 gchar *to = sip_uri_self(sip);
3797 gchar *tmp = get_contact(sip);
3798 gchar *hdr = g_strdup_printf(
3799 "Event: vnd-microsoft-roaming-self\r\n"
3800 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3801 "Supported: ms-benotify\r\n"
3802 "Proxy-Require: ms-benotify\r\n"
3803 "Supported: ms-piggyback-first-notify\r\n"
3804 "Contact: %s\r\n"
3805 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3807 gchar *body=g_strdup(
3808 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3809 "<roaming type=\"categories\"/>"
3810 "<roaming type=\"containers\"/>"
3811 "<roaming type=\"subscribers\"/></roamingList>");
3813 g_free(tmp);
3814 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3815 g_free(body);
3816 g_free(to);
3817 g_free(hdr);
3821 * For 2005 version
3823 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3825 gchar *to = sip_uri_self(sip);
3826 gchar *tmp = get_contact(sip);
3827 gchar *hdr = g_strdup_printf(
3828 "Event: vnd-microsoft-provisioning\r\n"
3829 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3830 "Supported: com.microsoft.autoextend\r\n"
3831 "Supported: ms-benotify\r\n"
3832 "Proxy-Require: ms-benotify\r\n"
3833 "Supported: ms-piggyback-first-notify\r\n"
3834 "Expires: 0\r\n"
3835 "Contact: %s\r\n", tmp);
3837 g_free(tmp);
3838 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3839 g_free(to);
3840 g_free(hdr);
3843 /** Subscription for provisioning information to help with initial
3844 * configuration. This subscription is a one-time query (denoted by the Expires header,
3845 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3846 * configuration, meeting policies, and policy settings that Communicator must enforce.
3847 * TODO: for what we need this information.
3850 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3852 gchar *to = sip_uri_self(sip);
3853 gchar *tmp = get_contact(sip);
3854 gchar *hdr = g_strdup_printf(
3855 "Event: vnd-microsoft-provisioning-v2\r\n"
3856 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3857 "Supported: com.microsoft.autoextend\r\n"
3858 "Supported: ms-benotify\r\n"
3859 "Proxy-Require: ms-benotify\r\n"
3860 "Supported: ms-piggyback-first-notify\r\n"
3861 "Expires: 0\r\n"
3862 "Contact: %s\r\n"
3863 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3864 gchar *body = g_strdup(
3865 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3866 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3867 "<provisioningGroup name=\"ucPolicy\"/>"
3868 "</provisioningGroupList>");
3870 g_free(tmp);
3871 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3872 g_free(body);
3873 g_free(to);
3874 g_free(hdr);
3877 static void
3878 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3879 gpointer value, gpointer user_data)
3881 struct sip_subscription *subscription = value;
3882 struct sip_dialog *dialog = &subscription->dialog;
3883 struct sipe_account_data *sip = user_data;
3884 gchar *tmp = get_contact(sip);
3885 gchar *hdr = g_strdup_printf(
3886 "Event: %s\r\n"
3887 "Expires: 0\r\n"
3888 "Contact: %s\r\n", subscription->event, tmp);
3889 g_free(tmp);
3891 /* Rate limit to max. 25 requests per seconds */
3892 g_usleep(1000000 / 25);
3894 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3895 g_free(hdr);
3898 /* IM Session (INVITE and MESSAGE methods) */
3900 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3901 static gchar *
3902 get_end_points (struct sipe_account_data *sip,
3903 struct sip_session *session)
3905 gchar *res;
3907 if (session == NULL) {
3908 return NULL;
3911 res = g_strdup_printf("<sip:%s>", sip->username);
3913 SIPE_DIALOG_FOREACH {
3914 gchar *tmp = res;
3915 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3916 g_free(tmp);
3918 if (dialog->theirepid) {
3919 tmp = res;
3920 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3921 g_free(tmp);
3923 } SIPE_DIALOG_FOREACH_END;
3925 return res;
3928 static gboolean
3929 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3930 struct sipmsg *msg,
3931 SIPE_UNUSED_PARAMETER struct transaction *trans)
3933 gboolean ret = TRUE;
3935 if (msg->response != 200) {
3936 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3937 return FALSE;
3940 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3942 return ret;
3946 * Asks UA/proxy about its capabilities.
3948 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3950 gchar *to = sip_uri(who);
3951 gchar *contact = get_contact(sip);
3952 gchar *request = g_strdup_printf(
3953 "Accept: application/sdp\r\n"
3954 "Contact: %s\r\n", contact);
3955 g_free(contact);
3957 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3959 g_free(to);
3960 g_free(request);
3963 static void
3964 sipe_notify_user(struct sipe_account_data *sip,
3965 struct sip_session *session,
3966 PurpleMessageFlags flags,
3967 const gchar *message)
3969 PurpleConversation *conv;
3971 if (!session->conv) {
3972 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3973 } else {
3974 conv = session->conv;
3976 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3979 void
3980 sipe_present_info(struct sipe_account_data *sip,
3981 struct sip_session *session,
3982 const gchar *message)
3984 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3987 static void
3988 sipe_present_err(struct sipe_account_data *sip,
3989 struct sip_session *session,
3990 const gchar *message)
3992 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3995 void
3996 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3997 struct sip_session *session,
3998 int sip_error,
3999 int sip_warning,
4000 const gchar *who,
4001 const gchar *message)
4003 char *msg, *msg_tmp, *msg_tmp2;
4004 const char *label;
4006 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
4007 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4008 g_free(msg_tmp);
4009 /* Service unavailable; Server Internal Error; Server Time-out */
4010 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4011 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4012 g_free(msg);
4013 msg = NULL;
4014 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4015 label = _("This message was not delivered to %s because the service is not available");
4016 } else if (sip_error == 486) { /* Busy Here */
4017 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4018 } else if (sip_error == 415) { /* Unsupported media type */
4019 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4020 } else {
4021 label = _("This message was not delivered to %s because one or more recipients are offline");
4024 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4025 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4026 msg ? ":" : "",
4027 msg ? msg : "");
4028 sipe_present_err(sip, session, msg_tmp);
4029 g_free(msg_tmp2);
4030 g_free(msg_tmp);
4031 g_free(msg);
4035 static gboolean
4036 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4037 SIPE_UNUSED_PARAMETER struct transaction *trans)
4039 gboolean ret = TRUE;
4040 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4041 struct sip_session *session = sipe_session_find_im(sip, with);
4042 struct sip_dialog *dialog;
4043 gchar *cseq;
4044 char *key;
4045 struct queued_message *message;
4047 if (!session) {
4048 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
4049 g_free(with);
4050 return FALSE;
4053 dialog = sipe_dialog_find(session, with);
4054 if (!dialog) {
4055 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
4056 g_free(with);
4057 return FALSE;
4060 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4061 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4062 g_free(cseq);
4063 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4065 if (msg->response >= 400) {
4066 PurpleBuddy *pbuddy;
4067 const char *alias = with;
4068 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4069 int warning = -1;
4071 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
4073 if (warn_hdr) {
4074 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4075 if (parts[0]) {
4076 warning = atoi(parts[0]);
4078 g_strfreev(parts);
4081 /* cancel file transfer as rejected by server */
4082 if (msg->response == 606 && /* Not acceptable all. */
4083 warning == 309 && /* Message contents not allowed by policy */
4084 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4086 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4087 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4088 sipe_utils_nameval_free(parsed_body);
4091 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4092 alias = purple_buddy_get_alias(pbuddy);
4095 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4097 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4098 if (msg->response == 408 || /* Request timeout */
4099 msg->response == 480 || /* Temporarily Unavailable */
4100 msg->response == 481) { /* Call/Transaction Does Not Exist */
4101 purple_debug_info("sipe", "process_message_response: assuming dangling IM session, dropping it.\n");
4102 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4105 ret = FALSE;
4106 } else {
4107 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4108 if (message_id) {
4109 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4110 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4111 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4114 g_hash_table_remove(session->unconfirmed_messages, key);
4115 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4116 key, g_hash_table_size(session->unconfirmed_messages));
4119 g_free(key);
4120 g_free(with);
4122 if (ret) sipe_im_process_queue(sip, session);
4123 return ret;
4126 static gboolean
4127 sipe_is_election_finished(struct sip_session *session);
4129 static void
4130 sipe_election_result(struct sipe_account_data *sip,
4131 void *sess);
4133 static gboolean
4134 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4135 SIPE_UNUSED_PARAMETER struct transaction *trans)
4137 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4138 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4139 struct sip_dialog *dialog;
4140 struct sip_session *session;
4142 session = sipe_session_find_chat_by_callid(sip, callid);
4143 if (!session) {
4144 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4145 return FALSE;
4148 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4149 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4150 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4151 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4153 if (xn_request_rm_response) {
4154 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4155 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4157 dialog = sipe_dialog_find(session, with);
4158 if (!dialog) {
4159 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4160 xmlnode_free(xn_action);
4161 return FALSE;
4164 if (allow && !g_strcasecmp(allow, "true")) {
4165 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4166 dialog->election_vote = 1;
4167 } else if (allow && !g_strcasecmp(allow, "false")) {
4168 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4169 dialog->election_vote = -1;
4172 if (sipe_is_election_finished(session)) {
4173 sipe_election_result(sip, session);
4176 } else if (xn_set_rm_response) {
4179 xmlnode_free(xn_action);
4183 return TRUE;
4186 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4188 gchar *hdr;
4189 gchar *tmp;
4190 char *msgtext = NULL;
4191 const gchar *msgr = "";
4192 gchar *tmp2 = NULL;
4194 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4195 char *msgformat;
4196 gchar *msgr_value;
4198 sipe_parse_html(msg, &msgformat, &msgtext);
4199 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4201 msgr_value = sipmsg_get_msgr_string(msgformat);
4202 g_free(msgformat);
4203 if (msgr_value) {
4204 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4205 g_free(msgr_value);
4207 } else {
4208 msgtext = g_strdup(msg);
4211 tmp = get_contact(sip);
4212 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4213 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4214 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4215 if (content_type == NULL)
4216 content_type = "text/plain";
4218 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4219 g_free(tmp);
4220 g_free(tmp2);
4222 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4223 g_free(msgtext);
4224 g_free(hdr);
4228 void
4229 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4231 GSList *entry2 = session->outgoing_message_queue;
4232 while (entry2) {
4233 struct queued_message *msg = entry2->data;
4235 /* for multiparty chat or conference */
4236 if (session->is_multiparty || session->focus_uri) {
4237 gchar *who = sip_uri_self(sip);
4238 serv_got_chat_in(sip->gc, session->chat_id, who,
4239 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4240 g_free(who);
4243 SIPE_DIALOG_FOREACH {
4244 char *key;
4245 struct queued_message *message;
4247 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4249 message = g_new0(struct queued_message,1);
4250 message->body = g_strdup(msg->body);
4251 if (msg->content_type != NULL)
4252 message->content_type = g_strdup(msg->content_type);
4254 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4255 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4256 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4257 key, g_hash_table_size(session->unconfirmed_messages));
4258 g_free(key);
4260 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4261 } SIPE_DIALOG_FOREACH_END;
4263 entry2 = sipe_session_dequeue_message(session);
4267 static void
4268 sipe_refer_notify(struct sipe_account_data *sip,
4269 struct sip_session *session,
4270 const gchar *who,
4271 int status,
4272 const gchar *desc)
4274 gchar *hdr;
4275 gchar *body;
4276 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4278 hdr = g_strdup_printf(
4279 "Event: refer\r\n"
4280 "Subscription-State: %s\r\n"
4281 "Content-Type: message/sipfrag\r\n",
4282 status >= 200 ? "terminated" : "active");
4284 body = g_strdup_printf(
4285 "SIP/2.0 %d %s\r\n",
4286 status, desc);
4288 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4290 g_free(hdr);
4291 g_free(body);
4294 static gboolean
4295 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4297 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4298 struct sip_session *session;
4299 struct sip_dialog *dialog;
4300 char *cseq;
4301 char *key;
4302 struct queued_message *message;
4303 struct sipmsg *request_msg = trans->msg;
4305 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4306 gchar *referred_by;
4308 session = sipe_session_find_chat_by_callid(sip, callid);
4309 if (!session) {
4310 session = sipe_session_find_im(sip, with);
4312 if (!session) {
4313 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4314 g_free(with);
4315 return FALSE;
4318 dialog = sipe_dialog_find(session, with);
4319 if (!dialog) {
4320 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4321 g_free(with);
4322 return FALSE;
4325 sipe_dialog_parse(dialog, msg, TRUE);
4327 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4328 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4329 g_free(cseq);
4330 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4332 if (msg->response != 200) {
4333 PurpleBuddy *pbuddy;
4334 const char *alias = with;
4335 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4336 int warning = -1;
4338 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4340 if (warn_hdr) {
4341 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4342 if (parts[0]) {
4343 warning = atoi(parts[0]);
4345 g_strfreev(parts);
4348 /* cancel file transfer as rejected by server */
4349 if (msg->response == 606 && /* Not acceptable all. */
4350 warning == 309 && /* Message contents not allowed by policy */
4351 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4353 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4354 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4355 sipe_utils_nameval_free(parsed_body);
4358 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4359 alias = purple_buddy_get_alias(pbuddy);
4362 if (message) {
4363 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4364 } else {
4365 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4366 sipe_present_err(sip, session, tmp_msg);
4367 g_free(tmp_msg);
4370 sipe_dialog_remove(session, with);
4372 g_free(key);
4373 g_free(with);
4374 return FALSE;
4377 dialog->cseq = 0;
4378 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4379 dialog->outgoing_invite = NULL;
4380 dialog->is_established = TRUE;
4382 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4383 if (referred_by) {
4384 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4385 g_free(referred_by);
4388 /* add user to chat if it is a multiparty session */
4389 if (session->is_multiparty) {
4390 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4391 with, NULL,
4392 PURPLE_CBFLAGS_NONE, TRUE);
4395 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4396 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4397 sipe_session_dequeue_message(session);
4400 sipe_im_process_queue(sip, session);
4402 g_hash_table_remove(session->unconfirmed_messages, key);
4403 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4404 key, g_hash_table_size(session->unconfirmed_messages));
4406 g_free(key);
4407 g_free(with);
4408 return TRUE;
4412 void
4413 sipe_invite(struct sipe_account_data *sip,
4414 struct sip_session *session,
4415 const gchar *who,
4416 const gchar *msg_body,
4417 const gchar *msg_content_type,
4418 const gchar *referred_by,
4419 const gboolean is_triggered)
4421 gchar *hdr;
4422 gchar *to;
4423 gchar *contact;
4424 gchar *body;
4425 gchar *self;
4426 char *ms_text_format = NULL;
4427 gchar *roster_manager;
4428 gchar *end_points;
4429 gchar *referred_by_str;
4430 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4432 if (dialog && dialog->is_established) {
4433 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4434 return;
4437 if (!dialog) {
4438 dialog = sipe_dialog_add(session);
4439 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4440 dialog->with = g_strdup(who);
4443 if (!(dialog->ourtag)) {
4444 dialog->ourtag = gentag();
4447 to = sip_uri(who);
4449 if (msg_body) {
4450 char *msgtext = NULL;
4451 char *base64_msg;
4452 const gchar *msgr = "";
4453 char *key;
4454 struct queued_message *message;
4455 gchar *tmp = NULL;
4457 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4458 char *msgformat;
4459 gchar *msgr_value;
4461 sipe_parse_html(msg_body, &msgformat, &msgtext);
4462 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4464 msgr_value = sipmsg_get_msgr_string(msgformat);
4465 g_free(msgformat);
4466 if (msgr_value) {
4467 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4468 g_free(msgr_value);
4470 } else {
4471 msgtext = g_strdup(msg_body);
4474 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4475 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4476 msg_content_type ? msg_content_type : "text/plain",
4477 msgr,
4478 base64_msg);
4479 g_free(msgtext);
4480 g_free(tmp);
4481 g_free(base64_msg);
4483 message = g_new0(struct queued_message,1);
4484 message->body = g_strdup(msg_body);
4485 if (msg_content_type != NULL)
4486 message->content_type = g_strdup(msg_content_type);
4488 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4489 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4490 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4491 key, g_hash_table_size(session->unconfirmed_messages));
4492 g_free(key);
4495 contact = get_contact(sip);
4496 end_points = get_end_points(sip, session);
4497 self = sip_uri_self(sip);
4498 roster_manager = g_strdup_printf(
4499 "Roster-Manager: %s\r\n"
4500 "EndPoints: %s\r\n",
4501 self,
4502 end_points);
4503 referred_by_str = referred_by ?
4504 g_strdup_printf(
4505 "Referred-By: %s\r\n",
4506 referred_by)
4507 : g_strdup("");
4508 hdr = g_strdup_printf(
4509 "Supported: ms-sender\r\n"
4510 "%s"
4511 "%s"
4512 "%s"
4513 "%s"
4514 "Contact: %s\r\n%s"
4515 "Content-Type: application/sdp\r\n",
4516 sipe_strequal(session->roster_manager, self) ? roster_manager : "",
4517 referred_by_str,
4518 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4519 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4520 contact,
4521 ms_text_format ? ms_text_format : "");
4522 g_free(ms_text_format);
4523 g_free(self);
4525 body = g_strdup_printf(
4526 "v=0\r\n"
4527 "o=- 0 0 IN IP4 %s\r\n"
4528 "s=session\r\n"
4529 "c=IN IP4 %s\r\n"
4530 "t=0 0\r\n"
4531 "m=%s %d sip null\r\n"
4532 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4533 purple_network_get_my_ip(-1),
4534 purple_network_get_my_ip(-1),
4535 sip->ocs2007 ? "message" : "x-ms-message",
4536 sip->realport);
4538 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4539 to, to, hdr, body, dialog, process_invite_response);
4541 g_free(to);
4542 g_free(roster_manager);
4543 g_free(end_points);
4544 g_free(referred_by_str);
4545 g_free(body);
4546 g_free(hdr);
4547 g_free(contact);
4550 static void
4551 sipe_refer(struct sipe_account_data *sip,
4552 struct sip_session *session,
4553 const gchar *who)
4555 gchar *hdr;
4556 gchar *contact;
4557 gchar *epid = get_epid(sip);
4558 struct sip_dialog *dialog = sipe_dialog_find(session,
4559 session->roster_manager);
4560 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4562 contact = get_contact(sip);
4563 hdr = g_strdup_printf(
4564 "Contact: %s\r\n"
4565 "Refer-to: <%s>\r\n"
4566 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4567 "Require: com.microsoft.rtc-multiparty\r\n",
4568 contact,
4569 who,
4570 sip->username,
4571 ourtag ? ";tag=" : "",
4572 ourtag ? ourtag : "",
4573 epid);
4574 g_free(epid);
4576 send_sip_request(sip->gc, "REFER",
4577 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4579 g_free(hdr);
4580 g_free(contact);
4583 static void
4584 sipe_send_election_request_rm(struct sipe_account_data *sip,
4585 struct sip_dialog *dialog,
4586 int bid)
4588 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4590 gchar *body = g_strdup_printf(
4591 "<?xml version=\"1.0\"?>\r\n"
4592 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4593 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4594 sip->username, bid);
4596 send_sip_request(sip->gc, "INFO",
4597 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4599 g_free(body);
4602 static void
4603 sipe_send_election_set_rm(struct sipe_account_data *sip,
4604 struct sip_dialog *dialog)
4606 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4608 gchar *body = g_strdup_printf(
4609 "<?xml version=\"1.0\"?>\r\n"
4610 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4611 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4612 sip->username);
4614 send_sip_request(sip->gc, "INFO",
4615 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4617 g_free(body);
4620 static void
4621 sipe_session_close(struct sipe_account_data *sip,
4622 struct sip_session * session)
4624 if (session && session->focus_uri) {
4625 sipe_conf_immcu_closed(sip, session);
4626 conf_session_close(sip, session);
4629 if (session) {
4630 SIPE_DIALOG_FOREACH {
4631 /* @TODO slow down BYE message sending rate */
4632 /* @see single subscription code */
4633 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4634 } SIPE_DIALOG_FOREACH_END;
4636 sipe_session_remove(sip, session);
4640 static void
4641 sipe_session_close_all(struct sipe_account_data *sip)
4643 GSList *entry;
4644 while ((entry = sip->sessions) != NULL) {
4645 sipe_session_close(sip, entry->data);
4649 static void
4650 sipe_convo_closed(PurpleConnection * gc, const char *who)
4652 struct sipe_account_data *sip = gc->proto_data;
4654 purple_debug_info("sipe", "conversation with %s closed\n", who);
4655 sipe_session_close(sip, sipe_session_find_im(sip, who));
4658 static void
4659 sipe_chat_leave (PurpleConnection *gc, int id)
4661 struct sipe_account_data *sip = gc->proto_data;
4662 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4664 sipe_session_close(sip, session);
4667 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4668 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4670 struct sipe_account_data *sip = gc->proto_data;
4671 struct sip_session *session;
4672 struct sip_dialog *dialog;
4673 gchar *uri = sip_uri(who);
4675 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4677 session = sipe_session_find_or_add_im(sip, uri);
4678 dialog = sipe_dialog_find(session, uri);
4680 // Queue the message
4681 sipe_session_enqueue_message(session, what, NULL);
4683 if (dialog && !dialog->outgoing_invite) {
4684 sipe_im_process_queue(sip, session);
4685 } else if (!dialog || !dialog->outgoing_invite) {
4686 // Need to send the INVITE to get the outgoing dialog setup
4687 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4690 g_free(uri);
4691 return 1;
4694 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4695 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4697 struct sipe_account_data *sip = gc->proto_data;
4698 struct sip_session *session;
4700 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4702 session = sipe_session_find_chat_by_id(sip, id);
4704 // Queue the message
4705 if (session && session->dialogs) {
4706 sipe_session_enqueue_message(session,what,NULL);
4707 sipe_im_process_queue(sip, session);
4708 } else if (sip) {
4709 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4710 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4712 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4713 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4715 if (sip->ocs2007) {
4716 struct sip_session *session = sipe_session_add_chat(sip);
4718 session->is_multiparty = FALSE;
4719 session->focus_uri = g_strdup(proto_chat_id);
4720 sipe_session_enqueue_message(session, what, NULL);
4721 sipe_invite_conf_focus(sip, session);
4725 return 1;
4728 /* End IM Session (INVITE and MESSAGE methods) */
4730 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4732 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4733 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4734 gchar *from;
4735 struct sip_session *session;
4737 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4739 /* Call Control protocol */
4740 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4742 process_incoming_info_csta(sip, msg);
4743 return;
4746 from = parse_from(sipmsg_find_header(msg, "From"));
4747 session = sipe_session_find_chat_by_callid(sip, callid);
4748 if (!session) {
4749 session = sipe_session_find_im(sip, from);
4751 if (!session) {
4752 g_free(from);
4753 return;
4756 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4758 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4759 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4760 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4762 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4764 if (xn_request_rm) {
4765 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4766 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4767 gchar *body = g_strdup_printf(
4768 "<?xml version=\"1.0\"?>\r\n"
4769 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4770 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4771 sip->username,
4772 session->bid < bid ? "true" : "false");
4773 send_sip_response(sip->gc, msg, 200, "OK", body);
4774 g_free(body);
4775 } else if (xn_set_rm) {
4776 gchar *body;
4777 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4778 g_free(session->roster_manager);
4779 session->roster_manager = g_strdup(rm);
4781 body = g_strdup_printf(
4782 "<?xml version=\"1.0\"?>\r\n"
4783 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4784 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4785 sip->username);
4786 send_sip_response(sip->gc, msg, 200, "OK", body);
4787 g_free(body);
4789 xmlnode_free(xn_action);
4792 else
4794 /* looks like purple lacks typing notification for chat */
4795 if (!session->is_multiparty && !session->focus_uri) {
4796 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4797 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4798 "status");
4799 if (sipe_strequal(status, "type")) {
4800 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4801 } else if (sipe_strequal(status, "idle")) {
4802 serv_got_typing_stopped(sip->gc, from);
4804 xmlnode_free(xn_keyboard_activity);
4807 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4809 g_free(from);
4812 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4814 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4815 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4816 struct sip_session *session;
4817 struct sip_dialog *dialog;
4819 /* collect dialog identification
4820 * we need callid, ourtag and theirtag to unambiguously identify dialog
4822 /* take data before 'msg' will be modified by send_sip_response */
4823 dialog = g_new0(struct sip_dialog, 1);
4824 dialog->callid = g_strdup(callid);
4825 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4826 dialog->with = g_strdup(from);
4827 sipe_dialog_parse(dialog, msg, FALSE);
4829 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4831 session = sipe_session_find_chat_by_callid(sip, callid);
4832 if (!session) {
4833 session = sipe_session_find_im(sip, from);
4835 if (!session) {
4836 sipe_dialog_free(dialog);
4837 g_free(from);
4838 return;
4841 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4842 g_free(session->roster_manager);
4843 session->roster_manager = NULL;
4846 /* This what BYE is essentially for - terminating dialog */
4847 sipe_dialog_remove_3(session, dialog);
4848 sipe_dialog_free(dialog);
4849 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4850 sipe_conf_immcu_closed(sip, session);
4851 } else if (session->is_multiparty) {
4852 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4855 g_free(from);
4858 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4860 gchar *self = sip_uri_self(sip);
4861 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4862 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4863 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4864 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4865 struct sip_session *session;
4866 struct sip_dialog *dialog;
4868 session = sipe_session_find_chat_by_callid(sip, callid);
4869 dialog = sipe_dialog_find(session, from);
4871 if (!session || !dialog || !session->roster_manager || !sipe_strequal(session->roster_manager, self)) {
4872 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4873 } else {
4874 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4876 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4879 g_free(self);
4880 g_free(from);
4881 g_free(refer_to);
4882 g_free(referred_by);
4885 static unsigned int
4886 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4888 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4889 struct sip_session *session;
4890 struct sip_dialog *dialog;
4892 if (state == PURPLE_NOT_TYPING)
4893 return 0;
4895 session = sipe_session_find_im(sip, who);
4896 dialog = sipe_dialog_find(session, who);
4898 if (session && dialog && dialog->is_established) {
4899 send_sip_request(gc, "INFO", who, who,
4900 "Content-Type: application/xml\r\n",
4901 SIPE_SEND_TYPING, dialog, NULL);
4903 return SIPE_TYPING_SEND_TIMEOUT;
4906 static gboolean resend_timeout(struct sipe_account_data *sip)
4908 GSList *tmp = sip->transactions;
4909 time_t currtime = time(NULL);
4910 while (tmp) {
4911 struct transaction *trans = tmp->data;
4912 tmp = tmp->next;
4913 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4914 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4915 /* TODO 408 */
4916 } else {
4917 if ((currtime - trans->time > 2) && trans->retries == 0) {
4918 trans->retries++;
4919 sendout_sipmsg(sip, trans->msg);
4923 return TRUE;
4926 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4927 SIPE_UNUSED_PARAMETER void *unused)
4929 /* register again when security token expires */
4930 /* we have to start a new authentication as the security token
4931 * is almost expired by sending a not signed REGISTER message */
4932 purple_debug_info("sipe", "do a full reauthentication\n");
4933 sipe_auth_free(&sip->registrar);
4934 sipe_auth_free(&sip->proxy);
4935 sip->registerstatus = 0;
4936 do_register(sip);
4937 sip->reauthenticate_set = FALSE;
4940 static gboolean
4941 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4942 struct sipmsg *msg,
4943 GSList *parsed_body)
4945 gboolean found = FALSE;
4947 if (parsed_body) {
4948 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4950 if (sipe_strequal(invitation_command, "INVITE")) {
4951 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4952 found = TRUE;
4953 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4954 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4955 found = TRUE;
4956 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4957 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4958 found = TRUE;
4961 return found;
4964 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4966 gchar *from;
4967 const gchar *contenttype;
4968 gboolean found = FALSE;
4970 from = parse_from(sipmsg_find_header(msg, "From"));
4972 if (!from) return;
4974 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4976 contenttype = sipmsg_find_header(msg, "Content-Type");
4977 if (g_str_has_prefix(contenttype, "text/plain")
4978 || g_str_has_prefix(contenttype, "text/html")
4979 || g_str_has_prefix(contenttype, "multipart/related")
4980 || g_str_has_prefix(contenttype, "multipart/alternative"))
4982 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4983 gchar *html = get_html_message(contenttype, msg->body);
4985 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4986 if (!session) {
4987 session = sipe_session_find_im(sip, from);
4990 if (session && session->focus_uri) { /* a conference */
4991 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4992 gchar *sender = parse_from(tmp);
4993 g_free(tmp);
4994 serv_got_chat_in(sip->gc, session->chat_id, sender,
4995 PURPLE_MESSAGE_RECV, html, time(NULL));
4996 g_free(sender);
4997 } else if (session && session->is_multiparty) { /* a multiparty chat */
4998 serv_got_chat_in(sip->gc, session->chat_id, from,
4999 PURPLE_MESSAGE_RECV, html, time(NULL));
5000 } else {
5001 serv_got_im(sip->gc, from, html, 0, time(NULL));
5003 g_free(html);
5004 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5005 found = TRUE;
5007 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5008 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5009 xmlnode *state;
5010 gchar *statedata;
5012 if (!isc) {
5013 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
5014 g_free(from);
5015 return;
5018 state = xmlnode_get_child(isc, "state");
5020 if (!state) {
5021 purple_debug_info("sipe", "process_incoming_message: no state found\n");
5022 xmlnode_free(isc);
5023 g_free(from);
5024 return;
5027 statedata = xmlnode_get_data(state);
5028 if (statedata) {
5029 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5030 else serv_got_typing_stopped(sip->gc, from);
5032 g_free(statedata);
5034 xmlnode_free(isc);
5035 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5036 found = TRUE;
5037 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5038 GSList *body = sipe_ft_parse_msg_body(msg->body);
5039 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5040 sipe_utils_nameval_free(body);
5041 if (found) {
5042 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5045 if (!found) {
5046 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5047 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5048 if (!session) {
5049 session = sipe_session_find_im(sip, from);
5051 if (session) {
5052 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5053 from);
5054 sipe_present_err(sip, session, errmsg);
5055 g_free(errmsg);
5058 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
5059 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5061 g_free(from);
5064 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5066 gchar *body;
5067 gchar *newTag;
5068 const gchar *oldHeader;
5069 gchar *newHeader;
5070 gboolean is_multiparty = FALSE;
5071 gboolean is_triggered = FALSE;
5072 gboolean was_multiparty = TRUE;
5073 gboolean just_joined = FALSE;
5074 gchar *from;
5075 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5076 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5077 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5078 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5079 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5080 GSList *end_points = NULL;
5081 char *tmp = NULL;
5082 struct sip_session *session;
5083 const gchar *ms_text_format;
5085 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
5086 g_free(tmp);
5088 /* Invitation to join conference */
5089 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5090 process_incoming_invite_conf(sip, msg);
5091 return;
5094 /* Only accept text invitations */
5095 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5096 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5097 return;
5100 // TODO There *must* be a better way to clean up the To header to add a tag...
5101 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
5102 oldHeader = sipmsg_find_header(msg, "To");
5103 newTag = gentag();
5104 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5105 sipmsg_remove_header_now(msg, "To");
5106 sipmsg_add_header_now(msg, "To", newHeader);
5107 g_free(newHeader);
5109 if (end_points_hdr) {
5110 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5112 if (g_slist_length(end_points) > 2) {
5113 is_multiparty = TRUE;
5116 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5117 is_triggered = TRUE;
5118 is_multiparty = TRUE;
5121 session = sipe_session_find_chat_by_callid(sip, callid);
5122 /* Convert to multiparty */
5123 if (session && is_multiparty && !session->is_multiparty) {
5124 g_free(session->with);
5125 session->with = NULL;
5126 was_multiparty = FALSE;
5127 session->is_multiparty = TRUE;
5128 session->chat_id = rand();
5131 if (!session && is_multiparty) {
5132 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5134 /* IM session */
5135 from = parse_from(sipmsg_find_header(msg, "From"));
5136 if (!session) {
5137 session = sipe_session_find_or_add_im(sip, from);
5140 if (session) {
5141 g_free(session->callid);
5142 session->callid = g_strdup(callid);
5144 session->is_multiparty = is_multiparty;
5145 if (roster_manager) {
5146 session->roster_manager = g_strdup(roster_manager);
5150 if (is_multiparty && end_points) {
5151 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5152 GSList *entry = end_points;
5153 while (entry) {
5154 struct sip_dialog *dialog;
5155 struct sipendpoint *end_point = entry->data;
5156 entry = entry->next;
5158 if (!g_strcasecmp(from, end_point->contact) ||
5159 !g_strcasecmp(to, end_point->contact))
5160 continue;
5162 dialog = sipe_dialog_find(session, end_point->contact);
5163 if (dialog) {
5164 g_free(dialog->theirepid);
5165 dialog->theirepid = end_point->epid;
5166 end_point->epid = NULL;
5167 } else {
5168 dialog = sipe_dialog_add(session);
5170 dialog->callid = g_strdup(session->callid);
5171 dialog->with = end_point->contact;
5172 end_point->contact = NULL;
5173 dialog->theirepid = end_point->epid;
5174 end_point->epid = NULL;
5176 just_joined = TRUE;
5178 /* send triggered INVITE */
5179 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5182 g_free(to);
5185 if (end_points) {
5186 GSList *entry = end_points;
5187 while (entry) {
5188 struct sipendpoint *end_point = entry->data;
5189 entry = entry->next;
5190 g_free(end_point->contact);
5191 g_free(end_point->epid);
5192 g_free(end_point);
5194 g_slist_free(end_points);
5197 if (session) {
5198 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5199 if (dialog) {
5200 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5201 sipe_dialog_parse_routes(dialog, msg, FALSE);
5202 } else {
5203 dialog = sipe_dialog_add(session);
5205 dialog->callid = g_strdup(session->callid);
5206 dialog->with = g_strdup(from);
5207 sipe_dialog_parse(dialog, msg, FALSE);
5209 if (!dialog->ourtag) {
5210 dialog->ourtag = newTag;
5211 newTag = NULL;
5214 just_joined = TRUE;
5216 } else {
5217 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5219 g_free(newTag);
5221 if (is_multiparty && !session->conv) {
5222 gchar *chat_title = sipe_chat_get_name(callid);
5223 gchar *self = sip_uri_self(sip);
5224 /* create prpl chat */
5225 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5226 session->chat_title = g_strdup(chat_title);
5227 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5228 /* add self */
5229 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5230 self, NULL,
5231 PURPLE_CBFLAGS_NONE, FALSE);
5232 g_free(chat_title);
5233 g_free(self);
5236 if (is_multiparty && !was_multiparty) {
5237 /* add current IM counterparty to chat */
5238 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5239 sipe_dialog_first(session)->with, NULL,
5240 PURPLE_CBFLAGS_NONE, FALSE);
5243 /* add inviting party to chat */
5244 if (just_joined && session->conv) {
5245 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5246 from, NULL,
5247 PURPLE_CBFLAGS_NONE, TRUE);
5250 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5252 /* This used only in 2005 official client, not 2007 or Reuters.
5253 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5254 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5256 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5257 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5258 if (is_multiparty ||
5259 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5261 if (ms_text_format) {
5262 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5264 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5265 if (tmp) {
5266 gchar *body = (gchar *) purple_base64_decode(tmp, NULL);
5268 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5270 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5271 sipe_utils_nameval_free(parsed_body);
5272 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5274 g_free(tmp);
5276 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5278 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5279 gchar *html = get_html_message(ms_text_format, NULL);
5280 if (html) {
5281 if (is_multiparty) {
5282 serv_got_chat_in(sip->gc, session->chat_id, from,
5283 PURPLE_MESSAGE_RECV, html, time(NULL));
5284 } else {
5285 serv_got_im(sip->gc, from, html, 0, time(NULL));
5287 g_free(html);
5288 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5294 g_free(from);
5296 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5297 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5298 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5300 body = g_strdup_printf(
5301 "v=0\r\n"
5302 "o=- 0 0 IN IP4 %s\r\n"
5303 "s=session\r\n"
5304 "c=IN IP4 %s\r\n"
5305 "t=0 0\r\n"
5306 "m=%s %d sip sip:%s\r\n"
5307 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5308 purple_network_get_my_ip(-1),
5309 purple_network_get_my_ip(-1),
5310 sip->ocs2007 ? "message" : "x-ms-message",
5311 sip->realport,
5312 sip->username);
5313 send_sip_response(sip->gc, msg, 200, "OK", body);
5314 g_free(body);
5317 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5319 gchar *body;
5321 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5322 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5323 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5325 body = g_strdup_printf(
5326 "v=0\r\n"
5327 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5328 "s=session\r\n"
5329 "c=IN IP4 0.0.0.0\r\n"
5330 "t=0 0\r\n"
5331 "m=%s %d sip sip:%s\r\n"
5332 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5333 sip->ocs2007 ? "message" : "x-ms-message",
5334 sip->realport,
5335 sip->username);
5336 send_sip_response(sip->gc, msg, 200, "OK", body);
5337 g_free(body);
5340 static const char*
5341 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5343 const char *res = "NTLM";
5344 #ifdef USE_KERBEROS
5345 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5346 res = "Kerberos";
5348 #else
5349 (void) sip; /* make compiler happy */
5350 #endif
5351 return res;
5354 static void sipe_connection_cleanup(struct sipe_account_data *);
5355 static void create_connection(struct sipe_account_data *, gchar *, int);
5357 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5358 SIPE_UNUSED_PARAMETER struct transaction *trans)
5360 gchar *tmp;
5361 const gchar *expires_header;
5362 int expires, i;
5363 GSList *hdr = msg->headers;
5364 struct sipnameval *elem;
5366 expires_header = sipmsg_find_header(msg, "Expires");
5367 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5368 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5370 switch (msg->response) {
5371 case 200:
5372 if (expires == 0) {
5373 sip->registerstatus = 0;
5374 } else {
5375 const gchar *contact_hdr;
5376 gchar *gruu = NULL;
5377 gchar *epid;
5378 gchar *uuid;
5379 gchar *timeout;
5380 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5381 const char *auth_scheme;
5383 if (!sip->reregister_set) {
5384 gchar *action_name = g_strdup_printf("<%s>", "registration");
5385 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5386 g_free(action_name);
5387 sip->reregister_set = TRUE;
5390 sip->registerstatus = 3;
5392 if (server_hdr && !sip->server_version) {
5393 sip->server_version = g_strdup(server_hdr);
5394 g_free(default_ua);
5395 default_ua = NULL;
5398 auth_scheme = sipe_get_auth_scheme_name(sip);
5399 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5401 if (tmp) {
5402 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5403 fill_auth(tmp, &sip->registrar);
5406 if (!sip->reauthenticate_set) {
5407 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5408 guint reauth_timeout;
5409 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5410 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5411 reauth_timeout = sip->registrar.expires - 300;
5412 } else {
5413 /* NTLM: we have to reauthenticate as our security token expires
5414 after eight hours (be five minutes early) */
5415 reauth_timeout = (8 * 3600) - 300;
5417 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5418 g_free(action_name);
5419 sip->reauthenticate_set = TRUE;
5422 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5424 epid = get_epid(sip);
5425 uuid = generateUUIDfromEPID(epid);
5426 g_free(epid);
5428 // There can be multiple Contact headers (one per location where the user is logged in) so
5429 // make sure to only get the one for this uuid
5430 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5431 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5432 if (valid_contact) {
5433 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5434 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5435 g_free(valid_contact);
5436 break;
5437 } else {
5438 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5441 g_free(uuid);
5443 g_free(sip->contact);
5444 if(gruu) {
5445 sip->contact = g_strdup_printf("<%s>", gruu);
5446 g_free(gruu);
5447 } else {
5448 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5449 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);
5451 sip->ocs2007 = FALSE;
5452 sip->batched_support = FALSE;
5454 while(hdr)
5456 elem = hdr->data;
5457 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5458 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5459 /* We interpret this as OCS2007+ indicator */
5460 sip->ocs2007 = TRUE;
5461 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5463 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5464 sip->batched_support = TRUE;
5465 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5468 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5469 gchar **caps = g_strsplit(elem->value,",",0);
5470 i = 0;
5471 while (caps[i]) {
5472 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5473 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5474 i++;
5476 g_strfreev(caps);
5478 hdr = g_slist_next(hdr);
5481 /* rejoin open chats to be able to use them by continue to send messages */
5482 purple_conversation_foreach(sipe_rejoin_chat);
5484 /* subscriptions */
5485 if (!sip->subscribed) { //do it just once, not every re-register
5487 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5488 (GCompareFunc)g_ascii_strcasecmp)) {
5489 sipe_subscribe_roaming_contacts(sip);
5492 /* For 2007+ it does not make sence to subscribe to:
5493 * vnd-microsoft-roaming-ACL
5494 * vnd-microsoft-provisioning (not v2)
5495 * presence.wpending
5496 * These are for backward compatibility.
5498 if (sip->ocs2007)
5500 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5501 (GCompareFunc)g_ascii_strcasecmp)) {
5502 sipe_subscribe_roaming_self(sip);
5504 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5505 (GCompareFunc)g_ascii_strcasecmp)) {
5506 sipe_subscribe_roaming_provisioning_v2(sip);
5509 /* For 2005- servers */
5510 else
5512 //sipe_options_request(sip, sip->sipdomain);
5514 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5515 (GCompareFunc)g_ascii_strcasecmp)) {
5516 sipe_subscribe_roaming_acl(sip);
5518 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5519 (GCompareFunc)g_ascii_strcasecmp)) {
5520 sipe_subscribe_roaming_provisioning(sip);
5522 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5523 (GCompareFunc)g_ascii_strcasecmp)) {
5524 sipe_subscribe_presence_wpending(sip, msg);
5527 /* For 2007+ we publish our initial statuses and calendar data only after
5528 * received our existing publications in sipe_process_roaming_self()
5529 * Only in this case we know versions of current publications made
5530 * on our behalf.
5532 /* For 2005- we publish our initial statuses only after
5533 * received our existing UserInfo data in response to
5534 * self subscription.
5535 * Only in this case we won't override existing UserInfo data
5536 * set earlier or by other client on our behalf.
5540 sip->subscribed = TRUE;
5543 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5544 "timeout=", ";", NULL);
5545 if (timeout != NULL) {
5546 sscanf(timeout, "%u", &sip->keepalive_timeout);
5547 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5548 sip->keepalive_timeout);
5549 g_free(timeout);
5552 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5554 break;
5555 case 301:
5557 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5559 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5560 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5561 gchar **tmp;
5562 gchar *hostname;
5563 int port = 0;
5564 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5565 int i = 1;
5567 tmp = g_strsplit(parts[0], ":", 0);
5568 hostname = g_strdup(tmp[0]);
5569 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5570 g_strfreev(tmp);
5572 while (parts[i]) {
5573 tmp = g_strsplit(parts[i], "=", 0);
5574 if (tmp[1]) {
5575 if (g_strcasecmp("transport", tmp[0]) == 0) {
5576 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5577 transport = SIPE_TRANSPORT_TCP;
5578 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5579 transport = SIPE_TRANSPORT_UDP;
5583 g_strfreev(tmp);
5584 i++;
5586 g_strfreev(parts);
5588 /* Close old connection */
5589 sipe_connection_cleanup(sip);
5591 /* Create new connection */
5592 sip->transport = transport;
5593 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5594 hostname, port, TRANSPORT_DESCRIPTOR);
5595 create_connection(sip, hostname, port);
5597 g_free(redirect);
5599 break;
5600 case 401:
5601 if (sip->registerstatus != 2) {
5602 const char *auth_scheme;
5603 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5604 if (sip->registrar.retries > 3) {
5605 sip->gc->wants_to_die = TRUE;
5606 purple_connection_error(sip->gc, _("Authentication failed"));
5607 return TRUE;
5610 auth_scheme = sipe_get_auth_scheme_name(sip);
5611 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5613 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5614 if (!tmp) {
5615 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5616 sip->gc->wants_to_die = TRUE;
5617 purple_connection_error(sip->gc, tmp2);
5618 g_free(tmp2);
5619 return TRUE;
5621 fill_auth(tmp, &sip->registrar);
5622 sip->registerstatus = 2;
5623 if (sip->account->disconnecting) {
5624 do_register_exp(sip, 0);
5625 } else {
5626 do_register(sip);
5629 break;
5630 case 403:
5632 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5633 gchar **reason = NULL;
5634 gchar *warning;
5635 if (diagnostics != NULL) {
5636 /* Example header:
5637 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5639 reason = g_strsplit(diagnostics, "\"", 0);
5641 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5642 (reason && reason[1]) ? reason[1] : _("no reason given"));
5643 g_strfreev(reason);
5645 sip->gc->wants_to_die = TRUE;
5646 purple_connection_error(sip->gc, warning);
5647 g_free(warning);
5648 return TRUE;
5650 break;
5651 case 404:
5653 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5654 gchar *reason = NULL;
5655 gchar *warning;
5656 if (diagnostics != NULL) {
5657 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5659 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5660 diagnostics ? (reason ? reason : _("no reason given")) :
5661 _("SIP is either not enabled for the destination URI or it does not exist"));
5662 g_free(reason);
5664 sip->gc->wants_to_die = TRUE;
5665 purple_connection_error(sip->gc, warning);
5666 g_free(warning);
5667 return TRUE;
5669 break;
5670 case 503:
5671 case 504: /* Server time-out */
5673 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5674 gchar *reason = NULL;
5675 gchar *warning;
5676 if (diagnostics != NULL) {
5677 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5679 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5680 g_free(reason);
5682 sip->gc->wants_to_die = TRUE;
5683 purple_connection_error(sip->gc, warning);
5684 g_free(warning);
5685 return TRUE;
5687 break;
5689 return TRUE;
5693 * Returns 2005-style activity and Availability.
5695 * @param status Sipe statis id.
5697 static void
5698 sipe_get_act_avail_by_status_2005(const char *status,
5699 int *activity,
5700 int *availability)
5702 int avail = 300; /* online */
5703 int act = 400; /* Available */
5705 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5706 act = 100;
5707 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5708 // act = 150;
5709 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5710 act = 300;
5711 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5712 act = 400;
5713 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5714 // act = 500;
5715 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5716 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5717 act = 600;
5718 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5719 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5720 avail = 0; /* offline */
5721 act = 100;
5722 } else {
5723 act = 400; /* Available */
5726 if (activity) *activity = act;
5727 if (availability) *availability = avail;
5731 * [MS-SIP] 2.2.1
5733 * @param activity 2005 aggregated activity. Ex.: 600
5734 * @param availablity 2005 aggregated availablity. Ex.: 300
5736 static const char *
5737 sipe_get_status_by_act_avail_2005(const int activity,
5738 const int availablity,
5739 char **activity_desc)
5741 const char *status_id = NULL;
5742 const char *act = NULL;
5744 if (activity < 150) {
5745 status_id = SIPE_STATUS_ID_AWAY;
5746 } else if (activity < 200) {
5747 //status_id = SIPE_STATUS_ID_LUNCH;
5748 status_id = SIPE_STATUS_ID_AWAY;
5749 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5750 } else if (activity < 300) {
5751 //status_id = SIPE_STATUS_ID_IDLE;
5752 status_id = SIPE_STATUS_ID_AWAY;
5753 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5754 } else if (activity < 400) {
5755 status_id = SIPE_STATUS_ID_BRB;
5756 } else if (activity < 500) {
5757 status_id = SIPE_STATUS_ID_AVAILABLE;
5758 } else if (activity < 600) {
5759 //status_id = SIPE_STATUS_ID_ON_PHONE;
5760 status_id = SIPE_STATUS_ID_BUSY;
5761 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5762 } else if (activity < 700) {
5763 status_id = SIPE_STATUS_ID_BUSY;
5764 } else if (activity < 800) {
5765 status_id = SIPE_STATUS_ID_AWAY;
5766 } else {
5767 status_id = SIPE_STATUS_ID_AVAILABLE;
5770 if (availablity < 100)
5771 status_id = SIPE_STATUS_ID_OFFLINE;
5773 if (activity_desc && act) {
5774 g_free(*activity_desc);
5775 *activity_desc = g_strdup(act);
5778 return status_id;
5782 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5784 static const char*
5785 sipe_get_status_by_availability(int avail,
5786 char** activity_desc)
5788 const char *status;
5789 const char *act = NULL;
5791 if (avail < 3000) {
5792 status = SIPE_STATUS_ID_OFFLINE;
5793 } else if (avail < 4500) {
5794 status = SIPE_STATUS_ID_AVAILABLE;
5795 } else if (avail < 6000) {
5796 //status = SIPE_STATUS_ID_IDLE;
5797 status = SIPE_STATUS_ID_AVAILABLE;
5798 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5799 } else if (avail < 7500) {
5800 status = SIPE_STATUS_ID_BUSY;
5801 } else if (avail < 9000) {
5802 //status = SIPE_STATUS_ID_BUSYIDLE;
5803 status = SIPE_STATUS_ID_BUSY;
5804 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5805 } else if (avail < 12000) {
5806 status = SIPE_STATUS_ID_DND;
5807 } else if (avail < 15000) {
5808 status = SIPE_STATUS_ID_BRB;
5809 } else if (avail < 18000) {
5810 status = SIPE_STATUS_ID_AWAY;
5811 } else {
5812 status = SIPE_STATUS_ID_OFFLINE;
5815 if (activity_desc && act) {
5816 g_free(*activity_desc);
5817 *activity_desc = g_strdup(act);
5820 return status;
5824 * Returns 2007-style availability value
5826 * @param sipe_status_id (in)
5827 * @param activity_token (out) Must be g_free()'d after use if consumed.
5829 static int
5830 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5832 int availability;
5833 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5835 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5836 availability = 15500;
5837 if (!activity_token || !(*activity_token)) {
5838 activity = SIPE_ACTIVITY_AWAY;
5840 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5841 availability = 12500;
5842 activity = SIPE_ACTIVITY_BRB;
5843 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5844 availability = 9500;
5845 activity = SIPE_ACTIVITY_DND;
5846 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5847 availability = 6500;
5848 if (!activity_token || !(*activity_token)) {
5849 activity = SIPE_ACTIVITY_BUSY;
5851 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5852 availability = 3500;
5853 activity = SIPE_ACTIVITY_ONLINE;
5854 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5855 availability = 0;
5856 } else {
5857 // Offline or invisible
5858 availability = 18500;
5859 activity = SIPE_ACTIVITY_OFFLINE;
5862 if (activity_token) {
5863 *activity_token = g_strdup(sipe_activity_map[activity].token);
5865 return availability;
5868 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5870 const char *uri;
5871 xmlnode *xn_categories;
5872 xmlnode *xn_category;
5873 xmlnode *xn_node;
5874 const char *status = NULL;
5875 gboolean do_update_status = FALSE;
5876 gboolean has_note_cleaned = FALSE;
5877 gboolean has_free_busy_cleaned = FALSE;
5879 xn_categories = xmlnode_from_str(data, len);
5880 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5882 for (xn_category = xmlnode_get_child(xn_categories, "category");
5883 xn_category ;
5884 xn_category = xmlnode_get_next_twin(xn_category) )
5886 const char *tmp;
5887 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5888 time_t publish_time = (tmp = xmlnode_get_attrib(xn_category, "publishTime")) ?
5889 sipe_utils_str_to_time(tmp) : 0;
5891 /* contactCard */
5892 if (sipe_strequal(attrVar, "contactCard"))
5894 xmlnode *node;
5895 /* identity - Display Name and email */
5896 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5897 if (node) {
5898 char* display_name = xmlnode_get_data(
5899 xmlnode_get_descendant(node, "name", "displayName", NULL));
5900 char* email = xmlnode_get_data(
5901 xmlnode_get_child(node, "email"));
5903 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5904 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5906 g_free(display_name);
5907 g_free(email);
5909 /* company */
5910 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5911 if (node) {
5912 char* company = xmlnode_get_data(node);
5913 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5914 g_free(company);
5916 /* department */
5917 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5918 if (node) {
5919 char* department = xmlnode_get_data(node);
5920 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5921 g_free(department);
5923 /* title */
5924 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5925 if (node) {
5926 char* title = xmlnode_get_data(node);
5927 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5928 g_free(title);
5930 /* office */
5931 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5932 if (node) {
5933 char* office = xmlnode_get_data(node);
5934 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5935 g_free(office);
5937 /* site (url) */
5938 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5939 if (node) {
5940 char* site = xmlnode_get_data(node);
5941 sipe_update_user_info(sip, uri, SITE_PROP, site);
5942 g_free(site);
5944 /* phone */
5945 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5946 node;
5947 node = xmlnode_get_next_twin(node))
5949 const char *phone_type = xmlnode_get_attrib(node, "type");
5950 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5951 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5953 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5955 g_free(phone);
5956 g_free(phone_display_string);
5958 /* address */
5959 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5960 node;
5961 node = xmlnode_get_next_twin(node))
5963 if (sipe_strequal(xmlnode_get_attrib(node, "type"), "work")) {
5964 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5965 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5966 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5967 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5968 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5970 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5971 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5972 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5973 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5974 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5976 g_free(street);
5977 g_free(city);
5978 g_free(state);
5979 g_free(zipcode);
5980 g_free(country_code);
5982 break;
5986 /* note */
5987 else if (sipe_strequal(attrVar, "note"))
5989 if (uri) {
5990 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5992 if (!has_note_cleaned) {
5993 has_note_cleaned = TRUE;
5995 g_free(sbuddy->note);
5996 sbuddy->note = NULL;
5997 sbuddy->is_oof_note = FALSE;
5998 sbuddy->note_since = publish_time;
6000 do_update_status = TRUE;
6002 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6003 /* clean up in case no 'note' element is supplied
6004 * which indicate note removal in client
6006 g_free(sbuddy->note);
6007 sbuddy->note = NULL;
6008 sbuddy->is_oof_note = FALSE;
6009 sbuddy->note_since = publish_time;
6011 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
6012 if (xn_node) {
6013 char *tmp;
6014 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
6015 g_free(tmp);
6016 sbuddy->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_node, "type"), "OOF");
6017 sbuddy->note_since = publish_time;
6019 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
6020 uri, sbuddy->note ? sbuddy->note : "");
6022 /* to trigger UI refresh in case no status info is supplied in this update */
6023 do_update_status = TRUE;
6027 /* state */
6028 else if(sipe_strequal(attrVar, "state"))
6030 char *tmp;
6031 int availability;
6032 xmlnode *xn_availability;
6033 xmlnode *xn_activity;
6034 xmlnode *xn_meeting_subject;
6035 xmlnode *xn_meeting_location;
6036 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6038 xn_node = xmlnode_get_child(xn_category, "state");
6039 if (!xn_node) continue;
6040 xn_availability = xmlnode_get_child(xn_node, "availability");
6041 if (!xn_availability) continue;
6042 xn_activity = xmlnode_get_child(xn_node, "activity");
6043 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
6044 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
6046 tmp = xmlnode_get_data(xn_availability);
6047 availability = atoi(tmp);
6048 g_free(tmp);
6050 /* activity, meeting_subject, meeting_location */
6051 if (sbuddy) {
6052 char *tmp = NULL;
6054 /* activity */
6055 g_free(sbuddy->activity);
6056 sbuddy->activity = NULL;
6057 if (xn_activity) {
6058 const char *token = xmlnode_get_attrib(xn_activity, "token");
6059 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
6061 /* from token */
6062 if (!is_empty(token)) {
6063 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6065 /* from custom element */
6066 if (xn_custom) {
6067 char *custom = xmlnode_get_data(xn_custom);
6069 if (!is_empty(custom)) {
6070 sbuddy->activity = custom;
6071 custom = NULL;
6073 g_free(custom);
6076 /* meeting_subject */
6077 g_free(sbuddy->meeting_subject);
6078 sbuddy->meeting_subject = NULL;
6079 if (xn_meeting_subject) {
6080 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
6082 if (!is_empty(meeting_subject)) {
6083 sbuddy->meeting_subject = meeting_subject;
6084 meeting_subject = NULL;
6086 g_free(meeting_subject);
6088 /* meeting_location */
6089 g_free(sbuddy->meeting_location);
6090 sbuddy->meeting_location = NULL;
6091 if (xn_meeting_location) {
6092 char *meeting_location = xmlnode_get_data(xn_meeting_location);
6094 if (!is_empty(meeting_location)) {
6095 sbuddy->meeting_location = meeting_location;
6096 meeting_location = NULL;
6098 g_free(meeting_location);
6101 status = sipe_get_status_by_availability(availability, &tmp);
6102 if (sbuddy->activity && tmp) {
6103 char *tmp2 = sbuddy->activity;
6105 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6106 g_free(tmp);
6107 g_free(tmp2);
6108 } else if (tmp) {
6109 sbuddy->activity = tmp;
6113 do_update_status = TRUE;
6115 /* calendarData */
6116 else if(sipe_strequal(attrVar, "calendarData"))
6118 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6119 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
6120 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
6122 if (sbuddy && xn_free_busy) {
6123 if (!has_free_busy_cleaned) {
6124 has_free_busy_cleaned = TRUE;
6126 g_free(sbuddy->cal_start_time);
6127 sbuddy->cal_start_time = NULL;
6129 g_free(sbuddy->cal_free_busy_base64);
6130 sbuddy->cal_free_busy_base64 = NULL;
6132 g_free(sbuddy->cal_free_busy);
6133 sbuddy->cal_free_busy = NULL;
6135 sbuddy->cal_free_busy_published = publish_time;
6138 if (publish_time >= sbuddy->cal_free_busy_published) {
6139 g_free(sbuddy->cal_start_time);
6140 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
6142 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
6143 15 : 0;
6145 g_free(sbuddy->cal_free_busy_base64);
6146 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
6148 g_free(sbuddy->cal_free_busy);
6149 sbuddy->cal_free_busy = NULL;
6151 sbuddy->cal_free_busy_published = publish_time;
6153 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);
6157 if (sbuddy && xn_working_hours) {
6158 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6163 if (do_update_status) {
6164 if (!status) { /* no status category in this update, using contact's current status */
6165 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6166 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6167 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6168 status = purple_status_get_id(pstatus);
6171 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
6172 sipe_got_user_status(sip, uri, status);
6175 xmlnode_free(xn_categories);
6178 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6180 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6181 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
6182 payload->host = g_strdup(host);
6183 payload->buddies = server;
6184 sipe_subscribe_presence_batched_routed(sip, payload);
6185 sipe_subscribe_presence_batched_routed_free(payload);
6188 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6190 xmlnode *xn_list;
6191 xmlnode *xn_resource;
6192 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6193 g_free, NULL);
6194 GSList *server;
6195 gchar *host;
6197 xn_list = xmlnode_from_str(data, len);
6199 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6200 xn_resource;
6201 xn_resource = xmlnode_get_next_twin(xn_resource) )
6203 const char *uri, *state;
6204 xmlnode *xn_instance;
6206 xn_instance = xmlnode_get_child(xn_resource, "instance");
6207 if (!xn_instance) continue;
6209 uri = xmlnode_get_attrib(xn_resource, "uri");
6210 state = xmlnode_get_attrib(xn_instance, "state");
6211 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6213 if (strstr(state, "resubscribe")) {
6214 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6216 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6217 gchar *user = g_strdup(uri);
6218 host = g_strdup(poolFqdn);
6219 server = g_hash_table_lookup(servers, host);
6220 server = g_slist_append(server, user);
6221 g_hash_table_insert(servers, host, server);
6222 } else {
6223 sipe_subscribe_presence_single(sip, (void *) uri);
6228 /* Send out any deferred poolFqdn subscriptions */
6229 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6230 g_hash_table_destroy(servers);
6232 xmlnode_free(xn_list);
6235 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6237 gchar *uri;
6238 gchar *getbasic;
6239 gchar *activity = NULL;
6240 xmlnode *pidf;
6241 xmlnode *basicstatus = NULL, *tuple, *status;
6242 gboolean isonline = FALSE;
6243 xmlnode *display_name_node;
6245 pidf = xmlnode_from_str(data, len);
6246 if (!pidf) {
6247 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6248 return;
6251 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6253 if ((status = xmlnode_get_child(tuple, "status"))) {
6254 basicstatus = xmlnode_get_child(status, "basic");
6258 if (!basicstatus) {
6259 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6260 xmlnode_free(pidf);
6261 return;
6264 getbasic = xmlnode_get_data(basicstatus);
6265 if (!getbasic) {
6266 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6267 xmlnode_free(pidf);
6268 return;
6271 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6272 if (strstr(getbasic, "open")) {
6273 isonline = TRUE;
6275 g_free(getbasic);
6277 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6279 display_name_node = xmlnode_get_child(pidf, "display-name");
6280 if (display_name_node) {
6281 char * display_name = xmlnode_get_data(display_name_node);
6283 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6284 g_free(display_name);
6287 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6288 if ((status = xmlnode_get_child(tuple, "status"))) {
6289 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6290 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6291 activity = xmlnode_get_data(basicstatus);
6292 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6298 if (isonline) {
6299 const gchar * status_id = NULL;
6300 if (activity) {
6301 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6302 status_id = SIPE_STATUS_ID_BUSY;
6303 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6304 status_id = SIPE_STATUS_ID_AWAY;
6308 if (!status_id) {
6309 status_id = SIPE_STATUS_ID_AVAILABLE;
6312 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6313 sipe_got_user_status(sip, uri, status_id);
6314 } else {
6315 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6318 g_free(activity);
6319 g_free(uri);
6320 xmlnode_free(pidf);
6323 /** 2005 */
6324 static void
6325 sipe_user_info_has_updated(struct sipe_account_data *sip,
6326 xmlnode *xn_userinfo)
6328 if (sip->user_info) {
6329 xmlnode_free(sip->user_info);
6331 sip->user_info = xmlnode_copy(xn_userinfo);
6333 /* Publish initial state if not yet.
6334 * Assuming this happens on initial responce to self subscription
6335 * so we've already updated our UserInfo.
6337 if (!sip->initial_state_published) {
6338 send_presence_soap(sip, FALSE);
6339 /* dalayed run */
6340 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6344 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6346 char *activity = NULL;
6347 const char *epid;
6348 const char *status_id = NULL;
6349 const char *name;
6350 char *uri;
6351 char *self_uri = sip_uri_self(sip);
6352 int avl;
6353 int act;
6354 const char *device_name = NULL;
6355 const char *cal_start_time = NULL;
6356 const char *cal_granularity = NULL;
6357 char *cal_free_busy_base64 = NULL;
6358 struct sipe_buddy *sbuddy;
6359 xmlnode *node;
6360 xmlnode *xn_presentity;
6361 xmlnode *xn_availability;
6362 xmlnode *xn_activity;
6363 xmlnode *xn_display_name;
6364 xmlnode *xn_email;
6365 xmlnode *xn_phone_number;
6366 xmlnode *xn_userinfo;
6367 xmlnode *xn_note;
6368 xmlnode *xn_oof;
6369 xmlnode *xn_state;
6370 xmlnode *xn_contact;
6371 char *note;
6372 char *free_activity;
6373 int user_avail;
6374 const char *user_avail_nil;
6375 int res_avail;
6376 time_t user_avail_since = 0;
6377 time_t activity_since = 0;
6379 /* fix for Reuters environment on Linux */
6380 if (data && strstr(data, "encoding=\"utf-16\"")) {
6381 char *tmp_data;
6382 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6383 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6384 g_free(tmp_data);
6385 } else {
6386 xn_presentity = xmlnode_from_str(data, len);
6389 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6390 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6391 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6392 xn_email = xmlnode_get_child(xn_presentity, "email");
6393 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6394 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6395 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6396 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6397 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6398 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6399 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6400 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6401 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6402 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6404 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6405 user_avail = 0;
6406 user_avail_since = 0;
6409 free_activity = NULL;
6411 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6412 uri = sip_uri_from_name(name);
6413 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6414 epid = xmlnode_get_attrib(xn_availability, "epid");
6415 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6417 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6418 res_avail = sipe_get_availability_by_status(status_id, NULL);
6419 if (user_avail > res_avail) {
6420 res_avail = user_avail;
6421 status_id = sipe_get_status_by_availability(user_avail, NULL);
6424 if (xn_display_name) {
6425 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6426 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6427 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6428 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6429 char *tel_uri = sip_to_tel_uri(phone_number);
6431 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6432 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6433 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6434 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6436 g_free(tel_uri);
6437 g_free(phone_label);
6438 g_free(phone_number);
6439 g_free(email);
6440 g_free(display_name);
6443 if (xn_contact) {
6444 /* tel */
6445 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6447 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6448 const char *phone_type = xmlnode_get_attrib(node, "type");
6449 char* phone = xmlnode_get_data(node);
6451 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6453 g_free(phone);
6457 /* devicePresence */
6458 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6459 xmlnode *xn_device_name;
6460 xmlnode *xn_calendar_info;
6461 xmlnode *xn_state;
6462 char *state;
6464 /* deviceName */
6465 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6466 xn_device_name = xmlnode_get_child(node, "deviceName");
6467 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6470 /* calendarInfo */
6471 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6472 if (xn_calendar_info) {
6473 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6475 if (cal_start_time) {
6476 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6477 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6479 if (cal_start_time_t_tmp > cal_start_time_t) {
6480 cal_start_time = cal_start_time_tmp;
6481 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6482 g_free(cal_free_busy_base64);
6483 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6485 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);
6487 } else {
6488 cal_start_time = cal_start_time_tmp;
6489 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6490 g_free(cal_free_busy_base64);
6491 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6493 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);
6497 /* state */
6498 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6499 if (xn_state) {
6500 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6501 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6503 state = xmlnode_get_data(xn_state);
6504 if (dev_avail_since > user_avail_since &&
6505 dev_avail >= res_avail)
6507 res_avail = dev_avail;
6508 if (!is_empty(state))
6510 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6511 g_free(activity);
6512 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6513 } else if (sipe_strequal(state, "presenting")) {
6514 g_free(activity);
6515 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6516 } else {
6517 activity = state;
6518 state = NULL;
6520 activity_since = dev_avail_since;
6522 status_id = sipe_get_status_by_availability(res_avail, &activity);
6524 g_free(state);
6528 /* oof */
6529 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6530 g_free(activity);
6531 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6532 activity_since = 0;
6535 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6536 if (sbuddy)
6538 g_free(sbuddy->activity);
6539 sbuddy->activity = activity;
6540 activity = NULL;
6542 sbuddy->activity_since = activity_since;
6544 sbuddy->user_avail = user_avail;
6545 sbuddy->user_avail_since = user_avail_since;
6547 g_free(sbuddy->note);
6548 sbuddy->note = NULL;
6549 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6551 sbuddy->is_oof_note = (xn_oof != NULL);
6553 g_free(sbuddy->device_name);
6554 sbuddy->device_name = NULL;
6555 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6557 if (!is_empty(cal_free_busy_base64)) {
6558 g_free(sbuddy->cal_start_time);
6559 sbuddy->cal_start_time = g_strdup(cal_start_time);
6561 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6563 g_free(sbuddy->cal_free_busy_base64);
6564 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6565 cal_free_busy_base64 = NULL;
6567 g_free(sbuddy->cal_free_busy);
6568 sbuddy->cal_free_busy = NULL;
6571 sbuddy->last_non_cal_status_id = status_id;
6572 g_free(sbuddy->last_non_cal_activity);
6573 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6575 if (sipe_strequal(sbuddy->name, self_uri)) {
6576 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6578 sip->is_oof_note = sbuddy->is_oof_note;
6580 g_free(sip->note);
6581 sip->note = g_strdup(sbuddy->note);
6583 sip->note_since = time(NULL);
6586 g_free(sip->status);
6587 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6590 g_free(cal_free_busy_base64);
6591 g_free(activity);
6593 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6594 sipe_got_user_status(sip, uri, status_id);
6596 if (!sip->ocs2007 && sipe_strequal(self_uri, uri)) {
6597 sipe_user_info_has_updated(sip, xn_userinfo);
6600 g_free(note);
6601 xmlnode_free(xn_presentity);
6602 g_free(uri);
6603 g_free(self_uri);
6606 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6608 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6610 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6612 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6613 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6615 const char *content = msg->body;
6616 unsigned length = msg->bodylen;
6617 PurpleMimeDocument *mime = NULL;
6619 if (strstr(ctype, "multipart"))
6621 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6622 const char *content_type;
6623 GList* parts;
6624 mime = purple_mime_document_parse(doc);
6625 parts = purple_mime_document_get_parts(mime);
6626 while(parts) {
6627 content = purple_mime_part_get_data(parts->data);
6628 length = purple_mime_part_get_length(parts->data);
6629 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6630 if(content_type && strstr(content_type,"application/rlmi+xml"))
6632 process_incoming_notify_rlmi_resub(sip, content, length);
6634 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6636 process_incoming_notify_msrtc(sip, content, length);
6638 else
6640 process_incoming_notify_rlmi(sip, content, length);
6642 parts = parts->next;
6644 g_free(doc);
6646 if (mime)
6648 purple_mime_document_free(mime);
6651 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6653 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6655 else if(strstr(ctype, "application/rlmi+xml"))
6657 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6660 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6662 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6664 else
6666 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6670 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6672 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6673 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6675 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6677 if (ctype &&
6678 strstr(ctype, "multipart") &&
6679 (strstr(ctype, "application/rlmi+xml") ||
6680 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6681 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6682 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6683 GList *parts = purple_mime_document_get_parts(mime);
6684 GSList *buddies = NULL;
6685 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6687 while (parts) {
6688 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6689 purple_mime_part_get_length(parts->data));
6691 if (xml && !sipe_strequal(xml->name, "list")) {
6692 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6694 buddies = g_slist_append(buddies, uri);
6696 xmlnode_free(xml);
6698 parts = parts->next;
6700 g_free(doc);
6701 if (mime) purple_mime_document_free(mime);
6703 payload->host = g_strdup(who);
6704 payload->buddies = buddies;
6705 sipe_schedule_action(action_name, timeout,
6706 sipe_subscribe_presence_batched_routed,
6707 sipe_subscribe_presence_batched_routed_free,
6708 sip, payload);
6709 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6711 } else {
6712 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6713 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6715 g_free(action_name);
6719 * Dispatcher for all incoming subscription information
6720 * whether it comes from NOTIFY, BENOTIFY requests or
6721 * piggy-backed to subscription's OK responce.
6723 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6724 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6726 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6728 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6729 const gchar *event = sipmsg_find_header(msg, "Event");
6730 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6731 char *tmp;
6733 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6734 event ? event : "",
6735 tmp = fix_newlines(msg->body));
6736 g_free(tmp);
6737 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6739 /* implicit subscriptions */
6740 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6741 sipe_process_imdn(sip, msg);
6744 if (event) {
6745 /* for one off subscriptions (send with Expire: 0) */
6746 if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6748 sipe_process_provisioning_v2(sip, msg);
6750 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6752 sipe_process_provisioning(sip, msg);
6754 else if (!g_ascii_strcasecmp(event, "presence"))
6756 sipe_process_presence(sip, msg);
6758 else if (!g_ascii_strcasecmp(event, "registration-notify"))
6760 sipe_process_registration_notify(sip, msg);
6763 if (!subscription_state || strstr(subscription_state, "active"))
6765 if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6767 sipe_process_roaming_contacts(sip, msg);
6769 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6771 sipe_process_roaming_self(sip, msg);
6773 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6775 sipe_process_roaming_acl(sip, msg);
6777 else if (!g_ascii_strcasecmp(event, "presence.wpending"))
6779 sipe_process_presence_wpending(sip, msg);
6781 else if (!g_ascii_strcasecmp(event, "conference"))
6783 sipe_process_conference(sip, msg);
6788 /* The server sends status 'terminated' */
6789 if (subscription_state && strstr(subscription_state, "terminated") ) {
6790 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6791 gchar *key = sipe_get_subscription_key(event, who);
6793 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6794 g_free(who);
6796 if (g_hash_table_lookup(sip->subscriptions, key)) {
6797 g_hash_table_remove(sip->subscriptions, key);
6798 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6801 g_free(key);
6804 if (!request && event) {
6805 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6806 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6807 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6809 if (timeout) {
6810 /* 2 min ahead of expiration */
6811 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6813 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6814 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6816 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6817 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6818 g_free(action_name);
6820 else if (!g_ascii_strcasecmp(event, "presence") &&
6821 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6823 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6824 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6826 if (sip->batched_support) {
6827 sipe_process_presence_timeout(sip, msg, who, timeout);
6829 else {
6830 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6831 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6833 g_free(action_name);
6834 g_free(who);
6839 /* The client responses on received a NOTIFY message */
6840 if (request && !benotify)
6842 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6847 * Whether user manually changed status or
6848 * it was changed automatically due to user
6849 * became inactive/active again
6851 static gboolean
6852 sipe_is_user_state(struct sipe_account_data *sip)
6854 gboolean res;
6855 time_t now = time(NULL);
6857 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6858 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6860 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6862 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6863 return res;
6866 static void
6867 send_presence_soap0(struct sipe_account_data *sip,
6868 gboolean do_publish_calendar,
6869 gboolean do_reset_status)
6871 struct sipe_ews* ews = sip->ews;
6872 int availability = 0;
6873 int activity = 0;
6874 gchar *body;
6875 gchar *tmp;
6876 gchar *tmp2 = NULL;
6877 gchar *res_note = NULL;
6878 gchar *res_oof = NULL;
6879 const gchar *note_pub = NULL;
6880 gchar *states = NULL;
6881 gchar *calendar_data = NULL;
6882 gchar *epid = get_epid(sip);
6883 time_t now = time(NULL);
6884 gchar *since_time_str = sipe_utils_time_to_str(now);
6885 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6886 const char *user_input;
6887 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6889 if (oof_note && sip->note) {
6890 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6891 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6894 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6896 if (!sip->initial_state_published ||
6897 do_reset_status)
6899 g_free(sip->status);
6900 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6903 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6905 /* Note */
6906 if (pub_oof) {
6907 note_pub = oof_note;
6908 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6909 ews->published = TRUE;
6910 } else if (sip->note) {
6911 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6912 g_free(sip->note);
6913 sip->note = NULL;
6914 sip->is_oof_note = FALSE;
6915 sip->note_since = 0;
6916 } else {
6917 note_pub = sip->note;
6918 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6922 if (note_pub)
6924 /* to protocol internal plain text format */
6925 tmp = purple_markup_strip_html(note_pub);
6926 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6927 g_free(tmp);
6930 /* User State */
6931 if (!do_reset_status) {
6932 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6934 gchar *activity_token = NULL;
6935 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6937 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6938 avail_2007,
6939 since_time_str,
6940 epid,
6941 activity_token);
6942 g_free(activity_token);
6944 else /* preserve existing publication */
6946 xmlnode *xn_states;
6947 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6948 states = xmlnode_to_str(xn_states, NULL);
6949 /* this is a hack-around to remove added newline after inner element,
6950 * state in this case, where it shouldn't be.
6951 * After several use of xmlnode_to_str, amount of added newlines
6952 * grows significantly.
6954 purple_str_strip_char(states, '\n');
6955 //purple_str_strip_char(states, '\r');
6958 } else {
6959 /* do nothing - then User state will be erased */
6961 sip->initial_state_published = TRUE;
6963 /* CalendarInfo */
6964 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6966 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6967 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6968 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6969 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6970 fb_start_str,
6971 free_busy_base64);
6972 g_free(fb_start_str);
6973 g_free(free_busy_base64);
6976 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6978 /* forming resulting XML */
6979 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6980 sip->username,
6981 availability,
6982 activity,
6983 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6984 res_note ? res_note : "",
6985 res_oof ? res_oof : "",
6986 states ? states : "",
6987 calendar_data ? calendar_data : "",
6988 epid,
6989 since_time_str,
6990 since_time_str,
6991 user_input);
6992 g_free(tmp);
6993 g_free(tmp2);
6994 g_free(res_note);
6995 g_free(states);
6996 g_free(calendar_data);
6998 send_soap_request(sip, body);
7000 g_free(body);
7001 g_free(since_time_str);
7002 g_free(epid);
7005 void
7006 send_presence_soap(struct sipe_account_data *sip,
7007 gboolean do_publish_calendar)
7009 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7013 static gboolean
7014 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7015 struct sipmsg *msg,
7016 struct transaction *trans)
7018 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7020 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7021 xmlnode *xml;
7022 xmlnode *node;
7023 gchar *fault_code;
7024 GHashTable *faults;
7025 int index_our;
7026 gboolean has_device_publication = FALSE;
7028 xml = xmlnode_from_str(msg->body, msg->bodylen);
7030 /* test if version mismatch fault */
7031 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7032 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7033 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
7034 g_free(fault_code);
7035 xmlnode_free(xml);
7036 return TRUE;
7038 g_free(fault_code);
7040 /* accumulating information about faulty versions */
7041 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7042 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7043 node;
7044 node = xmlnode_get_next_twin(node))
7046 const gchar *index = xmlnode_get_attrib(node, "index");
7047 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7049 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7050 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
7052 xmlnode_free(xml);
7054 /* here we are parsing own request to figure out what publication
7055 * referensed here only by index went wrong
7057 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7059 /* publication */
7060 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7061 index_our = 1; /* starts with 1 - our first publication */
7062 node;
7063 node = xmlnode_get_next_twin(node), index_our++)
7065 gchar *idx = g_strdup_printf("%d", index_our);
7066 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7067 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7068 g_free(idx);
7070 if (sipe_strequal("device", categoryName)) {
7071 has_device_publication = TRUE;
7074 if (curVersion) { /* fault exist on this index */
7075 const gchar *container = xmlnode_get_attrib(node, "container");
7076 const gchar *instance = xmlnode_get_attrib(node, "instance");
7077 /* key is <category><instance><container> */
7078 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7079 struct sipe_publication *publication =
7080 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
7082 purple_debug_info("sipe", "key is %s\n", key);
7084 if (publication) {
7085 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
7086 key, curVersion, publication->version);
7087 /* updating publication's version to the correct one */
7088 publication->version = atoi(curVersion);
7090 g_free(key);
7093 xmlnode_free(xml);
7094 g_hash_table_destroy(faults);
7096 /* rebublishing with right versions */
7097 if (has_device_publication) {
7098 send_publish_category_initial(sip);
7099 } else {
7100 send_presence_status(sip);
7103 return TRUE;
7107 * Returns 'device' XML part for publication.
7108 * Must be g_free'd after use.
7110 static gchar *
7111 sipe_publish_get_category_device(struct sipe_account_data *sip)
7113 gchar *uri;
7114 gchar *doc;
7115 gchar *epid = get_epid(sip);
7116 gchar *uuid = generateUUIDfromEPID(epid);
7117 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7118 /* key is <category><instance><container> */
7119 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7120 struct sipe_publication *publication =
7121 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7123 g_free(key);
7124 g_free(epid);
7126 uri = sip_uri_self(sip);
7127 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7128 device_instance,
7129 publication ? publication->version : 0,
7130 uuid,
7131 uri,
7132 "00:00:00+01:00", /* @TODO make timezone real*/
7133 sipe_get_host_name()
7136 g_free(uri);
7137 g_free(uuid);
7139 return doc;
7143 * A service method - use
7144 * - send_publish_get_category_state_machine and
7145 * - send_publish_get_category_state_user instead.
7146 * Must be g_free'd after use.
7148 static gchar *
7149 sipe_publish_get_category_state(struct sipe_account_data *sip,
7150 gboolean is_user_state)
7152 int availability = sipe_get_availability_by_status(sip->status, NULL);
7153 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7154 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7155 /* key is <category><instance><container> */
7156 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7157 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7158 struct sipe_publication *publication_2 =
7159 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7160 struct sipe_publication *publication_3 =
7161 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7163 g_free(key_2);
7164 g_free(key_3);
7166 if (publication_2 && (publication_2->availability == availability))
7168 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
7169 return NULL; /* nothing to update */
7172 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7173 instance,
7174 publication_2 ? publication_2->version : 0,
7175 availability,
7176 instance,
7177 publication_3 ? publication_3->version : 0,
7178 availability);
7182 * Only Busy and OOF calendar event are published.
7183 * Different instances are used for that.
7185 * Must be g_free'd after use.
7187 static gchar *
7188 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7189 struct sipe_cal_event *event,
7190 const char *uri,
7191 int cal_satus)
7193 gchar *start_time_str;
7194 int availability = 0;
7195 gchar *res;
7196 gchar *tmp = NULL;
7197 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7198 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7199 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7201 /* key is <category><instance><container> */
7202 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7203 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7204 struct sipe_publication *publication_2 =
7205 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7206 struct sipe_publication *publication_3 =
7207 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7209 g_free(key_2);
7210 g_free(key_3);
7212 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7213 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7214 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7215 return NULL;
7218 if (event &&
7219 publication_3 &&
7220 (publication_3->availability == availability) &&
7221 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7223 g_free(tmp);
7224 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7225 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7226 return NULL; /* nothing to update */
7228 g_free(tmp);
7230 if (event &&
7231 (event->cal_status == SIPE_CAL_BUSY ||
7232 event->cal_status == SIPE_CAL_OOF))
7234 gchar *availability_xml_str = NULL;
7235 gchar *activity_xml_str = NULL;
7237 if (event->cal_status == SIPE_CAL_BUSY) {
7238 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7241 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7242 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7243 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7244 "minAvailability=\"6500\"",
7245 "maxAvailability=\"8999\"");
7246 } else if (event->cal_status == SIPE_CAL_OOF) {
7247 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7248 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7249 "minAvailability=\"12000\"",
7250 "");
7252 start_time_str = sipe_utils_time_to_str(event->start_time);
7254 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7255 instance,
7256 publication_2 ? publication_2->version : 0,
7257 uri,
7258 start_time_str,
7259 availability_xml_str ? availability_xml_str : "",
7260 activity_xml_str ? activity_xml_str : "",
7261 event->subject ? event->subject : "",
7262 event->location ? event->location : "",
7264 instance,
7265 publication_3 ? publication_3->version : 0,
7266 uri,
7267 start_time_str,
7268 availability_xml_str ? availability_xml_str : "",
7269 activity_xml_str ? activity_xml_str : "",
7270 event->subject ? event->subject : "",
7271 event->location ? event->location : ""
7273 g_free(start_time_str);
7274 g_free(availability_xml_str);
7275 g_free(activity_xml_str);
7278 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7280 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7281 instance,
7282 publication_2 ? publication_2->version : 0,
7284 instance,
7285 publication_3 ? publication_3->version : 0
7289 return res;
7293 * Returns 'machineState' XML part for publication.
7294 * Must be g_free'd after use.
7296 static gchar *
7297 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7299 return sipe_publish_get_category_state(sip, FALSE);
7303 * Returns 'userState' XML part for publication.
7304 * Must be g_free'd after use.
7306 static gchar *
7307 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7309 return sipe_publish_get_category_state(sip, TRUE);
7313 * Returns 'note' XML part for publication.
7314 * Must be g_free'd after use.
7316 * Protocol format for Note is plain text.
7318 * @param note a note in Sipe internal HTML format
7319 * @param note_type either personal or OOF
7321 static gchar *
7322 sipe_publish_get_category_note(struct sipe_account_data *sip,
7323 const char *note, /* html */
7324 const char *note_type,
7325 time_t note_start,
7326 time_t note_end)
7328 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7329 /* key is <category><instance><container> */
7330 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7331 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7332 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7334 struct sipe_publication *publication_note_200 =
7335 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7336 struct sipe_publication *publication_note_300 =
7337 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7338 struct sipe_publication *publication_note_400 =
7339 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7341 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7342 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7343 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7344 char *res, *tmp1, *tmp2, *tmp3;
7345 char *start_time_attr;
7346 char *end_time_attr;
7348 g_free(tmp);
7349 tmp = NULL;
7350 g_free(key_note_200);
7351 g_free(key_note_300);
7352 g_free(key_note_400);
7354 /* we even need to republish empty note */
7355 if (sipe_strequal(n1, n2))
7357 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7358 g_free(n1);
7359 return NULL; /* nothing to update */
7362 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7363 g_free(tmp);
7364 tmp = NULL;
7365 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7366 g_free(tmp);
7368 if (n1) {
7369 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7370 instance,
7371 200,
7372 publication_note_200 ? publication_note_200->version : 0,
7373 note_type,
7374 start_time_attr ? start_time_attr : "",
7375 end_time_attr ? end_time_attr : "",
7376 n1);
7378 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7379 instance,
7380 300,
7381 publication_note_300 ? publication_note_300->version : 0,
7382 note_type,
7383 start_time_attr ? start_time_attr : "",
7384 end_time_attr ? end_time_attr : "",
7385 n1);
7387 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7388 instance,
7389 400,
7390 publication_note_400 ? publication_note_400->version : 0,
7391 note_type,
7392 start_time_attr ? start_time_attr : "",
7393 end_time_attr ? end_time_attr : "",
7394 n1);
7395 } else {
7396 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7397 "note",
7398 instance,
7399 200,
7400 publication_note_200 ? publication_note_200->version : 0,
7401 "static");
7402 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7403 "note",
7404 instance,
7405 300,
7406 publication_note_200 ? publication_note_200->version : 0,
7407 "static");
7408 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7409 "note",
7410 instance,
7411 400,
7412 publication_note_200 ? publication_note_200->version : 0,
7413 "static");
7415 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7417 g_free(start_time_attr);
7418 g_free(end_time_attr);
7419 g_free(tmp1);
7420 g_free(tmp2);
7421 g_free(tmp3);
7422 g_free(n1);
7424 return res;
7428 * Returns 'calendarData' XML part with WorkingHours for publication.
7429 * Must be g_free'd after use.
7431 static gchar *
7432 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7434 struct sipe_ews* ews = sip->ews;
7436 /* key is <category><instance><container> */
7437 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7438 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7439 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7440 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7441 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7442 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7444 struct sipe_publication *publication_cal_1 =
7445 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7446 struct sipe_publication *publication_cal_100 =
7447 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7448 struct sipe_publication *publication_cal_200 =
7449 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7450 struct sipe_publication *publication_cal_300 =
7451 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7452 struct sipe_publication *publication_cal_400 =
7453 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7454 struct sipe_publication *publication_cal_32000 =
7455 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7457 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7458 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7460 g_free(key_cal_1);
7461 g_free(key_cal_100);
7462 g_free(key_cal_200);
7463 g_free(key_cal_300);
7464 g_free(key_cal_400);
7465 g_free(key_cal_32000);
7467 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7468 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7469 return NULL;
7472 if (sipe_strequal(n1, n2))
7474 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7475 return NULL; /* nothing to update */
7478 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7479 /* 1 */
7480 publication_cal_1 ? publication_cal_1->version : 0,
7481 ews->email,
7482 ews->working_hours_xml_str,
7483 /* 100 - Public */
7484 publication_cal_100 ? publication_cal_100->version : 0,
7485 /* 200 - Company */
7486 publication_cal_200 ? publication_cal_200->version : 0,
7487 ews->email,
7488 ews->working_hours_xml_str,
7489 /* 300 - Team */
7490 publication_cal_300 ? publication_cal_300->version : 0,
7491 ews->email,
7492 ews->working_hours_xml_str,
7493 /* 400 - Personal */
7494 publication_cal_400 ? publication_cal_400->version : 0,
7495 ews->email,
7496 ews->working_hours_xml_str,
7497 /* 32000 - Blocked */
7498 publication_cal_32000 ? publication_cal_32000->version : 0
7503 * Returns 'calendarData' XML part with FreeBusy for publication.
7504 * Must be g_free'd after use.
7506 static gchar *
7507 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7509 struct sipe_ews* ews = sip->ews;
7510 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7511 char *fb_start_str;
7512 char *free_busy_base64;
7513 const char *st;
7514 const char *fb;
7515 char *res;
7517 /* key is <category><instance><container> */
7518 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7519 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7520 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7521 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7522 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7523 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7525 struct sipe_publication *publication_cal_1 =
7526 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7527 struct sipe_publication *publication_cal_100 =
7528 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7529 struct sipe_publication *publication_cal_200 =
7530 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7531 struct sipe_publication *publication_cal_300 =
7532 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7533 struct sipe_publication *publication_cal_400 =
7534 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7535 struct sipe_publication *publication_cal_32000 =
7536 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7538 g_free(key_cal_1);
7539 g_free(key_cal_100);
7540 g_free(key_cal_200);
7541 g_free(key_cal_300);
7542 g_free(key_cal_400);
7543 g_free(key_cal_32000);
7545 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7546 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7547 return NULL;
7550 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7551 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7553 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7554 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7556 /* we will rebuplish the same data to refresh publication time,
7557 * so if data from multiple sources, most recent will be choosen
7559 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7561 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7562 // g_free(fb_start_str);
7563 // g_free(free_busy_base64);
7564 // return NULL; /* nothing to update */
7567 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7568 /* 1 */
7569 cal_data_instance,
7570 publication_cal_1 ? publication_cal_1->version : 0,
7571 /* 100 - Public */
7572 cal_data_instance,
7573 publication_cal_100 ? publication_cal_100->version : 0,
7574 /* 200 - Company */
7575 cal_data_instance,
7576 publication_cal_200 ? publication_cal_200->version : 0,
7577 ews->email,
7578 fb_start_str,
7579 free_busy_base64,
7580 /* 300 - Team */
7581 cal_data_instance,
7582 publication_cal_300 ? publication_cal_300->version : 0,
7583 ews->email,
7584 fb_start_str,
7585 free_busy_base64,
7586 /* 400 - Personal */
7587 cal_data_instance,
7588 publication_cal_400 ? publication_cal_400->version : 0,
7589 ews->email,
7590 fb_start_str,
7591 free_busy_base64,
7592 /* 32000 - Blocked */
7593 cal_data_instance,
7594 publication_cal_32000 ? publication_cal_32000->version : 0
7597 g_free(fb_start_str);
7598 g_free(free_busy_base64);
7599 return res;
7602 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7604 gchar *uri;
7605 gchar *doc;
7606 gchar *tmp;
7607 gchar *hdr;
7609 uri = sip_uri_self(sip);
7610 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7611 uri,
7612 publications);
7614 tmp = get_contact(sip);
7615 hdr = g_strdup_printf("Contact: %s\r\n"
7616 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7618 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7620 g_free(tmp);
7621 g_free(hdr);
7622 g_free(uri);
7623 g_free(doc);
7626 static void
7627 send_publish_category_initial(struct sipe_account_data *sip)
7629 gchar *pub_device = sipe_publish_get_category_device(sip);
7630 gchar *pub_machine;
7631 gchar *publications;
7633 g_free(sip->status);
7634 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7636 pub_machine = sipe_publish_get_category_state_machine(sip);
7637 publications = g_strdup_printf("%s%s",
7638 pub_device,
7639 pub_machine ? pub_machine : "");
7640 g_free(pub_device);
7641 g_free(pub_machine);
7643 send_presence_publish(sip, publications);
7644 g_free(publications);
7647 static void
7648 send_presence_category_publish(struct sipe_account_data *sip)
7650 gchar *pub_state = sipe_is_user_state(sip) ?
7651 sipe_publish_get_category_state_user(sip) :
7652 sipe_publish_get_category_state_machine(sip);
7653 gchar *pub_note = sipe_publish_get_category_note(sip,
7654 sip->note,
7655 sip->is_oof_note ? "OOF" : "personal",
7658 gchar *publications;
7660 if (!pub_state && !pub_note) {
7661 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7662 return;
7665 publications = g_strdup_printf("%s%s",
7666 pub_state ? pub_state : "",
7667 pub_note ? pub_note : "");
7669 g_free(pub_state);
7670 g_free(pub_note);
7672 send_presence_publish(sip, publications);
7673 g_free(publications);
7677 * Publishes self status
7678 * based on own calendar information.
7680 * For 2007+
7682 void
7683 publish_calendar_status_self(struct sipe_account_data *sip)
7685 struct sipe_cal_event* event = NULL;
7686 gchar *pub_cal_working_hours = NULL;
7687 gchar *pub_cal_free_busy = NULL;
7688 gchar *pub_calendar = NULL;
7689 gchar *pub_calendar2 = NULL;
7690 gchar *pub_oof_note = NULL;
7691 const gchar *oof_note;
7692 time_t oof_start = 0;
7693 time_t oof_end = 0;
7695 if (!sip->ews) {
7696 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7697 return;
7700 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7701 if (sip->ews->cal_events) {
7702 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7705 if (!event) {
7706 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7707 } else {
7708 char *desc = sipe_cal_event_describe(event);
7709 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7710 g_free(desc);
7713 /* Logic
7714 if OOF
7715 OOF publish, Busy clean
7716 ilse if Busy
7717 OOF clean, Busy publish
7718 else
7719 OOF clean, Busy clean
7721 if (event && event->cal_status == SIPE_CAL_OOF) {
7722 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7723 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7724 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7725 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7726 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7727 } else {
7728 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7729 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7732 oof_note = sipe_ews_get_oof_note(sip->ews);
7733 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7734 oof_start = sip->ews->oof_start;
7735 oof_end = sip->ews->oof_end;
7737 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7739 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7740 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7742 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7743 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7744 } else {
7745 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7746 pub_cal_working_hours ? pub_cal_working_hours : "",
7747 pub_cal_free_busy ? pub_cal_free_busy : "",
7748 pub_calendar ? pub_calendar : "",
7749 pub_calendar2 ? pub_calendar2 : "",
7750 pub_oof_note ? pub_oof_note : "");
7752 send_presence_publish(sip, publications);
7753 g_free(publications);
7756 g_free(pub_cal_working_hours);
7757 g_free(pub_cal_free_busy);
7758 g_free(pub_calendar);
7759 g_free(pub_calendar2);
7760 g_free(pub_oof_note);
7762 /* repeat scheduling */
7763 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7766 static void send_presence_status(struct sipe_account_data *sip)
7768 PurpleStatus * status = purple_account_get_active_status(sip->account);
7770 if (!status) return;
7772 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7773 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7774 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7776 if (sip->ocs2007) {
7777 send_presence_category_publish(sip);
7778 } else {
7779 send_presence_soap(sip, FALSE);
7783 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7785 gboolean found = FALSE;
7786 const char *method = msg->method ? msg->method : "NOT FOUND";
7787 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7788 if (msg->response == 0) { /* request */
7789 if (sipe_strequal(method, "MESSAGE")) {
7790 process_incoming_message(sip, msg);
7791 found = TRUE;
7792 } else if (sipe_strequal(method, "NOTIFY")) {
7793 purple_debug_info("sipe","send->process_incoming_notify\n");
7794 process_incoming_notify(sip, msg, TRUE, FALSE);
7795 found = TRUE;
7796 } else if (sipe_strequal(method, "BENOTIFY")) {
7797 purple_debug_info("sipe","send->process_incoming_benotify\n");
7798 process_incoming_notify(sip, msg, TRUE, TRUE);
7799 found = TRUE;
7800 } else if (sipe_strequal(method, "INVITE")) {
7801 process_incoming_invite(sip, msg);
7802 found = TRUE;
7803 } else if (sipe_strequal(method, "REFER")) {
7804 process_incoming_refer(sip, msg);
7805 found = TRUE;
7806 } else if (sipe_strequal(method, "OPTIONS")) {
7807 process_incoming_options(sip, msg);
7808 found = TRUE;
7809 } else if (sipe_strequal(method, "INFO")) {
7810 process_incoming_info(sip, msg);
7811 found = TRUE;
7812 } else if (sipe_strequal(method, "ACK")) {
7813 // ACK's don't need any response
7814 found = TRUE;
7815 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7816 // LCS 2005 sends us these - just respond 200 OK
7817 found = TRUE;
7818 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7819 } else if (sipe_strequal(method, "BYE")) {
7820 process_incoming_bye(sip, msg);
7821 found = TRUE;
7822 } else {
7823 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7825 } else { /* response */
7826 struct transaction *trans = transactions_find(sip, msg);
7827 if (trans) {
7828 if (msg->response == 407) {
7829 gchar *resend, *auth;
7830 const gchar *ptmp;
7832 if (sip->proxy.retries > 30) return;
7833 sip->proxy.retries++;
7834 /* do proxy authentication */
7836 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7838 fill_auth(ptmp, &sip->proxy);
7839 auth = auth_header(sip, &sip->proxy, trans->msg);
7840 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7841 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7842 g_free(auth);
7843 resend = sipmsg_to_string(trans->msg);
7844 /* resend request */
7845 sendout_pkt(sip->gc, resend);
7846 g_free(resend);
7847 } else {
7848 if (msg->response < 200) {
7849 /* ignore provisional response */
7850 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7851 } else {
7852 sip->proxy.retries = 0;
7853 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7854 if (msg->response == 401)
7856 sip->registrar.retries++;
7858 else
7860 sip->registrar.retries = 0;
7862 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7863 } else {
7864 if (msg->response == 401) {
7865 gchar *resend, *auth, *ptmp;
7866 const char* auth_scheme;
7868 if (sip->registrar.retries > 4) return;
7869 sip->registrar.retries++;
7871 auth_scheme = sipe_get_auth_scheme_name(sip);
7872 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7874 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7875 if (!ptmp) {
7876 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7877 sip->gc->wants_to_die = TRUE;
7878 purple_connection_error(sip->gc, tmp2);
7879 g_free(tmp2);
7880 return;
7883 fill_auth(ptmp, &sip->registrar);
7884 auth = auth_header(sip, &sip->registrar, trans->msg);
7885 sipmsg_remove_header_now(trans->msg, "Authorization");
7886 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7887 g_free(auth);
7888 resend = sipmsg_to_string(trans->msg);
7889 /* resend request */
7890 sendout_pkt(sip->gc, resend);
7891 g_free(resend);
7895 if (trans->callback) {
7896 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7897 /* call the callback to process response*/
7898 (trans->callback)(sip, msg, trans);
7901 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7902 transactions_remove(sip, trans);
7906 found = TRUE;
7907 } else {
7908 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7911 if (!found) {
7912 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7916 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7918 char *cur;
7919 char *dummy;
7920 char *tmp;
7921 struct sipmsg *msg;
7922 int restlen;
7923 cur = conn->inbuf;
7925 /* according to the RFC remove CRLF at the beginning */
7926 while (*cur == '\r' || *cur == '\n') {
7927 cur++;
7929 if (cur != conn->inbuf) {
7930 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7931 conn->inbufused = strlen(conn->inbuf);
7934 /* Received a full Header? */
7935 sip->processing_input = TRUE;
7936 while (sip->processing_input &&
7937 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7938 time_t currtime = time(NULL);
7939 cur += 2;
7940 cur[0] = '\0';
7941 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7942 g_free(tmp);
7943 msg = sipmsg_parse_header(conn->inbuf);
7944 cur[0] = '\r';
7945 cur += 2;
7946 restlen = conn->inbufused - (cur - conn->inbuf);
7947 if (msg && restlen >= msg->bodylen) {
7948 dummy = g_malloc(msg->bodylen + 1);
7949 memcpy(dummy, cur, msg->bodylen);
7950 dummy[msg->bodylen] = '\0';
7951 msg->body = dummy;
7952 cur += msg->bodylen;
7953 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7954 conn->inbufused = strlen(conn->inbuf);
7955 } else {
7956 if (msg){
7957 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7958 sipmsg_free(msg);
7960 return;
7963 /*if (msg->body) {
7964 purple_debug_info("sipe", "body:\n%s", msg->body);
7967 // Verify the signature before processing it
7968 if (sip->registrar.gssapi_context) {
7969 struct sipmsg_breakdown msgbd;
7970 gchar *signature_input_str;
7971 gchar *rspauth;
7972 msgbd.msg = msg;
7973 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7974 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
7976 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7978 if (rspauth != NULL) {
7979 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7980 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7981 process_input_message(sip, msg);
7982 } else {
7983 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7984 purple_connection_error(sip->gc, _("Invalid message signature received"));
7985 sip->gc->wants_to_die = TRUE;
7987 } else if (msg->response == 401) {
7988 purple_connection_error(sip->gc, _("Authentication failed"));
7989 sip->gc->wants_to_die = TRUE;
7991 g_free(signature_input_str);
7993 g_free(rspauth);
7994 sipmsg_breakdown_free(&msgbd);
7995 } else {
7996 process_input_message(sip, msg);
7999 sipmsg_free(msg);
8003 static void sipe_udp_process(gpointer data, gint source,
8004 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8006 PurpleConnection *gc = data;
8007 struct sipe_account_data *sip = gc->proto_data;
8008 int len;
8010 static char buffer[65536];
8011 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8012 time_t currtime = time(NULL);
8013 struct sipmsg *msg;
8014 buffer[len] = '\0';
8015 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
8016 msg = sipmsg_parse_msg(buffer);
8017 if (msg) process_input_message(sip, msg);
8021 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8023 struct sipe_account_data *sip = gc->proto_data;
8024 PurpleSslConnection *gsc = sip->gsc;
8026 purple_debug_error("sipe", "%s",debug);
8027 purple_connection_error(gc, msg);
8029 /* Invalidate this connection. Next send will open a new one */
8030 if (gsc) {
8031 connection_remove(sip, gsc->fd);
8032 purple_ssl_close(gsc);
8034 sip->gsc = NULL;
8035 sip->fd = -1;
8038 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8039 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8041 PurpleConnection *gc = data;
8042 struct sipe_account_data *sip;
8043 struct sip_connection *conn;
8044 int readlen, len;
8045 gboolean firstread = TRUE;
8047 /* NOTE: This check *IS* necessary */
8048 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8049 purple_ssl_close(gsc);
8050 return;
8053 sip = gc->proto_data;
8054 conn = connection_find(sip, gsc->fd);
8055 if (conn == NULL) {
8056 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
8057 gc->wants_to_die = TRUE;
8058 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8059 return;
8062 /* Read all available data from the SSL connection */
8063 do {
8064 /* Increase input buffer size as needed */
8065 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8066 conn->inbuflen += SIMPLE_BUF_INC;
8067 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8068 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
8071 /* Try to read as much as there is space left in the buffer */
8072 readlen = conn->inbuflen - conn->inbufused - 1;
8073 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8075 if (len < 0 && errno == EAGAIN) {
8076 /* Try again later */
8077 return;
8078 } else if (len < 0) {
8079 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8080 return;
8081 } else if (firstread && (len == 0)) {
8082 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8083 return;
8086 conn->inbufused += len;
8087 firstread = FALSE;
8089 /* Equivalence indicates that there is possibly more data to read */
8090 } while (len == readlen);
8092 conn->inbuf[conn->inbufused] = '\0';
8093 process_input(sip, conn);
8097 static void sipe_input_cb(gpointer data, gint source,
8098 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8100 PurpleConnection *gc = data;
8101 struct sipe_account_data *sip = gc->proto_data;
8102 int len;
8103 struct sip_connection *conn = connection_find(sip, source);
8104 if (!conn) {
8105 purple_debug_error("sipe", "Connection not found!\n");
8106 return;
8109 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8110 conn->inbuflen += SIMPLE_BUF_INC;
8111 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8114 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8116 if (len < 0 && errno == EAGAIN)
8117 return;
8118 else if (len <= 0) {
8119 purple_debug_info("sipe", "sipe_input_cb: read error\n");
8120 connection_remove(sip, source);
8121 if (sip->fd == source) sip->fd = -1;
8122 return;
8125 conn->inbufused += len;
8126 conn->inbuf[conn->inbufused] = '\0';
8128 process_input(sip, conn);
8131 /* Callback for new connections on incoming TCP port */
8132 static void sipe_newconn_cb(gpointer data, gint source,
8133 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8135 PurpleConnection *gc = data;
8136 struct sipe_account_data *sip = gc->proto_data;
8137 struct sip_connection *conn;
8139 int newfd = accept(source, NULL, NULL);
8141 conn = connection_create(sip, newfd);
8143 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8146 static void login_cb(gpointer data, gint source,
8147 SIPE_UNUSED_PARAMETER const gchar *error_message)
8149 PurpleConnection *gc = data;
8150 struct sipe_account_data *sip;
8151 struct sip_connection *conn;
8153 if (!PURPLE_CONNECTION_IS_VALID(gc))
8155 if (source >= 0)
8156 close(source);
8157 return;
8160 if (source < 0) {
8161 purple_connection_error(gc, _("Could not connect"));
8162 return;
8165 sip = gc->proto_data;
8166 sip->fd = source;
8167 sip->last_keepalive = time(NULL);
8169 conn = connection_create(sip, source);
8171 do_register(sip);
8173 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8176 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8177 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8179 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8180 if (sip == NULL) return;
8182 do_register(sip);
8185 static guint sipe_ht_hash_nick(const char *nick)
8187 char *lc = g_utf8_strdown(nick, -1);
8188 guint bucket = g_str_hash(lc);
8189 g_free(lc);
8191 return bucket;
8194 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8196 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8199 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8201 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8203 sip->listen_data = NULL;
8205 if (listenfd == -1) {
8206 purple_connection_error(sip->gc, _("Could not create listen socket"));
8207 return;
8210 sip->fd = listenfd;
8212 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8213 sip->listenfd = sip->fd;
8215 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8217 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8218 do_register(sip);
8221 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8222 SIPE_UNUSED_PARAMETER const char *error_message)
8224 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8226 sip->query_data = NULL;
8228 if (!hosts || !hosts->data) {
8229 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8230 return;
8233 hosts = g_slist_remove(hosts, hosts->data);
8234 g_free(sip->serveraddr);
8235 sip->serveraddr = hosts->data;
8236 hosts = g_slist_remove(hosts, hosts->data);
8237 while (hosts) {
8238 void *tmp = hosts->data;
8239 hosts = g_slist_remove(hosts, tmp);
8240 hosts = g_slist_remove(hosts, tmp);
8241 g_free(tmp);
8244 /* create socket for incoming connections */
8245 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8246 sipe_udp_host_resolved_listen_cb, sip);
8247 if (sip->listen_data == NULL) {
8248 purple_connection_error(sip->gc, _("Could not create listen socket"));
8249 return;
8253 static const struct sipe_service_data *current_service = NULL;
8255 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8256 PurpleSslErrorType error,
8257 gpointer data)
8259 PurpleConnection *gc = data;
8260 struct sipe_account_data *sip;
8262 /* If the connection is already disconnected, we don't need to do anything else */
8263 if (!PURPLE_CONNECTION_IS_VALID(gc))
8264 return;
8266 sip = gc->proto_data;
8267 current_service = sip->service_data;
8268 if (current_service) {
8269 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8270 current_service->transport ? current_service->transport : "NULL",
8271 current_service->service ? current_service->service : "NULL");
8274 sip->fd = -1;
8275 sip->gsc = NULL;
8277 switch(error) {
8278 case PURPLE_SSL_CONNECT_FAILED:
8279 purple_connection_error(gc, _("Connection failed"));
8280 break;
8281 case PURPLE_SSL_HANDSHAKE_FAILED:
8282 purple_connection_error(gc, _("SSL handshake failed"));
8283 break;
8284 case PURPLE_SSL_CERTIFICATE_INVALID:
8285 purple_connection_error(gc, _("SSL certificate invalid"));
8286 break;
8290 static void
8291 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8293 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8294 PurpleProxyConnectData *connect_data;
8296 sip->listen_data = NULL;
8298 sip->listenfd = listenfd;
8299 if (sip->listenfd == -1) {
8300 purple_connection_error(sip->gc, _("Could not create listen socket"));
8301 return;
8304 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8305 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8306 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8307 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8308 sipe_newconn_cb, sip->gc);
8309 purple_debug_info("sipe", "connecting to %s port %d\n",
8310 sip->realhostname, sip->realport);
8311 /* open tcp connection to the server */
8312 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8313 sip->realport, login_cb, sip->gc);
8315 if (connect_data == NULL) {
8316 purple_connection_error(sip->gc, _("Could not create socket"));
8320 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8322 PurpleAccount *account = sip->account;
8323 PurpleConnection *gc = sip->gc;
8325 if (port == 0) {
8326 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8329 sip->realhostname = hostname;
8330 sip->realport = port;
8332 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8333 hostname, port);
8335 /* TODO: is there a good default grow size? */
8336 if (sip->transport != SIPE_TRANSPORT_UDP)
8337 sip->txbuf = purple_circ_buffer_new(0);
8339 if (sip->transport == SIPE_TRANSPORT_TLS) {
8340 /* SSL case */
8341 if (!purple_ssl_is_supported()) {
8342 gc->wants_to_die = TRUE;
8343 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8344 return;
8347 purple_debug_info("sipe", "using SSL\n");
8349 sip->gsc = purple_ssl_connect(account, hostname, port,
8350 login_cb_ssl, sipe_ssl_connect_failure, gc);
8351 if (sip->gsc == NULL) {
8352 purple_connection_error(gc, _("Could not create SSL context"));
8353 return;
8355 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8356 /* UDP case */
8357 purple_debug_info("sipe", "using UDP\n");
8359 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8360 if (sip->query_data == NULL) {
8361 purple_connection_error(gc, _("Could not resolve hostname"));
8363 } else {
8364 /* TCP case */
8365 purple_debug_info("sipe", "using TCP\n");
8366 /* create socket for incoming connections */
8367 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8368 sipe_tcp_connect_listen_cb, sip);
8369 if (sip->listen_data == NULL) {
8370 purple_connection_error(gc, _("Could not create listen socket"));
8371 return;
8376 /* Service list for autodection */
8377 static const struct sipe_service_data service_autodetect[] = {
8378 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8379 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8380 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8381 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8382 { NULL, NULL, 0 }
8385 /* Service list for SSL/TLS */
8386 static const struct sipe_service_data service_tls[] = {
8387 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8388 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8389 { NULL, NULL, 0 }
8392 /* Service list for TCP */
8393 static const struct sipe_service_data service_tcp[] = {
8394 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8395 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8396 { NULL, NULL, 0 }
8399 /* Service list for UDP */
8400 static const struct sipe_service_data service_udp[] = {
8401 { "sip", "udp", SIPE_TRANSPORT_UDP },
8402 { NULL, NULL, 0 }
8405 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8406 static void resolve_next_service(struct sipe_account_data *sip,
8407 const struct sipe_service_data *start)
8409 if (start) {
8410 sip->service_data = start;
8411 } else {
8412 sip->service_data++;
8413 if (sip->service_data->service == NULL) {
8414 gchar *hostname;
8415 /* Try connecting to the SIP hostname directly */
8416 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8417 if (sip->auto_transport) {
8418 // If SSL is supported, default to using it; OCS servers aren't configured
8419 // by default to accept TCP
8420 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8421 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8422 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8425 hostname = g_strdup(sip->sipdomain);
8426 create_connection(sip, hostname, 0);
8427 return;
8431 /* Try to resolve next service */
8432 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8433 sip->service_data->transport,
8434 sip->sipdomain,
8435 srvresolved, sip);
8438 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8440 struct sipe_account_data *sip = data;
8442 sip->srv_query_data = NULL;
8444 /* find the host to connect to */
8445 if (results) {
8446 gchar *hostname = g_strdup(resp->hostname);
8447 int port = resp->port;
8448 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8449 hostname, port);
8450 g_free(resp);
8452 sip->transport = sip->service_data->type;
8454 create_connection(sip, hostname, port);
8455 } else {
8456 resolve_next_service(sip, NULL);
8460 static void sipe_login(PurpleAccount *account)
8462 PurpleConnection *gc;
8463 struct sipe_account_data *sip;
8464 gchar **signinname_login, **userserver;
8465 const char *transport;
8466 const char *email;
8468 const char *username = purple_account_get_username(account);
8469 gc = purple_account_get_connection(account);
8471 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8473 if (strpbrk(username, "\t\v\r\n") != NULL) {
8474 gc->wants_to_die = TRUE;
8475 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8476 return;
8479 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8480 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8481 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8482 sip->gc = gc;
8483 sip->account = account;
8484 sip->reregister_set = FALSE;
8485 sip->reauthenticate_set = FALSE;
8486 sip->subscribed = FALSE;
8487 sip->subscribed_buddies = FALSE;
8488 sip->initial_state_published = FALSE;
8490 /* username format: <username>,[<optional login>] */
8491 signinname_login = g_strsplit(username, ",", 2);
8492 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8494 /* ensure that username format is name@domain */
8495 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8496 g_strfreev(signinname_login);
8497 gc->wants_to_die = TRUE;
8498 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8499 return;
8501 sip->username = g_strdup(signinname_login[0]);
8503 /* ensure that email format is name@domain if provided */
8504 email = purple_account_get_string(sip->account, "email", NULL);
8505 if (!is_empty(email) &&
8506 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8508 gc->wants_to_die = TRUE;
8509 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8510 return;
8512 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8514 /* login name specified? */
8515 if (signinname_login[1] && strlen(signinname_login[1])) {
8516 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8517 gboolean has_domain = domain_user[1] != NULL;
8518 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8519 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8520 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8521 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8522 sip->authdomain ? sip->authdomain : "", sip->authuser);
8523 g_strfreev(domain_user);
8526 userserver = g_strsplit(signinname_login[0], "@", 2);
8527 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8528 purple_connection_set_display_name(gc, userserver[0]);
8529 sip->sipdomain = g_strdup(userserver[1]);
8530 g_strfreev(userserver);
8531 g_strfreev(signinname_login);
8533 if (strchr(sip->username, ' ') != NULL) {
8534 gc->wants_to_die = TRUE;
8535 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8536 return;
8539 sip->password = g_strdup(purple_connection_get_password(gc));
8541 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8542 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8543 g_free, (GDestroyNotify)g_hash_table_destroy);
8544 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8545 g_free, (GDestroyNotify)sipe_subscription_free);
8547 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8549 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8551 g_free(sip->status);
8552 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8554 sip->auto_transport = FALSE;
8555 transport = purple_account_get_string(account, "transport", "auto");
8556 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8557 if (userserver[0]) {
8558 /* Use user specified server[:port] */
8559 int port = 0;
8561 if (userserver[1])
8562 port = atoi(userserver[1]);
8564 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8565 userserver[0], port);
8567 if (sipe_strequal(transport, "auto")) {
8568 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8569 } else if (sipe_strequal(transport, "tls")) {
8570 sip->transport = SIPE_TRANSPORT_TLS;
8571 } else if (sipe_strequal(transport, "tcp")) {
8572 sip->transport = SIPE_TRANSPORT_TCP;
8573 } else {
8574 sip->transport = SIPE_TRANSPORT_UDP;
8577 create_connection(sip, g_strdup(userserver[0]), port);
8578 } else {
8579 /* Server auto-discovery */
8580 if (sipe_strequal(transport, "auto")) {
8581 sip->auto_transport = TRUE;
8582 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8583 current_service++;
8584 resolve_next_service(sip, current_service);
8585 } else {
8586 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8588 } else if (sipe_strequal(transport, "tls")) {
8589 resolve_next_service(sip, service_tls);
8590 } else if (sipe_strequal(transport, "tcp")) {
8591 resolve_next_service(sip, service_tcp);
8592 } else {
8593 resolve_next_service(sip, service_udp);
8596 g_strfreev(userserver);
8599 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8601 connection_free_all(sip);
8603 g_free(sip->epid);
8604 sip->epid = NULL;
8606 if (sip->query_data != NULL)
8607 purple_dnsquery_destroy(sip->query_data);
8608 sip->query_data = NULL;
8610 if (sip->srv_query_data != NULL)
8611 purple_srv_cancel(sip->srv_query_data);
8612 sip->srv_query_data = NULL;
8614 if (sip->listen_data != NULL)
8615 purple_network_listen_cancel(sip->listen_data);
8616 sip->listen_data = NULL;
8618 if (sip->gsc != NULL)
8619 purple_ssl_close(sip->gsc);
8620 sip->gsc = NULL;
8622 sipe_auth_free(&sip->registrar);
8623 sipe_auth_free(&sip->proxy);
8625 if (sip->txbuf)
8626 purple_circ_buffer_destroy(sip->txbuf);
8627 sip->txbuf = NULL;
8629 g_free(sip->realhostname);
8630 sip->realhostname = NULL;
8632 g_free(sip->server_version);
8633 sip->server_version = NULL;
8635 if (sip->listenpa)
8636 purple_input_remove(sip->listenpa);
8637 sip->listenpa = 0;
8638 if (sip->tx_handler)
8639 purple_input_remove(sip->tx_handler);
8640 sip->tx_handler = 0;
8641 if (sip->resendtimeout)
8642 purple_timeout_remove(sip->resendtimeout);
8643 sip->resendtimeout = 0;
8644 if (sip->timeouts) {
8645 GSList *entry = sip->timeouts;
8646 while (entry) {
8647 struct scheduled_action *sched_action = entry->data;
8648 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8649 purple_timeout_remove(sched_action->timeout_handler);
8650 if (sched_action->destroy) {
8651 (*sched_action->destroy)(sched_action->payload);
8653 g_free(sched_action->name);
8654 g_free(sched_action);
8655 entry = entry->next;
8658 g_slist_free(sip->timeouts);
8660 if (sip->allow_events) {
8661 GSList *entry = sip->allow_events;
8662 while (entry) {
8663 g_free(entry->data);
8664 entry = entry->next;
8667 g_slist_free(sip->allow_events);
8669 if (sip->containers) {
8670 GSList *entry = sip->containers;
8671 while (entry) {
8672 free_container((struct sipe_container *)entry->data);
8673 entry = entry->next;
8676 g_slist_free(sip->containers);
8678 if (sip->contact)
8679 g_free(sip->contact);
8680 sip->contact = NULL;
8681 if (sip->regcallid)
8682 g_free(sip->regcallid);
8683 sip->regcallid = NULL;
8685 if (sip->serveraddr)
8686 g_free(sip->serveraddr);
8687 sip->serveraddr = NULL;
8689 if (sip->focus_factory_uri)
8690 g_free(sip->focus_factory_uri);
8691 sip->focus_factory_uri = NULL;
8693 sip->fd = -1;
8694 sip->processing_input = FALSE;
8696 if (sip->ews) {
8697 sipe_ews_free(sip->ews);
8699 sip->ews = NULL;
8703 * A callback for g_hash_table_foreach_remove
8705 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8706 SIPE_UNUSED_PARAMETER gpointer user_data)
8708 sipe_free_buddy((struct sipe_buddy *) buddy);
8710 /* We must return TRUE as the key/value have already been deleted */
8711 return(TRUE);
8714 static void sipe_close(PurpleConnection *gc)
8716 struct sipe_account_data *sip = gc->proto_data;
8718 if (sip) {
8719 /* leave all conversations */
8720 sipe_session_close_all(sip);
8721 sipe_session_remove_all(sip);
8723 if (sip->csta) {
8724 sip_csta_close(sip);
8727 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8728 /* unsubscribe all */
8729 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8731 /* unregister */
8732 do_register_exp(sip, 0);
8735 sipe_connection_cleanup(sip);
8736 g_free(sip->sipdomain);
8737 g_free(sip->username);
8738 g_free(sip->email);
8739 g_free(sip->password);
8740 g_free(sip->authdomain);
8741 g_free(sip->authuser);
8742 g_free(sip->status);
8743 g_free(sip->note);
8745 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8746 g_hash_table_destroy(sip->buddies);
8747 g_hash_table_destroy(sip->our_publications);
8748 g_hash_table_destroy(sip->user_state_publications);
8749 g_hash_table_destroy(sip->subscriptions);
8750 g_hash_table_destroy(sip->filetransfers);
8752 if (sip->groups) {
8753 GSList *entry = sip->groups;
8754 while (entry) {
8755 struct sipe_group *group = entry->data;
8756 g_free(group->name);
8757 g_free(group);
8758 entry = entry->next;
8761 g_slist_free(sip->groups);
8763 if (sip->our_publication_keys) {
8764 GSList *entry = sip->our_publication_keys;
8765 while (entry) {
8766 g_free(entry->data);
8767 entry = entry->next;
8770 g_slist_free(sip->our_publication_keys);
8772 while (sip->transactions)
8773 transactions_remove(sip, sip->transactions->data);
8775 g_free(gc->proto_data);
8776 gc->proto_data = NULL;
8779 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8780 SIPE_UNUSED_PARAMETER void *user_data)
8782 PurpleAccount *acct = purple_connection_get_account(gc);
8783 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8784 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8785 if (conv == NULL)
8786 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8787 purple_conversation_present(conv);
8788 g_free(id);
8791 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8792 SIPE_UNUSED_PARAMETER void *user_data)
8795 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8796 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8799 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8800 SIPE_UNUSED_PARAMETER struct transaction *trans)
8802 PurpleNotifySearchResults *results;
8803 PurpleNotifySearchColumn *column;
8804 xmlnode *searchResults;
8805 xmlnode *mrow;
8806 int match_count = 0;
8807 gboolean more = FALSE;
8808 gchar *secondary;
8810 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8812 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8813 if (!searchResults) {
8814 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8815 return FALSE;
8818 results = purple_notify_searchresults_new();
8820 if (results == NULL) {
8821 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8822 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8824 xmlnode_free(searchResults);
8825 return FALSE;
8828 column = purple_notify_searchresults_column_new(_("User name"));
8829 purple_notify_searchresults_column_add(results, column);
8831 column = purple_notify_searchresults_column_new(_("Name"));
8832 purple_notify_searchresults_column_add(results, column);
8834 column = purple_notify_searchresults_column_new(_("Company"));
8835 purple_notify_searchresults_column_add(results, column);
8837 column = purple_notify_searchresults_column_new(_("Country"));
8838 purple_notify_searchresults_column_add(results, column);
8840 column = purple_notify_searchresults_column_new(_("Email"));
8841 purple_notify_searchresults_column_add(results, column);
8843 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8844 GList *row = NULL;
8846 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8847 row = g_list_append(row, g_strdup(uri_parts[1]));
8848 g_strfreev(uri_parts);
8850 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8851 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8852 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8853 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8855 purple_notify_searchresults_row_add(results, row);
8856 match_count++;
8859 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8860 char *data = xmlnode_get_data_unescaped(mrow);
8861 more = (g_strcasecmp(data, "true") == 0);
8862 g_free(data);
8865 secondary = g_strdup_printf(
8866 dngettext(GETTEXT_PACKAGE,
8867 "Found %d contact%s:",
8868 "Found %d contacts%s:", match_count),
8869 match_count, more ? _(" (more matched your query)") : "");
8871 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8872 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8873 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8875 g_free(secondary);
8876 xmlnode_free(searchResults);
8877 return TRUE;
8880 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8882 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8883 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8884 unsigned i = 0;
8886 if (!attrs) return;
8888 do {
8889 PurpleRequestField *field = entries->data;
8890 const char *id = purple_request_field_get_id(field);
8891 const char *value = purple_request_field_string_get_value(field);
8893 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8895 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8896 } while ((entries = g_list_next(entries)) != NULL);
8897 attrs[i] = NULL;
8899 if (i > 0) {
8900 struct sipe_account_data *sip = gc->proto_data;
8901 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8902 gchar *query = g_strjoinv(NULL, attrs);
8903 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8904 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8905 send_soap_request_with_cb(sip, domain_uri, body,
8906 (TransCallback) process_search_contact_response, NULL);
8907 g_free(domain_uri);
8908 g_free(body);
8909 g_free(query);
8912 g_strfreev(attrs);
8915 static void sipe_show_find_contact(PurplePluginAction *action)
8917 PurpleConnection *gc = (PurpleConnection *) action->context;
8918 PurpleRequestFields *fields;
8919 PurpleRequestFieldGroup *group;
8920 PurpleRequestField *field;
8922 fields = purple_request_fields_new();
8923 group = purple_request_field_group_new(NULL);
8924 purple_request_fields_add_group(fields, group);
8926 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8927 purple_request_field_group_add_field(group, field);
8928 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8929 purple_request_field_group_add_field(group, field);
8930 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8931 purple_request_field_group_add_field(group, field);
8932 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8933 purple_request_field_group_add_field(group, field);
8935 purple_request_fields(gc,
8936 _("Search"),
8937 _("Search for a contact"),
8938 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8939 fields,
8940 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8941 _("_Cancel"), NULL,
8942 purple_connection_get_account(gc), NULL, NULL, gc);
8945 static void sipe_show_about_plugin(PurplePluginAction *action)
8947 PurpleConnection *gc = (PurpleConnection *) action->context;
8948 char *tmp = g_strdup_printf(
8950 * Non-translatable parts, like markup, are hard-coded
8951 * into the format string. This requires more translatable
8952 * texts but it makes the translations less error prone.
8954 "<b><font size=\"+1\">SIPE " SIPE_VERSION " </font></b><br/>"
8955 "<br/>"
8956 /* 1 */ "%s:<br/>"
8957 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8958 "<li> - MS Office Communications Server 2007</li><br/>"
8959 "<li> - MS Live Communications Server 2005</li><br/>"
8960 "<li> - MS Live Communications Server 2003</li><br/>"
8961 "<li> - Reuters Messaging</li><br/>"
8962 "<br/>"
8963 /* 2 */ "%s: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8964 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8965 /* 5 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8966 /* 6 */ "%s: GPLv2+<br/>"
8967 "<br/>"
8968 /* 7 */ "%s:<br/>"
8969 " - CERN<br/>"
8970 " - Reuters Messaging network<br/>"
8971 " - Deutsche Bank<br/>"
8972 " - Merrill Lynch<br/>"
8973 " - Wachovia<br/>"
8974 " - Intel<br/>"
8975 " - Nokia<br/>"
8976 " - HP<br/>"
8977 " - Symantec<br/>"
8978 " - Accenture<br/>"
8979 " - Siemens<br/>"
8980 " - Alcatel-Lucent<br/>"
8981 " - BT<br/>"
8982 "<br/>"
8983 /* 8,9 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
8984 "<br/>"
8985 /* 10 */ "<b>%s:</b><br/>"
8986 " - Anibal Avelar<br/>"
8987 " - Gabriel Burt<br/>"
8988 " - Stefan Becker<br/>"
8989 " - pier11<br/>"
8990 " - Jakub Adam<br/>"
8991 " - Tomáš Hrabčík<br/>"
8992 "<br/>"
8993 /* 11 */ "%s<br/>"
8995 /* The next 11 texts make up the SIPE about note text */
8996 /* About note, part 1/11: introduction */
8997 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
8998 /* About note, part 2/11: home page URL (label) */
8999 _("Home"),
9000 /* About note, part 3/11: support forum URL (label) */
9001 _("Support"),
9002 /* About note, part 4/11: support forum name (hyperlink text) */
9003 _("Help Forum"),
9004 /* About note, part 5/11: translation service URL (label) */
9005 _("Translations"),
9006 /* About note, part 6/11: license type (label) */
9007 _("License"),
9008 /* About note, part 7/11: known users */
9009 _("We support users in such organizations as"),
9010 /* About note, part 8/11: translation request, text before Transifex.net URL */
9011 /* append a space if text is not empty */
9012 _("Please help us to translate SIPE to your native language here at "),
9013 /* About note, part 9/11: translation request, text after Transifex.net URL */
9014 /* start with a space if text is not empty */
9015 _(" using convenient web interface"),
9016 /* About note, part 10/11: author list (header) */
9017 _("Authors"),
9018 /* About note, part 11/11: Localization credit */
9019 /* PLEASE NOTE: do *NOT* simply translate the english original */
9020 /* but write something similar to the following sentence: */
9021 /* "Localization for <language name> (<language code>): <name>" */
9022 _("Original texts in English (en): SIPE developers")
9024 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9025 g_free(tmp);
9028 static void sipe_republish_calendar(PurplePluginAction *action)
9030 PurpleConnection *gc = (PurpleConnection *) action->context;
9031 struct sipe_account_data *sip = gc->proto_data;
9033 sipe_update_calendar(sip);
9036 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9037 gpointer value,
9038 GString* str)
9040 struct sipe_publication *publication = value;
9042 g_string_append_printf( str,
9043 SIPE_PUB_XML_PUBLICATION_CLEAR,
9044 publication->category,
9045 publication->instance,
9046 publication->container,
9047 publication->version,
9048 "static");
9051 static void sipe_reset_status(PurplePluginAction *action)
9053 PurpleConnection *gc = (PurpleConnection *) action->context;
9054 struct sipe_account_data *sip = gc->proto_data;
9056 if (sip->ocs2007) /* 2007+ */
9058 GString* str = g_string_new(NULL);
9059 gchar *publications;
9061 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9062 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
9063 return;
9066 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9067 publications = g_string_free(str, FALSE);
9069 send_presence_publish(sip, publications);
9070 g_free(publications);
9072 else /* 2005 */
9074 send_presence_soap0(sip, FALSE, TRUE);
9078 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9079 gpointer context)
9081 PurpleConnection *gc = (PurpleConnection *)context;
9082 struct sipe_account_data *sip = gc->proto_data;
9083 GList *menu = NULL;
9084 PurplePluginAction *act;
9085 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9087 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9088 menu = g_list_prepend(menu, act);
9090 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9091 menu = g_list_prepend(menu, act);
9093 if (sipe_strequal(calendar, "EXCH")) {
9094 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9095 menu = g_list_prepend(menu, act);
9098 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9099 menu = g_list_prepend(menu, act);
9101 menu = g_list_reverse(menu);
9103 return menu;
9106 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9110 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9112 return TRUE;
9116 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9118 return TRUE;
9122 static char *sipe_status_text(PurpleBuddy *buddy)
9124 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9125 const PurpleStatus *status = purple_presence_get_active_status(presence);
9126 const char *status_id = purple_status_get_id(status);
9127 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9128 struct sipe_buddy *sbuddy;
9129 char *text = NULL;
9131 if (!sip) return NULL; /* happens on pidgin exit */
9133 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9134 if (sbuddy) {
9135 const char *activity_str = sbuddy->activity ?
9136 sbuddy->activity :
9137 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9138 purple_status_get_name(status) : NULL;
9140 if (activity_str && sbuddy->note)
9142 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9144 else if (activity_str)
9146 text = g_strdup(activity_str);
9148 else if (sbuddy->note)
9150 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9154 return text;
9157 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9159 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9160 const PurpleStatus *status = purple_presence_get_active_status(presence);
9161 struct sipe_account_data *sip;
9162 struct sipe_buddy *sbuddy;
9163 char *note = NULL;
9164 gboolean is_oof_note = FALSE;
9165 char *activity = NULL;
9166 char *calendar = NULL;
9167 char *meeting_subject = NULL;
9168 char *meeting_location = NULL;
9170 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9171 if (sip) //happens on pidgin exit
9173 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9174 if (sbuddy)
9176 note = sbuddy->note;
9177 is_oof_note = sbuddy->is_oof_note;
9178 activity = sbuddy->activity;
9179 calendar = sipe_cal_get_description(sbuddy);
9180 meeting_subject = sbuddy->meeting_subject;
9181 meeting_location = sbuddy->meeting_location;
9185 //Layout
9186 if (purple_presence_is_online(presence))
9188 const char *status_str = activity ? activity : purple_status_get_name(status);
9190 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9192 if (purple_presence_is_online(presence) &&
9193 !is_empty(calendar))
9195 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9197 g_free(calendar);
9198 if (!is_empty(meeting_location))
9200 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9202 if (!is_empty(meeting_subject))
9204 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9207 if (note)
9209 char *tmp = g_strdup_printf("<i>%s</i>", note);
9210 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9212 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9213 g_free(tmp);
9218 #if PURPLE_VERSION_CHECK(2,5,0)
9219 static GHashTable *
9220 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9222 GHashTable *table;
9223 table = g_hash_table_new(g_str_hash, g_str_equal);
9224 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9225 return table;
9227 #endif
9229 static PurpleBuddy *
9230 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9232 PurpleBuddy *clone;
9233 const gchar *server_alias, *email;
9234 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9236 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9238 purple_blist_add_buddy(clone, NULL, group, NULL);
9240 server_alias = purple_buddy_get_server_alias(buddy);
9241 if (server_alias) {
9242 purple_blist_server_alias_buddy(clone, server_alias);
9245 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9246 if (email) {
9247 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9250 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9251 //for UI to update;
9252 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9253 return clone;
9256 static void
9257 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9259 PurpleBuddy *buddy, *b;
9260 PurpleConnection *gc;
9261 PurpleGroup * group = purple_find_group(group_name);
9263 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9265 buddy = (PurpleBuddy *)node;
9267 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9268 gc = purple_account_get_connection(buddy->account);
9270 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9271 if (!b){
9272 purple_blist_add_buddy_clone(group, buddy);
9275 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9278 static void
9279 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9281 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9283 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9285 /* 2007+ conference */
9286 if (sip->ocs2007)
9288 sipe_conf_add(sip, buddy->name);
9290 else /* 2005- multiparty chat */
9292 gchar *self = sip_uri_self(sip);
9293 struct sip_session *session;
9295 session = sipe_session_add_chat(sip);
9296 session->chat_title = sipe_chat_get_name(session->callid);
9297 session->roster_manager = g_strdup(self);
9299 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9300 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9301 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9302 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9304 g_free(self);
9308 static gboolean
9309 sipe_is_election_finished(struct sip_session *session)
9311 gboolean res = TRUE;
9313 SIPE_DIALOG_FOREACH {
9314 if (dialog->election_vote == 0) {
9315 res = FALSE;
9316 break;
9318 } SIPE_DIALOG_FOREACH_END;
9320 if (res) {
9321 session->is_voting_in_progress = FALSE;
9323 return res;
9326 static void
9327 sipe_election_start(struct sipe_account_data *sip,
9328 struct sip_session *session)
9330 int election_timeout;
9332 if (session->is_voting_in_progress) {
9333 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9334 return;
9335 } else {
9336 session->is_voting_in_progress = TRUE;
9338 session->bid = rand();
9340 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9342 SIPE_DIALOG_FOREACH {
9343 /* reset election_vote for each chat participant */
9344 dialog->election_vote = 0;
9346 /* send RequestRM to each chat participant*/
9347 sipe_send_election_request_rm(sip, dialog, session->bid);
9348 } SIPE_DIALOG_FOREACH_END;
9350 election_timeout = 15; /* sec */
9351 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9355 * @param who a URI to whom to invite to chat
9357 void
9358 sipe_invite_to_chat(struct sipe_account_data *sip,
9359 struct sip_session *session,
9360 const gchar *who)
9362 /* a conference */
9363 if (session->focus_uri)
9365 sipe_invite_conf(sip, session, who);
9367 else /* a multi-party chat */
9369 gchar *self = sip_uri_self(sip);
9370 if (session->roster_manager) {
9371 if (sipe_strequal(session->roster_manager, self)) {
9372 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9373 } else {
9374 sipe_refer(sip, session, who);
9376 } else {
9377 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9379 session->pending_invite_queue = slist_insert_unique_sorted(
9380 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9382 sipe_election_start(sip, session);
9384 g_free(self);
9388 void
9389 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9390 struct sip_session *session)
9392 gchar *invitee;
9393 GSList *entry = session->pending_invite_queue;
9395 while (entry) {
9396 invitee = entry->data;
9397 sipe_invite_to_chat(sip, session, invitee);
9398 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9399 g_free(invitee);
9403 static void
9404 sipe_election_result(struct sipe_account_data *sip,
9405 void *sess)
9407 struct sip_session *session = (struct sip_session *)sess;
9408 gchar *rival;
9409 gboolean has_won = TRUE;
9411 if (session->roster_manager) {
9412 purple_debug_info("sipe",
9413 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9414 return;
9417 session->is_voting_in_progress = FALSE;
9419 SIPE_DIALOG_FOREACH {
9420 if (dialog->election_vote < 0) {
9421 has_won = FALSE;
9422 rival = dialog->with;
9423 break;
9425 } SIPE_DIALOG_FOREACH_END;
9427 if (has_won) {
9428 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9430 session->roster_manager = sip_uri_self(sip);
9432 SIPE_DIALOG_FOREACH {
9433 /* send SetRM to each chat participant*/
9434 sipe_send_election_set_rm(sip, dialog);
9435 } SIPE_DIALOG_FOREACH_END;
9436 } else {
9437 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9439 session->bid = 0;
9441 sipe_process_pending_invite_queue(sip, session);
9445 * For 2007+ conference only.
9447 static void
9448 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9450 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9451 struct sip_session *session;
9453 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9454 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9456 session = sipe_session_find_chat_by_title(sip, chat_title);
9458 sipe_conf_modify_user_role(sip, session, buddy->name);
9462 * For 2007+ conference only.
9464 static void
9465 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9467 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9468 struct sip_session *session;
9470 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9471 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9473 session = sipe_session_find_chat_by_title(sip, chat_title);
9475 sipe_conf_delete_user(sip, session, buddy->name);
9478 static void
9479 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9481 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9482 struct sip_session *session;
9484 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9485 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9487 session = sipe_session_find_chat_by_title(sip, chat_title);
9489 sipe_invite_to_chat(sip, session, buddy->name);
9492 static void
9493 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9495 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9497 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9498 if (phone) {
9499 char *tel_uri = sip_to_tel_uri(phone);
9501 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9502 sip_csta_make_call(sip, tel_uri);
9504 g_free(tel_uri);
9508 static void
9509 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9511 const gchar *email;
9512 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9514 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9515 if (email)
9517 char *mailto = g_strdup_printf("mailto:%s", email);
9518 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9519 #ifndef _WIN32
9521 pid_t pid;
9522 char *const parmList[] = {"xdg-email", mailto, NULL};
9523 if ((pid = fork()) == -1)
9525 purple_debug_info("sipe", "fork() error\n");
9527 else if (pid == 0)
9529 execvp(parmList[0], parmList);
9530 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9533 #else
9535 BOOL ret;
9536 _flushall();
9537 errno = 0;
9538 //@TODO resolve env variable %WINDIR% first
9539 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9540 if (errno)
9542 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9545 #endif
9547 g_free(mailto);
9549 else
9551 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9556 * A menu which appear when right-clicking on buddy in contact list.
9558 static GList *
9559 sipe_buddy_menu(PurpleBuddy *buddy)
9561 PurpleBlistNode *g_node;
9562 PurpleGroup *group, *gr_parent;
9563 PurpleMenuAction *act;
9564 GList *menu = NULL;
9565 GList *menu_groups = NULL;
9566 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9567 const char *email;
9568 const char *phone;
9569 const char *phone_disp_str;
9570 gchar *self = sip_uri_self(sip);
9572 SIPE_SESSION_FOREACH {
9573 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9575 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9577 PurpleConvChatBuddyFlags flags;
9578 PurpleConvChatBuddyFlags flags_us;
9580 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9581 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9582 if (session->focus_uri
9583 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9584 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9586 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9587 act = purple_menu_action_new(label,
9588 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9589 session->chat_title, NULL);
9590 g_free(label);
9591 menu = g_list_prepend(menu, act);
9594 if (session->focus_uri
9595 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9597 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9598 act = purple_menu_action_new(label,
9599 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9600 session->chat_title, NULL);
9601 g_free(label);
9602 menu = g_list_prepend(menu, act);
9605 else
9607 if (!session->focus_uri
9608 || (session->focus_uri && !session->locked))
9610 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9611 act = purple_menu_action_new(label,
9612 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9613 session->chat_title, NULL);
9614 g_free(label);
9615 menu = g_list_prepend(menu, act);
9619 } SIPE_SESSION_FOREACH_END;
9621 act = purple_menu_action_new(_("New chat"),
9622 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9623 NULL, NULL);
9624 menu = g_list_prepend(menu, act);
9626 if (sip->csta && !sip->csta->line_status) {
9627 gchar *tmp = NULL;
9628 /* work phone */
9629 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9630 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9631 if (phone) {
9632 gchar *label = g_strdup_printf(_("Work %s"),
9633 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9634 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9635 g_free(tmp);
9636 tmp = NULL;
9637 g_free(label);
9638 menu = g_list_prepend(menu, act);
9641 /* mobile phone */
9642 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9643 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9644 if (phone) {
9645 gchar *label = g_strdup_printf(_("Mobile %s"),
9646 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9647 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9648 g_free(tmp);
9649 tmp = NULL;
9650 g_free(label);
9651 menu = g_list_prepend(menu, act);
9654 /* home phone */
9655 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9656 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9657 if (phone) {
9658 gchar *label = g_strdup_printf(_("Home %s"),
9659 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9660 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9661 g_free(tmp);
9662 tmp = NULL;
9663 g_free(label);
9664 menu = g_list_prepend(menu, act);
9667 /* other phone */
9668 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9669 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9670 if (phone) {
9671 gchar *label = g_strdup_printf(_("Other %s"),
9672 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9673 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9674 g_free(tmp);
9675 tmp = NULL;
9676 g_free(label);
9677 menu = g_list_prepend(menu, act);
9680 /* custom1 phone */
9681 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9682 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9683 if (phone) {
9684 gchar *label = g_strdup_printf(_("Custom1 %s"),
9685 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9686 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9687 g_free(tmp);
9688 tmp = NULL;
9689 g_free(label);
9690 menu = g_list_prepend(menu, act);
9694 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9695 if (email) {
9696 act = purple_menu_action_new(_("Send email..."),
9697 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9698 NULL, NULL);
9699 menu = g_list_prepend(menu, act);
9702 gr_parent = purple_buddy_get_group(buddy);
9703 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9704 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9705 continue;
9707 group = (PurpleGroup *)g_node;
9708 if (group == gr_parent)
9709 continue;
9711 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9712 continue;
9714 act = purple_menu_action_new(purple_group_get_name(group),
9715 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9716 group->name, NULL);
9717 menu_groups = g_list_prepend(menu_groups, act);
9719 menu_groups = g_list_reverse(menu_groups);
9721 act = purple_menu_action_new(_("Copy to"),
9722 NULL,
9723 NULL, menu_groups);
9724 menu = g_list_prepend(menu, act);
9725 menu = g_list_reverse(menu);
9727 g_free(self);
9728 return menu;
9731 static void
9732 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9734 struct sipe_account_data *sip = chat->account->gc->proto_data;
9735 struct sip_session *session;
9737 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9738 sipe_conf_modify_conference_lock(sip, session, locked);
9741 static void
9742 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9744 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9745 sipe_conf_modify_lock(chat, FALSE);
9748 static void
9749 sipe_chat_menu_lock_cb(PurpleChat *chat)
9751 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9752 sipe_conf_modify_lock(chat, TRUE);
9755 static GList *
9756 sipe_chat_menu(PurpleChat *chat)
9758 PurpleMenuAction *act;
9759 PurpleConvChatBuddyFlags flags_us;
9760 GList *menu = NULL;
9761 struct sipe_account_data *sip = chat->account->gc->proto_data;
9762 struct sip_session *session;
9763 gchar *self;
9765 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9766 if (!session) return NULL;
9768 self = sip_uri_self(sip);
9769 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9771 if (session->focus_uri
9772 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9774 if (session->locked) {
9775 act = purple_menu_action_new(_("Unlock"),
9776 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9777 NULL, NULL);
9778 menu = g_list_prepend(menu, act);
9779 } else {
9780 act = purple_menu_action_new(_("Lock"),
9781 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9782 NULL, NULL);
9783 menu = g_list_prepend(menu, act);
9787 menu = g_list_reverse(menu);
9789 g_free(self);
9790 return menu;
9793 static GList *
9794 sipe_blist_node_menu(PurpleBlistNode *node)
9796 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9797 return sipe_buddy_menu((PurpleBuddy *) node);
9798 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9799 return sipe_chat_menu((PurpleChat *)node);
9800 } else {
9801 return NULL;
9805 static gboolean
9806 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9808 char *uri = trans->payload->data;
9810 PurpleNotifyUserInfo *info;
9811 PurpleBuddy *pbuddy = NULL;
9812 struct sipe_buddy *sbuddy;
9813 const char *alias = NULL;
9814 char *device_name = NULL;
9815 char *server_alias = NULL;
9816 char *phone_number = NULL;
9817 char *email = NULL;
9818 const char *site;
9819 char *first_name = NULL;
9820 char *last_name = NULL;
9822 if (!sip) return FALSE;
9824 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9826 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9827 alias = purple_buddy_get_local_alias(pbuddy);
9829 //will query buddy UA's capabilities and send answer to log
9830 sipe_options_request(sip, uri);
9832 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9833 if (sbuddy) {
9834 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9837 info = purple_notify_user_info_new();
9839 if (msg->response != 200) {
9840 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9841 } else {
9842 xmlnode *searchResults;
9843 xmlnode *mrow;
9845 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9846 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9847 if (!searchResults) {
9848 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9849 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9850 const char *value;
9851 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9852 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9853 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9855 /* For 2007 system we will take this from ContactCard -
9856 * it has cleaner tel: URIs at least
9858 if (!sip->ocs2007) {
9859 char *tel_uri = sip_to_tel_uri(phone_number);
9860 /* trims its parameters, so call first */
9861 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9862 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9863 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9864 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9865 g_free(tel_uri);
9868 if (server_alias && strlen(server_alias) > 0) {
9869 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9871 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9872 purple_notify_user_info_add_pair(info, _("Job title"), value);
9874 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9875 purple_notify_user_info_add_pair(info, _("Office"), value);
9877 if (phone_number && strlen(phone_number) > 0) {
9878 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9880 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9881 purple_notify_user_info_add_pair(info, _("Company"), value);
9883 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9884 purple_notify_user_info_add_pair(info, _("City"), value);
9886 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9887 purple_notify_user_info_add_pair(info, _("State"), value);
9889 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9890 purple_notify_user_info_add_pair(info, _("Country"), value);
9892 if (email && strlen(email) > 0) {
9893 purple_notify_user_info_add_pair(info, _("Email address"), email);
9897 xmlnode_free(searchResults);
9900 purple_notify_user_info_add_section_break(info);
9902 if (is_empty(server_alias)) {
9903 g_free(server_alias);
9904 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9905 if (server_alias) {
9906 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9910 /* present alias if it differs from server alias */
9911 if (alias && !sipe_strequal(alias, server_alias))
9913 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9916 if (is_empty(email)) {
9917 g_free(email);
9918 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9919 if (email) {
9920 purple_notify_user_info_add_pair(info, _("Email address"), email);
9924 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9925 if (site) {
9926 purple_notify_user_info_add_pair(info, _("Site"), site);
9929 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9930 if (first_name && last_name) {
9931 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9933 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9934 g_free(link);
9936 g_free(first_name);
9937 g_free(last_name);
9939 if (device_name) {
9940 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9943 /* show a buddy's user info in a nice dialog box */
9944 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9945 uri, /* buddy's URI */
9946 info, /* body */
9947 NULL, /* callback called when dialog closed */
9948 NULL); /* userdata for callback */
9950 g_free(phone_number);
9951 g_free(server_alias);
9952 g_free(email);
9953 g_free(device_name);
9955 return TRUE;
9959 * AD search first, LDAP based
9961 static void sipe_get_info(PurpleConnection *gc, const char *username)
9963 struct sipe_account_data *sip = gc->proto_data;
9964 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9965 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9966 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9967 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9969 payload->destroy = g_free;
9970 payload->data = g_strdup(username);
9972 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9973 send_soap_request_with_cb(sip, domain_uri, body,
9974 (TransCallback) process_get_info_response, payload);
9975 g_free(domain_uri);
9976 g_free(body);
9977 g_free(row);
9980 PurplePluginProtocolInfo prpl_info =
9982 OPT_PROTO_CHAT_TOPIC,
9983 NULL, /* user_splits */
9984 NULL, /* protocol_options */
9985 NO_BUDDY_ICONS, /* icon_spec */
9986 sipe_list_icon, /* list_icon */
9987 NULL, /* list_emblems */
9988 sipe_status_text, /* status_text */
9989 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9990 sipe_status_types, /* away_states */
9991 sipe_blist_node_menu, /* blist_node_menu */
9992 NULL, /* chat_info */
9993 NULL, /* chat_info_defaults */
9994 sipe_login, /* login */
9995 sipe_close, /* close */
9996 sipe_im_send, /* send_im */
9997 NULL, /* set_info */ // TODO maybe
9998 sipe_send_typing, /* send_typing */
9999 sipe_get_info, /* get_info */
10000 sipe_set_status, /* set_status */
10001 sipe_set_idle, /* set_idle */
10002 NULL, /* change_passwd */
10003 sipe_add_buddy, /* add_buddy */
10004 NULL, /* add_buddies */
10005 sipe_remove_buddy, /* remove_buddy */
10006 NULL, /* remove_buddies */
10007 sipe_add_permit, /* add_permit */
10008 sipe_add_deny, /* add_deny */
10009 sipe_add_deny, /* rem_permit */
10010 sipe_add_permit, /* rem_deny */
10011 dummy_permit_deny, /* set_permit_deny */
10012 NULL, /* join_chat */
10013 NULL, /* reject_chat */
10014 NULL, /* get_chat_name */
10015 sipe_chat_invite, /* chat_invite */
10016 sipe_chat_leave, /* chat_leave */
10017 NULL, /* chat_whisper */
10018 sipe_chat_send, /* chat_send */
10019 sipe_keep_alive, /* keepalive */
10020 NULL, /* register_user */
10021 NULL, /* get_cb_info */ // deprecated
10022 NULL, /* get_cb_away */ // deprecated
10023 sipe_alias_buddy, /* alias_buddy */
10024 sipe_group_buddy, /* group_buddy */
10025 sipe_rename_group, /* rename_group */
10026 NULL, /* buddy_free */
10027 sipe_convo_closed, /* convo_closed */
10028 purple_normalize_nocase, /* normalize */
10029 NULL, /* set_buddy_icon */
10030 sipe_remove_group, /* remove_group */
10031 NULL, /* get_cb_real_name */ // TODO?
10032 NULL, /* set_chat_topic */
10033 NULL, /* find_blist_chat */
10034 NULL, /* roomlist_get_list */
10035 NULL, /* roomlist_cancel */
10036 NULL, /* roomlist_expand_category */
10037 NULL, /* can_receive_file */
10038 sipe_ft_send_file, /* send_file */
10039 sipe_ft_new_xfer, /* new_xfer */
10040 NULL, /* offline_message */
10041 NULL, /* whiteboard_prpl_ops */
10042 sipe_send_raw, /* send_raw */
10043 NULL, /* roomlist_room_serialize */
10044 NULL, /* unregister_user */
10045 NULL, /* send_attention */
10046 NULL, /* get_attention_types */
10047 #if !PURPLE_VERSION_CHECK(2,5,0)
10048 /* Backward compatibility when compiling against 2.4.x API */
10049 (void (*)(void)) /* _purple_reserved4 */
10050 #endif
10051 sizeof(PurplePluginProtocolInfo), /* struct_size */
10052 #if PURPLE_VERSION_CHECK(2,5,0)
10053 sipe_get_account_text_table, /* get_account_text_table */
10054 #if PURPLE_VERSION_CHECK(2,6,0)
10055 NULL, /* initiate_media */
10056 NULL, /* get_media_caps */
10057 #endif
10058 #endif
10062 PurplePluginInfo info = {
10063 PURPLE_PLUGIN_MAGIC,
10064 PURPLE_MAJOR_VERSION,
10065 PURPLE_MINOR_VERSION,
10066 PURPLE_PLUGIN_PROTOCOL, /**< type */
10067 NULL, /**< ui_requirement */
10068 0, /**< flags */
10069 NULL, /**< dependencies */
10070 PURPLE_PRIORITY_DEFAULT, /**< priority */
10071 "prpl-sipe", /**< id */
10072 "Office Communicator", /**< name */
10073 SIPE_VERSION, /**< version */
10074 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10075 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10076 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10077 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10078 "Gabriel Burt <gburt@novell.com>, " /**< author */
10079 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10080 "pier11 <pier11@operamail.com>", /**< author */
10081 "http://sipe.sourceforge.net/", /**< homepage */
10082 sipe_plugin_load, /**< load */
10083 sipe_plugin_unload, /**< unload */
10084 sipe_plugin_destroy, /**< destroy */
10085 NULL, /**< ui_info */
10086 &prpl_info, /**< extra_info */
10087 NULL,
10088 sipe_actions,
10089 NULL,
10090 NULL,
10091 NULL,
10092 NULL
10096 Local Variables:
10097 mode: c
10098 c-file-style: "bsd"
10099 indent-tabs-mode: t
10100 tab-width: 8
10101 End: