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