core cleanup: move plugin generation to purple
[siplcs.git] / src / core / sipe.c
blobad3b8ca6217d0d020bdae9f1b2a498901ccd82dd
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 pier11 <pier11@operamail.com>
7 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
10 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
13 * ***
14 * Thanks to Google's Summer of Code Program and the helpful mentors
15 * ***
17 * Session-based SIP MESSAGE documentation:
18 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 #ifdef HAVE_CONFIG_H
36 #include "config.h"
37 #endif
39 #ifdef _WIN32
40 #ifdef _DLL
41 #define _WS2TCPIP_H_
42 #define _WINSOCK2API_
43 #define _LIBC_INTERNAL_
44 #endif /* _DLL */
45 #include "internal.h"
46 #else
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <netinet/in.h>
50 #endif /* _WIN32 */
52 #include <time.h>
53 #include <stdio.h>
54 #include <errno.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include <glib.h>
60 #include "accountopt.h"
61 #include "blist.h"
62 #include "conversation.h"
63 #include "dnsquery.h"
64 #include "debug.h"
65 #include "notify.h"
66 #include "savedstatuses.h"
67 #include "privacy.h"
68 #include "prpl.h"
69 #include "plugin.h"
70 #include "util.h"
71 #include "version.h"
72 #include "network.h"
73 #include "xmlnode.h"
74 #include "mime.h"
75 #include "core.h"
77 #include "core-depurple.h" /* Temporary for the core de-purple transition */
79 #include "sipe.h"
80 #include "sipe-cal.h"
81 #include "sipe-ews.h"
82 #include "sipe-chat.h"
83 #include "sipe-conf.h"
84 #include "sip-csta.h"
85 #include "sipe-dialog.h"
86 #include "sipe-nls.h"
87 #include "sipe-session.h"
88 #include "sipe-utils.h"
89 #include "sipe-ft.h"
90 #include "sipmsg.h"
91 #include "sipe-sign.h"
92 #include "dnssrv.h"
93 #include "request.h"
95 /* Backward compatibility when compiling against 2.4.x API */
96 #if !PURPLE_VERSION_CHECK(2,5,0)
97 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
98 #endif
100 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
102 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
103 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
105 /* Keep in sync with sipe_transport_type! */
106 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
107 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
109 /* Status identifiers (see also: sipe_status_types()) */
110 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
111 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
112 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
113 /* PURPLE_STATUS_UNAVAILABLE: */
114 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
115 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
116 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
117 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
118 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
119 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
120 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
121 /* PURPLE_STATUS_AWAY: */
122 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
123 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
124 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
125 /** Reuters status (user settable) */
126 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
127 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
128 /* ??? PURPLE_STATUS_MOBILE */
129 /* ??? PURPLE_STATUS_TUNE */
131 /* Status attributes (see also sipe_status_types() */
132 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
134 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
136 static struct sipe_activity_map_struct
138 sipe_activity type;
139 const char *token;
140 const char *desc;
141 const char *status_id;
143 } const sipe_activity_map[] =
145 /* This has nothing to do with Availability numbers, like 3500 (online).
146 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
148 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
149 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
150 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
151 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
152 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
153 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
154 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
155 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
156 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
157 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
158 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
159 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
160 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
161 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
162 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
164 /** @param x is sipe_activity */
165 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
168 /* Action name templates */
169 #define ACTION_NAME_PRESENCE "<presence><%s>"
171 static sipe_activity
172 sipe_get_activity_by_token(const char *token)
174 int i;
176 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
178 if (sipe_strequal(token, sipe_activity_map[i].token))
179 return sipe_activity_map[i].type;
182 return sipe_activity_map[0].type;
185 static const char *
186 sipe_get_activity_desc_by_token(const char *token)
188 if (!token) return NULL;
190 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
193 /** Allows to send typed messages from chat window again after account reinstantiation. */
194 static void
195 sipe_rejoin_chat(PurpleConversation *conv)
197 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
198 PURPLE_CONV_CHAT(conv)->left)
200 PURPLE_CONV_CHAT(conv)->left = FALSE;
201 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
205 static char *genbranch()
207 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
208 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
209 rand() & 0xFFFF, rand() & 0xFFFF);
213 static char *default_ua = NULL;
214 static const char*
215 sipe_get_useragent(struct sipe_account_data *sip)
217 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
218 if (is_empty(useragent)) {
219 if (!default_ua) {
220 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
221 /* ref: lzodefs.h */
222 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
223 #define SIPE_TARGET_PLATFORM "linux"
224 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
225 #define SIPE_TARGET_PLATFORM "bsd"
226 #elif defined(__APPLE__) || defined(__MACOS__)
227 #define SIPE_TARGET_PLATFORM "macosx"
228 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
229 #define SIPE_TARGET_PLATFORM "aix"
230 #elif defined(__solaris__) || defined(__sun)
231 #define SIPE_TARGET_PLATFORM "sun"
232 #elif defined(_WIN32)
233 #define SIPE_TARGET_PLATFORM "win"
234 #elif defined(__CYGWIN__)
235 #define SIPE_TARGET_PLATFORM "cygwin"
236 #elif defined(__hpux__)
237 #define SIPE_TARGET_PLATFORM "hpux"
238 #elif defined(__sgi__)
239 #define SIPE_TARGET_PLATFORM "irix"
240 #else
241 #define SIPE_TARGET_PLATFORM "unknown"
242 #endif
244 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
245 #define SIPE_TARGET_ARCH "x86_64"
246 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
247 #define SIPE_TARGET_ARCH "i386"
248 #elif defined(__ppc64__)
249 #define SIPE_TARGET_ARCH "ppc64"
250 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
251 #define SIPE_TARGET_ARCH "ppc"
252 #elif defined(__hppa__) || defined(__hppa)
253 #define SIPE_TARGET_ARCH "hppa"
254 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
255 #define SIPE_TARGET_ARCH "mips"
256 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
257 #define SIPE_TARGET_ARCH "s390"
258 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
259 #define SIPE_TARGET_ARCH "sparc"
260 #elif defined(__arm__)
261 #define SIPE_TARGET_ARCH "arm"
262 #else
263 #define SIPE_TARGET_ARCH "other"
264 #endif
266 default_ua = g_strdup_printf("Purple/%s Sipe/" SIPE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
267 purple_core_get_version(),
268 sip->server_version ? sip->server_version : "");
270 useragent = default_ua;
272 return useragent;
275 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
276 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
278 return "sipe";
281 static void sipe_plugin_destroy(PurplePlugin *plugin);
283 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
285 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
286 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
287 gpointer data);
289 static void sipe_close(PurpleConnection *gc);
291 static void send_presence_status(struct sipe_account_data *sip);
293 static void sendout_pkt(PurpleConnection *gc, const char *buf);
295 static void sipe_keep_alive(PurpleConnection *gc)
297 struct sipe_account_data *sip = gc->proto_data;
298 if (sip->transport == SIPE_TRANSPORT_UDP) {
299 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
300 gchar buf[2] = {0, 0};
301 purple_debug_info("sipe", "sending keep alive\n");
302 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
303 } else {
304 time_t now = time(NULL);
305 if ((sip->keepalive_timeout > 0) &&
306 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
307 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
309 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
310 sendout_pkt(gc, "\r\n\r\n");
311 sip->last_keepalive = now;
316 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
318 struct sip_connection *ret = NULL;
319 GSList *entry = sip->openconns;
320 while (entry) {
321 ret = entry->data;
322 if (ret->fd == fd) return ret;
323 entry = entry->next;
325 return NULL;
328 static void sipe_auth_free(struct sip_auth *auth)
330 g_free(auth->opaque);
331 auth->opaque = NULL;
332 g_free(auth->realm);
333 auth->realm = NULL;
334 g_free(auth->target);
335 auth->target = NULL;
336 auth->version = 0;
337 auth->type = AUTH_TYPE_UNSET;
338 auth->retries = 0;
339 auth->expires = 0;
340 g_free(auth->gssapi_data);
341 auth->gssapi_data = NULL;
342 sip_sec_destroy_context(auth->gssapi_context);
343 auth->gssapi_context = NULL;
346 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
348 struct sip_connection *ret = g_new0(struct sip_connection, 1);
349 ret->fd = fd;
350 sip->openconns = g_slist_append(sip->openconns, ret);
351 return ret;
354 static void connection_remove(struct sipe_account_data *sip, int fd)
356 struct sip_connection *conn = connection_find(sip, fd);
357 if (conn) {
358 sip->openconns = g_slist_remove(sip->openconns, conn);
359 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
360 g_free(conn->inbuf);
361 g_free(conn);
365 static void connection_free_all(struct sipe_account_data *sip)
367 struct sip_connection *ret = NULL;
368 GSList *entry = sip->openconns;
369 while (entry) {
370 ret = entry->data;
371 connection_remove(sip, ret->fd);
372 entry = sip->openconns;
376 static void
377 sipe_make_signature(struct sipe_account_data *sip,
378 struct sipmsg *msg);
380 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
382 gchar noncecount[9];
383 const char *authuser = sip->authuser;
384 gchar *response;
385 gchar *ret;
387 if (!authuser || strlen(authuser) < 1) {
388 authuser = sip->username;
391 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
392 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
393 gchar *version_str;
395 // If we have a signature for the message, include that
396 if (msg->signature) {
397 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);
400 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
401 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
402 gchar *gssapi_data;
403 gchar *opaque;
404 gchar *sign_str = NULL;
406 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
407 &(auth->expires),
408 auth->type,
409 purple_account_get_bool(sip->account, "sso", TRUE),
410 sip->authdomain ? sip->authdomain : "",
411 authuser,
412 sip->password,
413 auth->target,
414 auth->gssapi_data);
415 if (!gssapi_data || !auth->gssapi_context) {
416 sip->gc->wants_to_die = TRUE;
417 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
418 return NULL;
421 if (auth->version > 3) {
422 sipe_make_signature(sip, msg);
423 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
424 msg->rand, msg->num, msg->signature);
425 } else {
426 sign_str = g_strdup("");
429 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
430 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
431 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);
432 g_free(opaque);
433 g_free(gssapi_data);
434 g_free(version_str);
435 g_free(sign_str);
436 return ret;
439 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
440 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
441 g_free(version_str);
442 return ret;
444 } else { /* Digest */
446 /* Calculate new session key */
447 if (!auth->opaque) {
448 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
449 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
450 authuser, auth->realm, sip->password,
451 auth->gssapi_data, NULL);
454 sprintf(noncecount, "%08d", auth->nc++);
455 response = purple_cipher_http_digest_calculate_response("md5",
456 msg->method, msg->target, NULL, NULL,
457 auth->gssapi_data, noncecount, NULL,
458 auth->opaque);
459 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
461 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);
462 g_free(response);
463 return ret;
467 static char *parse_attribute(const char *attrname, const char *source)
469 const char *tmp, *tmp2;
470 char *retval = NULL;
471 int len = strlen(attrname);
473 if (g_str_has_prefix(source, attrname)) {
474 tmp = source + len;
475 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
476 if (tmp2)
477 retval = g_strndup(tmp, tmp2 - tmp);
478 else
479 retval = g_strdup(tmp);
482 return retval;
485 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
487 int i;
488 gchar **parts;
490 if (!hdr) {
491 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
492 return;
495 if (!g_strncasecmp(hdr, "NTLM", 4)) {
496 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
497 auth->type = AUTH_TYPE_NTLM;
498 hdr += 5;
499 auth->nc = 1;
500 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
501 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
502 auth->type = AUTH_TYPE_KERBEROS;
503 hdr += 9;
504 auth->nc = 3;
505 } else {
506 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
507 auth->type = AUTH_TYPE_DIGEST;
508 hdr += 7;
511 parts = g_strsplit(hdr, "\", ", 0);
512 for (i = 0; parts[i]; i++) {
513 char *tmp;
515 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
517 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
518 g_free(auth->gssapi_data);
519 auth->gssapi_data = tmp;
521 if (auth->type == AUTH_TYPE_NTLM) {
522 /* NTLM module extracts nonce from gssapi-data */
523 auth->nc = 3;
526 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
527 /* Only used with AUTH_TYPE_DIGEST */
528 g_free(auth->gssapi_data);
529 auth->gssapi_data = tmp;
530 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
531 g_free(auth->opaque);
532 auth->opaque = tmp;
533 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
534 g_free(auth->realm);
535 auth->realm = tmp;
537 if (auth->type == AUTH_TYPE_DIGEST) {
538 /* Throw away old session key */
539 g_free(auth->opaque);
540 auth->opaque = NULL;
541 auth->nc = 1;
543 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
544 g_free(auth->target);
545 auth->target = tmp;
546 } else if ((tmp = parse_attribute("version=", parts[i]))) {
547 auth->version = atoi(tmp);
548 g_free(tmp);
550 // uncomment to revert to previous functionality if version 3+ does not work.
551 // auth->version = 2;
553 g_strfreev(parts);
555 return;
558 static void sipe_canwrite_cb(gpointer data,
559 SIPE_UNUSED_PARAMETER gint source,
560 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
562 PurpleConnection *gc = data;
563 struct sipe_account_data *sip = gc->proto_data;
564 gsize max_write;
565 gssize written;
567 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
569 if (max_write == 0) {
570 if (sip->tx_handler != 0){
571 purple_input_remove(sip->tx_handler);
572 sip->tx_handler = 0;
574 return;
577 written = write(sip->fd, sip->txbuf->outptr, max_write);
579 if (written < 0 && errno == EAGAIN)
580 written = 0;
581 else if (written <= 0) {
582 /*TODO: do we really want to disconnect on a failure to write?*/
583 purple_connection_error(gc, _("Could not write"));
584 return;
587 purple_circ_buffer_mark_read(sip->txbuf, written);
590 static void sipe_canwrite_cb_ssl(gpointer data,
591 SIPE_UNUSED_PARAMETER gint src,
592 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
594 PurpleConnection *gc = data;
595 struct sipe_account_data *sip = gc->proto_data;
596 gsize max_write;
597 gssize written;
599 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
601 if (max_write == 0) {
602 if (sip->tx_handler != 0) {
603 purple_input_remove(sip->tx_handler);
604 sip->tx_handler = 0;
605 return;
609 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
611 if (written < 0 && errno == EAGAIN)
612 written = 0;
613 else if (written <= 0) {
614 /*TODO: do we really want to disconnect on a failure to write?*/
615 purple_connection_error(gc, _("Could not write"));
616 return;
619 purple_circ_buffer_mark_read(sip->txbuf, written);
622 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
624 static void send_later_cb(gpointer data, gint source,
625 SIPE_UNUSED_PARAMETER const gchar *error)
627 PurpleConnection *gc = data;
628 struct sipe_account_data *sip;
629 struct sip_connection *conn;
631 if (!PURPLE_CONNECTION_IS_VALID(gc))
633 if (source >= 0)
634 close(source);
635 return;
638 if (source < 0) {
639 purple_connection_error(gc, _("Could not connect"));
640 return;
643 sip = gc->proto_data;
644 sip->fd = source;
645 sip->connecting = FALSE;
646 sip->last_keepalive = time(NULL);
648 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
650 /* If there is more to write now, we need to register a handler */
651 if (sip->txbuf->bufused > 0)
652 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
654 conn = connection_create(sip, source);
655 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
658 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
660 struct sipe_account_data *sip;
662 if (!PURPLE_CONNECTION_IS_VALID(gc))
664 if (gsc) purple_ssl_close(gsc);
665 return NULL;
668 sip = gc->proto_data;
669 sip->fd = gsc->fd;
670 sip->gsc = gsc;
671 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
672 sip->connecting = FALSE;
673 sip->last_keepalive = time(NULL);
675 connection_create(sip, gsc->fd);
677 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
679 return sip;
682 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
683 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
685 PurpleConnection *gc = data;
686 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
687 if (sip == NULL) return;
689 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
691 /* If there is more to write now */
692 if (sip->txbuf->bufused > 0) {
693 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
698 static void sendlater(PurpleConnection *gc, const char *buf)
700 struct sipe_account_data *sip = gc->proto_data;
702 if (!sip->connecting) {
703 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
704 if (sip->transport == SIPE_TRANSPORT_TLS){
705 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
706 } else {
707 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
708 purple_connection_error(gc, _("Could not create socket"));
711 sip->connecting = TRUE;
714 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
715 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
717 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
720 static void sendout_pkt(PurpleConnection *gc, const char *buf)
722 struct sipe_account_data *sip = gc->proto_data;
723 time_t currtime = time(NULL);
724 int writelen = strlen(buf);
725 char *tmp;
727 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
728 g_free(tmp);
729 if (sip->transport == SIPE_TRANSPORT_UDP) {
730 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
731 purple_debug_info("sipe", "could not send packet\n");
733 } else {
734 int ret;
735 if (sip->fd < 0) {
736 sendlater(gc, buf);
737 return;
740 if (sip->tx_handler) {
741 ret = -1;
742 errno = EAGAIN;
743 } else{
744 if (sip->gsc){
745 ret = purple_ssl_write(sip->gsc, buf, writelen);
746 }else{
747 ret = write(sip->fd, buf, writelen);
751 if (ret < 0 && errno == EAGAIN)
752 ret = 0;
753 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
754 sendlater(gc, buf);
755 return;
758 if (ret < writelen) {
759 if (!sip->tx_handler){
760 if (sip->gsc){
761 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
763 else{
764 sip->tx_handler = purple_input_add(sip->fd,
765 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
766 gc);
770 /* XXX: is it OK to do this? You might get part of a request sent
771 with part of another. */
772 if (sip->txbuf->bufused > 0)
773 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
775 purple_circ_buffer_append(sip->txbuf, buf + ret,
776 writelen - ret);
781 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
783 sendout_pkt(gc, buf);
784 return len;
787 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
789 GSList *tmp = msg->headers;
790 gchar *name;
791 gchar *value;
792 GString *outstr = g_string_new("");
793 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
794 while (tmp) {
795 name = ((struct sipnameval*) (tmp->data))->name;
796 value = ((struct sipnameval*) (tmp->data))->value;
797 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
798 tmp = g_slist_next(tmp);
800 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
801 sendout_pkt(sip->gc, outstr->str);
802 g_string_free(outstr, TRUE);
805 static void
806 sipe_make_signature(struct sipe_account_data *sip,
807 struct sipmsg *msg)
809 if (sip->registrar.gssapi_context) {
810 struct sipmsg_breakdown msgbd;
811 gchar *signature_input_str;
812 msgbd.msg = msg;
813 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
814 msgbd.rand = g_strdup_printf("%08x", g_random_int());
815 sip->registrar.ntlm_num++;
816 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
817 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
818 if (signature_input_str != NULL) {
819 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
820 msg->signature = signature_hex;
821 msg->rand = g_strdup(msgbd.rand);
822 msg->num = g_strdup(msgbd.num);
823 g_free(signature_input_str);
825 sipmsg_breakdown_free(&msgbd);
829 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
831 gchar * buf;
833 if (sip->registrar.type == AUTH_TYPE_UNSET) {
834 return;
837 sipe_make_signature(sip, msg);
839 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
840 buf = auth_header(sip, &sip->registrar, msg);
841 if (buf) {
842 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
844 g_free(buf);
845 } 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")) {
846 sip->registrar.nc = 3;
847 sip->registrar.type = AUTH_TYPE_NTLM;
848 #ifdef USE_KERBEROS
849 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
850 sip->registrar.type = AUTH_TYPE_KERBEROS;
852 #endif
855 buf = auth_header(sip, &sip->registrar, msg);
856 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
857 g_free(buf);
858 } else {
859 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
863 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
864 const char *text, const char *body)
866 gchar *name;
867 gchar *value;
868 GString *outstr = g_string_new("");
869 struct sipe_account_data *sip = gc->proto_data;
870 gchar *contact;
871 GSList *tmp;
872 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
874 /* Can return NULL! */
875 contact = get_contact(sip);
876 if (contact) {
877 sipmsg_add_header(msg, "Contact", contact);
878 g_free(contact);
881 if (body) {
882 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
883 sipmsg_add_header(msg, "Content-Length", len);
884 g_free(len);
885 } else {
886 sipmsg_add_header(msg, "Content-Length", "0");
889 msg->response = code;
891 sipmsg_strip_headers(msg, keepers);
892 sipmsg_merge_new_headers(msg);
893 sign_outgoing_message(msg, sip, msg->method);
895 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
896 tmp = msg->headers;
897 while (tmp) {
898 name = ((struct sipnameval*) (tmp->data))->name;
899 value = ((struct sipnameval*) (tmp->data))->value;
901 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
902 tmp = g_slist_next(tmp);
904 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
905 sendout_pkt(gc, outstr->str);
906 g_string_free(outstr, TRUE);
909 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
911 if (sip->transactions) {
912 sip->transactions = g_slist_remove(sip->transactions, trans);
913 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
915 if (trans->msg) sipmsg_free(trans->msg);
916 if (trans->payload) {
917 (*trans->payload->destroy)(trans->payload->data);
918 g_free(trans->payload);
920 g_free(trans->key);
921 g_free(trans);
925 static struct transaction *
926 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
928 const gchar *call_id;
929 const gchar *cseq;
930 struct transaction *trans = g_new0(struct transaction, 1);
932 trans->time = time(NULL);
933 trans->msg = (struct sipmsg *)msg;
934 call_id = sipmsg_find_header(trans->msg, "Call-ID");
935 cseq = sipmsg_find_header(trans->msg, "CSeq");
936 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
937 trans->callback = callback;
938 sip->transactions = g_slist_append(sip->transactions, trans);
939 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
940 return trans;
943 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
945 struct transaction *trans;
946 GSList *transactions = sip->transactions;
947 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
948 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
949 gchar *key;
951 if (!call_id || !cseq) {
952 purple_debug(PURPLE_DEBUG_ERROR, "sipe", "transaction_find: no Call-ID or CSeq!\n");
953 return NULL;
956 key = g_strdup_printf("<%s><%s>", call_id, cseq);
957 while (transactions) {
958 trans = transactions->data;
959 if (!g_strcasecmp(trans->key, key)) {
960 g_free(key);
961 return trans;
963 transactions = transactions->next;
966 g_free(key);
967 return NULL;
970 struct transaction *
971 send_sip_request(PurpleConnection *gc, const gchar *method,
972 const gchar *url, const gchar *to, const gchar *addheaders,
973 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
975 struct sipe_account_data *sip = gc->proto_data;
976 const char *addh = "";
977 char *buf;
978 struct sipmsg *msg;
979 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
980 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
981 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
982 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
983 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
984 gchar *route = g_strdup("");
985 gchar *epid = get_epid(sip);
986 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
987 struct transaction *trans = NULL;
989 if (dialog && dialog->routes)
991 GSList *iter = dialog->routes;
993 while(iter)
995 char *tmp = route;
996 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
997 g_free(tmp);
998 iter = g_slist_next(iter);
1002 if (!ourtag && !dialog) {
1003 ourtag = gentag();
1006 if (sipe_strequal(method, "REGISTER")) {
1007 if (sip->regcallid) {
1008 g_free(callid);
1009 callid = g_strdup(sip->regcallid);
1010 } else {
1011 sip->regcallid = g_strdup(callid);
1013 cseq = ++sip->cseq;
1016 if (addheaders) addh = addheaders;
1018 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1019 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1020 "From: <sip:%s>%s%s;epid=%s\r\n"
1021 "To: <%s>%s%s%s%s\r\n"
1022 "Max-Forwards: 70\r\n"
1023 "CSeq: %d %s\r\n"
1024 "User-Agent: %s\r\n"
1025 "Call-ID: %s\r\n"
1026 "%s%s"
1027 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1028 method,
1029 dialog && dialog->request ? dialog->request : url,
1030 TRANSPORT_DESCRIPTOR,
1031 purple_network_get_my_ip(-1),
1032 sip->listenport,
1033 branch ? ";branch=" : "",
1034 branch ? branch : "",
1035 sip->username,
1036 ourtag ? ";tag=" : "",
1037 ourtag ? ourtag : "",
1038 epid,
1040 theirtag ? ";tag=" : "",
1041 theirtag ? theirtag : "",
1042 theirepid ? ";epid=" : "",
1043 theirepid ? theirepid : "",
1044 cseq,
1045 method,
1046 sipe_get_useragent(sip),
1047 callid,
1048 route,
1049 addh,
1050 body ? (gsize) strlen(body) : 0,
1051 body ? body : "");
1054 //printf ("parsing msg buf:\n%s\n\n", buf);
1055 msg = sipmsg_parse_msg(buf);
1057 g_free(buf);
1058 g_free(ourtag);
1059 g_free(theirtag);
1060 g_free(theirepid);
1061 g_free(branch);
1062 g_free(callid);
1063 g_free(route);
1064 g_free(epid);
1066 sign_outgoing_message (msg, sip, method);
1068 buf = sipmsg_to_string (msg);
1070 /* add to ongoing transactions */
1071 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1072 if (!sipe_strequal(method, "ACK")) {
1073 trans = transactions_add_buf(sip, msg, tc);
1074 } else {
1075 sipmsg_free(msg);
1077 sendout_pkt(gc, buf);
1078 g_free(buf);
1080 return trans;
1084 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1086 static void
1087 send_soap_request_with_cb(struct sipe_account_data *sip,
1088 gchar *from0,
1089 gchar *body,
1090 TransCallback callback,
1091 struct transaction_payload *payload)
1093 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1094 gchar *contact = get_contact(sip);
1095 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1096 "Content-Type: application/SOAP+xml\r\n",contact);
1098 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1099 trans->payload = payload;
1101 g_free(from);
1102 g_free(contact);
1103 g_free(hdr);
1106 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1108 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1111 static char *get_contact_register(struct sipe_account_data *sip)
1113 char *epid = get_epid(sip);
1114 char *uuid = generateUUIDfromEPID(epid);
1115 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);
1116 g_free(uuid);
1117 g_free(epid);
1118 return(buf);
1121 static void do_register_exp(struct sipe_account_data *sip, int expire)
1123 char *uri;
1124 char *expires;
1125 char *to;
1126 char *contact;
1127 char *hdr;
1129 if (!sip->sipdomain) return;
1131 uri = sip_uri_from_name(sip->sipdomain);
1132 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1133 to = sip_uri_self(sip);
1134 contact = get_contact_register(sip);
1135 hdr = g_strdup_printf("Contact: %s\r\n"
1136 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1137 "Event: registration\r\n"
1138 "Allow-Events: presence\r\n"
1139 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1140 "%s", contact, expires);
1141 g_free(contact);
1142 g_free(expires);
1144 sip->registerstatus = 1;
1146 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1147 process_register_response);
1149 g_free(hdr);
1150 g_free(uri);
1151 g_free(to);
1154 static void do_register_cb(struct sipe_account_data *sip,
1155 SIPE_UNUSED_PARAMETER void *unused)
1157 do_register_exp(sip, -1);
1158 sip->reregister_set = FALSE;
1161 static void do_register(struct sipe_account_data *sip)
1163 do_register_exp(sip, -1);
1166 static void
1167 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1169 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1170 send_soap_request(sip, body);
1171 g_free(body);
1174 static void
1175 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1177 if (allow) {
1178 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1179 } else {
1180 purple_debug_info("sipe", "Blocking contact %s\n", who);
1183 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1186 static
1187 void sipe_auth_user_cb(void * data)
1189 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1190 if (!job) return;
1192 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1193 g_free(job);
1196 static
1197 void sipe_deny_user_cb(void * data)
1199 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1200 if (!job) return;
1202 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1203 g_free(job);
1206 static void
1207 sipe_add_permit(PurpleConnection *gc, const char *name)
1209 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1210 sipe_contact_allow_deny(sip, name, TRUE);
1213 static void
1214 sipe_add_deny(PurpleConnection *gc, const char *name)
1216 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1217 sipe_contact_allow_deny(sip, name, FALSE);
1220 /*static void
1221 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1223 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1224 sipe_contact_set_acl(sip, name, "");
1227 static void
1228 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1230 xmlnode *watchers;
1231 xmlnode *watcher;
1232 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1233 if (msg->response != 0 && msg->response != 200) return;
1235 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1237 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1238 if (!watchers) return;
1240 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1241 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1242 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1243 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1245 // TODO pull out optional displayName to pass as alias
1246 if (remote_user) {
1247 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1248 job->who = remote_user;
1249 job->sip = sip;
1250 purple_account_request_authorization(
1251 sip->account,
1252 remote_user,
1253 _("you"), /* id */
1254 alias,
1255 NULL, /* message */
1256 on_list,
1257 sipe_auth_user_cb,
1258 sipe_deny_user_cb,
1259 (void *) job);
1264 xmlnode_free(watchers);
1265 return;
1268 static void
1269 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1271 PurpleGroup * purple_group = purple_find_group(group->name);
1272 if (!purple_group) {
1273 purple_group = purple_group_new(group->name);
1274 purple_blist_add_group(purple_group, NULL);
1277 if (purple_group) {
1278 group->purple_group = purple_group;
1279 sip->groups = g_slist_append(sip->groups, group);
1280 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1281 } else {
1282 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1286 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1288 struct sipe_group *group;
1289 GSList *entry;
1290 if (sip == NULL) {
1291 return NULL;
1294 entry = sip->groups;
1295 while (entry) {
1296 group = entry->data;
1297 if (group->id == id) {
1298 return group;
1300 entry = entry->next;
1302 return NULL;
1305 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1307 struct sipe_group *group;
1308 GSList *entry;
1309 if (!sip || !name) {
1310 return NULL;
1313 entry = sip->groups;
1314 while (entry) {
1315 group = entry->data;
1316 if (sipe_strequal(group->name, name)) {
1317 return group;
1319 entry = entry->next;
1321 return NULL;
1324 static void
1325 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1327 gchar *body;
1328 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1329 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1330 send_soap_request(sip, body);
1331 g_free(body);
1332 g_free(group->name);
1333 group->name = g_strdup(name);
1337 * Only appends if no such value already stored.
1338 * Like Set in Java.
1340 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1341 GSList * res = list;
1342 if (!g_slist_find_custom(list, data, func)) {
1343 res = g_slist_insert_sorted(list, data, func);
1345 return res;
1348 static int
1349 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1350 return group1->id - group2->id;
1354 * Returns string like "2 4 7 8" - group ids buddy belong to.
1356 static gchar *
1357 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1358 int i = 0;
1359 gchar *res;
1360 //creating array from GList, converting int to gchar*
1361 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1362 GSList *entry = buddy->groups;
1364 if (!ids_arr) return NULL;
1366 while (entry) {
1367 struct sipe_group * group = entry->data;
1368 ids_arr[i] = g_strdup_printf("%d", group->id);
1369 entry = entry->next;
1370 i++;
1372 ids_arr[i] = NULL;
1373 res = g_strjoinv(" ", ids_arr);
1374 g_strfreev(ids_arr);
1375 return res;
1379 * Sends buddy update to server
1381 static void
1382 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1384 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1385 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1387 if (buddy && purple_buddy) {
1388 const char *alias = purple_buddy_get_alias(purple_buddy);
1389 gchar *groups = sipe_get_buddy_groups_string(buddy);
1390 if (groups) {
1391 gchar *body;
1392 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1394 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1395 alias, groups, "true", buddy->name, sip->contacts_delta++
1397 send_soap_request(sip, body);
1398 g_free(groups);
1399 g_free(body);
1404 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1406 if (msg->response == 200) {
1407 struct sipe_group *group;
1408 struct group_user_context *ctx = trans->payload->data;
1409 xmlnode *xml;
1410 xmlnode *node;
1411 char *group_id;
1412 struct sipe_buddy *buddy;
1414 xml = xmlnode_from_str(msg->body, msg->bodylen);
1415 if (!xml) {
1416 return FALSE;
1419 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1420 if (!node) {
1421 xmlnode_free(xml);
1422 return FALSE;
1425 group_id = xmlnode_get_data(node);
1426 if (!group_id) {
1427 xmlnode_free(xml);
1428 return FALSE;
1431 group = g_new0(struct sipe_group, 1);
1432 group->id = (int)g_ascii_strtod(group_id, NULL);
1433 g_free(group_id);
1434 group->name = g_strdup(ctx->group_name);
1436 sipe_group_add(sip, group);
1438 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1439 if (buddy) {
1440 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1443 sipe_group_set_user(sip, ctx->user_name);
1445 xmlnode_free(xml);
1446 return TRUE;
1448 return FALSE;
1451 static void sipe_group_context_destroy(gpointer data)
1453 struct group_user_context *ctx = data;
1454 g_free(ctx->group_name);
1455 g_free(ctx->user_name);
1456 g_free(ctx);
1459 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1461 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1462 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1463 gchar *body;
1464 ctx->group_name = g_strdup(name);
1465 ctx->user_name = g_strdup(who);
1466 payload->destroy = sipe_group_context_destroy;
1467 payload->data = ctx;
1469 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1470 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1471 g_free(body);
1475 * Data structure for scheduled actions
1478 struct scheduled_action {
1480 * Name of action.
1481 * Format is <Event>[<Data>...]
1482 * Example: <presence><sip:user@domain.com> or <registration>
1484 gchar *name;
1485 guint timeout_handler;
1486 gboolean repetitive;
1487 Action action;
1488 GDestroyNotify destroy;
1489 struct sipe_account_data *sip;
1490 void *payload;
1494 * A timer callback
1495 * Should return FALSE if repetitive action is not needed
1497 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1499 gboolean ret;
1500 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1501 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1502 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1503 (sched_action->action)(sched_action->sip, sched_action->payload);
1504 ret = sched_action->repetitive;
1505 if (sched_action->destroy) {
1506 (*sched_action->destroy)(sched_action->payload);
1508 g_free(sched_action->name);
1509 g_free(sched_action);
1510 return ret;
1514 * Kills action timer effectively cancelling
1515 * scheduled action
1517 * @param name of action
1519 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1521 GSList *entry;
1523 if (!sip->timeouts || !name) return;
1525 entry = sip->timeouts;
1526 while (entry) {
1527 struct scheduled_action *sched_action = entry->data;
1528 if(sipe_strequal(sched_action->name, name)) {
1529 GSList *to_delete = entry;
1530 entry = entry->next;
1531 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1532 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1533 purple_timeout_remove(sched_action->timeout_handler);
1534 if (sched_action->destroy) {
1535 (*sched_action->destroy)(sched_action->payload);
1537 g_free(sched_action->name);
1538 g_free(sched_action);
1539 } else {
1540 entry = entry->next;
1545 static void
1546 sipe_schedule_action0(const gchar *name,
1547 int timeout,
1548 gboolean isSeconds,
1549 Action action,
1550 GDestroyNotify destroy,
1551 struct sipe_account_data *sip,
1552 void *payload)
1554 struct scheduled_action *sched_action;
1556 /* Make sure each action only exists once */
1557 sipe_cancel_scheduled_action(sip, name);
1559 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1560 sched_action = g_new0(struct scheduled_action, 1);
1561 sched_action->repetitive = FALSE;
1562 sched_action->name = g_strdup(name);
1563 sched_action->action = action;
1564 sched_action->destroy = destroy;
1565 sched_action->sip = sip;
1566 sched_action->payload = payload;
1567 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1568 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1569 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1570 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1573 void
1574 sipe_schedule_action(const gchar *name,
1575 int timeout,
1576 Action action,
1577 GDestroyNotify destroy,
1578 struct sipe_account_data *sip,
1579 void *payload)
1581 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1585 * Same as sipe_schedule_action() but timeout is in milliseconds.
1587 static void
1588 sipe_schedule_action_msec(const gchar *name,
1589 int timeout,
1590 Action action,
1591 GDestroyNotify destroy,
1592 struct sipe_account_data *sip,
1593 void *payload)
1595 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1598 static void
1599 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1600 time_t calculate_from);
1602 static int
1603 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1605 static const char*
1606 sipe_get_status_by_availability(int avail,
1607 char** activity);
1609 static void
1610 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1611 const char *status_id,
1612 const char *message,
1613 time_t do_not_publish[]);
1615 static void
1616 sipe_apply_calendar_status(struct sipe_account_data *sip,
1617 struct sipe_buddy *sbuddy,
1618 const char *status_id)
1620 time_t cal_avail_since;
1621 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1622 int avail;
1623 gchar *self_uri;
1625 if (!sbuddy) return;
1627 if (cal_status < SIPE_CAL_NO_DATA) {
1628 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_status : %d for %s\n", cal_status, sbuddy->name);
1629 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1632 /* scheduled Cal update call */
1633 if (!status_id) {
1634 status_id = sbuddy->last_non_cal_status_id;
1635 g_free(sbuddy->activity);
1636 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1639 if (!status_id) {
1640 purple_debug_info("sipe", "sipe_apply_calendar_status: status_id is NULL for %s, exiting.\n",
1641 sbuddy->name ? sbuddy->name : "" );
1642 return;
1645 /* adjust to calendar status */
1646 if (cal_status != SIPE_CAL_NO_DATA) {
1647 purple_debug_info("sipe", "sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1649 if (cal_status == SIPE_CAL_BUSY
1650 && cal_avail_since > sbuddy->user_avail_since
1651 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1653 status_id = SIPE_STATUS_ID_BUSY;
1654 g_free(sbuddy->activity);
1655 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1657 avail = sipe_get_availability_by_status(status_id, NULL);
1659 purple_debug_info("sipe", "sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1660 if (cal_avail_since > sbuddy->activity_since) {
1661 if (cal_status == SIPE_CAL_OOF
1662 && avail >= 15000) /* 12000 in 2007 */
1664 g_free(sbuddy->activity);
1665 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1670 /* then set status_id actually */
1671 purple_debug_info("sipe", "sipe_apply_calendar_status: to %s for %s\n", status_id, sbuddy->name ? sbuddy->name : "" );
1672 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1674 /* set our account state to the one in roaming (including calendar info) */
1675 self_uri = sip_uri_self(sip);
1676 if (sip->initial_state_published && sipe_strequal(sbuddy->name, self_uri)) {
1677 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1678 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1681 purple_debug_info("sipe", "sipe_apply_calendar_status: switch to '%s' for the account\n", sip->status);
1682 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1684 g_free(self_uri);
1687 static void
1688 sipe_got_user_status(struct sipe_account_data *sip,
1689 const char* uri,
1690 const char *status_id)
1692 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1694 if (!sbuddy) return;
1696 /* Check if on 2005 system contact's calendar,
1697 * then set/preserve it.
1699 if (!sip->ocs2007) {
1700 sipe_apply_calendar_status(sip, sbuddy, status_id);
1701 } else {
1702 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1706 static void
1707 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1708 struct sipe_buddy *sbuddy,
1709 struct sipe_account_data *sip)
1711 sipe_apply_calendar_status(sip, sbuddy, NULL);
1715 * Updates contact's status
1716 * based on their calendar information.
1718 * Applicability: 2005 systems
1720 static void
1721 update_calendar_status(struct sipe_account_data *sip)
1723 purple_debug_info("sipe", "update_calendar_status() started.\n");
1724 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1726 /* repeat scheduling */
1727 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1731 * Schedules process of contacts' status update
1732 * based on their calendar information.
1733 * Should be scheduled to the beginning of every
1734 * 15 min interval, like:
1735 * 13:00, 13:15, 13:30, 13:45, etc.
1737 * Applicability: 2005 systems
1739 static void
1740 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1741 time_t calculate_from)
1743 int interval = 15*60;
1744 /** start of the beginning of closest 15 min interval. */
1745 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1747 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1748 asctime(localtime(&calculate_from)));
1749 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1750 asctime(localtime(&next_start)));
1752 sipe_schedule_action("<+2005-cal-status>",
1753 (int)(next_start - time(NULL)),
1754 (Action)update_calendar_status,
1755 NULL,
1756 sip,
1757 NULL);
1761 * Schedules process of self status publish
1762 * based on own calendar information.
1763 * Should be scheduled to the beginning of every
1764 * 15 min interval, like:
1765 * 13:00, 13:15, 13:30, 13:45, etc.
1767 * Applicability: 2007+ systems
1769 static void
1770 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1771 time_t calculate_from)
1773 int interval = 5*60;
1774 /** start of the beginning of closest 5 min interval. */
1775 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1777 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1778 asctime(localtime(&calculate_from)));
1779 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1780 asctime(localtime(&next_start)));
1782 sipe_schedule_action("<+2007-cal-status>",
1783 (int)(next_start - time(NULL)),
1784 (Action)publish_calendar_status_self,
1785 NULL,
1786 sip,
1787 NULL);
1790 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1792 /** Should be g_free()'d
1794 static gchar *
1795 sipe_get_subscription_key(const gchar *event,
1796 const gchar *with)
1798 gchar *key = NULL;
1800 if (is_empty(event)) return NULL;
1802 if (event && !g_ascii_strcasecmp(event, "presence")) {
1803 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1804 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1806 /* @TODO drop participated buddies' just_added flag */
1807 } else if (event) {
1808 /* Subscription is identified by <event> key */
1809 key = g_strdup_printf("<%s>", event);
1812 return key;
1815 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1816 SIPE_UNUSED_PARAMETER struct transaction *trans)
1818 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1819 const gchar *event = sipmsg_find_header(msg, "Event");
1820 gchar *key;
1822 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1823 if (!event) {
1824 struct sipmsg *request_msg = trans->msg;
1825 event = sipmsg_find_header(request_msg, "Event");
1828 key = sipe_get_subscription_key(event, with);
1830 /* 200 OK; 481 Call Leg Does Not Exist */
1831 if (key && (msg->response == 200 || msg->response == 481)) {
1832 if (g_hash_table_lookup(sip->subscriptions, key)) {
1833 g_hash_table_remove(sip->subscriptions, key);
1834 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1838 /* create/store subscription dialog if not yet */
1839 if (msg->response == 200) {
1840 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1841 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1843 if (key) {
1844 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1845 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1847 subscription->dialog.callid = g_strdup(callid);
1848 subscription->dialog.cseq = atoi(cseq);
1849 subscription->dialog.with = g_strdup(with);
1850 subscription->event = g_strdup(event);
1851 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1853 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1856 g_free(cseq);
1859 g_free(key);
1860 g_free(with);
1862 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1864 process_incoming_notify(sip, msg, FALSE, FALSE);
1866 return TRUE;
1869 static void sipe_subscribe_resource_uri(const char *name,
1870 SIPE_UNUSED_PARAMETER gpointer value,
1871 gchar **resources_uri)
1873 gchar *tmp = *resources_uri;
1874 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1875 g_free(tmp);
1878 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1880 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1881 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1882 gchar *tmp = *resources_uri;
1884 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1886 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1887 g_free(tmp);
1891 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1892 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1893 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1894 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1895 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1898 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1900 gchar *key;
1901 gchar *contact = get_contact(sip);
1902 gchar *request;
1903 gchar *content;
1904 gchar *require = "";
1905 gchar *accept = "";
1906 gchar *autoextend = "";
1907 gchar *content_type;
1908 struct sip_dialog *dialog;
1910 if (sip->ocs2007) {
1911 require = ", categoryList";
1912 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1913 content_type = "application/msrtc-adrl-categorylist+xml";
1914 content = g_strdup_printf(
1915 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1916 "<action name=\"subscribe\" id=\"63792024\">\n"
1917 "<adhocList>\n%s</adhocList>\n"
1918 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1919 "<category name=\"calendarData\"/>\n"
1920 "<category name=\"contactCard\"/>\n"
1921 "<category name=\"note\"/>\n"
1922 "<category name=\"state\"/>\n"
1923 "</categoryList>\n"
1924 "</action>\n"
1925 "</batchSub>", sip->username, resources_uri);
1926 } else {
1927 autoextend = "Supported: com.microsoft.autoextend\r\n";
1928 content_type = "application/adrl+xml";
1929 content = g_strdup_printf(
1930 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1931 "<create xmlns=\"\">\n%s</create>\n"
1932 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1934 g_free(resources_uri);
1936 request = g_strdup_printf(
1937 "Require: adhoclist%s\r\n"
1938 "Supported: eventlist\r\n"
1939 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1940 "Supported: ms-piggyback-first-notify\r\n"
1941 "%sSupported: ms-benotify\r\n"
1942 "Proxy-Require: ms-benotify\r\n"
1943 "Event: presence\r\n"
1944 "Content-Type: %s\r\n"
1945 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1946 g_free(contact);
1948 /* subscribe to buddy presence */
1949 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1950 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1951 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1952 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1954 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1956 g_free(content);
1957 g_free(to);
1958 g_free(request);
1959 g_free(key);
1962 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1963 SIPE_UNUSED_PARAMETER void *unused)
1965 gchar *to = sip_uri_self(sip);
1966 gchar *resources_uri = g_strdup("");
1967 if (sip->ocs2007) {
1968 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1969 } else {
1970 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1973 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1976 struct presence_batched_routed {
1977 gchar *host;
1978 GSList *buddies;
1981 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1983 struct presence_batched_routed *data = payload;
1984 GSList *buddies = data->buddies;
1985 while (buddies) {
1986 g_free(buddies->data);
1987 buddies = buddies->next;
1989 g_slist_free(data->buddies);
1990 g_free(data->host);
1991 g_free(payload);
1994 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1996 struct presence_batched_routed *data = payload;
1997 GSList *buddies = data->buddies;
1998 gchar *resources_uri = g_strdup("");
1999 while (buddies) {
2000 gchar *tmp = resources_uri;
2001 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2002 g_free(tmp);
2003 buddies = buddies->next;
2005 sipe_subscribe_presence_batched_to(sip, resources_uri,
2006 g_strdup(data->host));
2010 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2011 * The user sends a single SUBSCRIBE request to the subscribed contact.
2012 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2016 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2019 gchar *key;
2020 gchar *to = sip_uri((char *)buddy_name);
2021 gchar *tmp = get_contact(sip);
2022 gchar *request;
2023 gchar *content = NULL;
2024 gchar *autoextend = "";
2025 gchar *content_type = "";
2026 struct sip_dialog *dialog;
2027 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2028 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2030 if (sbuddy) sbuddy->just_added = FALSE;
2032 if (sip->ocs2007) {
2033 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2034 } else {
2035 autoextend = "Supported: com.microsoft.autoextend\r\n";
2038 request = g_strdup_printf(
2039 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2040 "Supported: ms-piggyback-first-notify\r\n"
2041 "%s%sSupported: ms-benotify\r\n"
2042 "Proxy-Require: ms-benotify\r\n"
2043 "Event: presence\r\n"
2044 "Contact: %s\r\n", autoextend, content_type, tmp);
2046 if (sip->ocs2007) {
2047 content = g_strdup_printf(
2048 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2049 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2050 "<resource uri=\"%s\"%s\n"
2051 "</adhocList>\n"
2052 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2053 "<category name=\"calendarData\"/>\n"
2054 "<category name=\"contactCard\"/>\n"
2055 "<category name=\"note\"/>\n"
2056 "<category name=\"state\"/>\n"
2057 "</categoryList>\n"
2058 "</action>\n"
2059 "</batchSub>", sip->username, to, context);
2062 g_free(tmp);
2064 /* subscribe to buddy presence */
2065 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2066 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2067 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2068 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2070 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2072 g_free(content);
2073 g_free(to);
2074 g_free(request);
2075 g_free(key);
2078 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2080 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2082 if (!purple_status_is_active(status))
2083 return;
2085 if (account->gc) {
2086 struct sipe_account_data *sip = account->gc->proto_data;
2088 if (sip) {
2089 gchar *action_name;
2090 gchar *tmp;
2091 time_t now = time(NULL);
2092 const char *status_id = purple_status_get_id(status);
2093 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2094 sipe_activity activity = sipe_get_activity_by_token(status_id);
2095 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2097 /* when other point of presence clears note, but we are keeping
2098 * state if OOF note.
2100 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2101 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2102 do_not_publish = FALSE;
2105 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2106 status_id, (int)sip->do_not_publish[activity], (int)now);
2108 sip->do_not_publish[activity] = 0;
2109 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2110 status_id, (int)sip->do_not_publish[activity]);
2112 if (do_not_publish)
2114 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2115 return;
2118 g_free(sip->status);
2119 sip->status = g_strdup(status_id);
2121 /* hack to escape apostrof before comparison */
2122 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2124 /* this will preserve OOF flag as well */
2125 if (!sipe_strequal(tmp, sip->note)) {
2126 sip->is_oof_note = FALSE;
2127 g_free(sip->note);
2128 sip->note = g_strdup(note);
2129 sip->note_since = time(NULL);
2131 g_free(tmp);
2133 /* schedule 2 sec to capture idle flag */
2134 action_name = g_strdup_printf("<%s>", "+set-status");
2135 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2136 g_free(action_name);
2140 static void
2141 sipe_set_idle(PurpleConnection * gc,
2142 int interval)
2144 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2146 if (gc) {
2147 struct sipe_account_data *sip = gc->proto_data;
2149 if (sip) {
2150 sip->idle_switch = time(NULL);
2151 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2156 static void
2157 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2158 SIPE_UNUSED_PARAMETER const char *alias)
2160 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2161 sipe_group_set_user(sip, name);
2164 static void
2165 sipe_group_buddy(PurpleConnection *gc,
2166 const char *who,
2167 const char *old_group_name,
2168 const char *new_group_name)
2170 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2171 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2172 struct sipe_group * old_group = NULL;
2173 struct sipe_group * new_group;
2175 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2176 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2178 if(!buddy) { // buddy not in roaming list
2179 return;
2182 if (old_group_name) {
2183 old_group = sipe_group_find_by_name(sip, old_group_name);
2185 new_group = sipe_group_find_by_name(sip, new_group_name);
2187 if (old_group) {
2188 buddy->groups = g_slist_remove(buddy->groups, old_group);
2189 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2192 if (!new_group) {
2193 sipe_group_create(sip, new_group_name, who);
2194 } else {
2195 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2196 sipe_group_set_user(sip, who);
2200 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2202 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2204 /* libpurple can call us with undefined buddy or group */
2205 if (buddy && group) {
2206 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2208 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2209 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2210 purple_blist_rename_buddy(buddy, buddy_name);
2211 g_free(buddy_name);
2213 /* Prepend sip: if needed */
2214 if (!g_str_has_prefix(buddy->name, "sip:")) {
2215 gchar *buf = sip_uri_from_name(buddy->name);
2216 purple_blist_rename_buddy(buddy, buf);
2217 g_free(buf);
2220 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2221 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2222 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2223 b->name = g_strdup(buddy->name);
2224 b->just_added = TRUE;
2225 g_hash_table_insert(sip->buddies, b->name, b);
2226 sipe_group_buddy(gc, b->name, NULL, group->name);
2227 /* @TODO should go to callback */
2228 sipe_subscribe_presence_single(sip, b->name);
2229 } else {
2230 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2235 static void sipe_free_buddy(struct sipe_buddy *buddy)
2237 #ifndef _WIN32
2239 * We are calling g_hash_table_foreach_steal(). That means that no
2240 * key/value deallocation functions are called. Therefore the glib
2241 * hash code does not touch the key (buddy->name) or value (buddy)
2242 * of the to-be-deleted hash node at all. It follows that we
2244 * - MUST free the memory for the key ourselves and
2245 * - ARE allowed to do it in this function
2247 * Conclusion: glib must be broken on the Windows platform if sipe
2248 * crashes with SIGTRAP when closing. You'll have to live
2249 * with the memory leak until this is fixed.
2251 g_free(buddy->name);
2252 #endif
2253 g_free(buddy->activity);
2254 g_free(buddy->meeting_subject);
2255 g_free(buddy->meeting_location);
2256 g_free(buddy->note);
2258 g_free(buddy->cal_start_time);
2259 g_free(buddy->cal_free_busy_base64);
2260 g_free(buddy->cal_free_busy);
2261 g_free(buddy->last_non_cal_activity);
2263 sipe_cal_free_working_hours(buddy->cal_working_hours);
2265 g_free(buddy->device_name);
2266 g_slist_free(buddy->groups);
2267 g_free(buddy);
2271 * Unassociates buddy from group first.
2272 * Then see if no groups left, removes buddy completely.
2273 * Otherwise updates buddy groups on server.
2275 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2277 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2278 struct sipe_buddy *b;
2279 struct sipe_group *g = NULL;
2281 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2282 if (!buddy) return;
2284 b = g_hash_table_lookup(sip->buddies, buddy->name);
2285 if (!b) return;
2287 if (group) {
2288 g = sipe_group_find_by_name(sip, group->name);
2291 if (g) {
2292 b->groups = g_slist_remove(b->groups, g);
2293 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2296 if (g_slist_length(b->groups) < 1) {
2297 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2298 sipe_cancel_scheduled_action(sip, action_name);
2299 g_free(action_name);
2301 g_hash_table_remove(sip->buddies, buddy->name);
2303 if (b->name) {
2304 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2305 send_soap_request(sip, body);
2306 g_free(body);
2309 sipe_free_buddy(b);
2310 } else {
2311 //updates groups on server
2312 sipe_group_set_user(sip, b->name);
2317 static void
2318 sipe_rename_group(PurpleConnection *gc,
2319 const char *old_name,
2320 PurpleGroup *group,
2321 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2323 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2324 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2325 if (s_group) {
2326 sipe_group_rename(sip, s_group, group->name);
2327 } else {
2328 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2332 static void
2333 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2335 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2336 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2337 if (s_group) {
2338 gchar *body;
2339 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2340 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2341 send_soap_request(sip, body);
2342 g_free(body);
2344 sip->groups = g_slist_remove(sip->groups, s_group);
2345 g_free(s_group->name);
2346 g_free(s_group);
2347 } else {
2348 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2352 /** All statuses need message attribute to pass Note */
2353 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2355 PurpleStatusType *type;
2356 GList *types = NULL;
2358 /* Macros to reduce code repetition.
2359 Translators: noun */
2360 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2361 prim, id, name, \
2362 TRUE, user, FALSE, \
2363 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2364 NULL); \
2365 types = g_list_append(types, type);
2367 /* Online */
2368 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2369 NULL,
2370 NULL,
2371 TRUE);
2373 /* Busy */
2374 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2375 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2376 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2377 TRUE);
2379 /* Do Not Disturb */
2380 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2381 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2382 NULL,
2383 TRUE);
2385 /* Away */
2386 /* Goes first in the list as
2387 * purple picks the first status with the AWAY type
2388 * for idle.
2390 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2391 NULL,
2392 NULL,
2393 TRUE);
2395 /* Be Right Back */
2396 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2397 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2398 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2399 TRUE);
2401 /* Appear Offline */
2402 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2403 NULL,
2404 NULL,
2405 TRUE);
2407 /* Offline */
2408 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2409 NULL,
2410 NULL,
2411 TRUE);
2412 types = g_list_append(types, type);
2414 return types;
2418 * A callback for g_hash_table_foreach
2420 static void
2421 sipe_buddy_subscribe_cb(char *buddy_name,
2422 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2423 struct sipe_account_data *sip)
2425 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2426 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2427 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2428 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2430 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2431 g_free(action_name);
2435 * Removes entries from purple buddy list
2436 * that does not correspond ones in the roaming contact list.
2438 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2439 GSList *buddies = purple_find_buddies(sip->account, NULL);
2440 GSList *entry = buddies;
2441 struct sipe_buddy *buddy;
2442 PurpleBuddy *b;
2443 PurpleGroup *g;
2445 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2446 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2447 while (entry) {
2448 b = entry->data;
2449 g = purple_buddy_get_group(b);
2450 buddy = g_hash_table_lookup(sip->buddies, b->name);
2451 if(buddy) {
2452 gboolean in_sipe_groups = FALSE;
2453 GSList *entry2 = buddy->groups;
2454 while (entry2) {
2455 struct sipe_group *group = entry2->data;
2456 if (sipe_strequal(group->name, g->name)) {
2457 in_sipe_groups = TRUE;
2458 break;
2460 entry2 = entry2->next;
2462 if(!in_sipe_groups) {
2463 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2464 purple_blist_remove_buddy(b);
2466 } else {
2467 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2468 purple_blist_remove_buddy(b);
2470 entry = entry->next;
2472 g_slist_free(buddies);
2475 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2477 int len = msg->bodylen;
2479 const gchar *tmp = sipmsg_find_header(msg, "Event");
2480 xmlnode *item;
2481 xmlnode *isc;
2482 const gchar *contacts_delta;
2483 xmlnode *group_node;
2484 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2485 return FALSE;
2488 /* Convert the contact from XML to Purple Buddies */
2489 isc = xmlnode_from_str(msg->body, len);
2490 if (!isc) {
2491 return FALSE;
2494 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2495 if (contacts_delta) {
2496 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2499 if (sipe_strequal(isc->name, "contactList")) {
2501 /* Parse groups */
2502 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2503 struct sipe_group * group = g_new0(struct sipe_group, 1);
2504 const char *name = xmlnode_get_attrib(group_node, "name");
2506 if (g_str_has_prefix(name, "~")) {
2507 name = _("Other Contacts");
2509 group->name = g_strdup(name);
2510 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2512 sipe_group_add(sip, group);
2515 // Make sure we have at least one group
2516 if (g_slist_length(sip->groups) == 0) {
2517 struct sipe_group * group = g_new0(struct sipe_group, 1);
2518 PurpleGroup *purple_group;
2519 group->name = g_strdup(_("Other Contacts"));
2520 group->id = 1;
2521 purple_group = purple_group_new(group->name);
2522 purple_blist_add_group(purple_group, NULL);
2523 sip->groups = g_slist_append(sip->groups, group);
2526 /* Parse contacts */
2527 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2528 const gchar *uri = xmlnode_get_attrib(item, "uri");
2529 const gchar *name = xmlnode_get_attrib(item, "name");
2530 gchar *buddy_name;
2531 struct sipe_buddy *buddy = NULL;
2532 gchar *tmp;
2533 gchar **item_groups;
2534 int i = 0;
2536 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2537 tmp = sip_uri_from_name(uri);
2538 buddy_name = g_ascii_strdown(tmp, -1);
2539 g_free(tmp);
2541 /* assign to group Other Contacts if nothing else received */
2542 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2543 if(is_empty(tmp)) {
2544 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2545 g_free(tmp);
2546 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2548 item_groups = g_strsplit(tmp, " ", 0);
2549 g_free(tmp);
2551 while (item_groups[i]) {
2552 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2554 // If couldn't find the right group for this contact, just put them in the first group we have
2555 if (group == NULL && g_slist_length(sip->groups) > 0) {
2556 group = sip->groups->data;
2559 if (group != NULL) {
2560 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2561 if (!b){
2562 b = purple_buddy_new(sip->account, buddy_name, uri);
2563 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2565 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2568 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2569 if (name != NULL && strlen(name) != 0) {
2570 purple_blist_alias_buddy(b, name);
2572 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2576 if (!buddy) {
2577 buddy = g_new0(struct sipe_buddy, 1);
2578 buddy->name = g_strdup(b->name);
2579 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2582 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2584 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2585 } else {
2586 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2587 name);
2590 i++;
2591 } // while, contact groups
2592 g_strfreev(item_groups);
2593 g_free(buddy_name);
2595 } // for, contacts
2597 sipe_cleanup_local_blist(sip);
2599 /* Add self-contact if not there yet. 2005 systems. */
2600 /* This will resemble subscription to roaming_self in 2007 systems */
2601 if (!sip->ocs2007) {
2602 gchar *self_uri = sip_uri_self(sip);
2603 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2605 if (!buddy) {
2606 buddy = g_new0(struct sipe_buddy, 1);
2607 buddy->name = g_strdup(self_uri);
2608 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2610 g_free(self_uri);
2613 xmlnode_free(isc);
2615 /* subscribe to buddies */
2616 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2617 if (sip->batched_support) {
2618 sipe_subscribe_presence_batched(sip, NULL);
2619 } else {
2620 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2622 sip->subscribed_buddies = TRUE;
2624 /* for 2005 systems schedule contacts' status update
2625 * based on their calendar information
2627 if (!sip->ocs2007) {
2628 sipe_sched_calendar_status_update(sip, time(NULL));
2631 return 0;
2635 * Subscribe roaming contacts
2637 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2639 gchar *to = sip_uri_self(sip);
2640 gchar *tmp = get_contact(sip);
2641 gchar *hdr = g_strdup_printf(
2642 "Event: vnd-microsoft-roaming-contacts\r\n"
2643 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2644 "Supported: com.microsoft.autoextend\r\n"
2645 "Supported: ms-benotify\r\n"
2646 "Proxy-Require: ms-benotify\r\n"
2647 "Supported: ms-piggyback-first-notify\r\n"
2648 "Contact: %s\r\n", tmp);
2649 g_free(tmp);
2651 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2652 g_free(to);
2653 g_free(hdr);
2656 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2657 SIPE_UNUSED_PARAMETER void *unused)
2659 gchar *key;
2660 struct sip_dialog *dialog;
2661 gchar *to = sip_uri_self(sip);
2662 gchar *tmp = get_contact(sip);
2663 gchar *hdr = g_strdup_printf(
2664 "Event: presence.wpending\r\n"
2665 "Accept: text/xml+msrtc.wpending\r\n"
2666 "Supported: com.microsoft.autoextend\r\n"
2667 "Supported: ms-benotify\r\n"
2668 "Proxy-Require: ms-benotify\r\n"
2669 "Supported: ms-piggyback-first-notify\r\n"
2670 "Contact: %s\r\n", tmp);
2671 g_free(tmp);
2673 /* Subscription is identified by <event> key */
2674 key = g_strdup_printf("<%s>", "presence.wpending");
2675 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2676 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2678 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2680 g_free(to);
2681 g_free(hdr);
2682 g_free(key);
2686 * Fires on deregistration event initiated by server.
2687 * [MS-SIPREGE] SIP extension.
2690 // 2007 Example
2692 // Content-Type: text/registration-event
2693 // subscription-state: terminated;expires=0
2694 // ms-diagnostics-public: 4141;reason="User disabled"
2696 // deregistered;event=rejected
2698 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2700 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2701 gchar *event = NULL;
2702 gchar *reason = NULL;
2703 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2704 gchar *warning;
2706 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2707 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2709 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2710 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2711 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2712 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2713 } else {
2714 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2715 return;
2718 if (diagnostics != NULL) {
2719 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2720 } else { // for LCS2005
2721 int error_id = 0;
2722 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2723 error_id = 4140; // [MS-SIPREGE]
2724 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2725 reason = g_strdup(_("you are already signed in at another location"));
2726 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2727 error_id = 4141;
2728 reason = g_strdup(_("user disabled")); // [MS-OCER]
2729 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2730 error_id = 4142;
2731 reason = g_strdup(_("user moved")); // [MS-OCER]
2734 g_free(event);
2735 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2736 g_free(reason);
2738 sip->gc->wants_to_die = TRUE;
2739 purple_connection_error(sip->gc, warning);
2740 g_free(warning);
2744 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2746 xmlnode *xn_provision_group_list;
2747 xmlnode *node;
2749 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2751 /* provisionGroup */
2752 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2753 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2754 g_free(sip->focus_factory_uri);
2755 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2756 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2757 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2758 break;
2761 xmlnode_free(xn_provision_group_list);
2764 /** for 2005 system */
2765 static void
2766 sipe_process_provisioning(struct sipe_account_data *sip,
2767 struct sipmsg *msg)
2769 xmlnode *xn_provision;
2770 xmlnode *node;
2772 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2773 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2774 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2775 if ((node = xmlnode_get_child(node, "line"))) {
2776 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2777 const gchar *server = xmlnode_get_attrib(node, "server");
2778 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2779 sip_csta_open(sip, line_uri, server);
2782 xmlnode_free(xn_provision);
2785 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2787 const gchar *contacts_delta;
2788 xmlnode *xml;
2790 xml = xmlnode_from_str(msg->body, msg->bodylen);
2791 if (!xml)
2793 return;
2796 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2797 if (contacts_delta)
2799 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2802 xmlnode_free(xml);
2805 static void
2806 free_container(struct sipe_container *container)
2808 GSList *entry;
2810 if (!container) return;
2812 entry = container->members;
2813 while (entry) {
2814 void *data = entry->data;
2815 entry = g_slist_remove(entry, data);
2816 g_free(data);
2818 g_free(container);
2822 * Finds locally stored MS-PRES container member
2824 static struct sipe_container_member *
2825 sipe_find_container_member(struct sipe_container *container,
2826 const gchar *type,
2827 const gchar *value)
2829 struct sipe_container_member *member;
2830 GSList *entry;
2832 if (container == NULL || type == NULL) {
2833 return NULL;
2836 entry = container->members;
2837 while (entry) {
2838 member = entry->data;
2839 if (!g_strcasecmp(member->type, type)
2840 && ((!member->value && !value)
2841 || (value && member->value && !g_strcasecmp(member->value, value)))
2843 return member;
2845 entry = entry->next;
2847 return NULL;
2851 * Finds locally stored MS-PRES container by id
2853 static struct sipe_container *
2854 sipe_find_container(struct sipe_account_data *sip,
2855 guint id)
2857 struct sipe_container *container;
2858 GSList *entry;
2860 if (sip == NULL) {
2861 return NULL;
2864 entry = sip->containers;
2865 while (entry) {
2866 container = entry->data;
2867 if (id == container->id) {
2868 return container;
2870 entry = entry->next;
2872 return NULL;
2876 * Access Levels
2877 * 32000 - Blocked
2878 * 400 - Personal
2879 * 300 - Team
2880 * 200 - Company
2881 * 100 - Public
2883 static int
2884 sipe_find_access_level(struct sipe_account_data *sip,
2885 const gchar *type,
2886 const gchar *value)
2888 guint containers[] = {32000, 400, 300, 200, 100};
2889 int i = 0;
2891 for (i = 0; i < 5; i++) {
2892 struct sipe_container_member *member;
2893 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2894 if (!container) continue;
2896 member = sipe_find_container_member(container, type, value);
2897 if (member) {
2898 return containers[i];
2902 return -1;
2905 static void
2906 sipe_send_set_container_members(struct sipe_account_data *sip,
2907 guint container_id,
2908 guint container_version,
2909 const gchar* action,
2910 const gchar* type,
2911 const gchar* value)
2913 gchar *self = sip_uri_self(sip);
2914 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2915 gchar *contact;
2916 gchar *hdr;
2917 gchar *body = g_strdup_printf(
2918 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2919 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2920 "</setContainerMembers>",
2921 container_id,
2922 container_version,
2923 action,
2924 type,
2925 value_str);
2926 g_free(value_str);
2928 contact = get_contact(sip);
2929 hdr = g_strdup_printf("Contact: %s\r\n"
2930 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2931 g_free(contact);
2933 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2935 g_free(hdr);
2936 g_free(body);
2937 g_free(self);
2940 static void
2941 free_publication(struct sipe_publication *publication)
2943 g_free(publication->category);
2944 g_free(publication->cal_event_hash);
2945 g_free(publication->note);
2947 g_free(publication->working_hours_xml_str);
2948 g_free(publication->fb_start_str);
2949 g_free(publication->free_busy_base64);
2951 g_free(publication);
2954 /* key is <category><instance><container> */
2955 static gboolean
2956 sipe_is_our_publication(struct sipe_account_data *sip,
2957 const gchar *key)
2959 GSList *entry;
2961 /* filling keys for our publications if not yet cached */
2962 if (!sip->our_publication_keys) {
2963 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2964 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2965 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2966 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2967 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2968 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2969 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2971 purple_debug_info("sipe", "* Our Publication Instances *\n");
2972 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2973 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2974 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2975 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2976 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2977 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2978 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2979 purple_debug_info("sipe", "\tNote : %u\n", 0);
2980 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2982 /* device */
2983 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2984 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2986 /* state:machineState */
2987 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2988 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2989 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2990 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2992 /* state:userState */
2993 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2994 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2995 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2996 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2998 /* state:calendarState */
2999 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3000 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3001 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3002 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3004 /* state:calendarState OOF */
3005 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3006 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3007 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3008 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3010 /* note */
3011 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3012 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3013 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3014 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3015 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3016 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3018 /* note OOF */
3019 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3020 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3021 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3022 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3023 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3024 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3026 /* calendarData:WorkingHours */
3027 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3028 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3029 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3030 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3031 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3032 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3033 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3034 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3035 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3036 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3037 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3038 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3040 /* calendarData:FreeBusy */
3041 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3042 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3043 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3044 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3045 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3046 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3047 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3048 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3049 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3050 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3051 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3052 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3054 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
3055 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3058 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3060 entry = sip->our_publication_keys;
3061 while (entry) {
3062 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3063 if (sipe_strequal(entry->data, key)) {
3064 return TRUE;
3066 entry = entry->next;
3068 return FALSE;
3071 /** Property names to store in blist.xml */
3072 #define ALIAS_PROP "alias"
3073 #define EMAIL_PROP "email"
3074 #define PHONE_PROP "phone"
3075 #define PHONE_DISPLAY_PROP "phone-display"
3076 #define PHONE_MOBILE_PROP "phone-mobile"
3077 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3078 #define PHONE_HOME_PROP "phone-home"
3079 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3080 #define PHONE_OTHER_PROP "phone-other"
3081 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3082 #define PHONE_CUSTOM1_PROP "phone-custom1"
3083 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3084 #define SITE_PROP "site"
3085 #define COMPANY_PROP "company"
3086 #define DEPARTMENT_PROP "department"
3087 #define TITLE_PROP "title"
3088 #define OFFICE_PROP "office"
3089 /** implies work address */
3090 #define ADDRESS_STREET_PROP "address-street"
3091 #define ADDRESS_CITY_PROP "address-city"
3092 #define ADDRESS_STATE_PROP "address-state"
3093 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3094 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3097 * Tries to figure out user first and last name
3098 * based on Display Name and email properties.
3100 * Allocates memory - must be g_free()'d
3102 * Examples to parse:
3103 * First Last
3104 * First Last - Company Name
3105 * Last, First
3106 * Last, First M.
3107 * Last, First (C)(STP) (Company)
3108 * first.last@company.com (preprocessed as "first last")
3109 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3111 * Unusable examples:
3112 * user@company.com (preprocessed as "user")
3113 * first.m.last@company.com (preprocessed as "first m last")
3114 * user.company.com@reuters.net (preprocessed as "user company com")
3116 static void
3117 sipe_get_first_last_names(struct sipe_account_data *sip,
3118 const char *uri,
3119 char **first_name,
3120 char **last_name)
3122 PurpleBuddy *p_buddy;
3123 char *display_name;
3124 const char *email;
3125 const char *first, *last;
3126 char *tmp;
3127 char **parts;
3128 gboolean has_comma = FALSE;
3130 if (!sip || !uri) return;
3132 p_buddy = purple_find_buddy(sip->account, uri);
3134 if (!p_buddy) return;
3136 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3137 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3139 if (!display_name && !email) return;
3141 /* if no display name, make "first last anything_else" out of email */
3142 if (email && !display_name) {
3143 display_name = g_strndup(email, strstr(email, "@") - email);
3144 display_name = purple_strreplace((tmp = display_name), ".", " ");
3145 g_free(tmp);
3148 if (display_name) {
3149 has_comma = (strstr(display_name, ",") != NULL);
3150 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3151 g_free(tmp);
3152 display_name = purple_strreplace((tmp = display_name), ",", " ");
3153 g_free(tmp);
3156 parts = g_strsplit(display_name, " ", 0);
3158 if (!parts[0] || !parts[1]) {
3159 g_free(display_name);
3160 g_strfreev(parts);
3161 return;
3164 if (has_comma) {
3165 last = parts[0];
3166 first = parts[1];
3167 } else {
3168 first = parts[0];
3169 last = parts[1];
3172 if (first_name) {
3173 *first_name = g_strstrip(g_strdup(first));
3176 if (last_name) {
3177 *last_name = g_strstrip(g_strdup(last));
3180 g_free(display_name);
3181 g_strfreev(parts);
3185 * Update user information
3187 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3188 * @param property_name
3189 * @param property_value may be modified to strip white space
3191 static void
3192 sipe_update_user_info(struct sipe_account_data *sip,
3193 const char *uri,
3194 const char *property_name,
3195 char *property_value)
3197 GSList *buddies, *entry;
3199 if (!property_name || strlen(property_name) == 0) return;
3201 if (property_value)
3202 property_value = g_strstrip(property_value);
3204 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3205 while (entry) {
3206 const char *prop_str;
3207 const char *server_alias;
3208 PurpleBuddy *p_buddy = entry->data;
3210 /* for Display Name */
3211 if (sipe_strequal(property_name, ALIAS_PROP)) {
3212 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3213 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3214 purple_blist_alias_buddy(p_buddy, property_value);
3217 server_alias = purple_buddy_get_server_alias(p_buddy);
3218 if (!is_empty(property_value) &&
3219 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3221 purple_blist_server_alias_buddy(p_buddy, property_value);
3224 /* for other properties */
3225 else {
3226 if (!is_empty(property_value)) {
3227 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3228 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
3229 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3234 entry = entry->next;
3236 g_slist_free(buddies);
3240 * Update user phone
3241 * Suitable for both 2005 and 2007 systems.
3243 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3244 * @param phone_type
3245 * @param phone may be modified to strip white space
3246 * @param phone_display_string may be modified to strip white space
3248 static void
3249 sipe_update_user_phone(struct sipe_account_data *sip,
3250 const char *uri,
3251 const gchar *phone_type,
3252 gchar *phone,
3253 gchar *phone_display_string)
3255 const char *phone_node = PHONE_PROP; /* work phone by default */
3256 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3258 if(!phone || strlen(phone) == 0) return;
3260 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3261 phone_node = PHONE_MOBILE_PROP;
3262 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3263 } else if (sipe_strequal(phone_type, "home")) {
3264 phone_node = PHONE_HOME_PROP;
3265 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3266 } else if (sipe_strequal(phone_type, "other")) {
3267 phone_node = PHONE_OTHER_PROP;
3268 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3269 } else if (sipe_strequal(phone_type, "custom1")) {
3270 phone_node = PHONE_CUSTOM1_PROP;
3271 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3274 sipe_update_user_info(sip, uri, phone_node, phone);
3275 if (phone_display_string) {
3276 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3280 static void
3281 sipe_update_calendar(struct sipe_account_data *sip)
3283 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3285 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3287 if (sipe_strequal(calendar, "EXCH")) {
3288 sipe_ews_update_calendar(sip);
3291 /* schedule repeat */
3292 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3294 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3298 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3299 * by using standard Purple's means of signals and saved statuses.
3301 * Thus all UI elements get updated: Status Button with Note, docklet.
3302 * This is ablolutely important as both our status and note can come
3303 * inbound (roaming) or be updated programmatically (e.g. based on our
3304 * calendar data).
3306 static void
3307 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3308 const char *status_id,
3309 const char *message,
3310 time_t do_not_publish[])
3312 PurpleStatus *status = purple_account_get_active_status(account);
3313 gboolean changed = TRUE;
3315 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3316 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3318 changed = FALSE;
3321 if (purple_savedstatus_is_idleaway()) {
3322 changed = FALSE;
3325 if (changed) {
3326 PurpleSavedStatus *saved_status;
3327 const PurpleStatusType *acct_status_type =
3328 purple_status_type_find_with_id(account->status_types, status_id);
3329 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3330 sipe_activity activity = sipe_get_activity_by_token(status_id);
3332 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3333 if (saved_status) {
3334 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3337 /* If this type+message is unique then create a new transient saved status
3338 * Ref: gtkstatusbox.c
3340 if (!saved_status) {
3341 GList *tmp;
3342 GList *active_accts = purple_accounts_get_all_active();
3344 saved_status = purple_savedstatus_new(NULL, primitive);
3345 purple_savedstatus_set_message(saved_status, message);
3347 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3348 purple_savedstatus_set_substatus(saved_status,
3349 (PurpleAccount *)tmp->data, acct_status_type, message);
3351 g_list_free(active_accts);
3354 do_not_publish[activity] = time(NULL);
3355 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3356 status_id, (int)do_not_publish[activity]);
3358 /* Set the status for each account */
3359 purple_savedstatus_activate(saved_status);
3363 struct hash_table_delete_payload {
3364 GHashTable *hash_table;
3365 guint container;
3368 static void
3369 sipe_remove_category_container_publications_cb(const char *name,
3370 struct sipe_publication *publication,
3371 struct hash_table_delete_payload *payload)
3373 if (publication->container == payload->container) {
3374 g_hash_table_remove(payload->hash_table, name);
3377 static void
3378 sipe_remove_category_container_publications(GHashTable *our_publications,
3379 const char *category,
3380 guint container)
3382 struct hash_table_delete_payload payload;
3383 payload.hash_table = g_hash_table_lookup(our_publications, category);
3385 if (!payload.hash_table) return;
3387 payload.container = container;
3388 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3391 static void
3392 send_publish_category_initial(struct sipe_account_data *sip);
3395 * When we receive some self (BE) NOTIFY with a new subscriber
3396 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3399 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3401 gchar *contact;
3402 gchar *to;
3403 xmlnode *xml;
3404 xmlnode *node;
3405 xmlnode *node2;
3406 char *display_name = NULL;
3407 char *uri;
3408 GSList *category_names = NULL;
3409 int aggreg_avail = 0;
3410 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3411 gboolean do_update_status = FALSE;
3412 gboolean has_note_cleaned = FALSE;
3414 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3416 xml = xmlnode_from_str(msg->body, msg->bodylen);
3417 if (!xml) return;
3419 contact = get_contact(sip);
3420 to = sip_uri_self(sip);
3423 /* categories */
3424 /* set list of categories participating in this XML */
3425 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3426 const gchar *name = xmlnode_get_attrib(node, "name");
3427 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3429 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3430 category_names ? (int) g_slist_length(category_names) : -1);
3431 /* drop category information */
3432 if (category_names) {
3433 GSList *entry = category_names;
3434 while (entry) {
3435 GHashTable *cat_publications;
3436 const gchar *category = entry->data;
3437 entry = entry->next;
3438 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3439 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3440 if (cat_publications) {
3441 g_hash_table_remove(sip->our_publications, category);
3442 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3446 g_slist_free(category_names);
3447 /* filling our categories reflected in roaming data */
3448 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3449 const char *tmp;
3450 const gchar *name = xmlnode_get_attrib(node, "name");
3451 guint container = xmlnode_get_int_attrib(node, "container", -1);
3452 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3453 guint version = xmlnode_get_int_attrib(node, "version", 0);
3454 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3455 sipe_utils_str_to_time(tmp) : 0;
3456 gchar *key;
3457 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3459 /* Ex. clear note: <category name="note"/> */
3460 if (container == (guint)-1) {
3461 g_free(sip->note);
3462 sip->note = NULL;
3463 do_update_status = TRUE;
3464 continue;
3467 /* Ex. clear note: <category name="note" container="200"/> */
3468 if (instance == (guint)-1) {
3469 if (container == 200) {
3470 g_free(sip->note);
3471 sip->note = NULL;
3472 do_update_status = TRUE;
3474 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3475 sipe_remove_category_container_publications(
3476 sip->our_publications, name, container);
3477 continue;
3480 /* key is <category><instance><container> */
3481 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3482 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3484 /* capture all userState publication for later clean up if required */
3485 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3486 xmlnode *xn_state = xmlnode_get_child(node, "state");
3488 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3489 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3490 publication->category = g_strdup(name);
3491 publication->instance = instance;
3492 publication->container = container;
3493 publication->version = version;
3495 if (!sip->user_state_publications) {
3496 sip->user_state_publications = g_hash_table_new_full(
3497 g_str_hash, g_str_equal,
3498 g_free, (GDestroyNotify)free_publication);
3500 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3501 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3502 key, version);
3506 if (sipe_is_our_publication(sip, key)) {
3507 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3509 publication->category = g_strdup(name);
3510 publication->instance = instance;
3511 publication->container = container;
3512 publication->version = version;
3514 /* filling publication->availability */
3515 if (sipe_strequal(name, "state")) {
3516 xmlnode *xn_state = xmlnode_get_child(node, "state");
3517 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3519 if (xn_avail) {
3520 gchar *avail_str = xmlnode_get_data(xn_avail);
3521 if (avail_str) {
3522 publication->availability = atoi(avail_str);
3524 g_free(avail_str);
3526 /* for calendarState */
3527 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3528 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3529 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3531 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3532 if (xn_activity) {
3533 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3534 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3536 event->is_meeting = TRUE;
3539 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3540 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3542 publication->cal_event_hash = sipe_cal_event_hash(event);
3543 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3544 publication->cal_event_hash);
3545 sipe_cal_event_free(event);
3548 /* filling publication->note */
3549 if (sipe_strequal(name, "note")) {
3550 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3552 if (!has_note_cleaned) {
3553 has_note_cleaned = TRUE;
3555 g_free(sip->note);
3556 sip->note = NULL;
3557 sip->note_since = publish_time;
3559 do_update_status = TRUE;
3562 g_free(publication->note);
3563 publication->note = NULL;
3564 if (xn_body) {
3565 char *tmp;
3567 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3568 g_free(tmp);
3569 if (publish_time >= sip->note_since) {
3570 g_free(sip->note);
3571 sip->note = g_strdup(publication->note);
3572 sip->note_since = publish_time;
3573 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3575 do_update_status = TRUE;
3580 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3581 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3582 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3583 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3584 if (xn_free_busy) {
3585 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3586 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3588 if (xn_working_hours) {
3589 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3593 if (!cat_publications) {
3594 cat_publications = g_hash_table_new_full(
3595 g_str_hash, g_str_equal,
3596 g_free, (GDestroyNotify)free_publication);
3597 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3598 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3600 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3601 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3603 g_free(key);
3605 /* aggregateState (not an our publication) from 2-nd container */
3606 if (sipe_strequal(name, "state") && container == 2) {
3607 xmlnode *xn_state = xmlnode_get_child(node, "state");
3609 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3610 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3611 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3613 if (xn_avail) {
3614 gchar *avail_str = xmlnode_get_data(xn_avail);
3615 if (avail_str) {
3616 aggreg_avail = atoi(avail_str);
3618 g_free(avail_str);
3621 if (xn_activity) {
3622 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3624 aggreg_activity = sipe_get_activity_by_token(activity_token);
3627 do_update_status = TRUE;
3631 /* userProperties published by server from AD */
3632 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3633 xmlnode *line;
3634 /* line, for Remote Call Control (RCC) */
3635 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3636 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3637 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3638 gchar *line_uri;
3640 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3642 line_uri = xmlnode_get_data(line);
3643 if (line_uri) {
3644 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3645 sip_csta_open(sip, line_uri, line_server);
3647 g_free(line_uri);
3649 break;
3653 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3654 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3656 /* containers */
3657 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3658 guint id = xmlnode_get_int_attrib(node, "id", 0);
3659 struct sipe_container *container = sipe_find_container(sip, id);
3661 if (container) {
3662 sip->containers = g_slist_remove(sip->containers, container);
3663 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3664 free_container(container);
3666 container = g_new0(struct sipe_container, 1);
3667 container->id = id;
3668 container->version = xmlnode_get_int_attrib(node, "version", 0);
3669 sip->containers = g_slist_append(sip->containers, container);
3670 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3672 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3673 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3674 member->type = xmlnode_get_attrib(node2, "type");
3675 member->value = xmlnode_get_attrib(node2, "value");
3676 container->members = g_slist_append(container->members, member);
3677 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3678 member->type, member->value ? member->value : "");
3682 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3683 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3684 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3685 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3686 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3687 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3688 /* initial set-up to let counterparties see your status */
3689 if (sameEnterpriseAL < 0) {
3690 struct sipe_container *container = sipe_find_container(sip, 200);
3691 guint version = container ? container->version : 0;
3692 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3694 if (federatedAL < 0) {
3695 struct sipe_container *container = sipe_find_container(sip, 100);
3696 guint version = container ? container->version : 0;
3697 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3699 sip->access_level_set = TRUE;
3702 /* subscribers */
3703 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3704 const char *user;
3705 const char *acknowledged;
3706 gchar *hdr;
3707 gchar *body;
3709 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3710 if (!user) continue;
3711 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3712 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3713 uri = sip_uri_from_name(user);
3715 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3717 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3718 if(!g_ascii_strcasecmp(acknowledged,"false")){
3719 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3720 if (!purple_find_buddy(sip->account, uri)) {
3721 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3724 hdr = g_strdup_printf(
3725 "Contact: %s\r\n"
3726 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3728 body = g_strdup_printf(
3729 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3730 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3731 "</setSubscribers>", user);
3733 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3734 g_free(body);
3735 g_free(hdr);
3737 g_free(display_name);
3738 g_free(uri);
3741 g_free(contact);
3742 xmlnode_free(xml);
3744 /* Publish initial state if not yet.
3745 * Assuming this happens on initial responce to subscription to roaming-self
3746 * so we've already updated our roaming data in full.
3747 * Only for 2007+
3749 if (!sip->initial_state_published) {
3750 send_publish_category_initial(sip);
3751 sip->initial_state_published = TRUE;
3752 /* dalayed run */
3753 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3754 do_update_status = FALSE;
3755 } else if (aggreg_avail) {
3757 g_free(sip->status);
3758 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3759 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3760 } else {
3761 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3765 if (do_update_status) {
3766 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3767 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3770 g_free(to);
3773 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3775 gchar *to = sip_uri_self(sip);
3776 gchar *tmp = get_contact(sip);
3777 gchar *hdr = g_strdup_printf(
3778 "Event: vnd-microsoft-roaming-ACL\r\n"
3779 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3780 "Supported: com.microsoft.autoextend\r\n"
3781 "Supported: ms-benotify\r\n"
3782 "Proxy-Require: ms-benotify\r\n"
3783 "Supported: ms-piggyback-first-notify\r\n"
3784 "Contact: %s\r\n", tmp);
3785 g_free(tmp);
3787 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3788 g_free(to);
3789 g_free(hdr);
3793 * To request for presence information about the user, access level settings that have already been configured by the user
3794 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3795 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3798 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3800 gchar *to = sip_uri_self(sip);
3801 gchar *tmp = get_contact(sip);
3802 gchar *hdr = g_strdup_printf(
3803 "Event: vnd-microsoft-roaming-self\r\n"
3804 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3805 "Supported: ms-benotify\r\n"
3806 "Proxy-Require: ms-benotify\r\n"
3807 "Supported: ms-piggyback-first-notify\r\n"
3808 "Contact: %s\r\n"
3809 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3811 gchar *body=g_strdup(
3812 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3813 "<roaming type=\"categories\"/>"
3814 "<roaming type=\"containers\"/>"
3815 "<roaming type=\"subscribers\"/></roamingList>");
3817 g_free(tmp);
3818 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3819 g_free(body);
3820 g_free(to);
3821 g_free(hdr);
3825 * For 2005 version
3827 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3829 gchar *to = sip_uri_self(sip);
3830 gchar *tmp = get_contact(sip);
3831 gchar *hdr = g_strdup_printf(
3832 "Event: vnd-microsoft-provisioning\r\n"
3833 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3834 "Supported: com.microsoft.autoextend\r\n"
3835 "Supported: ms-benotify\r\n"
3836 "Proxy-Require: ms-benotify\r\n"
3837 "Supported: ms-piggyback-first-notify\r\n"
3838 "Expires: 0\r\n"
3839 "Contact: %s\r\n", tmp);
3841 g_free(tmp);
3842 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3843 g_free(to);
3844 g_free(hdr);
3847 /** Subscription for provisioning information to help with initial
3848 * configuration. This subscription is a one-time query (denoted by the Expires header,
3849 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3850 * configuration, meeting policies, and policy settings that Communicator must enforce.
3851 * TODO: for what we need this information.
3854 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3856 gchar *to = sip_uri_self(sip);
3857 gchar *tmp = get_contact(sip);
3858 gchar *hdr = g_strdup_printf(
3859 "Event: vnd-microsoft-provisioning-v2\r\n"
3860 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3861 "Supported: com.microsoft.autoextend\r\n"
3862 "Supported: ms-benotify\r\n"
3863 "Proxy-Require: ms-benotify\r\n"
3864 "Supported: ms-piggyback-first-notify\r\n"
3865 "Expires: 0\r\n"
3866 "Contact: %s\r\n"
3867 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3868 gchar *body = g_strdup(
3869 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3870 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3871 "<provisioningGroup name=\"ucPolicy\"/>"
3872 "</provisioningGroupList>");
3874 g_free(tmp);
3875 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3876 g_free(body);
3877 g_free(to);
3878 g_free(hdr);
3881 static void
3882 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3883 gpointer value, gpointer user_data)
3885 struct sip_subscription *subscription = value;
3886 struct sip_dialog *dialog = &subscription->dialog;
3887 struct sipe_account_data *sip = user_data;
3888 gchar *tmp = get_contact(sip);
3889 gchar *hdr = g_strdup_printf(
3890 "Event: %s\r\n"
3891 "Expires: 0\r\n"
3892 "Contact: %s\r\n", subscription->event, tmp);
3893 g_free(tmp);
3895 /* Rate limit to max. 25 requests per seconds */
3896 g_usleep(1000000 / 25);
3898 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3899 g_free(hdr);
3902 /* IM Session (INVITE and MESSAGE methods) */
3904 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3905 static gchar *
3906 get_end_points (struct sipe_account_data *sip,
3907 struct sip_session *session)
3909 gchar *res;
3911 if (session == NULL) {
3912 return NULL;
3915 res = g_strdup_printf("<sip:%s>", sip->username);
3917 SIPE_DIALOG_FOREACH {
3918 gchar *tmp = res;
3919 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3920 g_free(tmp);
3922 if (dialog->theirepid) {
3923 tmp = res;
3924 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3925 g_free(tmp);
3927 } SIPE_DIALOG_FOREACH_END;
3929 return res;
3932 static gboolean
3933 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3934 struct sipmsg *msg,
3935 SIPE_UNUSED_PARAMETER struct transaction *trans)
3937 gboolean ret = TRUE;
3939 if (msg->response != 200) {
3940 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3941 return FALSE;
3944 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3946 return ret;
3950 * Asks UA/proxy about its capabilities.
3952 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3954 gchar *to = sip_uri(who);
3955 gchar *contact = get_contact(sip);
3956 gchar *request = g_strdup_printf(
3957 "Accept: application/sdp\r\n"
3958 "Contact: %s\r\n", contact);
3959 g_free(contact);
3961 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3963 g_free(to);
3964 g_free(request);
3967 static void
3968 sipe_notify_user(struct sipe_account_data *sip,
3969 struct sip_session *session,
3970 PurpleMessageFlags flags,
3971 const gchar *message)
3973 PurpleConversation *conv;
3975 if (!session->conv) {
3976 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3977 } else {
3978 conv = session->conv;
3980 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3983 void
3984 sipe_present_info(struct sipe_account_data *sip,
3985 struct sip_session *session,
3986 const gchar *message)
3988 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3991 static void
3992 sipe_present_err(struct sipe_account_data *sip,
3993 struct sip_session *session,
3994 const gchar *message)
3996 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3999 void
4000 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4001 struct sip_session *session,
4002 int sip_error,
4003 int sip_warning,
4004 const gchar *who,
4005 const gchar *message)
4007 char *msg, *msg_tmp, *msg_tmp2;
4008 const char *label;
4010 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
4011 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4012 g_free(msg_tmp);
4013 /* Service unavailable; Server Internal Error; Server Time-out */
4014 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4015 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4016 g_free(msg);
4017 msg = NULL;
4018 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4019 label = _("This message was not delivered to %s because the service is not available");
4020 } else if (sip_error == 486) { /* Busy Here */
4021 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4022 } else if (sip_error == 415) { /* Unsupported media type */
4023 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4024 } else {
4025 label = _("This message was not delivered to %s because one or more recipients are offline");
4028 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4029 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4030 msg ? ":" : "",
4031 msg ? msg : "");
4032 sipe_present_err(sip, session, msg_tmp);
4033 g_free(msg_tmp2);
4034 g_free(msg_tmp);
4035 g_free(msg);
4039 static gboolean
4040 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4041 SIPE_UNUSED_PARAMETER struct transaction *trans)
4043 gboolean ret = TRUE;
4044 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4045 struct sip_session *session = sipe_session_find_im(sip, with);
4046 struct sip_dialog *dialog;
4047 gchar *cseq;
4048 char *key;
4049 struct queued_message *message;
4051 if (!session) {
4052 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
4053 g_free(with);
4054 return FALSE;
4057 dialog = sipe_dialog_find(session, with);
4058 if (!dialog) {
4059 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
4060 g_free(with);
4061 return FALSE;
4064 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4065 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4066 g_free(cseq);
4067 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4069 if (msg->response >= 400) {
4070 PurpleBuddy *pbuddy;
4071 const char *alias = with;
4072 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4073 int warning = -1;
4075 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
4077 if (warn_hdr) {
4078 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4079 if (parts[0]) {
4080 warning = atoi(parts[0]);
4082 g_strfreev(parts);
4085 /* cancel file transfer as rejected by server */
4086 if (msg->response == 606 && /* Not acceptable all. */
4087 warning == 309 && /* Message contents not allowed by policy */
4088 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4090 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4091 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4092 sipe_utils_nameval_free(parsed_body);
4095 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4096 alias = purple_buddy_get_alias(pbuddy);
4099 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4101 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4102 if (msg->response == 408 || /* Request timeout */
4103 msg->response == 480 || /* Temporarily Unavailable */
4104 msg->response == 481) { /* Call/Transaction Does Not Exist */
4105 purple_debug_info("sipe", "process_message_response: assuming dangling IM session, dropping it.\n");
4106 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4109 ret = FALSE;
4110 } else {
4111 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4112 if (message_id) {
4113 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4114 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4115 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4118 g_hash_table_remove(session->unconfirmed_messages, key);
4119 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4120 key, g_hash_table_size(session->unconfirmed_messages));
4123 g_free(key);
4124 g_free(with);
4126 if (ret) sipe_im_process_queue(sip, session);
4127 return ret;
4130 static gboolean
4131 sipe_is_election_finished(struct sip_session *session);
4133 static void
4134 sipe_election_result(struct sipe_account_data *sip,
4135 void *sess);
4137 static gboolean
4138 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4139 SIPE_UNUSED_PARAMETER struct transaction *trans)
4141 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4142 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4143 struct sip_dialog *dialog;
4144 struct sip_session *session;
4146 session = sipe_session_find_chat_by_callid(sip, callid);
4147 if (!session) {
4148 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4149 return FALSE;
4152 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4153 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4154 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4155 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4157 if (xn_request_rm_response) {
4158 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4159 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4161 dialog = sipe_dialog_find(session, with);
4162 if (!dialog) {
4163 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4164 xmlnode_free(xn_action);
4165 return FALSE;
4168 if (allow && !g_strcasecmp(allow, "true")) {
4169 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4170 dialog->election_vote = 1;
4171 } else if (allow && !g_strcasecmp(allow, "false")) {
4172 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4173 dialog->election_vote = -1;
4176 if (sipe_is_election_finished(session)) {
4177 sipe_election_result(sip, session);
4180 } else if (xn_set_rm_response) {
4183 xmlnode_free(xn_action);
4187 return TRUE;
4190 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4192 gchar *hdr;
4193 gchar *tmp;
4194 char *msgtext = NULL;
4195 const gchar *msgr = "";
4196 gchar *tmp2 = NULL;
4198 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4199 char *msgformat;
4200 gchar *msgr_value;
4202 sipe_parse_html(msg, &msgformat, &msgtext);
4203 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4205 msgr_value = sipmsg_get_msgr_string(msgformat);
4206 g_free(msgformat);
4207 if (msgr_value) {
4208 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4209 g_free(msgr_value);
4211 } else {
4212 msgtext = g_strdup(msg);
4215 tmp = get_contact(sip);
4216 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4217 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4218 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4219 if (content_type == NULL)
4220 content_type = "text/plain";
4222 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4223 g_free(tmp);
4224 g_free(tmp2);
4226 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4227 g_free(msgtext);
4228 g_free(hdr);
4232 void
4233 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4235 GSList *entry2 = session->outgoing_message_queue;
4236 while (entry2) {
4237 struct queued_message *msg = entry2->data;
4239 /* for multiparty chat or conference */
4240 if (session->is_multiparty || session->focus_uri) {
4241 gchar *who = sip_uri_self(sip);
4242 serv_got_chat_in(sip->gc, session->chat_id, who,
4243 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4244 g_free(who);
4247 SIPE_DIALOG_FOREACH {
4248 char *key;
4249 struct queued_message *message;
4251 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4253 message = g_new0(struct queued_message,1);
4254 message->body = g_strdup(msg->body);
4255 if (msg->content_type != NULL)
4256 message->content_type = g_strdup(msg->content_type);
4258 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4259 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4260 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4261 key, g_hash_table_size(session->unconfirmed_messages));
4262 g_free(key);
4264 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4265 } SIPE_DIALOG_FOREACH_END;
4267 entry2 = sipe_session_dequeue_message(session);
4271 static void
4272 sipe_refer_notify(struct sipe_account_data *sip,
4273 struct sip_session *session,
4274 const gchar *who,
4275 int status,
4276 const gchar *desc)
4278 gchar *hdr;
4279 gchar *body;
4280 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4282 hdr = g_strdup_printf(
4283 "Event: refer\r\n"
4284 "Subscription-State: %s\r\n"
4285 "Content-Type: message/sipfrag\r\n",
4286 status >= 200 ? "terminated" : "active");
4288 body = g_strdup_printf(
4289 "SIP/2.0 %d %s\r\n",
4290 status, desc);
4292 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4294 g_free(hdr);
4295 g_free(body);
4298 static gboolean
4299 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4301 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4302 struct sip_session *session;
4303 struct sip_dialog *dialog;
4304 char *cseq;
4305 char *key;
4306 struct queued_message *message;
4307 struct sipmsg *request_msg = trans->msg;
4309 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4310 gchar *referred_by;
4312 session = sipe_session_find_chat_by_callid(sip, callid);
4313 if (!session) {
4314 session = sipe_session_find_im(sip, with);
4316 if (!session) {
4317 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4318 g_free(with);
4319 return FALSE;
4322 dialog = sipe_dialog_find(session, with);
4323 if (!dialog) {
4324 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4325 g_free(with);
4326 return FALSE;
4329 sipe_dialog_parse(dialog, msg, TRUE);
4331 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4332 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4333 g_free(cseq);
4334 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4336 if (msg->response != 200) {
4337 PurpleBuddy *pbuddy;
4338 const char *alias = with;
4339 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4340 int warning = -1;
4342 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4344 if (warn_hdr) {
4345 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4346 if (parts[0]) {
4347 warning = atoi(parts[0]);
4349 g_strfreev(parts);
4352 /* cancel file transfer as rejected by server */
4353 if (msg->response == 606 && /* Not acceptable all. */
4354 warning == 309 && /* Message contents not allowed by policy */
4355 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4357 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4358 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4359 sipe_utils_nameval_free(parsed_body);
4362 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4363 alias = purple_buddy_get_alias(pbuddy);
4366 if (message) {
4367 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4368 } else {
4369 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4370 sipe_present_err(sip, session, tmp_msg);
4371 g_free(tmp_msg);
4374 sipe_dialog_remove(session, with);
4376 g_free(key);
4377 g_free(with);
4378 return FALSE;
4381 dialog->cseq = 0;
4382 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4383 dialog->outgoing_invite = NULL;
4384 dialog->is_established = TRUE;
4386 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4387 if (referred_by) {
4388 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4389 g_free(referred_by);
4392 /* add user to chat if it is a multiparty session */
4393 if (session->is_multiparty) {
4394 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4395 with, NULL,
4396 PURPLE_CBFLAGS_NONE, TRUE);
4399 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4400 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4401 sipe_session_dequeue_message(session);
4404 sipe_im_process_queue(sip, session);
4406 g_hash_table_remove(session->unconfirmed_messages, key);
4407 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4408 key, g_hash_table_size(session->unconfirmed_messages));
4410 g_free(key);
4411 g_free(with);
4412 return TRUE;
4416 void
4417 sipe_invite(struct sipe_account_data *sip,
4418 struct sip_session *session,
4419 const gchar *who,
4420 const gchar *msg_body,
4421 const gchar *msg_content_type,
4422 const gchar *referred_by,
4423 const gboolean is_triggered)
4425 gchar *hdr;
4426 gchar *to;
4427 gchar *contact;
4428 gchar *body;
4429 gchar *self;
4430 char *ms_text_format = NULL;
4431 gchar *roster_manager;
4432 gchar *end_points;
4433 gchar *referred_by_str;
4434 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4436 if (dialog && dialog->is_established) {
4437 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4438 return;
4441 if (!dialog) {
4442 dialog = sipe_dialog_add(session);
4443 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4444 dialog->with = g_strdup(who);
4447 if (!(dialog->ourtag)) {
4448 dialog->ourtag = gentag();
4451 to = sip_uri(who);
4453 if (msg_body) {
4454 char *msgtext = NULL;
4455 char *base64_msg;
4456 const gchar *msgr = "";
4457 char *key;
4458 struct queued_message *message;
4459 gchar *tmp = NULL;
4461 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4462 char *msgformat;
4463 gchar *msgr_value;
4465 sipe_parse_html(msg_body, &msgformat, &msgtext);
4466 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4468 msgr_value = sipmsg_get_msgr_string(msgformat);
4469 g_free(msgformat);
4470 if (msgr_value) {
4471 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4472 g_free(msgr_value);
4474 } else {
4475 msgtext = g_strdup(msg_body);
4478 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
4479 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4480 msg_content_type ? msg_content_type : "text/plain",
4481 msgr,
4482 base64_msg);
4483 g_free(msgtext);
4484 g_free(tmp);
4485 g_free(base64_msg);
4487 message = g_new0(struct queued_message,1);
4488 message->body = g_strdup(msg_body);
4489 if (msg_content_type != NULL)
4490 message->content_type = g_strdup(msg_content_type);
4492 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4493 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4494 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4495 key, g_hash_table_size(session->unconfirmed_messages));
4496 g_free(key);
4499 contact = get_contact(sip);
4500 end_points = get_end_points(sip, session);
4501 self = sip_uri_self(sip);
4502 roster_manager = g_strdup_printf(
4503 "Roster-Manager: %s\r\n"
4504 "EndPoints: %s\r\n",
4505 self,
4506 end_points);
4507 referred_by_str = referred_by ?
4508 g_strdup_printf(
4509 "Referred-By: %s\r\n",
4510 referred_by)
4511 : g_strdup("");
4512 hdr = g_strdup_printf(
4513 "Supported: ms-sender\r\n"
4514 "%s"
4515 "%s"
4516 "%s"
4517 "%s"
4518 "Contact: %s\r\n%s"
4519 "Content-Type: application/sdp\r\n",
4520 sipe_strequal(session->roster_manager, self) ? roster_manager : "",
4521 referred_by_str,
4522 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4523 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4524 contact,
4525 ms_text_format ? ms_text_format : "");
4526 g_free(ms_text_format);
4527 g_free(self);
4529 body = g_strdup_printf(
4530 "v=0\r\n"
4531 "o=- 0 0 IN IP4 %s\r\n"
4532 "s=session\r\n"
4533 "c=IN IP4 %s\r\n"
4534 "t=0 0\r\n"
4535 "m=%s %d sip null\r\n"
4536 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4537 purple_network_get_my_ip(-1),
4538 purple_network_get_my_ip(-1),
4539 sip->ocs2007 ? "message" : "x-ms-message",
4540 sip->realport);
4542 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4543 to, to, hdr, body, dialog, process_invite_response);
4545 g_free(to);
4546 g_free(roster_manager);
4547 g_free(end_points);
4548 g_free(referred_by_str);
4549 g_free(body);
4550 g_free(hdr);
4551 g_free(contact);
4554 static void
4555 sipe_refer(struct sipe_account_data *sip,
4556 struct sip_session *session,
4557 const gchar *who)
4559 gchar *hdr;
4560 gchar *contact;
4561 gchar *epid = get_epid(sip);
4562 struct sip_dialog *dialog = sipe_dialog_find(session,
4563 session->roster_manager);
4564 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4566 contact = get_contact(sip);
4567 hdr = g_strdup_printf(
4568 "Contact: %s\r\n"
4569 "Refer-to: <%s>\r\n"
4570 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4571 "Require: com.microsoft.rtc-multiparty\r\n",
4572 contact,
4573 who,
4574 sip->username,
4575 ourtag ? ";tag=" : "",
4576 ourtag ? ourtag : "",
4577 epid);
4578 g_free(epid);
4580 send_sip_request(sip->gc, "REFER",
4581 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4583 g_free(hdr);
4584 g_free(contact);
4587 static void
4588 sipe_send_election_request_rm(struct sipe_account_data *sip,
4589 struct sip_dialog *dialog,
4590 int bid)
4592 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4594 gchar *body = g_strdup_printf(
4595 "<?xml version=\"1.0\"?>\r\n"
4596 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4597 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4598 sip->username, bid);
4600 send_sip_request(sip->gc, "INFO",
4601 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4603 g_free(body);
4606 static void
4607 sipe_send_election_set_rm(struct sipe_account_data *sip,
4608 struct sip_dialog *dialog)
4610 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4612 gchar *body = g_strdup_printf(
4613 "<?xml version=\"1.0\"?>\r\n"
4614 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4615 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4616 sip->username);
4618 send_sip_request(sip->gc, "INFO",
4619 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4621 g_free(body);
4624 static void
4625 sipe_session_close(struct sipe_account_data *sip,
4626 struct sip_session * session)
4628 if (session && session->focus_uri) {
4629 sipe_conf_immcu_closed(sip, session);
4630 conf_session_close(sip, session);
4633 if (session) {
4634 SIPE_DIALOG_FOREACH {
4635 /* @TODO slow down BYE message sending rate */
4636 /* @see single subscription code */
4637 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4638 } SIPE_DIALOG_FOREACH_END;
4640 sipe_session_remove(sip, session);
4644 static void
4645 sipe_session_close_all(struct sipe_account_data *sip)
4647 GSList *entry;
4648 while ((entry = sip->sessions) != NULL) {
4649 sipe_session_close(sip, entry->data);
4653 static void
4654 sipe_convo_closed(PurpleConnection * gc, const char *who)
4656 struct sipe_account_data *sip = gc->proto_data;
4658 purple_debug_info("sipe", "conversation with %s closed\n", who);
4659 sipe_session_close(sip, sipe_session_find_im(sip, who));
4662 static void
4663 sipe_chat_leave (PurpleConnection *gc, int id)
4665 struct sipe_account_data *sip = gc->proto_data;
4666 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4668 sipe_session_close(sip, session);
4671 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4672 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4674 struct sipe_account_data *sip = gc->proto_data;
4675 struct sip_session *session;
4676 struct sip_dialog *dialog;
4677 gchar *uri = sip_uri(who);
4679 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4681 session = sipe_session_find_or_add_im(sip, uri);
4682 dialog = sipe_dialog_find(session, uri);
4684 // Queue the message
4685 sipe_session_enqueue_message(session, what, NULL);
4687 if (dialog && !dialog->outgoing_invite) {
4688 sipe_im_process_queue(sip, session);
4689 } else if (!dialog || !dialog->outgoing_invite) {
4690 // Need to send the INVITE to get the outgoing dialog setup
4691 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4694 g_free(uri);
4695 return 1;
4698 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4699 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4701 struct sipe_account_data *sip = gc->proto_data;
4702 struct sip_session *session;
4704 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4706 session = sipe_session_find_chat_by_id(sip, id);
4708 // Queue the message
4709 if (session && session->dialogs) {
4710 sipe_session_enqueue_message(session,what,NULL);
4711 sipe_im_process_queue(sip, session);
4712 } else if (sip) {
4713 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4714 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4716 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4717 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4719 if (sip->ocs2007) {
4720 struct sip_session *session = sipe_session_add_chat(sip);
4722 session->is_multiparty = FALSE;
4723 session->focus_uri = g_strdup(proto_chat_id);
4724 sipe_session_enqueue_message(session, what, NULL);
4725 sipe_invite_conf_focus(sip, session);
4729 return 1;
4732 /* End IM Session (INVITE and MESSAGE methods) */
4734 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4736 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4737 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4738 gchar *from;
4739 struct sip_session *session;
4741 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4743 /* Call Control protocol */
4744 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4746 process_incoming_info_csta(sip, msg);
4747 return;
4750 from = parse_from(sipmsg_find_header(msg, "From"));
4751 session = sipe_session_find_chat_by_callid(sip, callid);
4752 if (!session) {
4753 session = sipe_session_find_im(sip, from);
4755 if (!session) {
4756 g_free(from);
4757 return;
4760 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4762 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4763 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4764 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4766 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4768 if (xn_request_rm) {
4769 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4770 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4771 gchar *body = g_strdup_printf(
4772 "<?xml version=\"1.0\"?>\r\n"
4773 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4774 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4775 sip->username,
4776 session->bid < bid ? "true" : "false");
4777 send_sip_response(sip->gc, msg, 200, "OK", body);
4778 g_free(body);
4779 } else if (xn_set_rm) {
4780 gchar *body;
4781 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4782 g_free(session->roster_manager);
4783 session->roster_manager = g_strdup(rm);
4785 body = g_strdup_printf(
4786 "<?xml version=\"1.0\"?>\r\n"
4787 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4788 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4789 sip->username);
4790 send_sip_response(sip->gc, msg, 200, "OK", body);
4791 g_free(body);
4793 xmlnode_free(xn_action);
4796 else
4798 /* looks like purple lacks typing notification for chat */
4799 if (!session->is_multiparty && !session->focus_uri) {
4800 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4801 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4802 "status");
4803 if (sipe_strequal(status, "type")) {
4804 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4805 } else if (sipe_strequal(status, "idle")) {
4806 serv_got_typing_stopped(sip->gc, from);
4808 xmlnode_free(xn_keyboard_activity);
4811 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4813 g_free(from);
4816 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4818 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4819 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4820 struct sip_session *session;
4821 struct sip_dialog *dialog;
4823 /* collect dialog identification
4824 * we need callid, ourtag and theirtag to unambiguously identify dialog
4826 /* take data before 'msg' will be modified by send_sip_response */
4827 dialog = g_new0(struct sip_dialog, 1);
4828 dialog->callid = g_strdup(callid);
4829 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4830 dialog->with = g_strdup(from);
4831 sipe_dialog_parse(dialog, msg, FALSE);
4833 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4835 session = sipe_session_find_chat_by_callid(sip, callid);
4836 if (!session) {
4837 session = sipe_session_find_im(sip, from);
4839 if (!session) {
4840 sipe_dialog_free(dialog);
4841 g_free(from);
4842 return;
4845 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4846 g_free(session->roster_manager);
4847 session->roster_manager = NULL;
4850 /* This what BYE is essentially for - terminating dialog */
4851 sipe_dialog_remove_3(session, dialog);
4852 sipe_dialog_free(dialog);
4853 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4854 sipe_conf_immcu_closed(sip, session);
4855 } else if (session->is_multiparty) {
4856 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4859 g_free(from);
4862 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4864 gchar *self = sip_uri_self(sip);
4865 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4866 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4867 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4868 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4869 struct sip_session *session;
4870 struct sip_dialog *dialog;
4872 session = sipe_session_find_chat_by_callid(sip, callid);
4873 dialog = sipe_dialog_find(session, from);
4875 if (!session || !dialog || !session->roster_manager || !sipe_strequal(session->roster_manager, self)) {
4876 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4877 } else {
4878 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4880 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4883 g_free(self);
4884 g_free(from);
4885 g_free(refer_to);
4886 g_free(referred_by);
4889 static unsigned int
4890 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4892 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4893 struct sip_session *session;
4894 struct sip_dialog *dialog;
4896 if (state == PURPLE_NOT_TYPING)
4897 return 0;
4899 session = sipe_session_find_im(sip, who);
4900 dialog = sipe_dialog_find(session, who);
4902 if (session && dialog && dialog->is_established) {
4903 send_sip_request(gc, "INFO", who, who,
4904 "Content-Type: application/xml\r\n",
4905 SIPE_SEND_TYPING, dialog, NULL);
4907 return SIPE_TYPING_SEND_TIMEOUT;
4910 static gboolean resend_timeout(struct sipe_account_data *sip)
4912 GSList *tmp = sip->transactions;
4913 time_t currtime = time(NULL);
4914 while (tmp) {
4915 struct transaction *trans = tmp->data;
4916 tmp = tmp->next;
4917 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4918 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4919 /* TODO 408 */
4920 } else {
4921 if ((currtime - trans->time > 2) && trans->retries == 0) {
4922 trans->retries++;
4923 sendout_sipmsg(sip, trans->msg);
4927 return TRUE;
4930 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4931 SIPE_UNUSED_PARAMETER void *unused)
4933 /* register again when security token expires */
4934 /* we have to start a new authentication as the security token
4935 * is almost expired by sending a not signed REGISTER message */
4936 purple_debug_info("sipe", "do a full reauthentication\n");
4937 sipe_auth_free(&sip->registrar);
4938 sipe_auth_free(&sip->proxy);
4939 sip->registerstatus = 0;
4940 do_register(sip);
4941 sip->reauthenticate_set = FALSE;
4944 static gboolean
4945 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4946 struct sipmsg *msg,
4947 GSList *parsed_body)
4949 gboolean found = FALSE;
4951 if (parsed_body) {
4952 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4954 if (sipe_strequal(invitation_command, "INVITE")) {
4955 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4956 found = TRUE;
4957 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4958 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4959 found = TRUE;
4960 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4961 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4962 found = TRUE;
4965 return found;
4968 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4970 gchar *from;
4971 const gchar *contenttype;
4972 gboolean found = FALSE;
4974 from = parse_from(sipmsg_find_header(msg, "From"));
4976 if (!from) return;
4978 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4980 contenttype = sipmsg_find_header(msg, "Content-Type");
4981 if (g_str_has_prefix(contenttype, "text/plain")
4982 || g_str_has_prefix(contenttype, "text/html")
4983 || g_str_has_prefix(contenttype, "multipart/related")
4984 || g_str_has_prefix(contenttype, "multipart/alternative"))
4986 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4987 gchar *html = get_html_message(contenttype, msg->body);
4989 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4990 if (!session) {
4991 session = sipe_session_find_im(sip, from);
4994 if (session && session->focus_uri) { /* a conference */
4995 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4996 gchar *sender = parse_from(tmp);
4997 g_free(tmp);
4998 serv_got_chat_in(sip->gc, session->chat_id, sender,
4999 PURPLE_MESSAGE_RECV, html, time(NULL));
5000 g_free(sender);
5001 } else if (session && session->is_multiparty) { /* a multiparty chat */
5002 serv_got_chat_in(sip->gc, session->chat_id, from,
5003 PURPLE_MESSAGE_RECV, html, time(NULL));
5004 } else {
5005 serv_got_im(sip->gc, from, html, 0, time(NULL));
5007 g_free(html);
5008 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5009 found = TRUE;
5011 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5012 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5013 xmlnode *state;
5014 gchar *statedata;
5016 if (!isc) {
5017 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
5018 g_free(from);
5019 return;
5022 state = xmlnode_get_child(isc, "state");
5024 if (!state) {
5025 purple_debug_info("sipe", "process_incoming_message: no state found\n");
5026 xmlnode_free(isc);
5027 g_free(from);
5028 return;
5031 statedata = xmlnode_get_data(state);
5032 if (statedata) {
5033 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5034 else serv_got_typing_stopped(sip->gc, from);
5036 g_free(statedata);
5038 xmlnode_free(isc);
5039 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5040 found = TRUE;
5041 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5042 GSList *body = sipe_ft_parse_msg_body(msg->body);
5043 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5044 sipe_utils_nameval_free(body);
5045 if (found) {
5046 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5049 if (!found) {
5050 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5051 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5052 if (!session) {
5053 session = sipe_session_find_im(sip, from);
5055 if (session) {
5056 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5057 from);
5058 sipe_present_err(sip, session, errmsg);
5059 g_free(errmsg);
5062 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
5063 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5065 g_free(from);
5068 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5070 gchar *body;
5071 gchar *newTag;
5072 const gchar *oldHeader;
5073 gchar *newHeader;
5074 gboolean is_multiparty = FALSE;
5075 gboolean is_triggered = FALSE;
5076 gboolean was_multiparty = TRUE;
5077 gboolean just_joined = FALSE;
5078 gchar *from;
5079 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5080 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5081 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5082 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5083 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5084 GSList *end_points = NULL;
5085 char *tmp = NULL;
5086 struct sip_session *session;
5087 const gchar *ms_text_format;
5089 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
5090 g_free(tmp);
5092 /* Invitation to join conference */
5093 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5094 process_incoming_invite_conf(sip, msg);
5095 return;
5098 /* Only accept text invitations */
5099 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5100 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5101 return;
5104 // TODO There *must* be a better way to clean up the To header to add a tag...
5105 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
5106 oldHeader = sipmsg_find_header(msg, "To");
5107 newTag = gentag();
5108 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5109 sipmsg_remove_header_now(msg, "To");
5110 sipmsg_add_header_now(msg, "To", newHeader);
5111 g_free(newHeader);
5113 if (end_points_hdr) {
5114 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5116 if (g_slist_length(end_points) > 2) {
5117 is_multiparty = TRUE;
5120 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5121 is_triggered = TRUE;
5122 is_multiparty = TRUE;
5125 session = sipe_session_find_chat_by_callid(sip, callid);
5126 /* Convert to multiparty */
5127 if (session && is_multiparty && !session->is_multiparty) {
5128 g_free(session->with);
5129 session->with = NULL;
5130 was_multiparty = FALSE;
5131 session->is_multiparty = TRUE;
5132 session->chat_id = rand();
5135 if (!session && is_multiparty) {
5136 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5138 /* IM session */
5139 from = parse_from(sipmsg_find_header(msg, "From"));
5140 if (!session) {
5141 session = sipe_session_find_or_add_im(sip, from);
5144 if (session) {
5145 g_free(session->callid);
5146 session->callid = g_strdup(callid);
5148 session->is_multiparty = is_multiparty;
5149 if (roster_manager) {
5150 session->roster_manager = g_strdup(roster_manager);
5154 if (is_multiparty && end_points) {
5155 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5156 GSList *entry = end_points;
5157 while (entry) {
5158 struct sip_dialog *dialog;
5159 struct sipendpoint *end_point = entry->data;
5160 entry = entry->next;
5162 if (!g_strcasecmp(from, end_point->contact) ||
5163 !g_strcasecmp(to, end_point->contact))
5164 continue;
5166 dialog = sipe_dialog_find(session, end_point->contact);
5167 if (dialog) {
5168 g_free(dialog->theirepid);
5169 dialog->theirepid = end_point->epid;
5170 end_point->epid = NULL;
5171 } else {
5172 dialog = sipe_dialog_add(session);
5174 dialog->callid = g_strdup(session->callid);
5175 dialog->with = end_point->contact;
5176 end_point->contact = NULL;
5177 dialog->theirepid = end_point->epid;
5178 end_point->epid = NULL;
5180 just_joined = TRUE;
5182 /* send triggered INVITE */
5183 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5186 g_free(to);
5189 if (end_points) {
5190 GSList *entry = end_points;
5191 while (entry) {
5192 struct sipendpoint *end_point = entry->data;
5193 entry = entry->next;
5194 g_free(end_point->contact);
5195 g_free(end_point->epid);
5196 g_free(end_point);
5198 g_slist_free(end_points);
5201 if (session) {
5202 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5203 if (dialog) {
5204 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5205 sipe_dialog_parse_routes(dialog, msg, FALSE);
5206 } else {
5207 dialog = sipe_dialog_add(session);
5209 dialog->callid = g_strdup(session->callid);
5210 dialog->with = g_strdup(from);
5211 sipe_dialog_parse(dialog, msg, FALSE);
5213 if (!dialog->ourtag) {
5214 dialog->ourtag = newTag;
5215 newTag = NULL;
5218 just_joined = TRUE;
5220 } else {
5221 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5223 g_free(newTag);
5225 if (is_multiparty && !session->conv) {
5226 gchar *chat_title = sipe_chat_get_name(callid);
5227 gchar *self = sip_uri_self(sip);
5228 /* create prpl chat */
5229 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5230 session->chat_title = g_strdup(chat_title);
5231 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5232 /* add self */
5233 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5234 self, NULL,
5235 PURPLE_CBFLAGS_NONE, FALSE);
5236 g_free(chat_title);
5237 g_free(self);
5240 if (is_multiparty && !was_multiparty) {
5241 /* add current IM counterparty to chat */
5242 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5243 sipe_dialog_first(session)->with, NULL,
5244 PURPLE_CBFLAGS_NONE, FALSE);
5247 /* add inviting party to chat */
5248 if (just_joined && session->conv) {
5249 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5250 from, NULL,
5251 PURPLE_CBFLAGS_NONE, TRUE);
5254 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5256 /* This used only in 2005 official client, not 2007 or Reuters.
5257 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5258 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5260 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5261 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5262 if (is_multiparty ||
5263 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5265 if (ms_text_format) {
5266 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5268 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5269 if (tmp) {
5270 gchar *body = (gchar *) purple_base64_decode(tmp, NULL);
5272 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5274 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5275 sipe_utils_nameval_free(parsed_body);
5276 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5278 g_free(tmp);
5280 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5282 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5283 gchar *html = get_html_message(ms_text_format, NULL);
5284 if (html) {
5285 if (is_multiparty) {
5286 serv_got_chat_in(sip->gc, session->chat_id, from,
5287 PURPLE_MESSAGE_RECV, html, time(NULL));
5288 } else {
5289 serv_got_im(sip->gc, from, html, 0, time(NULL));
5291 g_free(html);
5292 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5298 g_free(from);
5300 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5301 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5302 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5304 body = g_strdup_printf(
5305 "v=0\r\n"
5306 "o=- 0 0 IN IP4 %s\r\n"
5307 "s=session\r\n"
5308 "c=IN IP4 %s\r\n"
5309 "t=0 0\r\n"
5310 "m=%s %d sip sip:%s\r\n"
5311 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5312 purple_network_get_my_ip(-1),
5313 purple_network_get_my_ip(-1),
5314 sip->ocs2007 ? "message" : "x-ms-message",
5315 sip->realport,
5316 sip->username);
5317 send_sip_response(sip->gc, msg, 200, "OK", body);
5318 g_free(body);
5321 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5323 gchar *body;
5325 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5326 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5327 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5329 body = g_strdup_printf(
5330 "v=0\r\n"
5331 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5332 "s=session\r\n"
5333 "c=IN IP4 0.0.0.0\r\n"
5334 "t=0 0\r\n"
5335 "m=%s %d sip sip:%s\r\n"
5336 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5337 sip->ocs2007 ? "message" : "x-ms-message",
5338 sip->realport,
5339 sip->username);
5340 send_sip_response(sip->gc, msg, 200, "OK", body);
5341 g_free(body);
5344 static const char*
5345 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5347 const char *res = "NTLM";
5348 #ifdef USE_KERBEROS
5349 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5350 res = "Kerberos";
5352 #else
5353 (void) sip; /* make compiler happy */
5354 #endif
5355 return res;
5358 static void sipe_connection_cleanup(struct sipe_account_data *);
5359 static void create_connection(struct sipe_account_data *, gchar *, int);
5361 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5362 SIPE_UNUSED_PARAMETER struct transaction *trans)
5364 gchar *tmp;
5365 const gchar *expires_header;
5366 int expires, i;
5367 GSList *hdr = msg->headers;
5368 struct sipnameval *elem;
5370 expires_header = sipmsg_find_header(msg, "Expires");
5371 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5372 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5374 switch (msg->response) {
5375 case 200:
5376 if (expires == 0) {
5377 sip->registerstatus = 0;
5378 } else {
5379 const gchar *contact_hdr;
5380 gchar *gruu = NULL;
5381 gchar *epid;
5382 gchar *uuid;
5383 gchar *timeout;
5384 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5385 const char *auth_scheme;
5387 if (!sip->reregister_set) {
5388 gchar *action_name = g_strdup_printf("<%s>", "registration");
5389 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5390 g_free(action_name);
5391 sip->reregister_set = TRUE;
5394 sip->registerstatus = 3;
5396 if (server_hdr && !sip->server_version) {
5397 sip->server_version = g_strdup(server_hdr);
5398 g_free(default_ua);
5399 default_ua = NULL;
5402 auth_scheme = sipe_get_auth_scheme_name(sip);
5403 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5405 if (tmp) {
5406 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5407 fill_auth(tmp, &sip->registrar);
5410 if (!sip->reauthenticate_set) {
5411 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5412 guint reauth_timeout;
5413 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5414 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5415 reauth_timeout = sip->registrar.expires - 300;
5416 } else {
5417 /* NTLM: we have to reauthenticate as our security token expires
5418 after eight hours (be five minutes early) */
5419 reauth_timeout = (8 * 3600) - 300;
5421 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5422 g_free(action_name);
5423 sip->reauthenticate_set = TRUE;
5426 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5428 epid = get_epid(sip);
5429 uuid = generateUUIDfromEPID(epid);
5430 g_free(epid);
5432 // There can be multiple Contact headers (one per location where the user is logged in) so
5433 // make sure to only get the one for this uuid
5434 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5435 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5436 if (valid_contact) {
5437 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5438 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5439 g_free(valid_contact);
5440 break;
5441 } else {
5442 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5445 g_free(uuid);
5447 g_free(sip->contact);
5448 if(gruu) {
5449 sip->contact = g_strdup_printf("<%s>", gruu);
5450 g_free(gruu);
5451 } else {
5452 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5453 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);
5455 sip->ocs2007 = FALSE;
5456 sip->batched_support = FALSE;
5458 while(hdr)
5460 elem = hdr->data;
5461 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
5462 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
5463 /* We interpret this as OCS2007+ indicator */
5464 sip->ocs2007 = TRUE;
5465 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5467 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
5468 sip->batched_support = TRUE;
5469 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5472 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
5473 gchar **caps = g_strsplit(elem->value,",",0);
5474 i = 0;
5475 while (caps[i]) {
5476 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5477 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5478 i++;
5480 g_strfreev(caps);
5482 hdr = g_slist_next(hdr);
5485 /* rejoin open chats to be able to use them by continue to send messages */
5486 purple_conversation_foreach(sipe_rejoin_chat);
5488 /* subscriptions */
5489 if (!sip->subscribed) { //do it just once, not every re-register
5491 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5492 (GCompareFunc)g_ascii_strcasecmp)) {
5493 sipe_subscribe_roaming_contacts(sip);
5496 /* For 2007+ it does not make sence to subscribe to:
5497 * vnd-microsoft-roaming-ACL
5498 * vnd-microsoft-provisioning (not v2)
5499 * presence.wpending
5500 * These are for backward compatibility.
5502 if (sip->ocs2007)
5504 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5505 (GCompareFunc)g_ascii_strcasecmp)) {
5506 sipe_subscribe_roaming_self(sip);
5508 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5509 (GCompareFunc)g_ascii_strcasecmp)) {
5510 sipe_subscribe_roaming_provisioning_v2(sip);
5513 /* For 2005- servers */
5514 else
5516 //sipe_options_request(sip, sip->sipdomain);
5518 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5519 (GCompareFunc)g_ascii_strcasecmp)) {
5520 sipe_subscribe_roaming_acl(sip);
5522 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5523 (GCompareFunc)g_ascii_strcasecmp)) {
5524 sipe_subscribe_roaming_provisioning(sip);
5526 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5527 (GCompareFunc)g_ascii_strcasecmp)) {
5528 sipe_subscribe_presence_wpending(sip, msg);
5531 /* For 2007+ we publish our initial statuses and calendar data only after
5532 * received our existing publications in sipe_process_roaming_self()
5533 * Only in this case we know versions of current publications made
5534 * on our behalf.
5536 /* For 2005- we publish our initial statuses only after
5537 * received our existing UserInfo data in response to
5538 * self subscription.
5539 * Only in this case we won't override existing UserInfo data
5540 * set earlier or by other client on our behalf.
5544 sip->subscribed = TRUE;
5547 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5548 "timeout=", ";", NULL);
5549 if (timeout != NULL) {
5550 sscanf(timeout, "%u", &sip->keepalive_timeout);
5551 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5552 sip->keepalive_timeout);
5553 g_free(timeout);
5556 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5558 break;
5559 case 301:
5561 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5563 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5564 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5565 gchar **tmp;
5566 gchar *hostname;
5567 int port = 0;
5568 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5569 int i = 1;
5571 tmp = g_strsplit(parts[0], ":", 0);
5572 hostname = g_strdup(tmp[0]);
5573 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5574 g_strfreev(tmp);
5576 while (parts[i]) {
5577 tmp = g_strsplit(parts[i], "=", 0);
5578 if (tmp[1]) {
5579 if (g_strcasecmp("transport", tmp[0]) == 0) {
5580 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5581 transport = SIPE_TRANSPORT_TCP;
5582 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5583 transport = SIPE_TRANSPORT_UDP;
5587 g_strfreev(tmp);
5588 i++;
5590 g_strfreev(parts);
5592 /* Close old connection */
5593 sipe_connection_cleanup(sip);
5595 /* Create new connection */
5596 sip->transport = transport;
5597 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5598 hostname, port, TRANSPORT_DESCRIPTOR);
5599 create_connection(sip, hostname, port);
5601 g_free(redirect);
5603 break;
5604 case 401:
5605 if (sip->registerstatus != 2) {
5606 const char *auth_scheme;
5607 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5608 if (sip->registrar.retries > 3) {
5609 sip->gc->wants_to_die = TRUE;
5610 purple_connection_error(sip->gc, _("Authentication failed"));
5611 return TRUE;
5614 auth_scheme = sipe_get_auth_scheme_name(sip);
5615 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5617 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5618 if (!tmp) {
5619 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5620 sip->gc->wants_to_die = TRUE;
5621 purple_connection_error(sip->gc, tmp2);
5622 g_free(tmp2);
5623 return TRUE;
5625 fill_auth(tmp, &sip->registrar);
5626 sip->registerstatus = 2;
5627 if (sip->account->disconnecting) {
5628 do_register_exp(sip, 0);
5629 } else {
5630 do_register(sip);
5633 break;
5634 case 403:
5636 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5637 gchar **reason = NULL;
5638 gchar *warning;
5639 if (diagnostics != NULL) {
5640 /* Example header:
5641 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5643 reason = g_strsplit(diagnostics, "\"", 0);
5645 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5646 (reason && reason[1]) ? reason[1] : _("no reason given"));
5647 g_strfreev(reason);
5649 sip->gc->wants_to_die = TRUE;
5650 purple_connection_error(sip->gc, warning);
5651 g_free(warning);
5652 return TRUE;
5654 break;
5655 case 404:
5657 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5658 gchar *reason = NULL;
5659 gchar *warning;
5660 if (diagnostics != NULL) {
5661 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5663 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5664 diagnostics ? (reason ? reason : _("no reason given")) :
5665 _("SIP is either not enabled for the destination URI or it does not exist"));
5666 g_free(reason);
5668 sip->gc->wants_to_die = TRUE;
5669 purple_connection_error(sip->gc, warning);
5670 g_free(warning);
5671 return TRUE;
5673 break;
5674 case 503:
5675 case 504: /* Server time-out */
5677 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5678 gchar *reason = NULL;
5679 gchar *warning;
5680 if (diagnostics != NULL) {
5681 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5683 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5684 g_free(reason);
5686 sip->gc->wants_to_die = TRUE;
5687 purple_connection_error(sip->gc, warning);
5688 g_free(warning);
5689 return TRUE;
5691 break;
5693 return TRUE;
5697 * Returns 2005-style activity and Availability.
5699 * @param status Sipe statis id.
5701 static void
5702 sipe_get_act_avail_by_status_2005(const char *status,
5703 int *activity,
5704 int *availability)
5706 int avail = 300; /* online */
5707 int act = 400; /* Available */
5709 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5710 act = 100;
5711 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5712 // act = 150;
5713 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5714 act = 300;
5715 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5716 act = 400;
5717 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5718 // act = 500;
5719 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5720 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5721 act = 600;
5722 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5723 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5724 avail = 0; /* offline */
5725 act = 100;
5726 } else {
5727 act = 400; /* Available */
5730 if (activity) *activity = act;
5731 if (availability) *availability = avail;
5735 * [MS-SIP] 2.2.1
5737 * @param activity 2005 aggregated activity. Ex.: 600
5738 * @param availablity 2005 aggregated availablity. Ex.: 300
5740 static const char *
5741 sipe_get_status_by_act_avail_2005(const int activity,
5742 const int availablity,
5743 char **activity_desc)
5745 const char *status_id = NULL;
5746 const char *act = NULL;
5748 if (activity < 150) {
5749 status_id = SIPE_STATUS_ID_AWAY;
5750 } else if (activity < 200) {
5751 //status_id = SIPE_STATUS_ID_LUNCH;
5752 status_id = SIPE_STATUS_ID_AWAY;
5753 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5754 } else if (activity < 300) {
5755 //status_id = SIPE_STATUS_ID_IDLE;
5756 status_id = SIPE_STATUS_ID_AWAY;
5757 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5758 } else if (activity < 400) {
5759 status_id = SIPE_STATUS_ID_BRB;
5760 } else if (activity < 500) {
5761 status_id = SIPE_STATUS_ID_AVAILABLE;
5762 } else if (activity < 600) {
5763 //status_id = SIPE_STATUS_ID_ON_PHONE;
5764 status_id = SIPE_STATUS_ID_BUSY;
5765 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5766 } else if (activity < 700) {
5767 status_id = SIPE_STATUS_ID_BUSY;
5768 } else if (activity < 800) {
5769 status_id = SIPE_STATUS_ID_AWAY;
5770 } else {
5771 status_id = SIPE_STATUS_ID_AVAILABLE;
5774 if (availablity < 100)
5775 status_id = SIPE_STATUS_ID_OFFLINE;
5777 if (activity_desc && act) {
5778 g_free(*activity_desc);
5779 *activity_desc = g_strdup(act);
5782 return status_id;
5786 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5788 static const char*
5789 sipe_get_status_by_availability(int avail,
5790 char** activity_desc)
5792 const char *status;
5793 const char *act = NULL;
5795 if (avail < 3000) {
5796 status = SIPE_STATUS_ID_OFFLINE;
5797 } else if (avail < 4500) {
5798 status = SIPE_STATUS_ID_AVAILABLE;
5799 } else if (avail < 6000) {
5800 //status = SIPE_STATUS_ID_IDLE;
5801 status = SIPE_STATUS_ID_AVAILABLE;
5802 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5803 } else if (avail < 7500) {
5804 status = SIPE_STATUS_ID_BUSY;
5805 } else if (avail < 9000) {
5806 //status = SIPE_STATUS_ID_BUSYIDLE;
5807 status = SIPE_STATUS_ID_BUSY;
5808 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5809 } else if (avail < 12000) {
5810 status = SIPE_STATUS_ID_DND;
5811 } else if (avail < 15000) {
5812 status = SIPE_STATUS_ID_BRB;
5813 } else if (avail < 18000) {
5814 status = SIPE_STATUS_ID_AWAY;
5815 } else {
5816 status = SIPE_STATUS_ID_OFFLINE;
5819 if (activity_desc && act) {
5820 g_free(*activity_desc);
5821 *activity_desc = g_strdup(act);
5824 return status;
5828 * Returns 2007-style availability value
5830 * @param sipe_status_id (in)
5831 * @param activity_token (out) Must be g_free()'d after use if consumed.
5833 static int
5834 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5836 int availability;
5837 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5839 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5840 availability = 15500;
5841 if (!activity_token || !(*activity_token)) {
5842 activity = SIPE_ACTIVITY_AWAY;
5844 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5845 availability = 12500;
5846 activity = SIPE_ACTIVITY_BRB;
5847 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5848 availability = 9500;
5849 activity = SIPE_ACTIVITY_DND;
5850 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5851 availability = 6500;
5852 if (!activity_token || !(*activity_token)) {
5853 activity = SIPE_ACTIVITY_BUSY;
5855 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5856 availability = 3500;
5857 activity = SIPE_ACTIVITY_ONLINE;
5858 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5859 availability = 0;
5860 } else {
5861 // Offline or invisible
5862 availability = 18500;
5863 activity = SIPE_ACTIVITY_OFFLINE;
5866 if (activity_token) {
5867 *activity_token = g_strdup(sipe_activity_map[activity].token);
5869 return availability;
5872 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5874 const char *uri;
5875 xmlnode *xn_categories;
5876 xmlnode *xn_category;
5877 xmlnode *xn_node;
5878 const char *status = NULL;
5879 gboolean do_update_status = FALSE;
5880 gboolean has_note_cleaned = FALSE;
5881 gboolean has_free_busy_cleaned = FALSE;
5883 xn_categories = xmlnode_from_str(data, len);
5884 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5886 for (xn_category = xmlnode_get_child(xn_categories, "category");
5887 xn_category ;
5888 xn_category = xmlnode_get_next_twin(xn_category) )
5890 const char *tmp;
5891 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5892 time_t publish_time = (tmp = xmlnode_get_attrib(xn_category, "publishTime")) ?
5893 sipe_utils_str_to_time(tmp) : 0;
5895 /* contactCard */
5896 if (sipe_strequal(attrVar, "contactCard"))
5898 xmlnode *node;
5899 /* identity - Display Name and email */
5900 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5901 if (node) {
5902 char* display_name = xmlnode_get_data(
5903 xmlnode_get_descendant(node, "name", "displayName", NULL));
5904 char* email = xmlnode_get_data(
5905 xmlnode_get_child(node, "email"));
5907 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5908 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5910 g_free(display_name);
5911 g_free(email);
5913 /* company */
5914 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5915 if (node) {
5916 char* company = xmlnode_get_data(node);
5917 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5918 g_free(company);
5920 /* department */
5921 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5922 if (node) {
5923 char* department = xmlnode_get_data(node);
5924 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5925 g_free(department);
5927 /* title */
5928 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5929 if (node) {
5930 char* title = xmlnode_get_data(node);
5931 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5932 g_free(title);
5934 /* office */
5935 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5936 if (node) {
5937 char* office = xmlnode_get_data(node);
5938 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5939 g_free(office);
5941 /* site (url) */
5942 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5943 if (node) {
5944 char* site = xmlnode_get_data(node);
5945 sipe_update_user_info(sip, uri, SITE_PROP, site);
5946 g_free(site);
5948 /* phone */
5949 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5950 node;
5951 node = xmlnode_get_next_twin(node))
5953 const char *phone_type = xmlnode_get_attrib(node, "type");
5954 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5955 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5957 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5959 g_free(phone);
5960 g_free(phone_display_string);
5962 /* address */
5963 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5964 node;
5965 node = xmlnode_get_next_twin(node))
5967 if (sipe_strequal(xmlnode_get_attrib(node, "type"), "work")) {
5968 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5969 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5970 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5971 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5972 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5974 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5975 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5976 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5977 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5978 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5980 g_free(street);
5981 g_free(city);
5982 g_free(state);
5983 g_free(zipcode);
5984 g_free(country_code);
5986 break;
5990 /* note */
5991 else if (sipe_strequal(attrVar, "note"))
5993 if (uri) {
5994 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5996 if (!has_note_cleaned) {
5997 has_note_cleaned = TRUE;
5999 g_free(sbuddy->note);
6000 sbuddy->note = NULL;
6001 sbuddy->is_oof_note = FALSE;
6002 sbuddy->note_since = publish_time;
6004 do_update_status = TRUE;
6006 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6007 /* clean up in case no 'note' element is supplied
6008 * which indicate note removal in client
6010 g_free(sbuddy->note);
6011 sbuddy->note = NULL;
6012 sbuddy->is_oof_note = FALSE;
6013 sbuddy->note_since = publish_time;
6015 xn_node = xmlnode_get_descendant(xn_category, "note", "body", NULL);
6016 if (xn_node) {
6017 char *tmp;
6018 sbuddy->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_node)), -1);
6019 g_free(tmp);
6020 sbuddy->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_node, "type"), "OOF");
6021 sbuddy->note_since = publish_time;
6023 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
6024 uri, sbuddy->note ? sbuddy->note : "");
6026 /* to trigger UI refresh in case no status info is supplied in this update */
6027 do_update_status = TRUE;
6031 /* state */
6032 else if(sipe_strequal(attrVar, "state"))
6034 char *tmp;
6035 int availability;
6036 xmlnode *xn_availability;
6037 xmlnode *xn_activity;
6038 xmlnode *xn_meeting_subject;
6039 xmlnode *xn_meeting_location;
6040 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6042 xn_node = xmlnode_get_child(xn_category, "state");
6043 if (!xn_node) continue;
6044 xn_availability = xmlnode_get_child(xn_node, "availability");
6045 if (!xn_availability) continue;
6046 xn_activity = xmlnode_get_child(xn_node, "activity");
6047 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
6048 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
6050 tmp = xmlnode_get_data(xn_availability);
6051 availability = atoi(tmp);
6052 g_free(tmp);
6054 /* activity, meeting_subject, meeting_location */
6055 if (sbuddy) {
6056 char *tmp = NULL;
6058 /* activity */
6059 g_free(sbuddy->activity);
6060 sbuddy->activity = NULL;
6061 if (xn_activity) {
6062 const char *token = xmlnode_get_attrib(xn_activity, "token");
6063 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
6065 /* from token */
6066 if (!is_empty(token)) {
6067 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6069 /* from custom element */
6070 if (xn_custom) {
6071 char *custom = xmlnode_get_data(xn_custom);
6073 if (!is_empty(custom)) {
6074 sbuddy->activity = custom;
6075 custom = NULL;
6077 g_free(custom);
6080 /* meeting_subject */
6081 g_free(sbuddy->meeting_subject);
6082 sbuddy->meeting_subject = NULL;
6083 if (xn_meeting_subject) {
6084 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
6086 if (!is_empty(meeting_subject)) {
6087 sbuddy->meeting_subject = meeting_subject;
6088 meeting_subject = NULL;
6090 g_free(meeting_subject);
6092 /* meeting_location */
6093 g_free(sbuddy->meeting_location);
6094 sbuddy->meeting_location = NULL;
6095 if (xn_meeting_location) {
6096 char *meeting_location = xmlnode_get_data(xn_meeting_location);
6098 if (!is_empty(meeting_location)) {
6099 sbuddy->meeting_location = meeting_location;
6100 meeting_location = NULL;
6102 g_free(meeting_location);
6105 status = sipe_get_status_by_availability(availability, &tmp);
6106 if (sbuddy->activity && tmp) {
6107 char *tmp2 = sbuddy->activity;
6109 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6110 g_free(tmp);
6111 g_free(tmp2);
6112 } else if (tmp) {
6113 sbuddy->activity = tmp;
6117 do_update_status = TRUE;
6119 /* calendarData */
6120 else if(sipe_strequal(attrVar, "calendarData"))
6122 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6123 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
6124 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
6126 if (sbuddy && xn_free_busy) {
6127 if (!has_free_busy_cleaned) {
6128 has_free_busy_cleaned = TRUE;
6130 g_free(sbuddy->cal_start_time);
6131 sbuddy->cal_start_time = NULL;
6133 g_free(sbuddy->cal_free_busy_base64);
6134 sbuddy->cal_free_busy_base64 = NULL;
6136 g_free(sbuddy->cal_free_busy);
6137 sbuddy->cal_free_busy = NULL;
6139 sbuddy->cal_free_busy_published = publish_time;
6142 if (publish_time >= sbuddy->cal_free_busy_published) {
6143 g_free(sbuddy->cal_start_time);
6144 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
6146 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
6147 15 : 0;
6149 g_free(sbuddy->cal_free_busy_base64);
6150 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
6152 g_free(sbuddy->cal_free_busy);
6153 sbuddy->cal_free_busy = NULL;
6155 sbuddy->cal_free_busy_published = publish_time;
6157 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);
6161 if (sbuddy && xn_working_hours) {
6162 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6167 if (do_update_status) {
6168 if (!status) { /* no status category in this update, using contact's current status */
6169 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6170 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6171 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6172 status = purple_status_get_id(pstatus);
6175 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
6176 sipe_got_user_status(sip, uri, status);
6179 xmlnode_free(xn_categories);
6182 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6184 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6185 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
6186 payload->host = g_strdup(host);
6187 payload->buddies = server;
6188 sipe_subscribe_presence_batched_routed(sip, payload);
6189 sipe_subscribe_presence_batched_routed_free(payload);
6192 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6194 xmlnode *xn_list;
6195 xmlnode *xn_resource;
6196 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6197 g_free, NULL);
6198 GSList *server;
6199 gchar *host;
6201 xn_list = xmlnode_from_str(data, len);
6203 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6204 xn_resource;
6205 xn_resource = xmlnode_get_next_twin(xn_resource) )
6207 const char *uri, *state;
6208 xmlnode *xn_instance;
6210 xn_instance = xmlnode_get_child(xn_resource, "instance");
6211 if (!xn_instance) continue;
6213 uri = xmlnode_get_attrib(xn_resource, "uri");
6214 state = xmlnode_get_attrib(xn_instance, "state");
6215 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6217 if (strstr(state, "resubscribe")) {
6218 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6220 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6221 gchar *user = g_strdup(uri);
6222 host = g_strdup(poolFqdn);
6223 server = g_hash_table_lookup(servers, host);
6224 server = g_slist_append(server, user);
6225 g_hash_table_insert(servers, host, server);
6226 } else {
6227 sipe_subscribe_presence_single(sip, (void *) uri);
6232 /* Send out any deferred poolFqdn subscriptions */
6233 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6234 g_hash_table_destroy(servers);
6236 xmlnode_free(xn_list);
6239 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6241 gchar *uri;
6242 gchar *getbasic;
6243 gchar *activity = NULL;
6244 xmlnode *pidf;
6245 xmlnode *basicstatus = NULL, *tuple, *status;
6246 gboolean isonline = FALSE;
6247 xmlnode *display_name_node;
6249 pidf = xmlnode_from_str(data, len);
6250 if (!pidf) {
6251 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6252 return;
6255 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6257 if ((status = xmlnode_get_child(tuple, "status"))) {
6258 basicstatus = xmlnode_get_child(status, "basic");
6262 if (!basicstatus) {
6263 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6264 xmlnode_free(pidf);
6265 return;
6268 getbasic = xmlnode_get_data(basicstatus);
6269 if (!getbasic) {
6270 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6271 xmlnode_free(pidf);
6272 return;
6275 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6276 if (strstr(getbasic, "open")) {
6277 isonline = TRUE;
6279 g_free(getbasic);
6281 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6283 display_name_node = xmlnode_get_child(pidf, "display-name");
6284 if (display_name_node) {
6285 char * display_name = xmlnode_get_data(display_name_node);
6287 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6288 g_free(display_name);
6291 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6292 if ((status = xmlnode_get_child(tuple, "status"))) {
6293 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6294 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6295 activity = xmlnode_get_data(basicstatus);
6296 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6302 if (isonline) {
6303 const gchar * status_id = NULL;
6304 if (activity) {
6305 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6306 status_id = SIPE_STATUS_ID_BUSY;
6307 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6308 status_id = SIPE_STATUS_ID_AWAY;
6312 if (!status_id) {
6313 status_id = SIPE_STATUS_ID_AVAILABLE;
6316 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6317 sipe_got_user_status(sip, uri, status_id);
6318 } else {
6319 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6322 g_free(activity);
6323 g_free(uri);
6324 xmlnode_free(pidf);
6327 /** 2005 */
6328 static void
6329 sipe_user_info_has_updated(struct sipe_account_data *sip,
6330 xmlnode *xn_userinfo)
6332 if (sip->user_info) {
6333 xmlnode_free(sip->user_info);
6335 sip->user_info = xmlnode_copy(xn_userinfo);
6337 /* Publish initial state if not yet.
6338 * Assuming this happens on initial responce to self subscription
6339 * so we've already updated our UserInfo.
6341 if (!sip->initial_state_published) {
6342 send_presence_soap(sip, FALSE);
6343 /* dalayed run */
6344 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6348 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6350 char *activity = NULL;
6351 const char *epid;
6352 const char *status_id = NULL;
6353 const char *name;
6354 char *uri;
6355 char *self_uri = sip_uri_self(sip);
6356 int avl;
6357 int act;
6358 const char *device_name = NULL;
6359 const char *cal_start_time = NULL;
6360 const char *cal_granularity = NULL;
6361 char *cal_free_busy_base64 = NULL;
6362 struct sipe_buddy *sbuddy;
6363 xmlnode *node;
6364 xmlnode *xn_presentity;
6365 xmlnode *xn_availability;
6366 xmlnode *xn_activity;
6367 xmlnode *xn_display_name;
6368 xmlnode *xn_email;
6369 xmlnode *xn_phone_number;
6370 xmlnode *xn_userinfo;
6371 xmlnode *xn_note;
6372 xmlnode *xn_oof;
6373 xmlnode *xn_state;
6374 xmlnode *xn_contact;
6375 char *note;
6376 char *free_activity;
6377 int user_avail;
6378 const char *user_avail_nil;
6379 int res_avail;
6380 time_t user_avail_since = 0;
6381 time_t activity_since = 0;
6383 /* fix for Reuters environment on Linux */
6384 if (data && strstr(data, "encoding=\"utf-16\"")) {
6385 char *tmp_data;
6386 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6387 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6388 g_free(tmp_data);
6389 } else {
6390 xn_presentity = xmlnode_from_str(data, len);
6393 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6394 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6395 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6396 xn_email = xmlnode_get_child(xn_presentity, "email");
6397 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6398 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6399 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6400 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6401 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6402 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6403 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6404 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6405 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6406 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6408 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6409 user_avail = 0;
6410 user_avail_since = 0;
6413 free_activity = NULL;
6415 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6416 uri = sip_uri_from_name(name);
6417 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6418 epid = xmlnode_get_attrib(xn_availability, "epid");
6419 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6421 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6422 res_avail = sipe_get_availability_by_status(status_id, NULL);
6423 if (user_avail > res_avail) {
6424 res_avail = user_avail;
6425 status_id = sipe_get_status_by_availability(user_avail, NULL);
6428 if (xn_display_name) {
6429 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6430 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6431 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6432 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6433 char *tel_uri = sip_to_tel_uri(phone_number);
6435 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6436 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6437 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6438 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6440 g_free(tel_uri);
6441 g_free(phone_label);
6442 g_free(phone_number);
6443 g_free(email);
6444 g_free(display_name);
6447 if (xn_contact) {
6448 /* tel */
6449 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6451 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6452 const char *phone_type = xmlnode_get_attrib(node, "type");
6453 char* phone = xmlnode_get_data(node);
6455 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6457 g_free(phone);
6461 /* devicePresence */
6462 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6463 xmlnode *xn_device_name;
6464 xmlnode *xn_calendar_info;
6465 xmlnode *xn_state;
6466 char *state;
6468 /* deviceName */
6469 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6470 xn_device_name = xmlnode_get_child(node, "deviceName");
6471 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6474 /* calendarInfo */
6475 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6476 if (xn_calendar_info) {
6477 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6479 if (cal_start_time) {
6480 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6481 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6483 if (cal_start_time_t_tmp > cal_start_time_t) {
6484 cal_start_time = cal_start_time_tmp;
6485 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6486 g_free(cal_free_busy_base64);
6487 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6489 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);
6491 } else {
6492 cal_start_time = cal_start_time_tmp;
6493 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6494 g_free(cal_free_busy_base64);
6495 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6497 purple_debug_info("sipe", "process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s\n", cal_start_time, cal_granularity, cal_free_busy_base64);
6501 /* state */
6502 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6503 if (xn_state) {
6504 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6505 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6507 state = xmlnode_get_data(xn_state);
6508 if (dev_avail_since > user_avail_since &&
6509 dev_avail >= res_avail)
6511 res_avail = dev_avail;
6512 if (!is_empty(state))
6514 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6515 g_free(activity);
6516 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6517 } else if (sipe_strequal(state, "presenting")) {
6518 g_free(activity);
6519 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6520 } else {
6521 activity = state;
6522 state = NULL;
6524 activity_since = dev_avail_since;
6526 status_id = sipe_get_status_by_availability(res_avail, &activity);
6528 g_free(state);
6532 /* oof */
6533 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6534 g_free(activity);
6535 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6536 activity_since = 0;
6539 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6540 if (sbuddy)
6542 g_free(sbuddy->activity);
6543 sbuddy->activity = activity;
6544 activity = NULL;
6546 sbuddy->activity_since = activity_since;
6548 sbuddy->user_avail = user_avail;
6549 sbuddy->user_avail_since = user_avail_since;
6551 g_free(sbuddy->note);
6552 sbuddy->note = NULL;
6553 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6555 sbuddy->is_oof_note = (xn_oof != NULL);
6557 g_free(sbuddy->device_name);
6558 sbuddy->device_name = NULL;
6559 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6561 if (!is_empty(cal_free_busy_base64)) {
6562 g_free(sbuddy->cal_start_time);
6563 sbuddy->cal_start_time = g_strdup(cal_start_time);
6565 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
6567 g_free(sbuddy->cal_free_busy_base64);
6568 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6569 cal_free_busy_base64 = NULL;
6571 g_free(sbuddy->cal_free_busy);
6572 sbuddy->cal_free_busy = NULL;
6575 sbuddy->last_non_cal_status_id = status_id;
6576 g_free(sbuddy->last_non_cal_activity);
6577 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6579 if (sipe_strequal(sbuddy->name, self_uri)) {
6580 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6582 sip->is_oof_note = sbuddy->is_oof_note;
6584 g_free(sip->note);
6585 sip->note = g_strdup(sbuddy->note);
6587 sip->note_since = time(NULL);
6590 g_free(sip->status);
6591 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6594 g_free(cal_free_busy_base64);
6595 g_free(activity);
6597 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6598 sipe_got_user_status(sip, uri, status_id);
6600 if (!sip->ocs2007 && sipe_strequal(self_uri, uri)) {
6601 sipe_user_info_has_updated(sip, xn_userinfo);
6604 g_free(note);
6605 xmlnode_free(xn_presentity);
6606 g_free(uri);
6607 g_free(self_uri);
6610 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6612 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6614 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6616 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6617 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6619 const char *content = msg->body;
6620 unsigned length = msg->bodylen;
6621 PurpleMimeDocument *mime = NULL;
6623 if (strstr(ctype, "multipart"))
6625 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6626 const char *content_type;
6627 GList* parts;
6628 mime = purple_mime_document_parse(doc);
6629 parts = purple_mime_document_get_parts(mime);
6630 while(parts) {
6631 content = purple_mime_part_get_data(parts->data);
6632 length = purple_mime_part_get_length(parts->data);
6633 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6634 if(content_type && strstr(content_type,"application/rlmi+xml"))
6636 process_incoming_notify_rlmi_resub(sip, content, length);
6638 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6640 process_incoming_notify_msrtc(sip, content, length);
6642 else
6644 process_incoming_notify_rlmi(sip, content, length);
6646 parts = parts->next;
6648 g_free(doc);
6650 if (mime)
6652 purple_mime_document_free(mime);
6655 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6657 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6659 else if(strstr(ctype, "application/rlmi+xml"))
6661 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6664 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6666 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6668 else
6670 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6674 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6676 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6677 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6679 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6681 if (ctype &&
6682 strstr(ctype, "multipart") &&
6683 (strstr(ctype, "application/rlmi+xml") ||
6684 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6685 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6686 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6687 GList *parts = purple_mime_document_get_parts(mime);
6688 GSList *buddies = NULL;
6689 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6691 while (parts) {
6692 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6693 purple_mime_part_get_length(parts->data));
6695 if (xml && !sipe_strequal(xml->name, "list")) {
6696 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6698 buddies = g_slist_append(buddies, uri);
6700 xmlnode_free(xml);
6702 parts = parts->next;
6704 g_free(doc);
6705 if (mime) purple_mime_document_free(mime);
6707 payload->host = g_strdup(who);
6708 payload->buddies = buddies;
6709 sipe_schedule_action(action_name, timeout,
6710 sipe_subscribe_presence_batched_routed,
6711 sipe_subscribe_presence_batched_routed_free,
6712 sip, payload);
6713 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6715 } else {
6716 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6717 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6719 g_free(action_name);
6723 * Dispatcher for all incoming subscription information
6724 * whether it comes from NOTIFY, BENOTIFY requests or
6725 * piggy-backed to subscription's OK responce.
6727 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6728 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6730 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6732 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6733 const gchar *event = sipmsg_find_header(msg, "Event");
6734 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6735 char *tmp;
6737 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6738 event ? event : "",
6739 tmp = fix_newlines(msg->body));
6740 g_free(tmp);
6741 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6743 /* implicit subscriptions */
6744 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6745 sipe_process_imdn(sip, msg);
6748 if (event) {
6749 /* for one off subscriptions (send with Expire: 0) */
6750 if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
6752 sipe_process_provisioning_v2(sip, msg);
6754 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
6756 sipe_process_provisioning(sip, msg);
6758 else if (!g_ascii_strcasecmp(event, "presence"))
6760 sipe_process_presence(sip, msg);
6762 else if (!g_ascii_strcasecmp(event, "registration-notify"))
6764 sipe_process_registration_notify(sip, msg);
6767 if (!subscription_state || strstr(subscription_state, "active"))
6769 if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
6771 sipe_process_roaming_contacts(sip, msg);
6773 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
6775 sipe_process_roaming_self(sip, msg);
6777 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
6779 sipe_process_roaming_acl(sip, msg);
6781 else if (!g_ascii_strcasecmp(event, "presence.wpending"))
6783 sipe_process_presence_wpending(sip, msg);
6785 else if (!g_ascii_strcasecmp(event, "conference"))
6787 sipe_process_conference(sip, msg);
6792 /* The server sends status 'terminated' */
6793 if (subscription_state && strstr(subscription_state, "terminated") ) {
6794 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6795 gchar *key = sipe_get_subscription_key(event, who);
6797 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6798 g_free(who);
6800 if (g_hash_table_lookup(sip->subscriptions, key)) {
6801 g_hash_table_remove(sip->subscriptions, key);
6802 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6805 g_free(key);
6808 if (!request && event) {
6809 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6810 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6811 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6813 if (timeout) {
6814 /* 2 min ahead of expiration */
6815 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6817 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
6818 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6820 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6821 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6822 g_free(action_name);
6824 else if (!g_ascii_strcasecmp(event, "presence") &&
6825 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6827 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6828 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6830 if (sip->batched_support) {
6831 sipe_process_presence_timeout(sip, msg, who, timeout);
6833 else {
6834 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6835 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6837 g_free(action_name);
6838 g_free(who);
6843 /* The client responses on received a NOTIFY message */
6844 if (request && !benotify)
6846 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6851 * Whether user manually changed status or
6852 * it was changed automatically due to user
6853 * became inactive/active again
6855 static gboolean
6856 sipe_is_user_state(struct sipe_account_data *sip)
6858 gboolean res;
6859 time_t now = time(NULL);
6861 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6862 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6864 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6866 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6867 return res;
6870 static void
6871 send_presence_soap0(struct sipe_account_data *sip,
6872 gboolean do_publish_calendar,
6873 gboolean do_reset_status)
6875 struct sipe_ews* ews = sip->ews;
6876 int availability = 0;
6877 int activity = 0;
6878 gchar *body;
6879 gchar *tmp;
6880 gchar *tmp2 = NULL;
6881 gchar *res_note = NULL;
6882 gchar *res_oof = NULL;
6883 const gchar *note_pub = NULL;
6884 gchar *states = NULL;
6885 gchar *calendar_data = NULL;
6886 gchar *epid = get_epid(sip);
6887 time_t now = time(NULL);
6888 gchar *since_time_str = sipe_utils_time_to_str(now);
6889 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6890 const char *user_input;
6891 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6893 if (oof_note && sip->note) {
6894 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6895 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6898 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6900 if (!sip->initial_state_published ||
6901 do_reset_status)
6903 g_free(sip->status);
6904 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6907 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6909 /* Note */
6910 if (pub_oof) {
6911 note_pub = oof_note;
6912 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6913 ews->published = TRUE;
6914 } else if (sip->note) {
6915 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6916 g_free(sip->note);
6917 sip->note = NULL;
6918 sip->is_oof_note = FALSE;
6919 sip->note_since = 0;
6920 } else {
6921 note_pub = sip->note;
6922 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6926 if (note_pub)
6928 /* to protocol internal plain text format */
6929 tmp = purple_markup_strip_html(note_pub);
6930 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6931 g_free(tmp);
6934 /* User State */
6935 if (!do_reset_status) {
6936 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6938 gchar *activity_token = NULL;
6939 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6941 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6942 avail_2007,
6943 since_time_str,
6944 epid,
6945 activity_token);
6946 g_free(activity_token);
6948 else /* preserve existing publication */
6950 xmlnode *xn_states;
6951 if (sip->user_info && (xn_states = xmlnode_get_child(sip->user_info, "states"))) {
6952 states = xmlnode_to_str(xn_states, NULL);
6953 /* this is a hack-around to remove added newline after inner element,
6954 * state in this case, where it shouldn't be.
6955 * After several use of xmlnode_to_str, amount of added newlines
6956 * grows significantly.
6958 purple_str_strip_char(states, '\n');
6959 //purple_str_strip_char(states, '\r');
6962 } else {
6963 /* do nothing - then User state will be erased */
6965 sip->initial_state_published = TRUE;
6967 /* CalendarInfo */
6968 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6970 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6971 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6972 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6973 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6974 fb_start_str,
6975 free_busy_base64);
6976 g_free(fb_start_str);
6977 g_free(free_busy_base64);
6980 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6982 /* forming resulting XML */
6983 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6984 sip->username,
6985 availability,
6986 activity,
6987 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
6988 res_note ? res_note : "",
6989 res_oof ? res_oof : "",
6990 states ? states : "",
6991 calendar_data ? calendar_data : "",
6992 epid,
6993 since_time_str,
6994 since_time_str,
6995 user_input);
6996 g_free(tmp);
6997 g_free(tmp2);
6998 g_free(res_note);
6999 g_free(states);
7000 g_free(calendar_data);
7002 send_soap_request(sip, body);
7004 g_free(body);
7005 g_free(since_time_str);
7006 g_free(epid);
7009 void
7010 send_presence_soap(struct sipe_account_data *sip,
7011 gboolean do_publish_calendar)
7013 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7017 static gboolean
7018 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7019 struct sipmsg *msg,
7020 struct transaction *trans)
7022 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7024 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7025 xmlnode *xml;
7026 xmlnode *node;
7027 gchar *fault_code;
7028 GHashTable *faults;
7029 int index_our;
7030 gboolean has_device_publication = FALSE;
7032 xml = xmlnode_from_str(msg->body, msg->bodylen);
7034 /* test if version mismatch fault */
7035 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7036 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7037 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
7038 g_free(fault_code);
7039 xmlnode_free(xml);
7040 return TRUE;
7042 g_free(fault_code);
7044 /* accumulating information about faulty versions */
7045 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7046 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7047 node;
7048 node = xmlnode_get_next_twin(node))
7050 const gchar *index = xmlnode_get_attrib(node, "index");
7051 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7053 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7054 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
7056 xmlnode_free(xml);
7058 /* here we are parsing own request to figure out what publication
7059 * referensed here only by index went wrong
7061 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7063 /* publication */
7064 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7065 index_our = 1; /* starts with 1 - our first publication */
7066 node;
7067 node = xmlnode_get_next_twin(node), index_our++)
7069 gchar *idx = g_strdup_printf("%d", index_our);
7070 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7071 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7072 g_free(idx);
7074 if (sipe_strequal("device", categoryName)) {
7075 has_device_publication = TRUE;
7078 if (curVersion) { /* fault exist on this index */
7079 const gchar *container = xmlnode_get_attrib(node, "container");
7080 const gchar *instance = xmlnode_get_attrib(node, "instance");
7081 /* key is <category><instance><container> */
7082 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7083 struct sipe_publication *publication =
7084 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
7086 purple_debug_info("sipe", "key is %s\n", key);
7088 if (publication) {
7089 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
7090 key, curVersion, publication->version);
7091 /* updating publication's version to the correct one */
7092 publication->version = atoi(curVersion);
7094 g_free(key);
7097 xmlnode_free(xml);
7098 g_hash_table_destroy(faults);
7100 /* rebublishing with right versions */
7101 if (has_device_publication) {
7102 send_publish_category_initial(sip);
7103 } else {
7104 send_presence_status(sip);
7107 return TRUE;
7111 * Returns 'device' XML part for publication.
7112 * Must be g_free'd after use.
7114 static gchar *
7115 sipe_publish_get_category_device(struct sipe_account_data *sip)
7117 gchar *uri;
7118 gchar *doc;
7119 gchar *epid = get_epid(sip);
7120 gchar *uuid = generateUUIDfromEPID(epid);
7121 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7122 /* key is <category><instance><container> */
7123 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7124 struct sipe_publication *publication =
7125 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7127 g_free(key);
7128 g_free(epid);
7130 uri = sip_uri_self(sip);
7131 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7132 device_instance,
7133 publication ? publication->version : 0,
7134 uuid,
7135 uri,
7136 "00:00:00+01:00", /* @TODO make timezone real*/
7137 sipe_get_host_name()
7140 g_free(uri);
7141 g_free(uuid);
7143 return doc;
7147 * A service method - use
7148 * - send_publish_get_category_state_machine and
7149 * - send_publish_get_category_state_user instead.
7150 * Must be g_free'd after use.
7152 static gchar *
7153 sipe_publish_get_category_state(struct sipe_account_data *sip,
7154 gboolean is_user_state)
7156 int availability = sipe_get_availability_by_status(sip->status, NULL);
7157 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7158 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7159 /* key is <category><instance><container> */
7160 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7161 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7162 struct sipe_publication *publication_2 =
7163 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7164 struct sipe_publication *publication_3 =
7165 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7167 g_free(key_2);
7168 g_free(key_3);
7170 if (publication_2 && (publication_2->availability == availability))
7172 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
7173 return NULL; /* nothing to update */
7176 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7177 instance,
7178 publication_2 ? publication_2->version : 0,
7179 availability,
7180 instance,
7181 publication_3 ? publication_3->version : 0,
7182 availability);
7186 * Only Busy and OOF calendar event are published.
7187 * Different instances are used for that.
7189 * Must be g_free'd after use.
7191 static gchar *
7192 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7193 struct sipe_cal_event *event,
7194 const char *uri,
7195 int cal_satus)
7197 gchar *start_time_str;
7198 int availability = 0;
7199 gchar *res;
7200 gchar *tmp = NULL;
7201 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7202 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7203 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7205 /* key is <category><instance><container> */
7206 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7207 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7208 struct sipe_publication *publication_2 =
7209 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7210 struct sipe_publication *publication_3 =
7211 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7213 g_free(key_2);
7214 g_free(key_3);
7216 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7217 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7218 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7219 return NULL;
7222 if (event &&
7223 publication_3 &&
7224 (publication_3->availability == availability) &&
7225 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7227 g_free(tmp);
7228 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7229 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7230 return NULL; /* nothing to update */
7232 g_free(tmp);
7234 if (event &&
7235 (event->cal_status == SIPE_CAL_BUSY ||
7236 event->cal_status == SIPE_CAL_OOF))
7238 gchar *availability_xml_str = NULL;
7239 gchar *activity_xml_str = NULL;
7241 if (event->cal_status == SIPE_CAL_BUSY) {
7242 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7245 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7246 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7247 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7248 "minAvailability=\"6500\"",
7249 "maxAvailability=\"8999\"");
7250 } else if (event->cal_status == SIPE_CAL_OOF) {
7251 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7252 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7253 "minAvailability=\"12000\"",
7254 "");
7256 start_time_str = sipe_utils_time_to_str(event->start_time);
7258 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7259 instance,
7260 publication_2 ? publication_2->version : 0,
7261 uri,
7262 start_time_str,
7263 availability_xml_str ? availability_xml_str : "",
7264 activity_xml_str ? activity_xml_str : "",
7265 event->subject ? event->subject : "",
7266 event->location ? event->location : "",
7268 instance,
7269 publication_3 ? publication_3->version : 0,
7270 uri,
7271 start_time_str,
7272 availability_xml_str ? availability_xml_str : "",
7273 activity_xml_str ? activity_xml_str : "",
7274 event->subject ? event->subject : "",
7275 event->location ? event->location : ""
7277 g_free(start_time_str);
7278 g_free(availability_xml_str);
7279 g_free(activity_xml_str);
7282 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7284 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7285 instance,
7286 publication_2 ? publication_2->version : 0,
7288 instance,
7289 publication_3 ? publication_3->version : 0
7293 return res;
7297 * Returns 'machineState' XML part for publication.
7298 * Must be g_free'd after use.
7300 static gchar *
7301 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7303 return sipe_publish_get_category_state(sip, FALSE);
7307 * Returns 'userState' XML part for publication.
7308 * Must be g_free'd after use.
7310 static gchar *
7311 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7313 return sipe_publish_get_category_state(sip, TRUE);
7317 * Returns 'note' XML part for publication.
7318 * Must be g_free'd after use.
7320 * Protocol format for Note is plain text.
7322 * @param note a note in Sipe internal HTML format
7323 * @param note_type either personal or OOF
7325 static gchar *
7326 sipe_publish_get_category_note(struct sipe_account_data *sip,
7327 const char *note, /* html */
7328 const char *note_type,
7329 time_t note_start,
7330 time_t note_end)
7332 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7333 /* key is <category><instance><container> */
7334 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7335 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7336 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7338 struct sipe_publication *publication_note_200 =
7339 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7340 struct sipe_publication *publication_note_300 =
7341 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7342 struct sipe_publication *publication_note_400 =
7343 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7345 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7346 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7347 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7348 char *res, *tmp1, *tmp2, *tmp3;
7349 char *start_time_attr;
7350 char *end_time_attr;
7352 g_free(tmp);
7353 tmp = NULL;
7354 g_free(key_note_200);
7355 g_free(key_note_300);
7356 g_free(key_note_400);
7358 /* we even need to republish empty note */
7359 if (sipe_strequal(n1, n2))
7361 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7362 g_free(n1);
7363 return NULL; /* nothing to update */
7366 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7367 g_free(tmp);
7368 tmp = NULL;
7369 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7370 g_free(tmp);
7372 if (n1) {
7373 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7374 instance,
7375 200,
7376 publication_note_200 ? publication_note_200->version : 0,
7377 note_type,
7378 start_time_attr ? start_time_attr : "",
7379 end_time_attr ? end_time_attr : "",
7380 n1);
7382 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7383 instance,
7384 300,
7385 publication_note_300 ? publication_note_300->version : 0,
7386 note_type,
7387 start_time_attr ? start_time_attr : "",
7388 end_time_attr ? end_time_attr : "",
7389 n1);
7391 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7392 instance,
7393 400,
7394 publication_note_400 ? publication_note_400->version : 0,
7395 note_type,
7396 start_time_attr ? start_time_attr : "",
7397 end_time_attr ? end_time_attr : "",
7398 n1);
7399 } else {
7400 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7401 "note",
7402 instance,
7403 200,
7404 publication_note_200 ? publication_note_200->version : 0,
7405 "static");
7406 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7407 "note",
7408 instance,
7409 300,
7410 publication_note_200 ? publication_note_200->version : 0,
7411 "static");
7412 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7413 "note",
7414 instance,
7415 400,
7416 publication_note_200 ? publication_note_200->version : 0,
7417 "static");
7419 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7421 g_free(start_time_attr);
7422 g_free(end_time_attr);
7423 g_free(tmp1);
7424 g_free(tmp2);
7425 g_free(tmp3);
7426 g_free(n1);
7428 return res;
7432 * Returns 'calendarData' XML part with WorkingHours for publication.
7433 * Must be g_free'd after use.
7435 static gchar *
7436 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7438 struct sipe_ews* ews = sip->ews;
7440 /* key is <category><instance><container> */
7441 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7442 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7443 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7444 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7445 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7446 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7448 struct sipe_publication *publication_cal_1 =
7449 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7450 struct sipe_publication *publication_cal_100 =
7451 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7452 struct sipe_publication *publication_cal_200 =
7453 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7454 struct sipe_publication *publication_cal_300 =
7455 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7456 struct sipe_publication *publication_cal_400 =
7457 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7458 struct sipe_publication *publication_cal_32000 =
7459 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7461 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7462 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7464 g_free(key_cal_1);
7465 g_free(key_cal_100);
7466 g_free(key_cal_200);
7467 g_free(key_cal_300);
7468 g_free(key_cal_400);
7469 g_free(key_cal_32000);
7471 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7472 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7473 return NULL;
7476 if (sipe_strequal(n1, n2))
7478 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7479 return NULL; /* nothing to update */
7482 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7483 /* 1 */
7484 publication_cal_1 ? publication_cal_1->version : 0,
7485 ews->email,
7486 ews->working_hours_xml_str,
7487 /* 100 - Public */
7488 publication_cal_100 ? publication_cal_100->version : 0,
7489 /* 200 - Company */
7490 publication_cal_200 ? publication_cal_200->version : 0,
7491 ews->email,
7492 ews->working_hours_xml_str,
7493 /* 300 - Team */
7494 publication_cal_300 ? publication_cal_300->version : 0,
7495 ews->email,
7496 ews->working_hours_xml_str,
7497 /* 400 - Personal */
7498 publication_cal_400 ? publication_cal_400->version : 0,
7499 ews->email,
7500 ews->working_hours_xml_str,
7501 /* 32000 - Blocked */
7502 publication_cal_32000 ? publication_cal_32000->version : 0
7507 * Returns 'calendarData' XML part with FreeBusy for publication.
7508 * Must be g_free'd after use.
7510 static gchar *
7511 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7513 struct sipe_ews* ews = sip->ews;
7514 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7515 char *fb_start_str;
7516 char *free_busy_base64;
7517 const char *st;
7518 const char *fb;
7519 char *res;
7521 /* key is <category><instance><container> */
7522 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7523 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7524 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7525 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7526 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7527 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7529 struct sipe_publication *publication_cal_1 =
7530 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7531 struct sipe_publication *publication_cal_100 =
7532 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7533 struct sipe_publication *publication_cal_200 =
7534 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7535 struct sipe_publication *publication_cal_300 =
7536 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7537 struct sipe_publication *publication_cal_400 =
7538 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7539 struct sipe_publication *publication_cal_32000 =
7540 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7542 g_free(key_cal_1);
7543 g_free(key_cal_100);
7544 g_free(key_cal_200);
7545 g_free(key_cal_300);
7546 g_free(key_cal_400);
7547 g_free(key_cal_32000);
7549 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7550 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7551 return NULL;
7554 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7555 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7557 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7558 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7560 /* we will rebuplish the same data to refresh publication time,
7561 * so if data from multiple sources, most recent will be choosen
7563 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7565 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7566 // g_free(fb_start_str);
7567 // g_free(free_busy_base64);
7568 // return NULL; /* nothing to update */
7571 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7572 /* 1 */
7573 cal_data_instance,
7574 publication_cal_1 ? publication_cal_1->version : 0,
7575 /* 100 - Public */
7576 cal_data_instance,
7577 publication_cal_100 ? publication_cal_100->version : 0,
7578 /* 200 - Company */
7579 cal_data_instance,
7580 publication_cal_200 ? publication_cal_200->version : 0,
7581 ews->email,
7582 fb_start_str,
7583 free_busy_base64,
7584 /* 300 - Team */
7585 cal_data_instance,
7586 publication_cal_300 ? publication_cal_300->version : 0,
7587 ews->email,
7588 fb_start_str,
7589 free_busy_base64,
7590 /* 400 - Personal */
7591 cal_data_instance,
7592 publication_cal_400 ? publication_cal_400->version : 0,
7593 ews->email,
7594 fb_start_str,
7595 free_busy_base64,
7596 /* 32000 - Blocked */
7597 cal_data_instance,
7598 publication_cal_32000 ? publication_cal_32000->version : 0
7601 g_free(fb_start_str);
7602 g_free(free_busy_base64);
7603 return res;
7606 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7608 gchar *uri;
7609 gchar *doc;
7610 gchar *tmp;
7611 gchar *hdr;
7613 uri = sip_uri_self(sip);
7614 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7615 uri,
7616 publications);
7618 tmp = get_contact(sip);
7619 hdr = g_strdup_printf("Contact: %s\r\n"
7620 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7622 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7624 g_free(tmp);
7625 g_free(hdr);
7626 g_free(uri);
7627 g_free(doc);
7630 static void
7631 send_publish_category_initial(struct sipe_account_data *sip)
7633 gchar *pub_device = sipe_publish_get_category_device(sip);
7634 gchar *pub_machine;
7635 gchar *publications;
7637 g_free(sip->status);
7638 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7640 pub_machine = sipe_publish_get_category_state_machine(sip);
7641 publications = g_strdup_printf("%s%s",
7642 pub_device,
7643 pub_machine ? pub_machine : "");
7644 g_free(pub_device);
7645 g_free(pub_machine);
7647 send_presence_publish(sip, publications);
7648 g_free(publications);
7651 static void
7652 send_presence_category_publish(struct sipe_account_data *sip)
7654 gchar *pub_state = sipe_is_user_state(sip) ?
7655 sipe_publish_get_category_state_user(sip) :
7656 sipe_publish_get_category_state_machine(sip);
7657 gchar *pub_note = sipe_publish_get_category_note(sip,
7658 sip->note,
7659 sip->is_oof_note ? "OOF" : "personal",
7662 gchar *publications;
7664 if (!pub_state && !pub_note) {
7665 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7666 return;
7669 publications = g_strdup_printf("%s%s",
7670 pub_state ? pub_state : "",
7671 pub_note ? pub_note : "");
7673 g_free(pub_state);
7674 g_free(pub_note);
7676 send_presence_publish(sip, publications);
7677 g_free(publications);
7681 * Publishes self status
7682 * based on own calendar information.
7684 * For 2007+
7686 void
7687 publish_calendar_status_self(struct sipe_account_data *sip)
7689 struct sipe_cal_event* event = NULL;
7690 gchar *pub_cal_working_hours = NULL;
7691 gchar *pub_cal_free_busy = NULL;
7692 gchar *pub_calendar = NULL;
7693 gchar *pub_calendar2 = NULL;
7694 gchar *pub_oof_note = NULL;
7695 const gchar *oof_note;
7696 time_t oof_start = 0;
7697 time_t oof_end = 0;
7699 if (!sip->ews) {
7700 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7701 return;
7704 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7705 if (sip->ews->cal_events) {
7706 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7709 if (!event) {
7710 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7711 } else {
7712 char *desc = sipe_cal_event_describe(event);
7713 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7714 g_free(desc);
7717 /* Logic
7718 if OOF
7719 OOF publish, Busy clean
7720 ilse if Busy
7721 OOF clean, Busy publish
7722 else
7723 OOF clean, Busy clean
7725 if (event && event->cal_status == SIPE_CAL_OOF) {
7726 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7727 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7728 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7729 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7730 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7731 } else {
7732 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7733 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7736 oof_note = sipe_ews_get_oof_note(sip->ews);
7737 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7738 oof_start = sip->ews->oof_start;
7739 oof_end = sip->ews->oof_end;
7741 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7743 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7744 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7746 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7747 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7748 } else {
7749 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7750 pub_cal_working_hours ? pub_cal_working_hours : "",
7751 pub_cal_free_busy ? pub_cal_free_busy : "",
7752 pub_calendar ? pub_calendar : "",
7753 pub_calendar2 ? pub_calendar2 : "",
7754 pub_oof_note ? pub_oof_note : "");
7756 send_presence_publish(sip, publications);
7757 g_free(publications);
7760 g_free(pub_cal_working_hours);
7761 g_free(pub_cal_free_busy);
7762 g_free(pub_calendar);
7763 g_free(pub_calendar2);
7764 g_free(pub_oof_note);
7766 /* repeat scheduling */
7767 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7770 static void send_presence_status(struct sipe_account_data *sip)
7772 PurpleStatus * status = purple_account_get_active_status(sip->account);
7774 if (!status) return;
7776 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7777 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7778 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7780 if (sip->ocs2007) {
7781 send_presence_category_publish(sip);
7782 } else {
7783 send_presence_soap(sip, FALSE);
7787 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7789 gboolean found = FALSE;
7790 const char *method = msg->method ? msg->method : "NOT FOUND";
7791 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7792 if (msg->response == 0) { /* request */
7793 if (sipe_strequal(method, "MESSAGE")) {
7794 process_incoming_message(sip, msg);
7795 found = TRUE;
7796 } else if (sipe_strequal(method, "NOTIFY")) {
7797 purple_debug_info("sipe","send->process_incoming_notify\n");
7798 process_incoming_notify(sip, msg, TRUE, FALSE);
7799 found = TRUE;
7800 } else if (sipe_strequal(method, "BENOTIFY")) {
7801 purple_debug_info("sipe","send->process_incoming_benotify\n");
7802 process_incoming_notify(sip, msg, TRUE, TRUE);
7803 found = TRUE;
7804 } else if (sipe_strequal(method, "INVITE")) {
7805 process_incoming_invite(sip, msg);
7806 found = TRUE;
7807 } else if (sipe_strequal(method, "REFER")) {
7808 process_incoming_refer(sip, msg);
7809 found = TRUE;
7810 } else if (sipe_strequal(method, "OPTIONS")) {
7811 process_incoming_options(sip, msg);
7812 found = TRUE;
7813 } else if (sipe_strequal(method, "INFO")) {
7814 process_incoming_info(sip, msg);
7815 found = TRUE;
7816 } else if (sipe_strequal(method, "ACK")) {
7817 // ACK's don't need any response
7818 found = TRUE;
7819 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7820 // LCS 2005 sends us these - just respond 200 OK
7821 found = TRUE;
7822 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7823 } else if (sipe_strequal(method, "BYE")) {
7824 process_incoming_bye(sip, msg);
7825 found = TRUE;
7826 } else {
7827 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7829 } else { /* response */
7830 struct transaction *trans = transactions_find(sip, msg);
7831 if (trans) {
7832 if (msg->response == 407) {
7833 gchar *resend, *auth;
7834 const gchar *ptmp;
7836 if (sip->proxy.retries > 30) return;
7837 sip->proxy.retries++;
7838 /* do proxy authentication */
7840 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7842 fill_auth(ptmp, &sip->proxy);
7843 auth = auth_header(sip, &sip->proxy, trans->msg);
7844 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7845 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7846 g_free(auth);
7847 resend = sipmsg_to_string(trans->msg);
7848 /* resend request */
7849 sendout_pkt(sip->gc, resend);
7850 g_free(resend);
7851 } else {
7852 if (msg->response < 200) {
7853 /* ignore provisional response */
7854 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7855 } else {
7856 sip->proxy.retries = 0;
7857 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7858 if (msg->response == 401)
7860 sip->registrar.retries++;
7862 else
7864 sip->registrar.retries = 0;
7866 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7867 } else {
7868 if (msg->response == 401) {
7869 gchar *resend, *auth, *ptmp;
7870 const char* auth_scheme;
7872 if (sip->registrar.retries > 4) return;
7873 sip->registrar.retries++;
7875 auth_scheme = sipe_get_auth_scheme_name(sip);
7876 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7878 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7879 if (!ptmp) {
7880 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7881 sip->gc->wants_to_die = TRUE;
7882 purple_connection_error(sip->gc, tmp2);
7883 g_free(tmp2);
7884 return;
7887 fill_auth(ptmp, &sip->registrar);
7888 auth = auth_header(sip, &sip->registrar, trans->msg);
7889 sipmsg_remove_header_now(trans->msg, "Authorization");
7890 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7891 g_free(auth);
7892 resend = sipmsg_to_string(trans->msg);
7893 /* resend request */
7894 sendout_pkt(sip->gc, resend);
7895 g_free(resend);
7899 if (trans->callback) {
7900 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7901 /* call the callback to process response*/
7902 (trans->callback)(sip, msg, trans);
7905 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7906 transactions_remove(sip, trans);
7910 found = TRUE;
7911 } else {
7912 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7915 if (!found) {
7916 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7920 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7922 char *cur;
7923 char *dummy;
7924 char *tmp;
7925 struct sipmsg *msg;
7926 int restlen;
7927 cur = conn->inbuf;
7929 /* according to the RFC remove CRLF at the beginning */
7930 while (*cur == '\r' || *cur == '\n') {
7931 cur++;
7933 if (cur != conn->inbuf) {
7934 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7935 conn->inbufused = strlen(conn->inbuf);
7938 /* Received a full Header? */
7939 sip->processing_input = TRUE;
7940 while (sip->processing_input &&
7941 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7942 time_t currtime = time(NULL);
7943 cur += 2;
7944 cur[0] = '\0';
7945 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7946 g_free(tmp);
7947 msg = sipmsg_parse_header(conn->inbuf);
7948 cur[0] = '\r';
7949 cur += 2;
7950 restlen = conn->inbufused - (cur - conn->inbuf);
7951 if (msg && restlen >= msg->bodylen) {
7952 dummy = g_malloc(msg->bodylen + 1);
7953 memcpy(dummy, cur, msg->bodylen);
7954 dummy[msg->bodylen] = '\0';
7955 msg->body = dummy;
7956 cur += msg->bodylen;
7957 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7958 conn->inbufused = strlen(conn->inbuf);
7959 } else {
7960 if (msg){
7961 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7962 sipmsg_free(msg);
7964 return;
7967 /*if (msg->body) {
7968 purple_debug_info("sipe", "body:\n%s", msg->body);
7971 // Verify the signature before processing it
7972 if (sip->registrar.gssapi_context) {
7973 struct sipmsg_breakdown msgbd;
7974 gchar *signature_input_str;
7975 gchar *rspauth;
7976 msgbd.msg = msg;
7977 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7978 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
7980 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7982 if (rspauth != NULL) {
7983 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
7984 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
7985 process_input_message(sip, msg);
7986 } else {
7987 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
7988 purple_connection_error(sip->gc, _("Invalid message signature received"));
7989 sip->gc->wants_to_die = TRUE;
7991 } else if (msg->response == 401) {
7992 purple_connection_error(sip->gc, _("Authentication failed"));
7993 sip->gc->wants_to_die = TRUE;
7995 g_free(signature_input_str);
7997 g_free(rspauth);
7998 sipmsg_breakdown_free(&msgbd);
7999 } else {
8000 process_input_message(sip, msg);
8003 sipmsg_free(msg);
8007 static void sipe_udp_process(gpointer data, gint source,
8008 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8010 PurpleConnection *gc = data;
8011 struct sipe_account_data *sip = gc->proto_data;
8012 int len;
8014 static char buffer[65536];
8015 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8016 time_t currtime = time(NULL);
8017 struct sipmsg *msg;
8018 buffer[len] = '\0';
8019 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
8020 msg = sipmsg_parse_msg(buffer);
8021 if (msg) process_input_message(sip, msg);
8025 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8027 struct sipe_account_data *sip = gc->proto_data;
8028 PurpleSslConnection *gsc = sip->gsc;
8030 purple_debug_error("sipe", "%s",debug);
8031 purple_connection_error(gc, msg);
8033 /* Invalidate this connection. Next send will open a new one */
8034 if (gsc) {
8035 connection_remove(sip, gsc->fd);
8036 purple_ssl_close(gsc);
8038 sip->gsc = NULL;
8039 sip->fd = -1;
8042 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8043 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8045 PurpleConnection *gc = data;
8046 struct sipe_account_data *sip;
8047 struct sip_connection *conn;
8048 int readlen, len;
8049 gboolean firstread = TRUE;
8051 /* NOTE: This check *IS* necessary */
8052 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8053 purple_ssl_close(gsc);
8054 return;
8057 sip = gc->proto_data;
8058 conn = connection_find(sip, gsc->fd);
8059 if (conn == NULL) {
8060 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
8061 gc->wants_to_die = TRUE;
8062 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8063 return;
8066 /* Read all available data from the SSL connection */
8067 do {
8068 /* Increase input buffer size as needed */
8069 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8070 conn->inbuflen += SIMPLE_BUF_INC;
8071 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8072 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
8075 /* Try to read as much as there is space left in the buffer */
8076 readlen = conn->inbuflen - conn->inbufused - 1;
8077 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8079 if (len < 0 && errno == EAGAIN) {
8080 /* Try again later */
8081 return;
8082 } else if (len < 0) {
8083 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8084 return;
8085 } else if (firstread && (len == 0)) {
8086 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8087 return;
8090 conn->inbufused += len;
8091 firstread = FALSE;
8093 /* Equivalence indicates that there is possibly more data to read */
8094 } while (len == readlen);
8096 conn->inbuf[conn->inbufused] = '\0';
8097 process_input(sip, conn);
8101 static void sipe_input_cb(gpointer data, gint source,
8102 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8104 PurpleConnection *gc = data;
8105 struct sipe_account_data *sip = gc->proto_data;
8106 int len;
8107 struct sip_connection *conn = connection_find(sip, source);
8108 if (!conn) {
8109 purple_debug_error("sipe", "Connection not found!\n");
8110 return;
8113 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8114 conn->inbuflen += SIMPLE_BUF_INC;
8115 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8118 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8120 if (len < 0 && errno == EAGAIN)
8121 return;
8122 else if (len <= 0) {
8123 purple_debug_info("sipe", "sipe_input_cb: read error\n");
8124 connection_remove(sip, source);
8125 if (sip->fd == source) sip->fd = -1;
8126 return;
8129 conn->inbufused += len;
8130 conn->inbuf[conn->inbufused] = '\0';
8132 process_input(sip, conn);
8135 /* Callback for new connections on incoming TCP port */
8136 static void sipe_newconn_cb(gpointer data, gint source,
8137 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8139 PurpleConnection *gc = data;
8140 struct sipe_account_data *sip = gc->proto_data;
8141 struct sip_connection *conn;
8143 int newfd = accept(source, NULL, NULL);
8145 conn = connection_create(sip, newfd);
8147 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8150 static void login_cb(gpointer data, gint source,
8151 SIPE_UNUSED_PARAMETER const gchar *error_message)
8153 PurpleConnection *gc = data;
8154 struct sipe_account_data *sip;
8155 struct sip_connection *conn;
8157 if (!PURPLE_CONNECTION_IS_VALID(gc))
8159 if (source >= 0)
8160 close(source);
8161 return;
8164 if (source < 0) {
8165 purple_connection_error(gc, _("Could not connect"));
8166 return;
8169 sip = gc->proto_data;
8170 sip->fd = source;
8171 sip->last_keepalive = time(NULL);
8173 conn = connection_create(sip, source);
8175 do_register(sip);
8177 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8180 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8181 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8183 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8184 if (sip == NULL) return;
8186 do_register(sip);
8189 static guint sipe_ht_hash_nick(const char *nick)
8191 char *lc = g_utf8_strdown(nick, -1);
8192 guint bucket = g_str_hash(lc);
8193 g_free(lc);
8195 return bucket;
8198 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8200 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8203 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8205 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8207 sip->listen_data = NULL;
8209 if (listenfd == -1) {
8210 purple_connection_error(sip->gc, _("Could not create listen socket"));
8211 return;
8214 sip->fd = listenfd;
8216 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8217 sip->listenfd = sip->fd;
8219 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8221 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8222 do_register(sip);
8225 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8226 SIPE_UNUSED_PARAMETER const char *error_message)
8228 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8230 sip->query_data = NULL;
8232 if (!hosts || !hosts->data) {
8233 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8234 return;
8237 hosts = g_slist_remove(hosts, hosts->data);
8238 g_free(sip->serveraddr);
8239 sip->serveraddr = hosts->data;
8240 hosts = g_slist_remove(hosts, hosts->data);
8241 while (hosts) {
8242 void *tmp = hosts->data;
8243 hosts = g_slist_remove(hosts, tmp);
8244 hosts = g_slist_remove(hosts, tmp);
8245 g_free(tmp);
8248 /* create socket for incoming connections */
8249 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8250 sipe_udp_host_resolved_listen_cb, sip);
8251 if (sip->listen_data == NULL) {
8252 purple_connection_error(sip->gc, _("Could not create listen socket"));
8253 return;
8257 static const struct sipe_service_data *current_service = NULL;
8259 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8260 PurpleSslErrorType error,
8261 gpointer data)
8263 PurpleConnection *gc = data;
8264 struct sipe_account_data *sip;
8266 /* If the connection is already disconnected, we don't need to do anything else */
8267 if (!PURPLE_CONNECTION_IS_VALID(gc))
8268 return;
8270 sip = gc->proto_data;
8271 current_service = sip->service_data;
8272 if (current_service) {
8273 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8274 current_service->transport ? current_service->transport : "NULL",
8275 current_service->service ? current_service->service : "NULL");
8278 sip->fd = -1;
8279 sip->gsc = NULL;
8281 switch(error) {
8282 case PURPLE_SSL_CONNECT_FAILED:
8283 purple_connection_error(gc, _("Connection failed"));
8284 break;
8285 case PURPLE_SSL_HANDSHAKE_FAILED:
8286 purple_connection_error(gc, _("SSL handshake failed"));
8287 break;
8288 case PURPLE_SSL_CERTIFICATE_INVALID:
8289 purple_connection_error(gc, _("SSL certificate invalid"));
8290 break;
8294 static void
8295 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8297 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8298 PurpleProxyConnectData *connect_data;
8300 sip->listen_data = NULL;
8302 sip->listenfd = listenfd;
8303 if (sip->listenfd == -1) {
8304 purple_connection_error(sip->gc, _("Could not create listen socket"));
8305 return;
8308 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8309 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8310 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8311 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8312 sipe_newconn_cb, sip->gc);
8313 purple_debug_info("sipe", "connecting to %s port %d\n",
8314 sip->realhostname, sip->realport);
8315 /* open tcp connection to the server */
8316 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8317 sip->realport, login_cb, sip->gc);
8319 if (connect_data == NULL) {
8320 purple_connection_error(sip->gc, _("Could not create socket"));
8324 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8326 PurpleAccount *account = sip->account;
8327 PurpleConnection *gc = sip->gc;
8329 if (port == 0) {
8330 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8333 sip->realhostname = hostname;
8334 sip->realport = port;
8336 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8337 hostname, port);
8339 /* TODO: is there a good default grow size? */
8340 if (sip->transport != SIPE_TRANSPORT_UDP)
8341 sip->txbuf = purple_circ_buffer_new(0);
8343 if (sip->transport == SIPE_TRANSPORT_TLS) {
8344 /* SSL case */
8345 if (!purple_ssl_is_supported()) {
8346 gc->wants_to_die = TRUE;
8347 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8348 return;
8351 purple_debug_info("sipe", "using SSL\n");
8353 sip->gsc = purple_ssl_connect(account, hostname, port,
8354 login_cb_ssl, sipe_ssl_connect_failure, gc);
8355 if (sip->gsc == NULL) {
8356 purple_connection_error(gc, _("Could not create SSL context"));
8357 return;
8359 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8360 /* UDP case */
8361 purple_debug_info("sipe", "using UDP\n");
8363 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8364 if (sip->query_data == NULL) {
8365 purple_connection_error(gc, _("Could not resolve hostname"));
8367 } else {
8368 /* TCP case */
8369 purple_debug_info("sipe", "using TCP\n");
8370 /* create socket for incoming connections */
8371 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8372 sipe_tcp_connect_listen_cb, sip);
8373 if (sip->listen_data == NULL) {
8374 purple_connection_error(gc, _("Could not create listen socket"));
8375 return;
8380 /* Service list for autodection */
8381 static const struct sipe_service_data service_autodetect[] = {
8382 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8383 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8384 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8385 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8386 { NULL, NULL, 0 }
8389 /* Service list for SSL/TLS */
8390 static const struct sipe_service_data service_tls[] = {
8391 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8392 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8393 { NULL, NULL, 0 }
8396 /* Service list for TCP */
8397 static const struct sipe_service_data service_tcp[] = {
8398 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8399 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8400 { NULL, NULL, 0 }
8403 /* Service list for UDP */
8404 static const struct sipe_service_data service_udp[] = {
8405 { "sip", "udp", SIPE_TRANSPORT_UDP },
8406 { NULL, NULL, 0 }
8409 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8410 static void resolve_next_service(struct sipe_account_data *sip,
8411 const struct sipe_service_data *start)
8413 if (start) {
8414 sip->service_data = start;
8415 } else {
8416 sip->service_data++;
8417 if (sip->service_data->service == NULL) {
8418 gchar *hostname;
8419 /* Try connecting to the SIP hostname directly */
8420 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8421 if (sip->auto_transport) {
8422 // If SSL is supported, default to using it; OCS servers aren't configured
8423 // by default to accept TCP
8424 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8425 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8426 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8429 hostname = g_strdup(sip->sipdomain);
8430 create_connection(sip, hostname, 0);
8431 return;
8435 /* Try to resolve next service */
8436 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8437 sip->service_data->transport,
8438 sip->sipdomain,
8439 srvresolved, sip);
8442 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8444 struct sipe_account_data *sip = data;
8446 sip->srv_query_data = NULL;
8448 /* find the host to connect to */
8449 if (results) {
8450 gchar *hostname = g_strdup(resp->hostname);
8451 int port = resp->port;
8452 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8453 hostname, port);
8454 g_free(resp);
8456 sip->transport = sip->service_data->type;
8458 create_connection(sip, hostname, port);
8459 } else {
8460 resolve_next_service(sip, NULL);
8464 static void sipe_login(PurpleAccount *account)
8466 PurpleConnection *gc;
8467 struct sipe_account_data *sip;
8468 gchar **signinname_login, **userserver;
8469 const char *transport;
8470 const char *email;
8472 const char *username = purple_account_get_username(account);
8473 gc = purple_account_get_connection(account);
8475 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8477 if (strpbrk(username, "\t\v\r\n") != NULL) {
8478 gc->wants_to_die = TRUE;
8479 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8480 return;
8483 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8484 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8485 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8486 sip->gc = gc;
8487 sip->account = account;
8488 sip->reregister_set = FALSE;
8489 sip->reauthenticate_set = FALSE;
8490 sip->subscribed = FALSE;
8491 sip->subscribed_buddies = FALSE;
8492 sip->initial_state_published = FALSE;
8494 /* username format: <username>,[<optional login>] */
8495 signinname_login = g_strsplit(username, ",", 2);
8496 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8498 /* ensure that username format is name@domain */
8499 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8500 g_strfreev(signinname_login);
8501 gc->wants_to_die = TRUE;
8502 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8503 return;
8505 sip->username = g_strdup(signinname_login[0]);
8507 /* ensure that email format is name@domain if provided */
8508 email = purple_account_get_string(sip->account, "email", NULL);
8509 if (!is_empty(email) &&
8510 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8512 gc->wants_to_die = TRUE;
8513 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8514 return;
8516 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8518 /* login name specified? */
8519 if (signinname_login[1] && strlen(signinname_login[1])) {
8520 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8521 gboolean has_domain = domain_user[1] != NULL;
8522 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8523 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8524 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8525 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8526 sip->authdomain ? sip->authdomain : "", sip->authuser);
8527 g_strfreev(domain_user);
8530 userserver = g_strsplit(signinname_login[0], "@", 2);
8531 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8532 purple_connection_set_display_name(gc, userserver[0]);
8533 sip->sipdomain = g_strdup(userserver[1]);
8534 g_strfreev(userserver);
8535 g_strfreev(signinname_login);
8537 if (strchr(sip->username, ' ') != NULL) {
8538 gc->wants_to_die = TRUE;
8539 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8540 return;
8543 sip->password = g_strdup(purple_connection_get_password(gc));
8545 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8546 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8547 g_free, (GDestroyNotify)g_hash_table_destroy);
8548 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8549 g_free, (GDestroyNotify)sipe_subscription_free);
8551 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8553 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8555 g_free(sip->status);
8556 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8558 sip->auto_transport = FALSE;
8559 transport = purple_account_get_string(account, "transport", "auto");
8560 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8561 if (userserver[0]) {
8562 /* Use user specified server[:port] */
8563 int port = 0;
8565 if (userserver[1])
8566 port = atoi(userserver[1]);
8568 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8569 userserver[0], port);
8571 if (sipe_strequal(transport, "auto")) {
8572 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8573 } else if (sipe_strequal(transport, "tls")) {
8574 sip->transport = SIPE_TRANSPORT_TLS;
8575 } else if (sipe_strequal(transport, "tcp")) {
8576 sip->transport = SIPE_TRANSPORT_TCP;
8577 } else {
8578 sip->transport = SIPE_TRANSPORT_UDP;
8581 create_connection(sip, g_strdup(userserver[0]), port);
8582 } else {
8583 /* Server auto-discovery */
8584 if (sipe_strequal(transport, "auto")) {
8585 sip->auto_transport = TRUE;
8586 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8587 current_service++;
8588 resolve_next_service(sip, current_service);
8589 } else {
8590 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8592 } else if (sipe_strequal(transport, "tls")) {
8593 resolve_next_service(sip, service_tls);
8594 } else if (sipe_strequal(transport, "tcp")) {
8595 resolve_next_service(sip, service_tcp);
8596 } else {
8597 resolve_next_service(sip, service_udp);
8600 g_strfreev(userserver);
8603 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8605 connection_free_all(sip);
8607 g_free(sip->epid);
8608 sip->epid = NULL;
8610 if (sip->query_data != NULL)
8611 purple_dnsquery_destroy(sip->query_data);
8612 sip->query_data = NULL;
8614 if (sip->srv_query_data != NULL)
8615 purple_srv_cancel(sip->srv_query_data);
8616 sip->srv_query_data = NULL;
8618 if (sip->listen_data != NULL)
8619 purple_network_listen_cancel(sip->listen_data);
8620 sip->listen_data = NULL;
8622 if (sip->gsc != NULL)
8623 purple_ssl_close(sip->gsc);
8624 sip->gsc = NULL;
8626 sipe_auth_free(&sip->registrar);
8627 sipe_auth_free(&sip->proxy);
8629 if (sip->txbuf)
8630 purple_circ_buffer_destroy(sip->txbuf);
8631 sip->txbuf = NULL;
8633 g_free(sip->realhostname);
8634 sip->realhostname = NULL;
8636 g_free(sip->server_version);
8637 sip->server_version = NULL;
8639 if (sip->listenpa)
8640 purple_input_remove(sip->listenpa);
8641 sip->listenpa = 0;
8642 if (sip->tx_handler)
8643 purple_input_remove(sip->tx_handler);
8644 sip->tx_handler = 0;
8645 if (sip->resendtimeout)
8646 purple_timeout_remove(sip->resendtimeout);
8647 sip->resendtimeout = 0;
8648 if (sip->timeouts) {
8649 GSList *entry = sip->timeouts;
8650 while (entry) {
8651 struct scheduled_action *sched_action = entry->data;
8652 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8653 purple_timeout_remove(sched_action->timeout_handler);
8654 if (sched_action->destroy) {
8655 (*sched_action->destroy)(sched_action->payload);
8657 g_free(sched_action->name);
8658 g_free(sched_action);
8659 entry = entry->next;
8662 g_slist_free(sip->timeouts);
8664 if (sip->allow_events) {
8665 GSList *entry = sip->allow_events;
8666 while (entry) {
8667 g_free(entry->data);
8668 entry = entry->next;
8671 g_slist_free(sip->allow_events);
8673 if (sip->containers) {
8674 GSList *entry = sip->containers;
8675 while (entry) {
8676 free_container((struct sipe_container *)entry->data);
8677 entry = entry->next;
8680 g_slist_free(sip->containers);
8682 if (sip->contact)
8683 g_free(sip->contact);
8684 sip->contact = NULL;
8685 if (sip->regcallid)
8686 g_free(sip->regcallid);
8687 sip->regcallid = NULL;
8689 if (sip->serveraddr)
8690 g_free(sip->serveraddr);
8691 sip->serveraddr = NULL;
8693 if (sip->focus_factory_uri)
8694 g_free(sip->focus_factory_uri);
8695 sip->focus_factory_uri = NULL;
8697 sip->fd = -1;
8698 sip->processing_input = FALSE;
8700 if (sip->ews) {
8701 sipe_ews_free(sip->ews);
8703 sip->ews = NULL;
8707 * A callback for g_hash_table_foreach_remove
8709 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8710 SIPE_UNUSED_PARAMETER gpointer user_data)
8712 sipe_free_buddy((struct sipe_buddy *) buddy);
8714 /* We must return TRUE as the key/value have already been deleted */
8715 return(TRUE);
8718 static void sipe_close(PurpleConnection *gc)
8720 struct sipe_account_data *sip = gc->proto_data;
8722 if (sip) {
8723 /* leave all conversations */
8724 sipe_session_close_all(sip);
8725 sipe_session_remove_all(sip);
8727 if (sip->csta) {
8728 sip_csta_close(sip);
8731 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8732 /* unsubscribe all */
8733 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8735 /* unregister */
8736 do_register_exp(sip, 0);
8739 sipe_connection_cleanup(sip);
8740 g_free(sip->sipdomain);
8741 g_free(sip->username);
8742 g_free(sip->email);
8743 g_free(sip->password);
8744 g_free(sip->authdomain);
8745 g_free(sip->authuser);
8746 g_free(sip->status);
8747 g_free(sip->note);
8749 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8750 g_hash_table_destroy(sip->buddies);
8751 g_hash_table_destroy(sip->our_publications);
8752 g_hash_table_destroy(sip->user_state_publications);
8753 g_hash_table_destroy(sip->subscriptions);
8754 g_hash_table_destroy(sip->filetransfers);
8756 if (sip->groups) {
8757 GSList *entry = sip->groups;
8758 while (entry) {
8759 struct sipe_group *group = entry->data;
8760 g_free(group->name);
8761 g_free(group);
8762 entry = entry->next;
8765 g_slist_free(sip->groups);
8767 if (sip->our_publication_keys) {
8768 GSList *entry = sip->our_publication_keys;
8769 while (entry) {
8770 g_free(entry->data);
8771 entry = entry->next;
8774 g_slist_free(sip->our_publication_keys);
8776 while (sip->transactions)
8777 transactions_remove(sip, sip->transactions->data);
8779 g_free(gc->proto_data);
8780 gc->proto_data = NULL;
8783 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8784 SIPE_UNUSED_PARAMETER void *user_data)
8786 PurpleAccount *acct = purple_connection_get_account(gc);
8787 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8788 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8789 if (conv == NULL)
8790 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8791 purple_conversation_present(conv);
8792 g_free(id);
8795 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8796 SIPE_UNUSED_PARAMETER void *user_data)
8799 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8800 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8803 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8804 SIPE_UNUSED_PARAMETER struct transaction *trans)
8806 PurpleNotifySearchResults *results;
8807 PurpleNotifySearchColumn *column;
8808 xmlnode *searchResults;
8809 xmlnode *mrow;
8810 int match_count = 0;
8811 gboolean more = FALSE;
8812 gchar *secondary;
8814 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8816 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8817 if (!searchResults) {
8818 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8819 return FALSE;
8822 results = purple_notify_searchresults_new();
8824 if (results == NULL) {
8825 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8826 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8828 xmlnode_free(searchResults);
8829 return FALSE;
8832 column = purple_notify_searchresults_column_new(_("User name"));
8833 purple_notify_searchresults_column_add(results, column);
8835 column = purple_notify_searchresults_column_new(_("Name"));
8836 purple_notify_searchresults_column_add(results, column);
8838 column = purple_notify_searchresults_column_new(_("Company"));
8839 purple_notify_searchresults_column_add(results, column);
8841 column = purple_notify_searchresults_column_new(_("Country"));
8842 purple_notify_searchresults_column_add(results, column);
8844 column = purple_notify_searchresults_column_new(_("Email"));
8845 purple_notify_searchresults_column_add(results, column);
8847 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8848 GList *row = NULL;
8850 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8851 row = g_list_append(row, g_strdup(uri_parts[1]));
8852 g_strfreev(uri_parts);
8854 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8855 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8856 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8857 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8859 purple_notify_searchresults_row_add(results, row);
8860 match_count++;
8863 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8864 char *data = xmlnode_get_data_unescaped(mrow);
8865 more = (g_strcasecmp(data, "true") == 0);
8866 g_free(data);
8869 secondary = g_strdup_printf(
8870 dngettext(GETTEXT_PACKAGE,
8871 "Found %d contact%s:",
8872 "Found %d contacts%s:", match_count),
8873 match_count, more ? _(" (more matched your query)") : "");
8875 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8876 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8877 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8879 g_free(secondary);
8880 xmlnode_free(searchResults);
8881 return TRUE;
8884 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8886 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8887 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8888 unsigned i = 0;
8890 if (!attrs) return;
8892 do {
8893 PurpleRequestField *field = entries->data;
8894 const char *id = purple_request_field_get_id(field);
8895 const char *value = purple_request_field_string_get_value(field);
8897 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8899 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8900 } while ((entries = g_list_next(entries)) != NULL);
8901 attrs[i] = NULL;
8903 if (i > 0) {
8904 struct sipe_account_data *sip = gc->proto_data;
8905 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8906 gchar *query = g_strjoinv(NULL, attrs);
8907 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8908 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8909 send_soap_request_with_cb(sip, domain_uri, body,
8910 (TransCallback) process_search_contact_response, NULL);
8911 g_free(domain_uri);
8912 g_free(body);
8913 g_free(query);
8916 g_strfreev(attrs);
8919 static void sipe_show_find_contact(PurplePluginAction *action)
8921 PurpleConnection *gc = (PurpleConnection *) action->context;
8922 PurpleRequestFields *fields;
8923 PurpleRequestFieldGroup *group;
8924 PurpleRequestField *field;
8926 fields = purple_request_fields_new();
8927 group = purple_request_field_group_new(NULL);
8928 purple_request_fields_add_group(fields, group);
8930 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8931 purple_request_field_group_add_field(group, field);
8932 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8933 purple_request_field_group_add_field(group, field);
8934 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8935 purple_request_field_group_add_field(group, field);
8936 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8937 purple_request_field_group_add_field(group, field);
8939 purple_request_fields(gc,
8940 _("Search"),
8941 _("Search for a contact"),
8942 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8943 fields,
8944 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8945 _("_Cancel"), NULL,
8946 purple_connection_get_account(gc), NULL, NULL, gc);
8949 static void sipe_show_about_plugin(PurplePluginAction *action)
8951 PurpleConnection *gc = (PurpleConnection *) action->context;
8952 char *tmp = g_strdup_printf(
8954 * Non-translatable parts, like markup, are hard-coded
8955 * into the format string. This requires more translatable
8956 * texts but it makes the translations less error prone.
8958 "<b><font size=\"+1\">SIPE " SIPE_VERSION " </font></b><br/>"
8959 "<br/>"
8960 /* 1 */ "%s:<br/>"
8961 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8962 "<li> - MS Office Communications Server 2007</li><br/>"
8963 "<li> - MS Live Communications Server 2005</li><br/>"
8964 "<li> - MS Live Communications Server 2003</li><br/>"
8965 "<li> - Reuters Messaging</li><br/>"
8966 "<br/>"
8967 /* 2 */ "%s: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8968 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8969 /* 5 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8970 /* 6 */ "%s: GPLv2+<br/>"
8971 "<br/>"
8972 /* 7 */ "%s:<br/>"
8973 " - CERN<br/>"
8974 " - Reuters Messaging network<br/>"
8975 " - Deutsche Bank<br/>"
8976 " - Merrill Lynch<br/>"
8977 " - Wachovia<br/>"
8978 " - Intel<br/>"
8979 " - Nokia<br/>"
8980 " - HP<br/>"
8981 " - Symantec<br/>"
8982 " - Accenture<br/>"
8983 " - Siemens<br/>"
8984 " - Alcatel-Lucent<br/>"
8985 " - BT<br/>"
8986 "<br/>"
8987 /* 8,9 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
8988 "<br/>"
8989 /* 10 */ "<b>%s:</b><br/>"
8990 " - Anibal Avelar<br/>"
8991 " - Gabriel Burt<br/>"
8992 " - Stefan Becker<br/>"
8993 " - pier11<br/>"
8994 " - Jakub Adam<br/>"
8995 " - Tomáš Hrabčík<br/>"
8996 "<br/>"
8997 /* 11 */ "%s<br/>"
8999 /* The next 11 texts make up the SIPE about note text */
9000 /* About note, part 1/11: introduction */
9001 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9002 /* About note, part 2/11: home page URL (label) */
9003 _("Home"),
9004 /* About note, part 3/11: support forum URL (label) */
9005 _("Support"),
9006 /* About note, part 4/11: support forum name (hyperlink text) */
9007 _("Help Forum"),
9008 /* About note, part 5/11: translation service URL (label) */
9009 _("Translations"),
9010 /* About note, part 6/11: license type (label) */
9011 _("License"),
9012 /* About note, part 7/11: known users */
9013 _("We support users in such organizations as"),
9014 /* About note, part 8/11: translation request, text before Transifex.net URL */
9015 /* append a space if text is not empty */
9016 _("Please help us to translate SIPE to your native language here at "),
9017 /* About note, part 9/11: translation request, text after Transifex.net URL */
9018 /* start with a space if text is not empty */
9019 _(" using convenient web interface"),
9020 /* About note, part 10/11: author list (header) */
9021 _("Authors"),
9022 /* About note, part 11/11: Localization credit */
9023 /* PLEASE NOTE: do *NOT* simply translate the english original */
9024 /* but write something similar to the following sentence: */
9025 /* "Localization for <language name> (<language code>): <name>" */
9026 _("Original texts in English (en): SIPE developers")
9028 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9029 g_free(tmp);
9032 static void sipe_republish_calendar(PurplePluginAction *action)
9034 PurpleConnection *gc = (PurpleConnection *) action->context;
9035 struct sipe_account_data *sip = gc->proto_data;
9037 sipe_update_calendar(sip);
9040 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9041 gpointer value,
9042 GString* str)
9044 struct sipe_publication *publication = value;
9046 g_string_append_printf( str,
9047 SIPE_PUB_XML_PUBLICATION_CLEAR,
9048 publication->category,
9049 publication->instance,
9050 publication->container,
9051 publication->version,
9052 "static");
9055 static void sipe_reset_status(PurplePluginAction *action)
9057 PurpleConnection *gc = (PurpleConnection *) action->context;
9058 struct sipe_account_data *sip = gc->proto_data;
9060 if (sip->ocs2007) /* 2007+ */
9062 GString* str = g_string_new(NULL);
9063 gchar *publications;
9065 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9066 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
9067 return;
9070 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9071 publications = g_string_free(str, FALSE);
9073 send_presence_publish(sip, publications);
9074 g_free(publications);
9076 else /* 2005 */
9078 send_presence_soap0(sip, FALSE, TRUE);
9082 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9083 gpointer context)
9085 PurpleConnection *gc = (PurpleConnection *)context;
9086 struct sipe_account_data *sip = gc->proto_data;
9087 GList *menu = NULL;
9088 PurplePluginAction *act;
9089 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9091 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9092 menu = g_list_prepend(menu, act);
9094 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9095 menu = g_list_prepend(menu, act);
9097 if (sipe_strequal(calendar, "EXCH")) {
9098 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9099 menu = g_list_prepend(menu, act);
9102 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9103 menu = g_list_prepend(menu, act);
9105 menu = g_list_reverse(menu);
9107 return menu;
9110 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9114 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9116 return TRUE;
9120 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9122 return TRUE;
9126 static char *sipe_status_text(PurpleBuddy *buddy)
9128 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9129 const PurpleStatus *status = purple_presence_get_active_status(presence);
9130 const char *status_id = purple_status_get_id(status);
9131 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9132 struct sipe_buddy *sbuddy;
9133 char *text = NULL;
9135 if (!sip) return NULL; /* happens on pidgin exit */
9137 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9138 if (sbuddy) {
9139 const char *activity_str = sbuddy->activity ?
9140 sbuddy->activity :
9141 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9142 purple_status_get_name(status) : NULL;
9144 if (activity_str && sbuddy->note)
9146 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9148 else if (activity_str)
9150 text = g_strdup(activity_str);
9152 else if (sbuddy->note)
9154 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9158 return text;
9161 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9163 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9164 const PurpleStatus *status = purple_presence_get_active_status(presence);
9165 struct sipe_account_data *sip;
9166 struct sipe_buddy *sbuddy;
9167 char *note = NULL;
9168 gboolean is_oof_note = FALSE;
9169 char *activity = NULL;
9170 char *calendar = NULL;
9171 char *meeting_subject = NULL;
9172 char *meeting_location = NULL;
9174 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9175 if (sip) //happens on pidgin exit
9177 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9178 if (sbuddy)
9180 note = sbuddy->note;
9181 is_oof_note = sbuddy->is_oof_note;
9182 activity = sbuddy->activity;
9183 calendar = sipe_cal_get_description(sbuddy);
9184 meeting_subject = sbuddy->meeting_subject;
9185 meeting_location = sbuddy->meeting_location;
9189 //Layout
9190 if (purple_presence_is_online(presence))
9192 const char *status_str = activity ? activity : purple_status_get_name(status);
9194 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9196 if (purple_presence_is_online(presence) &&
9197 !is_empty(calendar))
9199 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9201 g_free(calendar);
9202 if (!is_empty(meeting_location))
9204 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9206 if (!is_empty(meeting_subject))
9208 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9211 if (note)
9213 char *tmp = g_strdup_printf("<i>%s</i>", note);
9214 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9216 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9217 g_free(tmp);
9222 #if PURPLE_VERSION_CHECK(2,5,0)
9223 static GHashTable *
9224 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9226 GHashTable *table;
9227 table = g_hash_table_new(g_str_hash, g_str_equal);
9228 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9229 return table;
9231 #endif
9233 static PurpleBuddy *
9234 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9236 PurpleBuddy *clone;
9237 const gchar *server_alias, *email;
9238 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9240 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9242 purple_blist_add_buddy(clone, NULL, group, NULL);
9244 server_alias = purple_buddy_get_server_alias(buddy);
9245 if (server_alias) {
9246 purple_blist_server_alias_buddy(clone, server_alias);
9249 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9250 if (email) {
9251 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9254 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9255 //for UI to update;
9256 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9257 return clone;
9260 static void
9261 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9263 PurpleBuddy *buddy, *b;
9264 PurpleConnection *gc;
9265 PurpleGroup * group = purple_find_group(group_name);
9267 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9269 buddy = (PurpleBuddy *)node;
9271 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9272 gc = purple_account_get_connection(buddy->account);
9274 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9275 if (!b){
9276 purple_blist_add_buddy_clone(group, buddy);
9279 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9282 static void
9283 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9285 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9287 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9289 /* 2007+ conference */
9290 if (sip->ocs2007)
9292 sipe_conf_add(sip, buddy->name);
9294 else /* 2005- multiparty chat */
9296 gchar *self = sip_uri_self(sip);
9297 struct sip_session *session;
9299 session = sipe_session_add_chat(sip);
9300 session->chat_title = sipe_chat_get_name(session->callid);
9301 session->roster_manager = g_strdup(self);
9303 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9304 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9305 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9306 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9308 g_free(self);
9312 static gboolean
9313 sipe_is_election_finished(struct sip_session *session)
9315 gboolean res = TRUE;
9317 SIPE_DIALOG_FOREACH {
9318 if (dialog->election_vote == 0) {
9319 res = FALSE;
9320 break;
9322 } SIPE_DIALOG_FOREACH_END;
9324 if (res) {
9325 session->is_voting_in_progress = FALSE;
9327 return res;
9330 static void
9331 sipe_election_start(struct sipe_account_data *sip,
9332 struct sip_session *session)
9334 int election_timeout;
9336 if (session->is_voting_in_progress) {
9337 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9338 return;
9339 } else {
9340 session->is_voting_in_progress = TRUE;
9342 session->bid = rand();
9344 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9346 SIPE_DIALOG_FOREACH {
9347 /* reset election_vote for each chat participant */
9348 dialog->election_vote = 0;
9350 /* send RequestRM to each chat participant*/
9351 sipe_send_election_request_rm(sip, dialog, session->bid);
9352 } SIPE_DIALOG_FOREACH_END;
9354 election_timeout = 15; /* sec */
9355 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9359 * @param who a URI to whom to invite to chat
9361 void
9362 sipe_invite_to_chat(struct sipe_account_data *sip,
9363 struct sip_session *session,
9364 const gchar *who)
9366 /* a conference */
9367 if (session->focus_uri)
9369 sipe_invite_conf(sip, session, who);
9371 else /* a multi-party chat */
9373 gchar *self = sip_uri_self(sip);
9374 if (session->roster_manager) {
9375 if (sipe_strequal(session->roster_manager, self)) {
9376 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9377 } else {
9378 sipe_refer(sip, session, who);
9380 } else {
9381 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9383 session->pending_invite_queue = slist_insert_unique_sorted(
9384 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9386 sipe_election_start(sip, session);
9388 g_free(self);
9392 void
9393 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9394 struct sip_session *session)
9396 gchar *invitee;
9397 GSList *entry = session->pending_invite_queue;
9399 while (entry) {
9400 invitee = entry->data;
9401 sipe_invite_to_chat(sip, session, invitee);
9402 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9403 g_free(invitee);
9407 static void
9408 sipe_election_result(struct sipe_account_data *sip,
9409 void *sess)
9411 struct sip_session *session = (struct sip_session *)sess;
9412 gchar *rival;
9413 gboolean has_won = TRUE;
9415 if (session->roster_manager) {
9416 purple_debug_info("sipe",
9417 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9418 return;
9421 session->is_voting_in_progress = FALSE;
9423 SIPE_DIALOG_FOREACH {
9424 if (dialog->election_vote < 0) {
9425 has_won = FALSE;
9426 rival = dialog->with;
9427 break;
9429 } SIPE_DIALOG_FOREACH_END;
9431 if (has_won) {
9432 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9434 session->roster_manager = sip_uri_self(sip);
9436 SIPE_DIALOG_FOREACH {
9437 /* send SetRM to each chat participant*/
9438 sipe_send_election_set_rm(sip, dialog);
9439 } SIPE_DIALOG_FOREACH_END;
9440 } else {
9441 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9443 session->bid = 0;
9445 sipe_process_pending_invite_queue(sip, session);
9449 * For 2007+ conference only.
9451 static void
9452 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9454 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9455 struct sip_session *session;
9457 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9458 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9460 session = sipe_session_find_chat_by_title(sip, chat_title);
9462 sipe_conf_modify_user_role(sip, session, buddy->name);
9466 * For 2007+ conference only.
9468 static void
9469 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9471 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9472 struct sip_session *session;
9474 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9475 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9477 session = sipe_session_find_chat_by_title(sip, chat_title);
9479 sipe_conf_delete_user(sip, session, buddy->name);
9482 static void
9483 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9485 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9486 struct sip_session *session;
9488 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9489 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9491 session = sipe_session_find_chat_by_title(sip, chat_title);
9493 sipe_invite_to_chat(sip, session, buddy->name);
9496 static void
9497 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9499 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9501 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9502 if (phone) {
9503 char *tel_uri = sip_to_tel_uri(phone);
9505 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9506 sip_csta_make_call(sip, tel_uri);
9508 g_free(tel_uri);
9512 static void
9513 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9515 const gchar *email;
9516 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9518 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9519 if (email)
9521 char *mailto = g_strdup_printf("mailto:%s", email);
9522 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9523 #ifndef _WIN32
9525 pid_t pid;
9526 char *const parmList[] = {"xdg-email", mailto, NULL};
9527 if ((pid = fork()) == -1)
9529 purple_debug_info("sipe", "fork() error\n");
9531 else if (pid == 0)
9533 execvp(parmList[0], parmList);
9534 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9537 #else
9539 BOOL ret;
9540 _flushall();
9541 errno = 0;
9542 //@TODO resolve env variable %WINDIR% first
9543 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9544 if (errno)
9546 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9549 #endif
9551 g_free(mailto);
9553 else
9555 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9560 * A menu which appear when right-clicking on buddy in contact list.
9562 static GList *
9563 sipe_buddy_menu(PurpleBuddy *buddy)
9565 PurpleBlistNode *g_node;
9566 PurpleGroup *group, *gr_parent;
9567 PurpleMenuAction *act;
9568 GList *menu = NULL;
9569 GList *menu_groups = NULL;
9570 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9571 const char *email;
9572 const char *phone;
9573 const char *phone_disp_str;
9574 gchar *self = sip_uri_self(sip);
9576 SIPE_SESSION_FOREACH {
9577 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
9579 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9581 PurpleConvChatBuddyFlags flags;
9582 PurpleConvChatBuddyFlags flags_us;
9584 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9585 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9586 if (session->focus_uri
9587 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9588 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9590 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9591 act = purple_menu_action_new(label,
9592 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9593 session->chat_title, NULL);
9594 g_free(label);
9595 menu = g_list_prepend(menu, act);
9598 if (session->focus_uri
9599 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9601 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9602 act = purple_menu_action_new(label,
9603 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9604 session->chat_title, NULL);
9605 g_free(label);
9606 menu = g_list_prepend(menu, act);
9609 else
9611 if (!session->focus_uri
9612 || (session->focus_uri && !session->locked))
9614 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9615 act = purple_menu_action_new(label,
9616 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9617 session->chat_title, NULL);
9618 g_free(label);
9619 menu = g_list_prepend(menu, act);
9623 } SIPE_SESSION_FOREACH_END;
9625 act = purple_menu_action_new(_("New chat"),
9626 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9627 NULL, NULL);
9628 menu = g_list_prepend(menu, act);
9630 if (sip->csta && !sip->csta->line_status) {
9631 gchar *tmp = NULL;
9632 /* work phone */
9633 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9634 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9635 if (phone) {
9636 gchar *label = g_strdup_printf(_("Work %s"),
9637 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9638 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9639 g_free(tmp);
9640 tmp = NULL;
9641 g_free(label);
9642 menu = g_list_prepend(menu, act);
9645 /* mobile phone */
9646 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9647 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9648 if (phone) {
9649 gchar *label = g_strdup_printf(_("Mobile %s"),
9650 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9651 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9652 g_free(tmp);
9653 tmp = NULL;
9654 g_free(label);
9655 menu = g_list_prepend(menu, act);
9658 /* home phone */
9659 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9660 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9661 if (phone) {
9662 gchar *label = g_strdup_printf(_("Home %s"),
9663 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9664 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9665 g_free(tmp);
9666 tmp = NULL;
9667 g_free(label);
9668 menu = g_list_prepend(menu, act);
9671 /* other phone */
9672 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9673 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9674 if (phone) {
9675 gchar *label = g_strdup_printf(_("Other %s"),
9676 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9677 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9678 g_free(tmp);
9679 tmp = NULL;
9680 g_free(label);
9681 menu = g_list_prepend(menu, act);
9684 /* custom1 phone */
9685 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9686 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9687 if (phone) {
9688 gchar *label = g_strdup_printf(_("Custom1 %s"),
9689 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9690 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9691 g_free(tmp);
9692 tmp = NULL;
9693 g_free(label);
9694 menu = g_list_prepend(menu, act);
9698 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9699 if (email) {
9700 act = purple_menu_action_new(_("Send email..."),
9701 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9702 NULL, NULL);
9703 menu = g_list_prepend(menu, act);
9706 gr_parent = purple_buddy_get_group(buddy);
9707 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9708 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9709 continue;
9711 group = (PurpleGroup *)g_node;
9712 if (group == gr_parent)
9713 continue;
9715 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9716 continue;
9718 act = purple_menu_action_new(purple_group_get_name(group),
9719 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9720 group->name, NULL);
9721 menu_groups = g_list_prepend(menu_groups, act);
9723 menu_groups = g_list_reverse(menu_groups);
9725 act = purple_menu_action_new(_("Copy to"),
9726 NULL,
9727 NULL, menu_groups);
9728 menu = g_list_prepend(menu, act);
9729 menu = g_list_reverse(menu);
9731 g_free(self);
9732 return menu;
9735 static void
9736 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9738 struct sipe_account_data *sip = chat->account->gc->proto_data;
9739 struct sip_session *session;
9741 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9742 sipe_conf_modify_conference_lock(sip, session, locked);
9745 static void
9746 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9748 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9749 sipe_conf_modify_lock(chat, FALSE);
9752 static void
9753 sipe_chat_menu_lock_cb(PurpleChat *chat)
9755 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9756 sipe_conf_modify_lock(chat, TRUE);
9759 static GList *
9760 sipe_chat_menu(PurpleChat *chat)
9762 PurpleMenuAction *act;
9763 PurpleConvChatBuddyFlags flags_us;
9764 GList *menu = NULL;
9765 struct sipe_account_data *sip = chat->account->gc->proto_data;
9766 struct sip_session *session;
9767 gchar *self;
9769 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9770 if (!session) return NULL;
9772 self = sip_uri_self(sip);
9773 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9775 if (session->focus_uri
9776 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9778 if (session->locked) {
9779 act = purple_menu_action_new(_("Unlock"),
9780 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9781 NULL, NULL);
9782 menu = g_list_prepend(menu, act);
9783 } else {
9784 act = purple_menu_action_new(_("Lock"),
9785 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9786 NULL, NULL);
9787 menu = g_list_prepend(menu, act);
9791 menu = g_list_reverse(menu);
9793 g_free(self);
9794 return menu;
9797 static GList *
9798 sipe_blist_node_menu(PurpleBlistNode *node)
9800 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9801 return sipe_buddy_menu((PurpleBuddy *) node);
9802 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9803 return sipe_chat_menu((PurpleChat *)node);
9804 } else {
9805 return NULL;
9809 static gboolean
9810 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9812 char *uri = trans->payload->data;
9814 PurpleNotifyUserInfo *info;
9815 PurpleBuddy *pbuddy = NULL;
9816 struct sipe_buddy *sbuddy;
9817 const char *alias = NULL;
9818 char *device_name = NULL;
9819 char *server_alias = NULL;
9820 char *phone_number = NULL;
9821 char *email = NULL;
9822 const char *site;
9823 char *first_name = NULL;
9824 char *last_name = NULL;
9826 if (!sip) return FALSE;
9828 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9830 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9831 alias = purple_buddy_get_local_alias(pbuddy);
9833 //will query buddy UA's capabilities and send answer to log
9834 sipe_options_request(sip, uri);
9836 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9837 if (sbuddy) {
9838 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9841 info = purple_notify_user_info_new();
9843 if (msg->response != 200) {
9844 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9845 } else {
9846 xmlnode *searchResults;
9847 xmlnode *mrow;
9849 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9850 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9851 if (!searchResults) {
9852 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9853 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9854 const char *value;
9855 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9856 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9857 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9859 /* For 2007 system we will take this from ContactCard -
9860 * it has cleaner tel: URIs at least
9862 if (!sip->ocs2007) {
9863 char *tel_uri = sip_to_tel_uri(phone_number);
9864 /* trims its parameters, so call first */
9865 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9866 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9867 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9868 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9869 g_free(tel_uri);
9872 if (server_alias && strlen(server_alias) > 0) {
9873 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9875 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9876 purple_notify_user_info_add_pair(info, _("Job title"), value);
9878 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9879 purple_notify_user_info_add_pair(info, _("Office"), value);
9881 if (phone_number && strlen(phone_number) > 0) {
9882 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9884 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9885 purple_notify_user_info_add_pair(info, _("Company"), value);
9887 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9888 purple_notify_user_info_add_pair(info, _("City"), value);
9890 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9891 purple_notify_user_info_add_pair(info, _("State"), value);
9893 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9894 purple_notify_user_info_add_pair(info, _("Country"), value);
9896 if (email && strlen(email) > 0) {
9897 purple_notify_user_info_add_pair(info, _("Email address"), email);
9901 xmlnode_free(searchResults);
9904 purple_notify_user_info_add_section_break(info);
9906 if (is_empty(server_alias)) {
9907 g_free(server_alias);
9908 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9909 if (server_alias) {
9910 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9914 /* present alias if it differs from server alias */
9915 if (alias && !sipe_strequal(alias, server_alias))
9917 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9920 if (is_empty(email)) {
9921 g_free(email);
9922 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9923 if (email) {
9924 purple_notify_user_info_add_pair(info, _("Email address"), email);
9928 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9929 if (site) {
9930 purple_notify_user_info_add_pair(info, _("Site"), site);
9933 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9934 if (first_name && last_name) {
9935 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9937 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9938 g_free(link);
9940 g_free(first_name);
9941 g_free(last_name);
9943 if (device_name) {
9944 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9947 /* show a buddy's user info in a nice dialog box */
9948 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9949 uri, /* buddy's URI */
9950 info, /* body */
9951 NULL, /* callback called when dialog closed */
9952 NULL); /* userdata for callback */
9954 g_free(phone_number);
9955 g_free(server_alias);
9956 g_free(email);
9957 g_free(device_name);
9959 return TRUE;
9963 * AD search first, LDAP based
9965 static void sipe_get_info(PurpleConnection *gc, const char *username)
9967 struct sipe_account_data *sip = gc->proto_data;
9968 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9969 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9970 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9971 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9973 payload->destroy = g_free;
9974 payload->data = g_strdup(username);
9976 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
9977 send_soap_request_with_cb(sip, domain_uri, body,
9978 (TransCallback) process_get_info_response, payload);
9979 g_free(domain_uri);
9980 g_free(body);
9981 g_free(row);
9984 static PurplePlugin *my_protocol = NULL;
9986 static PurplePluginProtocolInfo prpl_info =
9988 OPT_PROTO_CHAT_TOPIC,
9989 NULL, /* user_splits */
9990 NULL, /* protocol_options */
9991 NO_BUDDY_ICONS, /* icon_spec */
9992 sipe_list_icon, /* list_icon */
9993 NULL, /* list_emblems */
9994 sipe_status_text, /* status_text */
9995 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
9996 sipe_status_types, /* away_states */
9997 sipe_blist_node_menu, /* blist_node_menu */
9998 NULL, /* chat_info */
9999 NULL, /* chat_info_defaults */
10000 sipe_login, /* login */
10001 sipe_close, /* close */
10002 sipe_im_send, /* send_im */
10003 NULL, /* set_info */ // TODO maybe
10004 sipe_send_typing, /* send_typing */
10005 sipe_get_info, /* get_info */
10006 sipe_set_status, /* set_status */
10007 sipe_set_idle, /* set_idle */
10008 NULL, /* change_passwd */
10009 sipe_add_buddy, /* add_buddy */
10010 NULL, /* add_buddies */
10011 sipe_remove_buddy, /* remove_buddy */
10012 NULL, /* remove_buddies */
10013 sipe_add_permit, /* add_permit */
10014 sipe_add_deny, /* add_deny */
10015 sipe_add_deny, /* rem_permit */
10016 sipe_add_permit, /* rem_deny */
10017 dummy_permit_deny, /* set_permit_deny */
10018 NULL, /* join_chat */
10019 NULL, /* reject_chat */
10020 NULL, /* get_chat_name */
10021 sipe_chat_invite, /* chat_invite */
10022 sipe_chat_leave, /* chat_leave */
10023 NULL, /* chat_whisper */
10024 sipe_chat_send, /* chat_send */
10025 sipe_keep_alive, /* keepalive */
10026 NULL, /* register_user */
10027 NULL, /* get_cb_info */ // deprecated
10028 NULL, /* get_cb_away */ // deprecated
10029 sipe_alias_buddy, /* alias_buddy */
10030 sipe_group_buddy, /* group_buddy */
10031 sipe_rename_group, /* rename_group */
10032 NULL, /* buddy_free */
10033 sipe_convo_closed, /* convo_closed */
10034 purple_normalize_nocase, /* normalize */
10035 NULL, /* set_buddy_icon */
10036 sipe_remove_group, /* remove_group */
10037 NULL, /* get_cb_real_name */ // TODO?
10038 NULL, /* set_chat_topic */
10039 NULL, /* find_blist_chat */
10040 NULL, /* roomlist_get_list */
10041 NULL, /* roomlist_cancel */
10042 NULL, /* roomlist_expand_category */
10043 NULL, /* can_receive_file */
10044 sipe_ft_send_file, /* send_file */
10045 sipe_ft_new_xfer, /* new_xfer */
10046 NULL, /* offline_message */
10047 NULL, /* whiteboard_prpl_ops */
10048 sipe_send_raw, /* send_raw */
10049 NULL, /* roomlist_room_serialize */
10050 NULL, /* unregister_user */
10051 NULL, /* send_attention */
10052 NULL, /* get_attention_types */
10053 #if !PURPLE_VERSION_CHECK(2,5,0)
10054 /* Backward compatibility when compiling against 2.4.x API */
10055 (void (*)(void)) /* _purple_reserved4 */
10056 #endif
10057 sizeof(PurplePluginProtocolInfo), /* struct_size */
10058 #if PURPLE_VERSION_CHECK(2,5,0)
10059 sipe_get_account_text_table, /* get_account_text_table */
10060 #if PURPLE_VERSION_CHECK(2,6,0)
10061 NULL, /* initiate_media */
10062 NULL, /* get_media_caps */
10063 #endif
10064 #endif
10068 PurplePluginInfo info = {
10069 PURPLE_PLUGIN_MAGIC,
10070 PURPLE_MAJOR_VERSION,
10071 PURPLE_MINOR_VERSION,
10072 PURPLE_PLUGIN_PROTOCOL, /**< type */
10073 NULL, /**< ui_requirement */
10074 0, /**< flags */
10075 NULL, /**< dependencies */
10076 PURPLE_PRIORITY_DEFAULT, /**< priority */
10077 "prpl-sipe", /**< id */
10078 "Office Communicator", /**< name */
10079 SIPE_VERSION, /**< version */
10080 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10081 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10082 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10083 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10084 "Gabriel Burt <gburt@novell.com>, " /**< author */
10085 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10086 "pier11 <pier11@operamail.com>", /**< author */
10087 "http://sipe.sourceforge.net/", /**< homepage */
10088 sipe_plugin_load, /**< load */
10089 sipe_plugin_unload, /**< unload */
10090 sipe_plugin_destroy, /**< destroy */
10091 NULL, /**< ui_info */
10092 &prpl_info, /**< extra_info */
10093 NULL,
10094 sipe_actions,
10095 NULL,
10096 NULL,
10097 NULL,
10098 NULL
10101 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
10103 GList *entry;
10105 sip_sec_destroy();
10107 entry = prpl_info.protocol_options;
10108 while (entry) {
10109 purple_account_option_destroy(entry->data);
10110 entry = g_list_delete_link(entry, entry);
10112 prpl_info.protocol_options = NULL;
10114 entry = prpl_info.user_splits;
10115 while (entry) {
10116 purple_account_user_split_destroy(entry->data);
10117 entry = g_list_delete_link(entry, entry);
10119 prpl_info.user_splits = NULL;
10122 void init_plugin(PurplePlugin *plugin)
10124 PurpleAccountUserSplit *split;
10125 PurpleAccountOption *option;
10127 srand(time(NULL));
10128 sip_sec_init();
10130 #ifdef ENABLE_NLS
10131 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
10132 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
10133 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
10134 textdomain(GETTEXT_PACKAGE);
10135 #endif
10137 purple_plugin_register(plugin);
10139 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
10140 purple_account_user_split_set_reverse(split, FALSE);
10141 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
10143 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
10144 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10146 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
10147 purple_account_option_add_list_item(option, _("Auto"), "auto");
10148 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
10149 purple_account_option_add_list_item(option, _("TCP"), "tcp");
10150 purple_account_option_add_list_item(option, _("UDP"), "udp");
10151 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10153 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
10154 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
10156 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
10157 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10159 #ifdef USE_KERBEROS
10160 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
10161 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10163 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
10164 * No login/password is taken into account if this option present,
10165 * instead used default credentials stored in OS.
10167 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
10168 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10169 #endif
10171 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
10172 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
10173 purple_account_option_add_list_item(option, _("None"), "NONE");
10174 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10176 /** Example: https://server.company.com/EWS/Exchange.asmx */
10177 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
10178 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10180 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
10181 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10183 /** Example: DOMAIN\user or user@company.com */
10184 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
10185 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10187 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
10188 purple_account_option_set_masked(option, TRUE);
10189 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
10191 my_protocol = plugin;
10194 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
10197 Local Variables:
10198 mode: c
10199 c-file-style: "bsd"
10200 indent-tabs-mode: t
10201 tab-width: 8
10202 End: