Notify user about messages with unrecognized content
[siplcs.git] / src / core / sipe.c
blob5e232c108ac10cf820bb9581934060baf91e4974
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 pier11 <pier11@operamail.com>
7 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
10 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
13 * ***
14 * Thanks to Google's Summer of Code Program and the helpful mentors
15 * ***
17 * Session-based SIP MESSAGE documentation:
18 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 #ifndef _WIN32
36 #include <sys/socket.h>
37 #include <sys/ioctl.h>
38 #include <sys/types.h>
39 #include <netinet/in.h>
40 #include <net/if.h>
41 #else
42 #ifdef _DLL
43 #define _WS2TCPIP_H_
44 #define _WINSOCK2API_
45 #define _LIBC_INTERNAL_
46 #endif /* _DLL */
47 #include "internal.h"
48 #endif /* _WIN32 */
50 #include <time.h>
51 #include <stdio.h>
52 #include <errno.h>
53 #include <string.h>
54 #include <unistd.h>
55 #include <glib.h>
58 #include "accountopt.h"
59 #include "blist.h"
60 #include "conversation.h"
61 #include "dnsquery.h"
62 #include "debug.h"
63 #include "notify.h"
64 #include "privacy.h"
65 #include "prpl.h"
66 #include "plugin.h"
67 #include "util.h"
68 #include "version.h"
69 #include "network.h"
70 #include "xmlnode.h"
71 #include "mime.h"
72 #include "core.h"
74 #include "sipe.h"
75 #include "sipe-cal.h"
76 #include "sipe-ews.h"
77 #include "sipe-chat.h"
78 #include "sipe-conf.h"
79 #include "sip-csta.h"
80 #include "sipe-dialog.h"
81 #include "sipe-nls.h"
82 #include "sipe-session.h"
83 #include "sipe-utils.h"
84 #include "sipmsg.h"
85 #include "sipe-sign.h"
86 #include "dnssrv.h"
87 #include "request.h"
89 /* Backward compatibility when compiling against 2.4.x API */
90 #if !PURPLE_VERSION_CHECK(2,5,0)
91 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
92 #endif
94 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
95 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
97 /* Keep in sync with sipe_transport_type! */
98 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
99 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
101 /* Status identifiers (see also: sipe_status_types()) */
102 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
103 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
104 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
105 /* PURPLE_STATUS_UNAVAILABLE: */
106 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
107 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
108 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
109 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On the phone */
110 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
111 /* PURPLE_STATUS_AWAY: */
112 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
113 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
114 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
115 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
116 /* Calendar-driven status for 2005 systems */
117 #define SIPE_STATUS_ID_MEETING "in-a-meeting" /* In a meeting */
118 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
119 /* ??? PURPLE_STATUS_MOBILE */
120 /* ??? PURPLE_STATUS_TUNE */
122 /* Status attributes (see also sipe_status_types() */
123 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
125 /* Action name templates */
126 #define ACTION_NAME_PRESENCE "<presence><%s>"
128 /** Allows to send typed messages from chat window again after account reinstantiation. */
129 static void
130 sipe_rejoin_chat(PurpleConversation *conv)
132 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
133 PURPLE_CONV_CHAT(conv)->left)
135 PURPLE_CONV_CHAT(conv)->left = FALSE;
136 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
140 static char *genbranch()
142 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
143 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
144 rand() & 0xFFFF, rand() & 0xFFFF);
148 static char *default_ua = NULL;
149 static const char*
150 sipe_get_useragent(struct sipe_account_data *sip)
152 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
153 if (is_empty(useragent)) {
154 if (!default_ua) {
155 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
156 /* ref: lzodefs.h */
157 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
158 #define SIPE_TARGET_PLATFORM "linux"
159 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
160 #define SIPE_TARGET_PLATFORM "bsd"
161 # elif defined(__APPLE__) || defined(__MACOS__)
162 #define SIPE_TARGET_PLATFORM "macosx"
163 #elif defined(__solaris__) || defined(__sun)
164 #define SIPE_TARGET_PLATFORM "sun"
165 #elif defined(_WIN32)
166 #define SIPE_TARGET_PLATFORM "win"
167 #else
168 #define SIPE_TARGET_PLATFORM "generic"
169 #endif
171 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
172 #define SIPE_TARGET_ARCH "x86_64"
173 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
174 #define SIPE_TARGET_ARCH "i386"
175 #elif defined(__ppc64__)
176 #define SIPE_TARGET_ARCH "ppc64"
177 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
178 #define SIPE_TARGET_ARCH "ppc"
179 #else
180 #define SIPE_TARGET_ARCH "other"
181 #endif
183 default_ua = g_strdup_printf("Purple/%s Sipe/%s (%s-%s; %s)",
184 purple_core_get_version(),
185 SIPE_VERSION,
186 SIPE_TARGET_PLATFORM,
187 SIPE_TARGET_ARCH,
188 sip->server_version ? sip->server_version : "");
190 useragent = default_ua;
192 return useragent;
195 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
196 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
198 return "sipe";
201 static void sipe_plugin_destroy(PurplePlugin *plugin);
203 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
205 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
206 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
207 gpointer data);
209 static void sipe_close(PurpleConnection *gc);
211 static void send_presence_status(struct sipe_account_data *sip);
213 static void sendout_pkt(PurpleConnection *gc, const char *buf);
215 static void sipe_keep_alive(PurpleConnection *gc)
217 struct sipe_account_data *sip = gc->proto_data;
218 if (sip->transport == SIPE_TRANSPORT_UDP) {
219 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
220 gchar buf[2] = {0, 0};
221 purple_debug_info("sipe", "sending keep alive\n");
222 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
223 } else {
224 time_t now = time(NULL);
225 if ((sip->keepalive_timeout > 0) &&
226 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
227 #if PURPLE_VERSION_CHECK(2,4,0)
228 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
229 #endif
231 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
232 sendout_pkt(gc, "\r\n\r\n");
233 sip->last_keepalive = now;
238 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
240 struct sip_connection *ret = NULL;
241 GSList *entry = sip->openconns;
242 while (entry) {
243 ret = entry->data;
244 if (ret->fd == fd) return ret;
245 entry = entry->next;
247 return NULL;
250 static void sipe_auth_free(struct sip_auth *auth)
252 g_free(auth->opaque);
253 auth->opaque = NULL;
254 g_free(auth->realm);
255 auth->realm = NULL;
256 g_free(auth->target);
257 auth->target = NULL;
258 auth->type = AUTH_TYPE_UNSET;
259 auth->retries = 0;
260 auth->expires = 0;
261 g_free(auth->gssapi_data);
262 auth->gssapi_data = NULL;
263 sip_sec_destroy_context(auth->gssapi_context);
264 auth->gssapi_context = NULL;
267 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
269 struct sip_connection *ret = g_new0(struct sip_connection, 1);
270 ret->fd = fd;
271 sip->openconns = g_slist_append(sip->openconns, ret);
272 return ret;
275 static void connection_remove(struct sipe_account_data *sip, int fd)
277 struct sip_connection *conn = connection_find(sip, fd);
278 if (conn) {
279 sip->openconns = g_slist_remove(sip->openconns, conn);
280 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
281 g_free(conn->inbuf);
282 g_free(conn);
286 static void connection_free_all(struct sipe_account_data *sip)
288 struct sip_connection *ret = NULL;
289 GSList *entry = sip->openconns;
290 while (entry) {
291 ret = entry->data;
292 connection_remove(sip, ret->fd);
293 entry = sip->openconns;
297 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
299 gchar noncecount[9];
300 const char *authuser = sip->authuser;
301 gchar *response;
302 gchar *ret;
304 if (!authuser || strlen(authuser) < 1) {
305 authuser = sip->username;
308 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
309 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
311 // If we have a signature for the message, include that
312 if (msg->signature) {
313 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);
316 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
317 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
318 gchar *gssapi_data;
319 gchar *opaque;
321 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
322 &(auth->expires),
323 auth->type,
324 purple_account_get_bool(sip->account, "sso", TRUE),
325 sip->authdomain ? sip->authdomain : "",
326 authuser,
327 sip->password,
328 auth->target,
329 auth->gssapi_data);
330 if (!gssapi_data || !auth->gssapi_context) {
331 sip->gc->wants_to_die = TRUE;
332 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
333 return NULL;
336 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
337 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
338 g_free(opaque);
339 g_free(gssapi_data);
340 return ret;
343 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
345 } else { /* Digest */
347 /* Calculate new session key */
348 if (!auth->opaque) {
349 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
350 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
351 authuser, auth->realm, sip->password,
352 auth->gssapi_data, NULL);
355 sprintf(noncecount, "%08d", auth->nc++);
356 response = purple_cipher_http_digest_calculate_response("md5",
357 msg->method, msg->target, NULL, NULL,
358 auth->gssapi_data, noncecount, NULL,
359 auth->opaque);
360 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
362 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);
363 g_free(response);
364 return ret;
368 static char *parse_attribute(const char *attrname, const char *source)
370 const char *tmp, *tmp2;
371 char *retval = NULL;
372 int len = strlen(attrname);
374 if (!strncmp(source, attrname, len)) {
375 tmp = source + len;
376 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
377 if (tmp2)
378 retval = g_strndup(tmp, tmp2 - tmp);
379 else
380 retval = g_strdup(tmp);
383 return retval;
386 static void fill_auth(gchar *hdr, struct sip_auth *auth)
388 int i;
389 gchar **parts;
391 if (!hdr) {
392 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
393 return;
396 if (!g_strncasecmp(hdr, "NTLM", 4)) {
397 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
398 auth->type = AUTH_TYPE_NTLM;
399 hdr += 5;
400 auth->nc = 1;
401 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
402 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
403 auth->type = AUTH_TYPE_KERBEROS;
404 hdr += 9;
405 auth->nc = 3;
406 } else {
407 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
408 auth->type = AUTH_TYPE_DIGEST;
409 hdr += 7;
412 parts = g_strsplit(hdr, "\", ", 0);
413 for (i = 0; parts[i]; i++) {
414 char *tmp;
416 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
418 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
419 g_free(auth->gssapi_data);
420 auth->gssapi_data = tmp;
422 if (auth->type == AUTH_TYPE_NTLM) {
423 /* NTLM module extracts nonce from gssapi-data */
424 auth->nc = 3;
427 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
428 /* Only used with AUTH_TYPE_DIGEST */
429 g_free(auth->gssapi_data);
430 auth->gssapi_data = tmp;
431 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
432 g_free(auth->opaque);
433 auth->opaque = tmp;
434 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
435 g_free(auth->realm);
436 auth->realm = tmp;
438 if (auth->type == AUTH_TYPE_DIGEST) {
439 /* Throw away old session key */
440 g_free(auth->opaque);
441 auth->opaque = NULL;
442 auth->nc = 1;
445 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
446 g_free(auth->target);
447 auth->target = tmp;
450 g_strfreev(parts);
452 return;
455 static void sipe_canwrite_cb(gpointer data,
456 SIPE_UNUSED_PARAMETER gint source,
457 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
459 PurpleConnection *gc = data;
460 struct sipe_account_data *sip = gc->proto_data;
461 gsize max_write;
462 gssize written;
464 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
466 if (max_write == 0) {
467 if (sip->tx_handler != 0){
468 purple_input_remove(sip->tx_handler);
469 sip->tx_handler = 0;
471 return;
474 written = write(sip->fd, sip->txbuf->outptr, max_write);
476 if (written < 0 && errno == EAGAIN)
477 written = 0;
478 else if (written <= 0) {
479 /*TODO: do we really want to disconnect on a failure to write?*/
480 purple_connection_error(gc, _("Could not write"));
481 return;
484 purple_circ_buffer_mark_read(sip->txbuf, written);
487 static void sipe_canwrite_cb_ssl(gpointer data,
488 SIPE_UNUSED_PARAMETER gint src,
489 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
491 PurpleConnection *gc = data;
492 struct sipe_account_data *sip = gc->proto_data;
493 gsize max_write;
494 gssize written;
496 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
498 if (max_write == 0) {
499 if (sip->tx_handler != 0) {
500 purple_input_remove(sip->tx_handler);
501 sip->tx_handler = 0;
502 return;
506 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
508 if (written < 0 && errno == EAGAIN)
509 written = 0;
510 else if (written <= 0) {
511 /*TODO: do we really want to disconnect on a failure to write?*/
512 purple_connection_error(gc, _("Could not write"));
513 return;
516 purple_circ_buffer_mark_read(sip->txbuf, written);
519 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
521 static void send_later_cb(gpointer data, gint source,
522 SIPE_UNUSED_PARAMETER const gchar *error)
524 PurpleConnection *gc = data;
525 struct sipe_account_data *sip;
526 struct sip_connection *conn;
528 if (!PURPLE_CONNECTION_IS_VALID(gc))
530 if (source >= 0)
531 close(source);
532 return;
535 if (source < 0) {
536 purple_connection_error(gc, _("Could not connect"));
537 return;
540 sip = gc->proto_data;
541 sip->fd = source;
542 sip->connecting = FALSE;
543 sip->last_keepalive = time(NULL);
545 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
547 /* If there is more to write now, we need to register a handler */
548 if (sip->txbuf->bufused > 0)
549 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
551 conn = connection_create(sip, source);
552 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
555 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
557 struct sipe_account_data *sip;
558 struct sip_connection *conn;
560 if (!PURPLE_CONNECTION_IS_VALID(gc))
562 if (gsc) purple_ssl_close(gsc);
563 return NULL;
566 sip = gc->proto_data;
567 sip->fd = gsc->fd;
568 sip->gsc = gsc;
569 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
570 sip->connecting = FALSE;
571 sip->last_keepalive = time(NULL);
573 conn = connection_create(sip, gsc->fd);
575 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
577 return sip;
580 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
581 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
583 PurpleConnection *gc = data;
584 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
585 if (sip == NULL) return;
587 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
589 /* If there is more to write now */
590 if (sip->txbuf->bufused > 0) {
591 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
596 static void sendlater(PurpleConnection *gc, const char *buf)
598 struct sipe_account_data *sip = gc->proto_data;
600 if (!sip->connecting) {
601 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
602 if (sip->transport == SIPE_TRANSPORT_TLS){
603 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
604 } else {
605 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
606 purple_connection_error(gc, _("Could not create socket"));
609 sip->connecting = TRUE;
612 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
613 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
615 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
618 static void sendout_pkt(PurpleConnection *gc, const char *buf)
620 struct sipe_account_data *sip = gc->proto_data;
621 time_t currtime = time(NULL);
622 int writelen = strlen(buf);
623 char *tmp;
625 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
626 g_free(tmp);
627 if (sip->transport == SIPE_TRANSPORT_UDP) {
628 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
629 purple_debug_info("sipe", "could not send packet\n");
631 } else {
632 int ret;
633 if (sip->fd < 0) {
634 sendlater(gc, buf);
635 return;
638 if (sip->tx_handler) {
639 ret = -1;
640 errno = EAGAIN;
641 } else{
642 if (sip->gsc){
643 ret = purple_ssl_write(sip->gsc, buf, writelen);
644 }else{
645 ret = write(sip->fd, buf, writelen);
649 if (ret < 0 && errno == EAGAIN)
650 ret = 0;
651 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
652 sendlater(gc, buf);
653 return;
656 if (ret < writelen) {
657 if (!sip->tx_handler){
658 if (sip->gsc){
659 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
661 else{
662 sip->tx_handler = purple_input_add(sip->fd,
663 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
664 gc);
668 /* XXX: is it OK to do this? You might get part of a request sent
669 with part of another. */
670 if (sip->txbuf->bufused > 0)
671 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
673 purple_circ_buffer_append(sip->txbuf, buf + ret,
674 writelen - ret);
679 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
681 sendout_pkt(gc, buf);
682 return len;
685 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
687 GSList *tmp = msg->headers;
688 gchar *name;
689 gchar *value;
690 GString *outstr = g_string_new("");
691 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
692 while (tmp) {
693 name = ((struct siphdrelement*) (tmp->data))->name;
694 value = ((struct siphdrelement*) (tmp->data))->value;
695 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
696 tmp = g_slist_next(tmp);
698 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
699 sendout_pkt(sip->gc, outstr->str);
700 g_string_free(outstr, TRUE);
703 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
705 gchar * buf;
707 if (sip->registrar.type == AUTH_TYPE_UNSET) {
708 return;
711 if (sip->registrar.gssapi_context) {
712 struct sipmsg_breakdown msgbd;
713 gchar *signature_input_str;
714 msgbd.msg = msg;
715 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
716 msgbd.rand = g_strdup_printf("%08x", g_random_int());
717 sip->registrar.ntlm_num++;
718 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
719 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
720 if (signature_input_str != NULL) {
721 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
722 msg->signature = signature_hex;
723 msg->rand = g_strdup(msgbd.rand);
724 msg->num = g_strdup(msgbd.num);
725 g_free(signature_input_str);
727 sipmsg_breakdown_free(&msgbd);
730 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
731 buf = auth_header(sip, &sip->registrar, msg);
732 if (buf) {
733 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
735 g_free(buf);
736 } else if (!strcmp(method,"SUBSCRIBE") || !strcmp(method,"SERVICE") || !strcmp(method,"MESSAGE") || !strcmp(method,"INVITE") || !strcmp(method, "ACK") || !strcmp(method, "NOTIFY") || !strcmp(method, "BYE") || !strcmp(method, "INFO") || !strcmp(method, "OPTIONS") || !strcmp(method, "REFER")) {
737 sip->registrar.nc = 3;
738 #ifdef USE_KERBEROS
739 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
740 #endif
741 sip->registrar.type = AUTH_TYPE_NTLM;
742 #ifdef USE_KERBEROS
743 } else {
744 sip->registrar.type = AUTH_TYPE_KERBEROS;
746 #endif
749 buf = auth_header(sip, &sip->registrar, msg);
750 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
751 g_free(buf);
752 } else {
753 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
757 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
758 const char *text, const char *body)
760 gchar *name;
761 gchar *value;
762 GString *outstr = g_string_new("");
763 struct sipe_account_data *sip = gc->proto_data;
764 gchar *contact;
765 GSList *tmp;
766 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
768 contact = get_contact(sip);
769 sipmsg_add_header(msg, "Contact", contact);
770 g_free(contact);
772 if (body) {
773 gchar len[12];
774 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
775 sipmsg_add_header(msg, "Content-Length", len);
776 } else {
777 sipmsg_add_header(msg, "Content-Length", "0");
780 msg->response = code;
782 sipmsg_strip_headers(msg, keepers);
783 sipmsg_merge_new_headers(msg);
784 sign_outgoing_message(msg, sip, msg->method);
786 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
787 tmp = msg->headers;
788 while (tmp) {
789 name = ((struct siphdrelement*) (tmp->data))->name;
790 value = ((struct siphdrelement*) (tmp->data))->value;
792 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
793 tmp = g_slist_next(tmp);
795 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
796 sendout_pkt(gc, outstr->str);
797 g_string_free(outstr, TRUE);
800 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
802 sip->transactions = g_slist_remove(sip->transactions, trans);
803 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
804 if (trans->msg) sipmsg_free(trans->msg);
805 if (trans->payload) {
806 (*trans->payload->destroy)(trans->payload->data);
807 g_free(trans->payload);
809 g_free(trans->key);
810 g_free(trans);
813 static struct transaction *
814 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
816 gchar *call_id = NULL;
817 gchar *cseq = NULL;
818 struct transaction *trans = g_new0(struct transaction, 1);
820 trans->time = time(NULL);
821 trans->msg = (struct sipmsg *)msg;
822 call_id = sipmsg_find_header(trans->msg, "Call-ID");
823 cseq = sipmsg_find_header(trans->msg, "CSeq");
824 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
825 trans->callback = callback;
826 sip->transactions = g_slist_append(sip->transactions, trans);
827 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
828 return trans;
831 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
833 struct transaction *trans;
834 GSList *transactions = sip->transactions;
835 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
836 gchar *cseq = sipmsg_find_header(msg, "CSeq");
837 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
839 while (transactions) {
840 trans = transactions->data;
841 if (!g_strcasecmp(trans->key, key)) {
842 g_free(key);
843 return trans;
845 transactions = transactions->next;
848 g_free(key);
849 return NULL;
852 struct transaction *
853 send_sip_request(PurpleConnection *gc, const gchar *method,
854 const gchar *url, const gchar *to, const gchar *addheaders,
855 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
857 struct sipe_account_data *sip = gc->proto_data;
858 const char *addh = "";
859 char *buf;
860 struct sipmsg *msg;
861 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
862 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
863 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
864 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
865 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
866 gchar *route = g_strdup("");
867 gchar *epid = get_epid(sip);
868 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
869 struct transaction *trans = NULL;
871 if (dialog && dialog->routes)
873 GSList *iter = dialog->routes;
875 while(iter)
877 char *tmp = route;
878 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
879 g_free(tmp);
880 iter = g_slist_next(iter);
884 if (!ourtag && !dialog) {
885 ourtag = gentag();
888 if (!strcmp(method, "REGISTER")) {
889 if (sip->regcallid) {
890 g_free(callid);
891 callid = g_strdup(sip->regcallid);
892 } else {
893 sip->regcallid = g_strdup(callid);
895 cseq = ++sip->cseq;
898 if (addheaders) addh = addheaders;
900 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
901 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
902 "From: <sip:%s>%s%s;epid=%s\r\n"
903 "To: <%s>%s%s%s%s\r\n"
904 "Max-Forwards: 70\r\n"
905 "CSeq: %d %s\r\n"
906 "User-Agent: %s\r\n"
907 "Call-ID: %s\r\n"
908 "%s%s"
909 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
910 method,
911 dialog && dialog->request ? dialog->request : url,
912 TRANSPORT_DESCRIPTOR,
913 purple_network_get_my_ip(-1),
914 sip->listenport,
915 branch ? ";branch=" : "",
916 branch ? branch : "",
917 sip->username,
918 ourtag ? ";tag=" : "",
919 ourtag ? ourtag : "",
920 epid,
922 theirtag ? ";tag=" : "",
923 theirtag ? theirtag : "",
924 theirepid ? ";epid=" : "",
925 theirepid ? theirepid : "",
926 cseq,
927 method,
928 sipe_get_useragent(sip),
929 callid,
930 route,
931 addh,
932 body ? (gsize) strlen(body) : 0,
933 body ? body : "");
936 //printf ("parsing msg buf:\n%s\n\n", buf);
937 msg = sipmsg_parse_msg(buf);
939 g_free(buf);
940 g_free(ourtag);
941 g_free(theirtag);
942 g_free(theirepid);
943 g_free(branch);
944 g_free(callid);
945 g_free(route);
946 g_free(epid);
948 sign_outgoing_message (msg, sip, method);
950 buf = sipmsg_to_string (msg);
952 /* add to ongoing transactions */
953 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
954 if (strcmp(method, "ACK")) {
955 trans = transactions_add_buf(sip, msg, tc);
956 } else {
957 sipmsg_free(msg);
959 sendout_pkt(gc, buf);
960 g_free(buf);
962 return trans;
966 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
968 static void
969 send_soap_request_with_cb(struct sipe_account_data *sip,
970 gchar *from0,
971 gchar *body,
972 TransCallback callback,
973 struct transaction_payload *payload)
975 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
976 gchar *contact = get_contact(sip);
977 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
978 "Content-Type: application/SOAP+xml\r\n",contact);
980 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
981 trans->payload = payload;
983 g_free(from);
984 g_free(contact);
985 g_free(hdr);
988 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
990 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
993 static char *get_contact_register(struct sipe_account_data *sip)
995 char *epid = get_epid(sip);
996 char *uuid = generateUUIDfromEPID(epid);
997 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);
998 g_free(uuid);
999 g_free(epid);
1000 return(buf);
1003 static void do_register_exp(struct sipe_account_data *sip, int expire)
1005 char *uri;
1006 char *expires;
1007 char *to;
1008 char *contact;
1009 char *hdr;
1011 if (!sip->sipdomain) return;
1013 uri = sip_uri_from_name(sip->sipdomain);
1014 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1015 to = sip_uri_self(sip);
1016 contact = get_contact_register(sip);
1017 hdr = g_strdup_printf("Contact: %s\r\n"
1018 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1019 "Event: registration\r\n"
1020 "Allow-Events: presence\r\n"
1021 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1022 "%s", contact, expires);
1023 g_free(contact);
1024 g_free(expires);
1026 sip->registerstatus = 1;
1028 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1029 process_register_response);
1031 g_free(hdr);
1032 g_free(uri);
1033 g_free(to);
1036 static void do_register_cb(struct sipe_account_data *sip,
1037 SIPE_UNUSED_PARAMETER void *unused)
1039 do_register_exp(sip, -1);
1040 sip->reregister_set = FALSE;
1043 static void do_register(struct sipe_account_data *sip)
1045 do_register_exp(sip, -1);
1048 static void
1049 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1051 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1052 send_soap_request(sip, body);
1053 g_free(body);
1056 static void
1057 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1059 if (allow) {
1060 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1061 } else {
1062 purple_debug_info("sipe", "Blocking contact %s\n", who);
1065 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1068 static
1069 void sipe_auth_user_cb(void * data)
1071 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1072 if (!job) return;
1074 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1075 g_free(job);
1078 static
1079 void sipe_deny_user_cb(void * data)
1081 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1082 if (!job) return;
1084 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1085 g_free(job);
1088 static void
1089 sipe_add_permit(PurpleConnection *gc, const char *name)
1091 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1092 sipe_contact_allow_deny(sip, name, TRUE);
1095 static void
1096 sipe_add_deny(PurpleConnection *gc, const char *name)
1098 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1099 sipe_contact_allow_deny(sip, name, FALSE);
1102 /*static void
1103 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1105 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1106 sipe_contact_set_acl(sip, name, "");
1109 static void
1110 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1112 xmlnode *watchers;
1113 xmlnode *watcher;
1114 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1115 if (msg->response != 0 && msg->response != 200) return;
1117 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1119 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1120 if (!watchers) return;
1122 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1123 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1124 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1125 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1127 // TODO pull out optional displayName to pass as alias
1128 if (remote_user) {
1129 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1130 job->who = remote_user;
1131 job->sip = sip;
1132 purple_account_request_authorization(
1133 sip->account,
1134 remote_user,
1135 _("you"), /* id */
1136 alias,
1137 NULL, /* message */
1138 on_list,
1139 sipe_auth_user_cb,
1140 sipe_deny_user_cb,
1141 (void *) job);
1146 xmlnode_free(watchers);
1147 return;
1150 static void
1151 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1153 PurpleGroup * purple_group = purple_find_group(group->name);
1154 if (!purple_group) {
1155 purple_group = purple_group_new(group->name);
1156 purple_blist_add_group(purple_group, NULL);
1159 if (purple_group) {
1160 group->purple_group = purple_group;
1161 sip->groups = g_slist_append(sip->groups, group);
1162 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1163 } else {
1164 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1168 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1170 struct sipe_group *group;
1171 GSList *entry;
1172 if (sip == NULL) {
1173 return NULL;
1176 entry = sip->groups;
1177 while (entry) {
1178 group = entry->data;
1179 if (group->id == id) {
1180 return group;
1182 entry = entry->next;
1184 return NULL;
1187 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1189 struct sipe_group *group;
1190 GSList *entry;
1191 if (sip == NULL) {
1192 return NULL;
1195 entry = sip->groups;
1196 while (entry) {
1197 group = entry->data;
1198 if (!strcmp(group->name, name)) {
1199 return group;
1201 entry = entry->next;
1203 return NULL;
1206 static void
1207 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1209 gchar *body;
1210 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1211 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1212 send_soap_request(sip, body);
1213 g_free(body);
1214 g_free(group->name);
1215 group->name = g_strdup(name);
1219 * Only appends if no such value already stored.
1220 * Like Set in Java.
1222 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1223 GSList * res = list;
1224 if (!g_slist_find_custom(list, data, func)) {
1225 res = g_slist_insert_sorted(list, data, func);
1227 return res;
1230 static int
1231 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1232 return group1->id - group2->id;
1236 * Returns string like "2 4 7 8" - group ids buddy belong to.
1238 static gchar *
1239 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1240 int i = 0;
1241 gchar *res;
1242 //creating array from GList, converting int to gchar*
1243 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1244 GSList *entry = buddy->groups;
1245 while (entry) {
1246 struct sipe_group * group = entry->data;
1247 ids_arr[i] = g_strdup_printf("%d", group->id);
1248 entry = entry->next;
1249 i++;
1251 ids_arr[i] = NULL;
1252 res = g_strjoinv(" ", ids_arr);
1253 g_strfreev(ids_arr);
1254 return res;
1258 * Sends buddy update to server
1260 static void
1261 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1263 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1264 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1266 if (buddy && purple_buddy) {
1267 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1268 gchar *body;
1269 gchar *groups = sipe_get_buddy_groups_string(buddy);
1270 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1272 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1273 alias, groups, "true", buddy->name, sip->contacts_delta++
1275 send_soap_request(sip, body);
1276 g_free(groups);
1277 g_free(body);
1281 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1283 if (msg->response == 200) {
1284 struct sipe_group *group;
1285 struct group_user_context *ctx = trans->payload->data;
1286 xmlnode *xml;
1287 xmlnode *node;
1288 char *group_id;
1289 struct sipe_buddy *buddy;
1291 xml = xmlnode_from_str(msg->body, msg->bodylen);
1292 if (!xml) {
1293 return FALSE;
1296 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1297 if (!node) {
1298 xmlnode_free(xml);
1299 return FALSE;
1302 group_id = xmlnode_get_data(node);
1303 if (!group_id) {
1304 xmlnode_free(xml);
1305 return FALSE;
1308 group = g_new0(struct sipe_group, 1);
1309 group->id = (int)g_ascii_strtod(group_id, NULL);
1310 g_free(group_id);
1311 group->name = g_strdup(ctx->group_name);
1313 sipe_group_add(sip, group);
1315 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1316 if (buddy) {
1317 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1320 sipe_group_set_user(sip, ctx->user_name);
1322 xmlnode_free(xml);
1323 return TRUE;
1325 return FALSE;
1328 static void sipe_group_context_destroy(gpointer data)
1330 struct group_user_context *ctx = data;
1331 g_free(ctx->group_name);
1332 g_free(ctx->user_name);
1333 g_free(ctx);
1336 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1338 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1339 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1340 gchar *body;
1341 ctx->group_name = g_strdup(name);
1342 ctx->user_name = g_strdup(who);
1343 payload->destroy = sipe_group_context_destroy;
1344 payload->data = ctx;
1346 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1347 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1348 g_free(body);
1352 * Data structure for scheduled actions
1355 struct scheduled_action {
1357 * Name of action.
1358 * Format is <Event>[<Data>...]
1359 * Example: <presence><sip:user@domain.com> or <registration>
1361 gchar *name;
1362 guint timeout_handler;
1363 gboolean repetitive;
1364 Action action;
1365 GDestroyNotify destroy;
1366 struct sipe_account_data *sip;
1367 void *payload;
1371 * A timer callback
1372 * Should return FALSE if repetitive action is not needed
1374 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1376 gboolean ret;
1377 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1378 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1379 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1380 (sched_action->action)(sched_action->sip, sched_action->payload);
1381 ret = sched_action->repetitive;
1382 if (sched_action->destroy) {
1383 (*sched_action->destroy)(sched_action->payload);
1385 g_free(sched_action->name);
1386 g_free(sched_action);
1387 return ret;
1391 * Kills action timer effectively cancelling
1392 * scheduled action
1394 * @param name of action
1396 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1398 GSList *entry;
1400 if (!sip->timeouts || !name) return;
1402 entry = sip->timeouts;
1403 while (entry) {
1404 struct scheduled_action *sched_action = entry->data;
1405 if(!strcmp(sched_action->name, name)) {
1406 GSList *to_delete = entry;
1407 entry = entry->next;
1408 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1409 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1410 purple_timeout_remove(sched_action->timeout_handler);
1411 if (sched_action->destroy) {
1412 (*sched_action->destroy)(sched_action->payload);
1414 g_free(sched_action->name);
1415 g_free(sched_action);
1416 } else {
1417 entry = entry->next;
1422 static void
1423 sipe_schedule_action0(const gchar *name,
1424 int timeout,
1425 gboolean isSeconds,
1426 Action action,
1427 GDestroyNotify destroy,
1428 struct sipe_account_data *sip,
1429 void *payload)
1431 struct scheduled_action *sched_action;
1433 /* Make sure each action only exists once */
1434 sipe_cancel_scheduled_action(sip, name);
1436 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1437 sched_action = g_new0(struct scheduled_action, 1);
1438 sched_action->repetitive = FALSE;
1439 sched_action->name = g_strdup(name);
1440 sched_action->action = action;
1441 sched_action->destroy = destroy;
1442 sched_action->sip = sip;
1443 sched_action->payload = payload;
1444 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1445 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1446 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1447 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1450 void
1451 sipe_schedule_action(const gchar *name,
1452 int timeout,
1453 Action action,
1454 GDestroyNotify destroy,
1455 struct sipe_account_data *sip,
1456 void *payload)
1458 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1462 * Same as sipe_schedule_action() but timeout is in milliseconds.
1464 static void
1465 sipe_schedule_action_msec(const gchar *name,
1466 int timeout,
1467 Action action,
1468 GDestroyNotify destroy,
1469 struct sipe_account_data *sip,
1470 void *payload)
1472 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1475 static void
1476 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1477 time_t calculate_from);
1479 static void
1480 sipe_got_user_status(struct sipe_account_data *sip,
1481 const char* uri,
1482 const char *status_id)
1484 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1486 /* Check if on 2005 system contact's calendar is in Busy status,
1487 * then set/preserve it.
1489 if (!sip->ocs2007 && sipe_cal_get_status(sbuddy, time(NULL))== SIPE_CAL_BUSY) {
1490 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, sbuddy->name);
1491 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
1492 const PurpleStatus *status = purple_presence_get_active_status(presence);
1494 sbuddy->last_non_cal_status_id = g_strdup(status_id);
1495 sbuddy->last_non_cal_activity = sbuddy->activity;
1496 sbuddy->activity = g_strdup(_("In a meeting"));
1498 /* not yet set */
1499 if (strcmp(purple_status_get_id(status), SIPE_STATUS_ID_MEETING)) {
1500 purple_debug_info("sipe", "sipe_got_user_status: to %s for %s\n", SIPE_STATUS_ID_MEETING, sbuddy->name);
1501 purple_prpl_got_user_status(sip->account, sbuddy->name, SIPE_STATUS_ID_MEETING, NULL);
1503 } else {
1504 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1508 static void
1509 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1510 struct sipe_buddy *sbuddy,
1511 struct sipe_account_data *sip)
1513 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, sbuddy->name);
1514 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
1515 const PurpleStatus *status = purple_presence_get_active_status(presence);
1516 int cal_status = sipe_cal_get_status(sbuddy, time(NULL) + 2*60 /* 2 min */);
1518 if (cal_status < SIPE_CAL_NO_DATA) {
1519 purple_debug_info("sipe", "update_calendar_status_cb: cal_status:%d for %s\n", cal_status, sbuddy->name);
1521 if (cal_status == SIPE_CAL_BUSY) {
1522 /* not yet set */
1523 if (strcmp(purple_status_get_id(status), SIPE_STATUS_ID_MEETING)) {
1524 purple_debug_info("sipe", "update_calendar_status_cb: to %s for %s\n", SIPE_STATUS_ID_MEETING, sbuddy->name);
1525 sbuddy->last_non_cal_status_id = g_strdup(purple_status_get_id(status));
1526 sbuddy->last_non_cal_activity = sbuddy->activity;
1527 sbuddy->activity = g_strdup(_("In a meeting"));
1528 purple_prpl_got_user_status(sip->account, sbuddy->name, SIPE_STATUS_ID_MEETING, NULL);
1530 } else {
1531 if (!strcmp(purple_status_get_id(status), SIPE_STATUS_ID_MEETING)) {
1532 /* resetting status to last known */
1533 purple_debug_info("sipe", "update_calendar_status_cb: resetting to %s for %s\n",
1534 sbuddy->last_non_cal_status_id, sbuddy->name);
1535 g_free(sbuddy->last_non_cal_status_id);
1536 sbuddy->last_non_cal_status_id = NULL;
1537 purple_prpl_got_user_status(sip->account, sbuddy->name, sbuddy->last_non_cal_status_id, NULL);
1538 g_free(sbuddy->activity);
1539 sbuddy->activity = sbuddy->last_non_cal_activity;
1540 sbuddy->last_non_cal_activity = NULL;
1546 * Updates contact's status
1547 * based on their calendar information.
1549 * Applicability: 2005 systems
1551 static void
1552 update_calendar_status(struct sipe_account_data *sip)
1554 purple_debug_info("sipe", "update_calendar_status() started.\n");
1555 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1557 /* repeat scheduling */
1558 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1562 * Schedules process of contacts' status update
1563 * based on their calendar information.
1564 * Should be scheduled to the beginning of every
1565 * 15 min interval, like:
1566 * 13:00, 13:15, 13:30, 13:45, etc.
1568 * Applicability: 2005 systems
1570 static void
1571 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1572 time_t calculate_from)
1574 int interval = 15*60;
1575 /** start of the beginning of closest 15 min interval. */
1576 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1578 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1579 asctime(localtime(&calculate_from)));
1580 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1581 asctime(localtime(&next_start)));
1583 sipe_schedule_action("<+2005-cal-status>",
1584 (int)(next_start - time(NULL)),
1585 (Action)update_calendar_status,
1586 NULL,
1587 sip,
1588 NULL);
1592 * Schedules process of self status publish
1593 * based on own calendar information.
1594 * Should be scheduled to the beginning of every
1595 * 15 min interval, like:
1596 * 13:00, 13:15, 13:30, 13:45, etc.
1598 * Applicability: 2007+ systems
1600 static void
1601 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1602 time_t calculate_from)
1604 int interval = 5*60;
1605 /** start of the beginning of closest 5 min interval. */
1606 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1608 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1609 asctime(localtime(&calculate_from)));
1610 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1611 asctime(localtime(&next_start)));
1613 sipe_schedule_action("<+2007-cal-status>",
1614 (int)(next_start - time(NULL)),
1615 (Action)publish_calendar_status_self,
1616 NULL,
1617 sip,
1618 NULL);
1621 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1623 /** Should be g_free()'d
1625 static gchar *
1626 sipe_get_subscription_key(gchar *event,
1627 gchar *with)
1629 gchar *key = NULL;
1631 if (is_empty(event)) return NULL;
1633 if (event && !g_ascii_strcasecmp(event, "presence")) {
1634 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1635 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1637 /* @TODO drop participated buddies' just_added flag */
1638 } else if (event) {
1639 /* Subscription is identified by <event> key */
1640 key = g_strdup_printf("<%s>", event);
1643 return key;
1646 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1647 SIPE_UNUSED_PARAMETER struct transaction *trans)
1649 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1650 gchar *event = sipmsg_find_header(msg, "Event");
1651 gchar *key;
1653 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1654 if (!event) {
1655 struct sipmsg *request_msg = trans->msg;
1656 event = sipmsg_find_header(request_msg, "Event");
1659 key = sipe_get_subscription_key(event, with);
1661 /* 200 OK; 481 Call Leg Does Not Exist */
1662 if (key && (msg->response == 200 || msg->response == 481)) {
1663 if (g_hash_table_lookup(sip->subscriptions, key)) {
1664 g_hash_table_remove(sip->subscriptions, key);
1665 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1669 /* create/store subscription dialog if not yet */
1670 if (msg->response == 200) {
1671 gchar *callid = sipmsg_find_header(msg, "Call-ID");
1672 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1674 if (key) {
1675 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1676 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1678 subscription->dialog.callid = g_strdup(callid);
1679 subscription->dialog.cseq = atoi(cseq);
1680 subscription->dialog.with = g_strdup(with);
1681 subscription->event = g_strdup(event);
1682 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1684 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1687 g_free(cseq);
1690 g_free(key);
1691 g_free(with);
1693 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1695 process_incoming_notify(sip, msg, FALSE, FALSE);
1697 return TRUE;
1700 static void sipe_subscribe_resource_uri(const char *name,
1701 SIPE_UNUSED_PARAMETER gpointer value,
1702 gchar **resources_uri)
1704 gchar *tmp = *resources_uri;
1705 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1706 g_free(tmp);
1709 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1711 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1712 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1713 gchar *tmp = *resources_uri;
1715 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1717 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1718 g_free(tmp);
1722 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1723 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1724 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1725 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1726 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1729 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1731 gchar *key;
1732 gchar *contact = get_contact(sip);
1733 gchar *request;
1734 gchar *content;
1735 gchar *require = "";
1736 gchar *accept = "";
1737 gchar *autoextend = "";
1738 gchar *content_type;
1739 struct sip_dialog *dialog;
1741 if (sip->ocs2007) {
1742 require = ", categoryList";
1743 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1744 content_type = "application/msrtc-adrl-categorylist+xml";
1745 content = g_strdup_printf(
1746 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1747 "<action name=\"subscribe\" id=\"63792024\">\n"
1748 "<adhocList>\n%s</adhocList>\n"
1749 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1750 "<category name=\"calendarData\"/>\n"
1751 "<category name=\"contactCard\"/>\n"
1752 "<category name=\"note\"/>\n"
1753 "<category name=\"state\"/>\n"
1754 "</categoryList>\n"
1755 "</action>\n"
1756 "</batchSub>", sip->username, resources_uri);
1757 } else {
1758 autoextend = "Supported: com.microsoft.autoextend\r\n";
1759 content_type = "application/adrl+xml";
1760 content = g_strdup_printf(
1761 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1762 "<create xmlns=\"\">\n%s</create>\n"
1763 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1765 g_free(resources_uri);
1767 request = g_strdup_printf(
1768 "Require: adhoclist%s\r\n"
1769 "Supported: eventlist\r\n"
1770 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1771 "Supported: ms-piggyback-first-notify\r\n"
1772 "%sSupported: ms-benotify\r\n"
1773 "Proxy-Require: ms-benotify\r\n"
1774 "Event: presence\r\n"
1775 "Content-Type: %s\r\n"
1776 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1777 g_free(contact);
1779 /* subscribe to buddy presence */
1780 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1781 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1782 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1783 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1785 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1787 g_free(content);
1788 g_free(to);
1789 g_free(request);
1790 g_free(key);
1793 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1794 SIPE_UNUSED_PARAMETER void *unused)
1796 gchar *to = sip_uri_self(sip);
1797 gchar *resources_uri = g_strdup("");
1798 if (sip->ocs2007) {
1799 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1800 } else {
1801 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1803 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1806 struct presence_batched_routed {
1807 gchar *host;
1808 GSList *buddies;
1811 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1813 struct presence_batched_routed *data = payload;
1814 GSList *buddies = data->buddies;
1815 while (buddies) {
1816 g_free(buddies->data);
1817 buddies = buddies->next;
1819 g_slist_free(data->buddies);
1820 g_free(data->host);
1821 g_free(payload);
1824 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1826 struct presence_batched_routed *data = payload;
1827 GSList *buddies = data->buddies;
1828 gchar *resources_uri = g_strdup("");
1829 while (buddies) {
1830 gchar *tmp = resources_uri;
1831 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1832 g_free(tmp);
1833 buddies = buddies->next;
1835 sipe_subscribe_presence_batched_to(sip, resources_uri,
1836 g_strdup(data->host));
1840 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1841 * The user sends a single SUBSCRIBE request to the subscribed contact.
1842 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1846 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1849 gchar *key;
1850 gchar *to = sip_uri((char *)buddy_name);
1851 gchar *tmp = get_contact(sip);
1852 gchar *request;
1853 gchar *content = NULL;
1854 gchar *autoextend = "";
1855 gchar *content_type = "";
1856 struct sip_dialog *dialog;
1857 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
1858 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1860 if (sbuddy) sbuddy->just_added = FALSE;
1862 if (sip->ocs2007) {
1863 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1864 } else {
1865 autoextend = "Supported: com.microsoft.autoextend\r\n";
1868 request = g_strdup_printf(
1869 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1870 "Supported: ms-piggyback-first-notify\r\n"
1871 "%s%sSupported: ms-benotify\r\n"
1872 "Proxy-Require: ms-benotify\r\n"
1873 "Event: presence\r\n"
1874 "Contact: %s\r\n", autoextend, content_type, tmp);
1876 if (sip->ocs2007) {
1877 content = g_strdup_printf(
1878 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1879 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1880 "<resource uri=\"%s\"%s\n"
1881 "</adhocList>\n"
1882 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1883 "<category name=\"calendarData\"/>\n"
1884 "<category name=\"contactCard\"/>\n"
1885 "<category name=\"note\"/>\n"
1886 "<category name=\"state\"/>\n"
1887 "</categoryList>\n"
1888 "</action>\n"
1889 "</batchSub>", sip->username, to, context);
1892 g_free(tmp);
1894 /* subscribe to buddy presence */
1895 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1896 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1897 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1898 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1900 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1902 g_free(content);
1903 g_free(to);
1904 g_free(request);
1905 g_free(key);
1908 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1910 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
1912 if (!purple_status_is_active(status))
1913 return;
1915 if (account->gc) {
1916 struct sipe_account_data *sip = account->gc->proto_data;
1918 if (sip) {
1919 gchar *action_name;
1920 g_free(sip->status);
1921 sip->status = g_strdup(purple_status_get_id(status));
1923 /* schedule 2 sec to capture idle flag */
1924 action_name = g_strdup_printf("<%s>", "+set-status");
1925 sipe_schedule_action(action_name, 2, (Action)send_presence_status, NULL, sip, NULL);
1926 g_free(action_name);
1930 static void
1931 sipe_set_idle(PurpleConnection * gc,
1932 int time)
1934 purple_debug_info("sipe", "sipe_set_idle: time=%d\n", time);
1936 if (gc) {
1937 struct sipe_account_data *sip = gc->proto_data;
1939 if (sip) {
1940 sip->was_idle = sip->is_idle;
1941 sip->is_idle = (time > 0);
1946 static void
1947 sipe_alias_buddy(PurpleConnection *gc, const char *name,
1948 SIPE_UNUSED_PARAMETER const char *alias)
1950 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1951 sipe_group_set_user(sip, name);
1954 static void
1955 sipe_group_buddy(PurpleConnection *gc,
1956 const char *who,
1957 const char *old_group_name,
1958 const char *new_group_name)
1960 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1961 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1962 struct sipe_group * old_group = NULL;
1963 struct sipe_group * new_group;
1965 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1966 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1968 if(!buddy) { // buddy not in roaming list
1969 return;
1972 if (old_group_name) {
1973 old_group = sipe_group_find_by_name(sip, old_group_name);
1975 new_group = sipe_group_find_by_name(sip, new_group_name);
1977 if (old_group) {
1978 buddy->groups = g_slist_remove(buddy->groups, old_group);
1979 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1982 if (!new_group) {
1983 sipe_group_create(sip, new_group_name, who);
1984 } else {
1985 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1986 sipe_group_set_user(sip, who);
1990 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1992 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1994 /* libpurple can call us with undefined buddy or group */
1995 if (buddy && group) {
1996 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1998 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1999 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2000 purple_blist_rename_buddy(buddy, buddy_name);
2001 g_free(buddy_name);
2003 /* Prepend sip: if needed */
2004 if (strncmp("sip:", buddy->name, 4)) {
2005 gchar *buf = sip_uri_from_name(buddy->name);
2006 purple_blist_rename_buddy(buddy, buf);
2007 g_free(buf);
2010 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2011 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2012 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2013 b->name = g_strdup(buddy->name);
2014 b->just_added = TRUE;
2015 g_hash_table_insert(sip->buddies, b->name, b);
2016 sipe_group_buddy(gc, b->name, NULL, group->name);
2017 /* @TODO should go to callback */
2018 sipe_subscribe_presence_single(sip, b->name);
2019 } else {
2020 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2025 static void sipe_free_buddy(struct sipe_buddy *buddy)
2027 #ifndef _WIN32
2029 * We are calling g_hash_table_foreach_steal(). That means that no
2030 * key/value deallocation functions are called. Therefore the glib
2031 * hash code does not touch the key (buddy->name) or value (buddy)
2032 * of the to-be-deleted hash node at all. It follows that we
2034 * - MUST free the memory for the key ourselves and
2035 * - ARE allowed to do it in this function
2037 * Conclusion: glib must be broken on the Windows platform if sipe
2038 * crashes with SIGTRAP when closing. You'll have to live
2039 * with the memory leak until this is fixed.
2041 g_free(buddy->name);
2042 #endif
2043 g_free(buddy->activity);
2044 g_free(buddy->meeting_subject);
2045 g_free(buddy->meeting_location);
2046 g_free(buddy->annotation);
2048 g_free(buddy->cal_start_time);
2049 g_free(buddy->cal_free_busy_base64);
2050 g_free(buddy->cal_free_busy);
2051 g_free(buddy->last_non_cal_status_id);
2052 g_free(buddy->last_non_cal_activity);
2054 sipe_cal_free_working_hours(buddy->cal_working_hours);
2056 g_free(buddy->device_name);
2057 g_slist_free(buddy->groups);
2058 g_free(buddy);
2062 * Unassociates buddy from group first.
2063 * Then see if no groups left, removes buddy completely.
2064 * Otherwise updates buddy groups on server.
2066 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2068 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2069 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
2070 struct sipe_group *g = NULL;
2072 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2074 if (!b) return;
2076 if (group) {
2077 g = sipe_group_find_by_name(sip, group->name);
2080 if (g) {
2081 b->groups = g_slist_remove(b->groups, g);
2082 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2085 if (g_slist_length(b->groups) < 1) {
2086 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2087 sipe_cancel_scheduled_action(sip, action_name);
2088 g_free(action_name);
2090 g_hash_table_remove(sip->buddies, buddy->name);
2092 if (b->name) {
2093 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2094 send_soap_request(sip, body);
2095 g_free(body);
2098 sipe_free_buddy(b);
2099 } else {
2100 //updates groups on server
2101 sipe_group_set_user(sip, b->name);
2106 static void
2107 sipe_rename_group(PurpleConnection *gc,
2108 const char *old_name,
2109 PurpleGroup *group,
2110 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2112 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2113 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2114 if (s_group) {
2115 sipe_group_rename(sip, s_group, group->name);
2116 } else {
2117 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2121 static void
2122 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2124 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2125 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2126 if (s_group) {
2127 gchar *body;
2128 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2129 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2130 send_soap_request(sip, body);
2131 g_free(body);
2133 sip->groups = g_slist_remove(sip->groups, s_group);
2134 g_free(s_group->name);
2135 g_free(s_group);
2136 } else {
2137 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2141 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2143 PurpleStatusType *type;
2144 GList *types = NULL;
2146 /* Macros to reduce code repetition.
2147 Translators: noun */
2148 #define SIPE_ADD_STATUS(prim,id,name) type = purple_status_type_new_with_attrs( \
2149 prim, id, name, \
2150 TRUE, TRUE, FALSE, \
2151 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2152 NULL); \
2153 types = g_list_append(types, type);
2154 #define SIPE_ADD_STATUS_NO_MSG(prim,id,name,user) type = purple_status_type_new( \
2155 prim, id, name, user); \
2156 types = g_list_append(types, type);
2158 /* Online */
2159 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2160 NULL, NULL);
2162 /* Busy */
2163 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2164 SIPE_STATUS_ID_BUSY, _("Busy"));
2166 /* BusyIdle (not user settable) */
2167 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_UNAVAILABLE,
2168 SIPE_STATUS_ID_BUSYIDLE, _("BusyIdle"),
2169 FALSE);
2171 /* Do Not Disturb (not user settable) */
2172 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_UNAVAILABLE,
2173 SIPE_STATUS_ID_DND, NULL,
2174 FALSE);
2176 /* In a meeting (not user settable)
2177 * Calendar-driven status for 2005 systems.
2179 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_UNAVAILABLE,
2180 SIPE_STATUS_ID_MEETING, _("Busy"),
2181 FALSE);
2183 /* Be Right Back */
2184 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2185 SIPE_STATUS_ID_BRB, _("Be right back"));
2187 /* Away */
2188 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2189 NULL, NULL);
2191 /* On The Phone */
2192 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2193 SIPE_STATUS_ID_ONPHONE, _("On the phone"));
2195 /* Out To Lunch */
2196 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2197 SIPE_STATUS_ID_LUNCH, _("Out to lunch"));
2199 /* Idle/Inactive (not user settable) */
2200 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_AVAILABLE,
2201 SIPE_STATUS_ID_IDLE, _("Inactive"),
2202 FALSE);
2204 /* Appear Offline */
2205 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_INVISIBLE,
2206 NULL, NULL,
2207 TRUE);
2209 /* Offline */
2210 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_OFFLINE,
2211 NULL, NULL,
2212 TRUE);
2214 return types;
2218 * A callback for g_hash_table_foreach
2220 static void sipe_buddy_subscribe_cb(SIPE_UNUSED_PARAMETER char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
2222 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2223 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2224 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2225 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2227 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy->name));
2228 g_free(action_name);
2232 * Removes entries from purple buddy list
2233 * that does not correspond ones in the roaming contact list.
2235 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2236 GSList *buddies = purple_find_buddies(sip->account, NULL);
2237 GSList *entry = buddies;
2238 struct sipe_buddy *buddy;
2239 PurpleBuddy *b;
2240 PurpleGroup *g;
2242 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2243 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2244 while (entry) {
2245 b = entry->data;
2246 g = purple_buddy_get_group(b);
2247 buddy = g_hash_table_lookup(sip->buddies, b->name);
2248 if(buddy) {
2249 gboolean in_sipe_groups = FALSE;
2250 GSList *entry2 = buddy->groups;
2251 while (entry2) {
2252 struct sipe_group *group = entry2->data;
2253 if (!strcmp(group->name, g->name)) {
2254 in_sipe_groups = TRUE;
2255 break;
2257 entry2 = entry2->next;
2259 if(!in_sipe_groups) {
2260 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2261 purple_blist_remove_buddy(b);
2263 } else {
2264 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2265 purple_blist_remove_buddy(b);
2267 entry = entry->next;
2269 g_slist_free(buddies);
2272 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2274 int len = msg->bodylen;
2276 gchar *tmp = sipmsg_find_header(msg, "Event");
2277 xmlnode *item;
2278 xmlnode *isc;
2279 const gchar *contacts_delta;
2280 xmlnode *group_node;
2281 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
2282 return FALSE;
2285 /* Convert the contact from XML to Purple Buddies */
2286 isc = xmlnode_from_str(msg->body, len);
2287 if (!isc) {
2288 return FALSE;
2291 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2292 if (contacts_delta) {
2293 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2296 if (!strcmp(isc->name, "contactList")) {
2298 /* Parse groups */
2299 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2300 struct sipe_group * group = g_new0(struct sipe_group, 1);
2301 const char *name = xmlnode_get_attrib(group_node, "name");
2303 if (!strncmp(name, "~", 1)) {
2304 name = _("Other Contacts");
2306 group->name = g_strdup(name);
2307 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2309 sipe_group_add(sip, group);
2312 // Make sure we have at least one group
2313 if (g_slist_length(sip->groups) == 0) {
2314 struct sipe_group * group = g_new0(struct sipe_group, 1);
2315 PurpleGroup *purple_group;
2316 group->name = g_strdup(_("Other Contacts"));
2317 group->id = 1;
2318 purple_group = purple_group_new(group->name);
2319 purple_blist_add_group(purple_group, NULL);
2320 sip->groups = g_slist_append(sip->groups, group);
2323 /* Parse contacts */
2324 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2325 const gchar *uri = xmlnode_get_attrib(item, "uri");
2326 const gchar *name = xmlnode_get_attrib(item, "name");
2327 gchar *buddy_name;
2328 struct sipe_buddy *buddy = NULL;
2329 gchar *tmp;
2330 gchar **item_groups;
2331 int i = 0;
2333 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2334 tmp = sip_uri_from_name(uri);
2335 buddy_name = g_ascii_strdown(tmp, -1);
2336 g_free(tmp);
2338 /* assign to group Other Contacts if nothing else received */
2339 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2340 if(!tmp || !strcmp("", tmp) ) {
2341 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2342 g_free(tmp);
2343 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2345 item_groups = g_strsplit(tmp, " ", 0);
2346 g_free(tmp);
2348 while (item_groups[i]) {
2349 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2351 // If couldn't find the right group for this contact, just put them in the first group we have
2352 if (group == NULL && g_slist_length(sip->groups) > 0) {
2353 group = sip->groups->data;
2356 if (group != NULL) {
2357 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2358 if (!b){
2359 b = purple_buddy_new(sip->account, buddy_name, uri);
2360 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2362 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2365 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2366 if (name != NULL && strlen(name) != 0) {
2367 purple_blist_alias_buddy(b, name);
2369 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2373 if (!buddy) {
2374 buddy = g_new0(struct sipe_buddy, 1);
2375 buddy->name = g_strdup(b->name);
2376 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2379 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2381 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2382 } else {
2383 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2384 name);
2387 i++;
2388 } // while, contact groups
2389 g_strfreev(item_groups);
2390 g_free(buddy_name);
2392 } // for, contacts
2394 sipe_cleanup_local_blist(sip);
2396 xmlnode_free(isc);
2398 //subscribe to buddies
2399 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2400 if(sip->batched_support){
2401 sipe_subscribe_presence_batched(sip, NULL);
2403 else{
2404 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2406 sip->subscribed_buddies = TRUE;
2408 /* for 2005 systems schedule contacts' status update
2409 * based on their calendar information
2411 if (!sip->ocs2007) {
2412 //sipe_sched_calendar_status_update(sip, time(NULL));
2415 return 0;
2419 * Subscribe roaming contacts
2421 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2423 gchar *to = sip_uri_self(sip);
2424 gchar *tmp = get_contact(sip);
2425 gchar *hdr = g_strdup_printf(
2426 "Event: vnd-microsoft-roaming-contacts\r\n"
2427 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2428 "Supported: com.microsoft.autoextend\r\n"
2429 "Supported: ms-benotify\r\n"
2430 "Proxy-Require: ms-benotify\r\n"
2431 "Supported: ms-piggyback-first-notify\r\n"
2432 "Contact: %s\r\n", tmp);
2433 g_free(tmp);
2435 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2436 g_free(to);
2437 g_free(hdr);
2440 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2441 SIPE_UNUSED_PARAMETER void *unused)
2443 gchar *key;
2444 struct sip_dialog *dialog;
2445 gchar *to = sip_uri_self(sip);
2446 gchar *tmp = get_contact(sip);
2447 gchar *hdr = g_strdup_printf(
2448 "Event: presence.wpending\r\n"
2449 "Accept: text/xml+msrtc.wpending\r\n"
2450 "Supported: com.microsoft.autoextend\r\n"
2451 "Supported: ms-benotify\r\n"
2452 "Proxy-Require: ms-benotify\r\n"
2453 "Supported: ms-piggyback-first-notify\r\n"
2454 "Contact: %s\r\n", tmp);
2455 g_free(tmp);
2457 /* Subscription is identified by <event> key */
2458 key = g_strdup_printf("<%s>", "presence.wpending");
2459 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2460 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2462 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2464 g_free(to);
2465 g_free(hdr);
2466 g_free(key);
2470 * Fires on deregistration event initiated by server.
2471 * [MS-SIPREGE] SIP extension.
2474 // 2007 Example
2476 // Content-Type: text/registration-event
2477 // subscription-state: terminated;expires=0
2478 // ms-diagnostics-public: 4141;reason="User disabled"
2480 // deregistered;event=rejected
2482 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2484 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2485 gchar *event = NULL;
2486 gchar *reason = NULL;
2487 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2489 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2490 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2492 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2493 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2494 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2495 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2496 } else {
2497 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2498 return;
2501 if (warning != NULL) {
2502 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2503 } else { // for LCS2005
2504 int error_id = 0;
2505 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2506 error_id = 4140; // [MS-SIPREGE]
2507 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2508 reason = g_strdup(_("you are already signed in at another location"));
2509 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2510 error_id = 4141;
2511 reason = g_strdup(_("user disabled")); // [MS-OCER]
2512 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2513 error_id = 4142;
2514 reason = g_strdup(_("user moved")); // [MS-OCER]
2517 g_free(event);
2518 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2519 g_free(reason);
2521 sip->gc->wants_to_die = TRUE;
2522 purple_connection_error(sip->gc, warning);
2523 g_free(warning);
2527 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2529 xmlnode *xn_provision_group_list;
2530 xmlnode *node;
2532 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2534 /* provisionGroup */
2535 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2536 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2537 g_free(sip->focus_factory_uri);
2538 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2539 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2540 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2541 break;
2544 xmlnode_free(xn_provision_group_list);
2547 /** for 2005 system */
2548 static void
2549 sipe_process_provisioning(struct sipe_account_data *sip,
2550 struct sipmsg *msg)
2552 xmlnode *xn_provision;
2553 xmlnode *node;
2555 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2556 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2557 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2558 if ((node = xmlnode_get_child(node, "line"))) {
2559 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2560 const gchar *server = xmlnode_get_attrib(node, "server");
2561 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2562 sip_csta_open(sip, line_uri, server);
2565 xmlnode_free(xn_provision);
2568 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2570 const gchar *contacts_delta;
2571 xmlnode *xml;
2573 xml = xmlnode_from_str(msg->body, msg->bodylen);
2574 if (!xml)
2576 return;
2579 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2580 if (contacts_delta)
2582 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2585 xmlnode_free(xml);
2588 static void
2589 free_container(struct sipe_container *container)
2591 GSList *entry;
2593 if (!container) return;
2595 entry = container->members;
2596 while (entry) {
2597 g_free(entry->data);
2598 entry = g_slist_remove(entry, entry->data);
2600 g_free(container);
2604 * Finds locally stored MS-PRES container member
2606 static struct sipe_container_member *
2607 sipe_find_container_member(struct sipe_container *container,
2608 const gchar *type,
2609 const gchar *value)
2611 struct sipe_container_member *member;
2612 GSList *entry;
2614 if (container == NULL || type == NULL) {
2615 return NULL;
2618 entry = container->members;
2619 while (entry) {
2620 member = entry->data;
2621 if (!g_strcasecmp(member->type, type)
2622 && ((!member->value && !value)
2623 || (value && member->value && !g_strcasecmp(member->value, value)))
2625 return member;
2627 entry = entry->next;
2629 return NULL;
2633 * Finds locally stored MS-PRES container by id
2635 static struct sipe_container *
2636 sipe_find_container(struct sipe_account_data *sip,
2637 guint id)
2639 struct sipe_container *container;
2640 GSList *entry;
2642 if (sip == NULL) {
2643 return NULL;
2646 entry = sip->containers;
2647 while (entry) {
2648 container = entry->data;
2649 if (id == container->id) {
2650 return container;
2652 entry = entry->next;
2654 return NULL;
2658 * Access Levels
2659 * 32000 - Blocked
2660 * 400 - Personal
2661 * 300 - Team
2662 * 200 - Company
2663 * 100 - Public
2665 static int
2666 sipe_find_access_level(struct sipe_account_data *sip,
2667 const gchar *type,
2668 const gchar *value)
2670 guint containers[] = {32000, 400, 300, 200, 100};
2671 int i = 0;
2673 for (i = 0; i < 5; i++) {
2674 struct sipe_container_member *member;
2675 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2676 if (!container) continue;
2678 member = sipe_find_container_member(container, type, value);
2679 if (member) {
2680 return containers[i];
2684 return -1;
2687 static void
2688 sipe_send_set_container_members(struct sipe_account_data *sip,
2689 guint container_id,
2690 guint container_version,
2691 const gchar* action,
2692 const gchar* type,
2693 const gchar* value)
2695 gchar *self = sip_uri_self(sip);
2696 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2697 gchar *contact;
2698 gchar *hdr;
2699 gchar *body = g_strdup_printf(
2700 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2701 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2702 "</setContainerMembers>",
2703 container_id,
2704 container_version,
2705 action,
2706 type,
2707 value_str);
2708 g_free(value_str);
2710 contact = get_contact(sip);
2711 hdr = g_strdup_printf("Contact: %s\r\n"
2712 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2713 g_free(contact);
2715 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2717 g_free(hdr);
2718 g_free(body);
2719 g_free(self);
2722 static void
2723 free_publication(struct sipe_publication *publication)
2725 g_free(publication->category);
2726 g_free(publication->cal_event_hash);
2727 g_free(publication->note);
2729 g_free(publication->working_hours_xml_str);
2730 g_free(publication->fb_start_str);
2731 g_free(publication->free_busy_base64);
2733 g_free(publication);
2736 /* key is <category><instance><container> */
2737 static gboolean
2738 sipe_is_our_publication(struct sipe_account_data *sip,
2739 const gchar *key)
2741 GSList *entry;
2743 /* filling keys for our publications if not yet cached */
2744 if (!sip->our_publication_keys) {
2745 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2746 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2747 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2748 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2749 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2750 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2751 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2753 /* device */
2754 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2755 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2757 /* state:machineState */
2758 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2759 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2760 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2761 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2763 /* state:userState */
2764 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2765 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2766 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2767 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2769 /* state:calendarState */
2770 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2771 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2772 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2773 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2775 /* state:calendarState OOF */
2776 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2777 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2778 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2779 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2781 /* note */
2782 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2783 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2784 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2785 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2786 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2787 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2789 /* note OOF */
2790 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2791 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2792 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2793 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2794 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2795 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2797 /* calendarData:WorkingHours */
2798 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2799 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2800 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2801 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2802 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2803 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2804 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2805 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2806 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2807 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2808 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2809 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2811 /* calendarData:FreeBusy */
2812 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2813 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2814 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2815 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2816 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2817 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2818 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2819 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2820 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2821 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2822 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2823 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2825 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2826 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2829 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
2831 entry = sip->our_publication_keys;
2832 while (entry) {
2833 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
2834 if (!strcmp(entry->data, key)) {
2835 return TRUE;
2837 entry = entry->next;
2839 return FALSE;
2842 /** Property names to store in blist.xml */
2843 #define ALIAS_PROP "alias"
2844 #define EMAIL_PROP "email"
2845 #define PHONE_PROP "phone"
2846 #define PHONE_DISPLAY_PROP "phone-display"
2847 #define PHONE_MOBILE_PROP "phone-mobile"
2848 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2849 #define PHONE_HOME_PROP "phone-home"
2850 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2851 #define PHONE_OTHER_PROP "phone-other"
2852 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2853 #define PHONE_CUSTOM1_PROP "phone-custom1"
2854 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2855 #define SITE_PROP "site"
2856 #define COMPANY_PROP "company"
2857 #define DEPARTMENT_PROP "department"
2858 #define TITLE_PROP "title"
2859 #define OFFICE_PROP "office"
2860 /** implies work address */
2861 #define ADDRESS_STREET_PROP "address-street"
2862 #define ADDRESS_CITY_PROP "address-city"
2863 #define ADDRESS_STATE_PROP "address-state"
2864 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2865 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
2867 * Update user information
2869 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2870 * @param property_name
2871 * @param property_value may be modified to strip white space
2873 static void
2874 sipe_update_user_info(struct sipe_account_data *sip,
2875 const char *uri,
2876 const char *property_name,
2877 char *property_value)
2879 GSList *buddies, *entry;
2881 if (!property_name || strlen(property_name) == 0) return;
2883 if (property_value)
2884 property_value = g_strstrip(property_value);
2886 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
2887 while (entry) {
2888 const char *prop_str;
2889 const char *server_alias;
2890 PurpleBuddy *p_buddy = entry->data;
2892 /* for Display Name */
2893 if (!strcmp(property_name, ALIAS_PROP)) {
2894 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
2895 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
2896 purple_blist_alias_buddy(p_buddy, property_value);
2899 server_alias = purple_buddy_get_server_alias(p_buddy);
2900 if (property_value && strlen(property_value) > 0 &&
2901 ( (server_alias && strcmp(property_value, server_alias))
2902 || !server_alias || strlen(server_alias) == 0 )
2904 purple_blist_server_alias_buddy(p_buddy, property_value);
2907 /* for other properties */
2908 else {
2909 if (property_value && strlen(property_value) > 0) {
2910 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
2911 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
2912 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
2917 entry = entry->next;
2919 g_slist_free(buddies);
2923 * Update user phone
2924 * Suitable for both 2005 and 2007 systems.
2926 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2927 * @param phone_type
2928 * @param phone may be modified to strip white space
2929 * @param phone_display_string may be modified to strip white space
2931 static void
2932 sipe_update_user_phone(struct sipe_account_data *sip,
2933 const char *uri,
2934 const gchar *phone_type,
2935 gchar *phone,
2936 gchar *phone_display_string)
2938 const char *phone_node = PHONE_PROP; /* work phone by default */
2939 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
2941 if(!phone || strlen(phone) == 0) return;
2943 if (phone_type && (!strcmp(phone_type, "mobile") || !strcmp(phone_type, "cell"))) {
2944 phone_node = PHONE_MOBILE_PROP;
2945 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
2946 } else if (phone_type && !strcmp(phone_type, "home")) {
2947 phone_node = PHONE_HOME_PROP;
2948 phone_display_node = PHONE_HOME_DISPLAY_PROP;
2949 } else if (phone_type && !strcmp(phone_type, "other")) {
2950 phone_node = PHONE_OTHER_PROP;
2951 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
2952 } else if (phone_type && !strcmp(phone_type, "custom1")) {
2953 phone_node = PHONE_CUSTOM1_PROP;
2954 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
2957 sipe_update_user_info(sip, uri, phone_node, phone);
2958 if (phone_display_string) {
2959 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
2963 static void
2964 sipe_update_calendar(struct sipe_account_data *sip)
2966 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
2968 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
2970 if (!strcmp(calendar, "EXCH")) {
2971 sipe_ews_update_calendar(sip);
2974 /* schedule repeat */
2975 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
2977 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
2980 static void
2981 send_publish_category_initial(struct sipe_account_data *sip);
2984 * When we receive some self (BE) NOTIFY with a new subscriber
2985 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2988 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
2990 gchar *contact;
2991 gchar *to;
2992 xmlnode *xml;
2993 xmlnode *node;
2994 xmlnode *node2;
2995 char *display_name = NULL;
2996 char *uri;
2997 GSList *category_names = NULL;
2999 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3001 xml = xmlnode_from_str(msg->body, msg->bodylen);
3002 if (!xml) return;
3004 contact = get_contact(sip);
3005 to = sip_uri_self(sip);
3008 /* categories */
3009 /* set list of categories participating in this XML */
3010 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3011 const gchar *name = xmlnode_get_attrib(node, "name");
3012 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3014 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3015 category_names ? (int) g_slist_length(category_names) : -1);
3016 /* drop category information */
3017 if (category_names) {
3018 GSList *entry = category_names;
3019 while (entry) {
3020 GHashTable *cat_publications;
3021 const gchar *category = entry->data;
3022 entry = entry->next;
3023 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3024 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3025 if (cat_publications) {
3026 g_hash_table_remove(sip->our_publications, category);
3027 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3031 g_slist_free(category_names);
3032 /* filling our categories reflected in roaming data */
3033 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3034 const gchar *name = xmlnode_get_attrib(node, "name");
3035 const gchar *container = xmlnode_get_attrib(node, "container");
3036 const gchar *instance = xmlnode_get_attrib(node, "instance");
3037 const gchar *version = xmlnode_get_attrib(node, "version");
3038 guint version_int = version ? atoi(version) : 0;
3039 gchar *key;
3041 if (!container || !instance) continue;
3043 /* key is <category><instance><container> */
3044 key = g_strdup_printf("<%s><%s><%s>", name, instance, container);
3045 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version_int);
3046 if (sipe_is_our_publication(sip, key)) {
3047 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3049 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3050 publication->category = g_strdup(name);
3051 publication->instance = atoi(instance);
3052 publication->container = atoi(container);
3053 publication->version = version_int;
3054 /* filling publication->availability */
3055 if (!strcmp(name, "state")) {
3056 xmlnode *xn_state = xmlnode_get_child(node, "state");
3057 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3059 if (xn_avail) {
3060 gchar *avail_str = xmlnode_get_data(xn_avail);
3061 if (avail_str) {
3062 publication->availability = atoi(avail_str);
3064 g_free(avail_str);
3066 /* for calendarState */
3067 if (xn_state && !strcmp(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3068 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3069 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3071 event->start_time = purple_str_to_time(xmlnode_get_attrib(xn_state, "startTime"),
3072 FALSE, NULL, NULL, NULL);
3073 if (xn_activity) {
3074 if (!strcmp(xmlnode_get_attrib(xn_activity, "token"), "in-a-meeting")) {
3075 event->is_meeting = TRUE;
3078 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3079 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3081 publication->cal_event_hash = sipe_cal_event_hash(event);
3082 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3083 publication->cal_event_hash);
3084 sipe_cal_event_free(event);
3087 /* filling publication->note */
3088 if (!strcmp(name, "note")) {
3089 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3090 if (xn_body) {
3091 publication->note = xmlnode_get_data(xn_body);
3095 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3096 if (!strcmp(name, "calendarData") && (publication->container == 300)) {
3097 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3098 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3099 if (xn_free_busy) {
3100 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3101 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3103 if (xn_working_hours) {
3104 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3108 if (!cat_publications) {
3109 cat_publications = g_hash_table_new_full(
3110 g_str_hash, g_str_equal,
3111 g_free, (GDestroyNotify)free_publication);
3112 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3113 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3115 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3116 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version_int);
3118 g_free(key);
3120 /* userProperties published by server from AD */
3121 if (!sip->csta && !strcmp(name, "userProperties")) {
3122 xmlnode *line;
3123 /* line, for Remote Call Control (RCC) */
3124 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3125 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3126 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3127 gchar *line_uri;
3129 if (!line_server || (strcmp(line_type, "Rcc") && strcmp(line_type, "Dual"))) continue;
3131 line_uri = xmlnode_get_data(line);
3132 if (line_uri) {
3133 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3134 sip_csta_open(sip, line_uri, line_server);
3136 g_free(line_uri);
3138 break;
3142 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3143 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3145 /* containers */
3146 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3147 guint id = atoi(xmlnode_get_attrib(node, "id"));
3148 struct sipe_container *container = sipe_find_container(sip, id);
3150 if (container) {
3151 sip->containers = g_slist_remove(sip->containers, container);
3152 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3153 free_container(container);
3155 container = g_new0(struct sipe_container, 1);
3156 container->id = id;
3157 container->version = atoi(xmlnode_get_attrib(node, "version"));
3158 sip->containers = g_slist_append(sip->containers, container);
3159 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3161 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3162 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3163 member->type = xmlnode_get_attrib(node2, "type");
3164 member->value = xmlnode_get_attrib(node2, "value");
3165 container->members = g_slist_append(container->members, member);
3166 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3167 member->type, member->value ? member->value : "");
3171 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3172 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3173 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3174 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3175 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3176 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3177 /* initial set-up to let counterparties see your status */
3178 if (sameEnterpriseAL < 0) {
3179 struct sipe_container *container = sipe_find_container(sip, 200);
3180 guint version = container ? container->version : 0;
3181 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3183 if (federatedAL < 0) {
3184 struct sipe_container *container = sipe_find_container(sip, 100);
3185 guint version = container ? container->version : 0;
3186 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3188 sip->access_level_set = TRUE;
3191 /* subscribers */
3192 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3193 const char *user;
3194 const char *acknowledged;
3195 gchar *hdr;
3196 gchar *body;
3198 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3199 if (!user) continue;
3200 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3201 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3202 uri = sip_uri_from_name(user);
3204 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3206 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3207 if(!g_ascii_strcasecmp(acknowledged,"false")){
3208 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3209 if (!purple_find_buddy(sip->account, uri)) {
3210 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3213 hdr = g_strdup_printf(
3214 "Contact: %s\r\n"
3215 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3217 body = g_strdup_printf(
3218 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3219 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3220 "</setSubscribers>", user);
3222 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3223 g_free(body);
3224 g_free(hdr);
3226 g_free(display_name);
3227 g_free(uri);
3230 g_free(to);
3231 g_free(contact);
3232 xmlnode_free(xml);
3234 /* Publish initial state if not yet.
3235 * Assuming this happens on initial responce to subscription to roaming-self
3236 * so we've already updated our roaming data in full.
3237 * Only for 2007+
3239 if (sip->ocs2007 && !sip->initial_state_published) {
3240 send_publish_category_initial(sip);
3241 sip->initial_state_published = TRUE;
3242 /* dalayed run */
3243 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3247 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3249 gchar *to = sip_uri_self(sip);
3250 gchar *tmp = get_contact(sip);
3251 gchar *hdr = g_strdup_printf(
3252 "Event: vnd-microsoft-roaming-ACL\r\n"
3253 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3254 "Supported: com.microsoft.autoextend\r\n"
3255 "Supported: ms-benotify\r\n"
3256 "Proxy-Require: ms-benotify\r\n"
3257 "Supported: ms-piggyback-first-notify\r\n"
3258 "Contact: %s\r\n", tmp);
3259 g_free(tmp);
3261 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3262 g_free(to);
3263 g_free(hdr);
3267 * To request for presence information about the user, access level settings that have already been configured by the user
3268 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3269 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3272 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3274 gchar *to = sip_uri_self(sip);
3275 gchar *tmp = get_contact(sip);
3276 gchar *hdr = g_strdup_printf(
3277 "Event: vnd-microsoft-roaming-self\r\n"
3278 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3279 "Supported: ms-benotify\r\n"
3280 "Proxy-Require: ms-benotify\r\n"
3281 "Supported: ms-piggyback-first-notify\r\n"
3282 "Contact: %s\r\n"
3283 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3285 gchar *body=g_strdup(
3286 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3287 "<roaming type=\"categories\"/>"
3288 "<roaming type=\"containers\"/>"
3289 "<roaming type=\"subscribers\"/></roamingList>");
3291 g_free(tmp);
3292 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3293 g_free(body);
3294 g_free(to);
3295 g_free(hdr);
3299 * For 2005 version
3301 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3303 gchar *to = sip_uri_self(sip);
3304 gchar *tmp = get_contact(sip);
3305 gchar *hdr = g_strdup_printf(
3306 "Event: vnd-microsoft-provisioning\r\n"
3307 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3308 "Supported: com.microsoft.autoextend\r\n"
3309 "Supported: ms-benotify\r\n"
3310 "Proxy-Require: ms-benotify\r\n"
3311 "Supported: ms-piggyback-first-notify\r\n"
3312 "Expires: 0\r\n"
3313 "Contact: %s\r\n", tmp);
3315 g_free(tmp);
3316 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3317 g_free(to);
3318 g_free(hdr);
3321 /** Subscription for provisioning information to help with initial
3322 * configuration. This subscription is a one-time query (denoted by the Expires header,
3323 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3324 * configuration, meeting policies, and policy settings that Communicator must enforce.
3325 * TODO: for what we need this information.
3328 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3330 gchar *to = sip_uri_self(sip);
3331 gchar *tmp = get_contact(sip);
3332 gchar *hdr = g_strdup_printf(
3333 "Event: vnd-microsoft-provisioning-v2\r\n"
3334 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3335 "Supported: com.microsoft.autoextend\r\n"
3336 "Supported: ms-benotify\r\n"
3337 "Proxy-Require: ms-benotify\r\n"
3338 "Supported: ms-piggyback-first-notify\r\n"
3339 "Expires: 0\r\n"
3340 "Contact: %s\r\n"
3341 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3342 gchar *body = g_strdup(
3343 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3344 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3345 "<provisioningGroup name=\"ucPolicy\"/>"
3346 "</provisioningGroupList>");
3348 g_free(tmp);
3349 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3350 g_free(body);
3351 g_free(to);
3352 g_free(hdr);
3355 static void
3356 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3357 gpointer value, gpointer user_data)
3359 struct sip_subscription *subscription = value;
3360 struct sip_dialog *dialog = &subscription->dialog;
3361 struct sipe_account_data *sip = user_data;
3362 gchar *tmp = get_contact(sip);
3363 gchar *hdr = g_strdup_printf(
3364 "Event: %s\r\n"
3365 "Expires: 0\r\n"
3366 "Contact: %s\r\n", subscription->event, tmp);
3367 g_free(tmp);
3369 /* Rate limit to max. 25 requests per seconds */
3370 g_usleep(1000000 / 25);
3372 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3373 g_free(hdr);
3376 /* IM Session (INVITE and MESSAGE methods) */
3378 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3379 static gchar *
3380 get_end_points (struct sipe_account_data *sip,
3381 struct sip_session *session)
3383 gchar *res;
3385 if (session == NULL) {
3386 return NULL;
3389 res = g_strdup_printf("<sip:%s>", sip->username);
3391 SIPE_DIALOG_FOREACH {
3392 gchar *tmp = res;
3393 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3394 g_free(tmp);
3396 if (dialog->theirepid) {
3397 tmp = res;
3398 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3399 g_free(tmp);
3401 } SIPE_DIALOG_FOREACH_END;
3403 return res;
3406 static gboolean
3407 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3408 struct sipmsg *msg,
3409 SIPE_UNUSED_PARAMETER struct transaction *trans)
3411 gboolean ret = TRUE;
3413 if (msg->response != 200) {
3414 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3415 return FALSE;
3418 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3420 return ret;
3424 * Asks UA/proxy about its capabilities.
3426 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3428 gchar *to = sip_uri(who);
3429 gchar *contact = get_contact(sip);
3430 gchar *request = g_strdup_printf(
3431 "Accept: application/sdp\r\n"
3432 "Contact: %s\r\n", contact);
3433 g_free(contact);
3435 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3437 g_free(to);
3438 g_free(request);
3441 static void
3442 sipe_notify_user(struct sipe_account_data *sip,
3443 struct sip_session *session,
3444 PurpleMessageFlags flags,
3445 const gchar *message)
3447 PurpleConversation *conv;
3449 if (!session->conv) {
3450 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3451 } else {
3452 conv = session->conv;
3454 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3457 void
3458 sipe_present_info(struct sipe_account_data *sip,
3459 struct sip_session *session,
3460 const gchar *message)
3462 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3465 static void
3466 sipe_present_err(struct sipe_account_data *sip,
3467 struct sip_session *session,
3468 const gchar *message)
3470 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3473 void
3474 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3475 struct sip_session *session,
3476 int sip_error,
3477 const gchar *who,
3478 const gchar *message)
3480 char *msg, *msg_tmp, *msg_tmp2;
3481 const char *label;
3483 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
3484 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3485 g_free(msg_tmp);
3486 /* Service unavailable; Server Internal Error; Server Time-out */
3487 if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3488 label = _("This message was not delivered to %s because the service is not available");
3489 } else if (sip_error == 486) { /* Busy Here */
3490 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3491 } else {
3492 label = _("This message was not delivered to %s because one or more recipients are offline");
3495 msg_tmp = g_strdup_printf( "%s:\n%s" ,
3496 msg_tmp2 = g_strdup_printf(label, who ? who : ""), msg ? msg : "");
3497 sipe_present_err(sip, session, msg_tmp);
3498 g_free(msg_tmp2);
3499 g_free(msg_tmp);
3500 g_free(msg);
3504 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
3506 static gboolean
3507 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
3508 SIPE_UNUSED_PARAMETER struct transaction *trans)
3510 gboolean ret = TRUE;
3511 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3512 struct sip_session *session = sipe_session_find_im(sip, with);
3513 struct sip_dialog *dialog;
3514 gchar *cseq;
3515 char *key;
3516 gchar *message;
3518 if (!session) {
3519 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3520 g_free(with);
3521 return FALSE;
3524 dialog = sipe_dialog_find(session, with);
3525 if (!dialog) {
3526 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3527 g_free(with);
3528 return FALSE;
3531 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3532 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3533 g_free(cseq);
3534 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3536 if (msg->response >= 400) {
3537 PurpleBuddy *pbuddy;
3538 gchar *alias = with;
3540 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3542 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3543 alias = (gchar *)purple_buddy_get_alias(pbuddy);
3546 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
3547 ret = FALSE;
3548 } else {
3549 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3550 if (message_id) {
3551 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
3552 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
3553 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3556 g_hash_table_remove(session->unconfirmed_messages, key);
3557 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
3558 key, g_hash_table_size(session->unconfirmed_messages));
3561 g_free(key);
3562 g_free(with);
3564 if (ret) sipe_im_process_queue(sip, session);
3565 return ret;
3568 static gboolean
3569 sipe_is_election_finished(struct sip_session *session);
3571 static void
3572 sipe_election_result(struct sipe_account_data *sip,
3573 void *sess);
3575 static gboolean
3576 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
3577 SIPE_UNUSED_PARAMETER struct transaction *trans)
3579 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3580 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3581 struct sip_dialog *dialog;
3582 struct sip_session *session;
3584 session = sipe_session_find_chat_by_callid(sip, callid);
3585 if (!session) {
3586 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
3587 return FALSE;
3590 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
3591 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3592 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
3593 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
3595 if (xn_request_rm_response) {
3596 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
3597 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
3599 dialog = sipe_dialog_find(session, with);
3600 if (!dialog) {
3601 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
3602 return FALSE;
3605 if (allow && !g_strcasecmp(allow, "true")) {
3606 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
3607 dialog->election_vote = 1;
3608 } else if (allow && !g_strcasecmp(allow, "false")) {
3609 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
3610 dialog->election_vote = -1;
3613 if (sipe_is_election_finished(session)) {
3614 sipe_election_result(sip, session);
3617 } else if (xn_set_rm_response) {
3620 xmlnode_free(xn_action);
3624 return TRUE;
3627 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
3629 gchar *hdr;
3630 gchar *tmp;
3631 char *msgformat;
3632 char *msgtext;
3633 gchar *msgr_value;
3634 gchar *msgr;
3636 sipe_parse_html(msg, &msgformat, &msgtext);
3637 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
3639 msgr_value = sipmsg_get_msgr_string(msgformat);
3640 g_free(msgformat);
3641 if (msgr_value) {
3642 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3643 g_free(msgr_value);
3644 } else {
3645 msgr = g_strdup("");
3648 tmp = get_contact(sip);
3649 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3650 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3651 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3652 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
3653 g_free(tmp);
3654 g_free(msgr);
3656 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
3657 g_free(msgtext);
3658 g_free(hdr);
3662 static void
3663 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
3665 GSList *entry2 = session->outgoing_message_queue;
3666 while (entry2) {
3667 char *queued_msg = entry2->data;
3669 /* for multiparty chat or conference */
3670 if (session->is_multiparty || session->focus_uri) {
3671 gchar *who = sip_uri_self(sip);
3672 serv_got_chat_in(sip->gc, session->chat_id, who,
3673 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
3674 g_free(who);
3677 SIPE_DIALOG_FOREACH {
3678 char *key;
3680 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3682 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3683 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
3684 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
3685 key, g_hash_table_size(session->unconfirmed_messages));
3686 g_free(key);
3688 sipe_send_message(sip, dialog, queued_msg);
3689 } SIPE_DIALOG_FOREACH_END;
3691 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3692 g_free(queued_msg);
3696 static void
3697 sipe_refer_notify(struct sipe_account_data *sip,
3698 struct sip_session *session,
3699 const gchar *who,
3700 int status,
3701 const gchar *desc)
3703 gchar *hdr;
3704 gchar *body;
3705 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3707 hdr = g_strdup_printf(
3708 "Event: refer\r\n"
3709 "Subscription-State: %s\r\n"
3710 "Content-Type: message/sipfrag\r\n",
3711 status >= 200 ? "terminated" : "active");
3713 body = g_strdup_printf(
3714 "SIP/2.0 %d %s\r\n",
3715 status, desc);
3717 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
3719 g_free(hdr);
3720 g_free(body);
3723 static gboolean
3724 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
3726 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3727 struct sip_session *session;
3728 struct sip_dialog *dialog;
3729 char *cseq;
3730 char *key;
3731 gchar *message;
3732 struct sipmsg *request_msg = trans->msg;
3734 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3735 gchar *referred_by;
3737 session = sipe_session_find_chat_by_callid(sip, callid);
3738 if (!session) {
3739 session = sipe_session_find_im(sip, with);
3741 if (!session) {
3742 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
3743 g_free(with);
3744 return FALSE;
3747 dialog = sipe_dialog_find(session, with);
3748 if (!dialog) {
3749 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
3750 g_free(with);
3751 return FALSE;
3754 sipe_dialog_parse(dialog, msg, TRUE);
3756 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3757 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3758 g_free(cseq);
3759 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3761 if (msg->response != 200) {
3762 PurpleBuddy *pbuddy;
3763 gchar *alias = with;
3765 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
3767 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3768 alias = (gchar *)purple_buddy_get_alias(pbuddy);
3771 if (message) {
3772 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
3773 } else {
3774 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3775 sipe_present_err(sip, session, tmp_msg);
3776 g_free(tmp_msg);
3779 sipe_dialog_remove(session, with);
3781 g_free(key);
3782 g_free(with);
3783 return FALSE;
3786 dialog->cseq = 0;
3787 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3788 dialog->outgoing_invite = NULL;
3789 dialog->is_established = TRUE;
3791 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3792 if (referred_by) {
3793 sipe_refer_notify(sip, session, referred_by, 200, "OK");
3794 g_free(referred_by);
3797 /* add user to chat if it is a multiparty session */
3798 if (session->is_multiparty) {
3799 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3800 with, NULL,
3801 PURPLE_CBFLAGS_NONE, TRUE);
3804 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3805 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
3806 if (session->outgoing_message_queue) {
3807 char *queued_msg = session->outgoing_message_queue->data;
3808 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3809 g_free(queued_msg);
3813 sipe_im_process_queue(sip, session);
3815 g_hash_table_remove(session->unconfirmed_messages, key);
3816 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
3817 key, g_hash_table_size(session->unconfirmed_messages));
3819 g_free(key);
3820 g_free(with);
3821 return TRUE;
3825 void
3826 sipe_invite(struct sipe_account_data *sip,
3827 struct sip_session *session,
3828 const gchar *who,
3829 const gchar *msg_body,
3830 const gchar *referred_by,
3831 const gboolean is_triggered)
3833 gchar *hdr;
3834 gchar *to;
3835 gchar *contact;
3836 gchar *body;
3837 gchar *self;
3838 char *ms_text_format = NULL;
3839 gchar *roster_manager;
3840 gchar *end_points;
3841 gchar *referred_by_str;
3842 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3844 if (dialog && dialog->is_established) {
3845 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
3846 return;
3849 if (!dialog) {
3850 dialog = sipe_dialog_add(session);
3851 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3852 dialog->with = g_strdup(who);
3855 if (!(dialog->ourtag)) {
3856 dialog->ourtag = gentag();
3859 to = sip_uri(who);
3861 if (msg_body) {
3862 char *msgformat;
3863 char *msgtext;
3864 char *base64_msg;
3865 gchar *msgr_value;
3866 gchar *msgr;
3867 char *key;
3869 sipe_parse_html(msg_body, &msgformat, &msgtext);
3870 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
3872 msgr_value = sipmsg_get_msgr_string(msgformat);
3873 g_free(msgformat);
3874 msgr = "";
3875 if (msgr_value) {
3876 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3877 g_free(msgr_value);
3880 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
3881 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
3882 g_free(msgtext);
3883 g_free(msgr);
3884 g_free(base64_msg);
3886 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3887 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
3888 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
3889 key, g_hash_table_size(session->unconfirmed_messages));
3890 g_free(key);
3893 contact = get_contact(sip);
3894 end_points = get_end_points(sip, session);
3895 self = sip_uri_self(sip);
3896 roster_manager = g_strdup_printf(
3897 "Roster-Manager: %s\r\n"
3898 "EndPoints: %s\r\n",
3899 self,
3900 end_points);
3901 referred_by_str = referred_by ?
3902 g_strdup_printf(
3903 "Referred-By: %s\r\n",
3904 referred_by)
3905 : g_strdup("");
3906 hdr = g_strdup_printf(
3907 "Supported: ms-sender\r\n"
3908 "%s"
3909 "%s"
3910 "%s"
3911 "%s"
3912 "Contact: %s\r\n%s"
3913 "Content-Type: application/sdp\r\n",
3914 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3915 referred_by_str,
3916 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3917 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3918 contact,
3919 ms_text_format ? ms_text_format : "");
3920 g_free(ms_text_format);
3921 g_free(self);
3923 body = g_strdup_printf(
3924 "v=0\r\n"
3925 "o=- 0 0 IN IP4 %s\r\n"
3926 "s=session\r\n"
3927 "c=IN IP4 %s\r\n"
3928 "t=0 0\r\n"
3929 "m=%s %d sip null\r\n"
3930 "a=accept-types:text/plain text/html image/gif "
3931 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3932 purple_network_get_my_ip(-1),
3933 purple_network_get_my_ip(-1),
3934 sip->ocs2007 ? "message" : "x-ms-message",
3935 sip->realport);
3937 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3938 to, to, hdr, body, dialog, process_invite_response);
3940 g_free(to);
3941 g_free(roster_manager);
3942 g_free(end_points);
3943 g_free(referred_by_str);
3944 g_free(body);
3945 g_free(hdr);
3946 g_free(contact);
3949 static void
3950 sipe_refer(struct sipe_account_data *sip,
3951 struct sip_session *session,
3952 const gchar *who)
3954 gchar *hdr;
3955 gchar *contact;
3956 struct sip_dialog *dialog = sipe_dialog_find(session,
3957 session->roster_manager);
3959 contact = get_contact(sip);
3960 hdr = g_strdup_printf(
3961 "Contact: %s\r\n"
3962 "Refer-to: <%s>\r\n"
3963 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3964 "Require: com.microsoft.rtc-multiparty\r\n",
3965 contact,
3966 who,
3967 sip->username,
3968 dialog->ourtag ? ";tag=" : "",
3969 dialog->ourtag ? dialog->ourtag : "",
3970 get_epid(sip));
3972 send_sip_request(sip->gc, "REFER",
3973 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3975 g_free(hdr);
3976 g_free(contact);
3979 static void
3980 sipe_send_election_request_rm(struct sipe_account_data *sip,
3981 struct sip_dialog *dialog,
3982 int bid)
3984 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3986 gchar *body = g_strdup_printf(
3987 "<?xml version=\"1.0\"?>\r\n"
3988 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3989 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3990 sip->username, bid);
3992 send_sip_request(sip->gc, "INFO",
3993 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3995 g_free(body);
3998 static void
3999 sipe_send_election_set_rm(struct sipe_account_data *sip,
4000 struct sip_dialog *dialog)
4002 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4004 gchar *body = g_strdup_printf(
4005 "<?xml version=\"1.0\"?>\r\n"
4006 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4007 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4008 sip->username);
4010 send_sip_request(sip->gc, "INFO",
4011 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4013 g_free(body);
4016 static void
4017 sipe_session_close(struct sipe_account_data *sip,
4018 struct sip_session * session)
4020 if (session && session->focus_uri) {
4021 sipe_conf_immcu_closed(sip, session);
4022 conf_session_close(sip, session);
4025 if (session) {
4026 SIPE_DIALOG_FOREACH {
4027 /* @TODO slow down BYE message sending rate */
4028 /* @see single subscription code */
4029 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4030 } SIPE_DIALOG_FOREACH_END;
4032 sipe_session_remove(sip, session);
4036 static void
4037 sipe_session_close_all(struct sipe_account_data *sip)
4039 GSList *entry;
4040 while ((entry = sip->sessions) != NULL) {
4041 sipe_session_close(sip, entry->data);
4045 static void
4046 sipe_convo_closed(PurpleConnection * gc, const char *who)
4048 struct sipe_account_data *sip = gc->proto_data;
4050 purple_debug_info("sipe", "conversation with %s closed\n", who);
4051 sipe_session_close(sip, sipe_session_find_im(sip, who));
4054 static void
4055 sipe_chat_leave (PurpleConnection *gc, int id)
4057 struct sipe_account_data *sip = gc->proto_data;
4058 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4060 sipe_session_close(sip, session);
4063 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4064 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4066 struct sipe_account_data *sip = gc->proto_data;
4067 struct sip_session *session;
4068 struct sip_dialog *dialog;
4069 gchar *uri = sip_uri(who);
4071 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4073 session = sipe_session_find_or_add_im(sip, uri);
4074 dialog = sipe_dialog_find(session, uri);
4076 // Queue the message
4077 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
4079 if (dialog && !dialog->outgoing_invite) {
4080 sipe_im_process_queue(sip, session);
4081 } else if (!dialog || !dialog->outgoing_invite) {
4082 // Need to send the INVITE to get the outgoing dialog setup
4083 sipe_invite(sip, session, uri, what, NULL, FALSE);
4086 g_free(uri);
4087 return 1;
4090 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4091 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4093 struct sipe_account_data *sip = gc->proto_data;
4094 struct sip_session *session;
4096 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4098 session = sipe_session_find_chat_by_id(sip, id);
4100 // Queue the message
4101 if (session && session->dialogs) {
4102 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4103 g_strdup(what));
4104 sipe_im_process_queue(sip, session);
4105 } else if (sip) {
4106 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4107 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4109 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4110 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4112 if (sip->ocs2007) {
4113 struct sip_session *session = sipe_session_add_chat(sip);
4115 session->is_multiparty = FALSE;
4116 session->focus_uri = g_strdup(proto_chat_id);
4117 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
4118 g_strdup(what));
4119 sipe_invite_conf_focus(sip, session);
4123 return 1;
4126 /* End IM Session (INVITE and MESSAGE methods) */
4128 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4130 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4131 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4132 gchar *from;
4133 struct sip_session *session;
4135 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4137 /* Call Control protocol */
4138 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4140 process_incoming_info_csta(sip, msg);
4141 return;
4144 from = parse_from(sipmsg_find_header(msg, "From"));
4145 session = sipe_session_find_chat_by_callid(sip, callid);
4146 if (!session) {
4147 session = sipe_session_find_im(sip, from);
4149 if (!session) {
4150 g_free(from);
4151 return;
4154 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4156 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4157 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4158 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4160 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4162 if (xn_request_rm) {
4163 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4164 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
4165 gchar *body = g_strdup_printf(
4166 "<?xml version=\"1.0\"?>\r\n"
4167 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4168 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4169 sip->username,
4170 session->bid < bid ? "true" : "false");
4171 send_sip_response(sip->gc, msg, 200, "OK", body);
4172 g_free(body);
4173 } else if (xn_set_rm) {
4174 gchar *body;
4175 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4176 g_free(session->roster_manager);
4177 session->roster_manager = g_strdup(rm);
4179 body = g_strdup_printf(
4180 "<?xml version=\"1.0\"?>\r\n"
4181 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4182 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4183 sip->username);
4184 send_sip_response(sip->gc, msg, 200, "OK", body);
4185 g_free(body);
4187 xmlnode_free(xn_action);
4190 else
4192 /* looks like purple lacks typing notification for chat */
4193 if (!session->is_multiparty && !session->focus_uri) {
4194 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4195 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4196 "status");
4197 if (status && !strcmp(status, "type")) {
4198 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4199 } else if (status && !strcmp(status, "idle")) {
4200 serv_got_typing_stopped(sip->gc, from);
4202 xmlnode_free(xn_keyboard_activity);
4205 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4207 g_free(from);
4210 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4212 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4213 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4214 struct sip_session *session;
4215 struct sip_dialog *dialog;
4217 /* collect dialog identification
4218 * we need callid, ourtag and theirtag to unambiguously identify dialog
4220 /* take data before 'msg' will be modified by send_sip_response */
4221 dialog = g_new0(struct sip_dialog, 1);
4222 dialog->callid = g_strdup(callid);
4223 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4224 dialog->with = g_strdup(from);
4225 sipe_dialog_parse(dialog, msg, FALSE);
4227 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4229 session = sipe_session_find_chat_by_callid(sip, callid);
4230 if (!session) {
4231 session = sipe_session_find_im(sip, from);
4233 if (!session) {
4234 g_free(from);
4235 return;
4238 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4239 g_free(session->roster_manager);
4240 session->roster_manager = NULL;
4243 /* This what BYE is essentially for - terminating dialog */
4244 sipe_dialog_remove_3(session, dialog);
4245 sipe_dialog_free(dialog);
4246 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4247 sipe_conf_immcu_closed(sip, session);
4248 } else if (session->is_multiparty) {
4249 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4252 g_free(from);
4255 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4257 gchar *self = sip_uri_self(sip);
4258 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4259 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4260 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4261 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4262 struct sip_session *session;
4263 struct sip_dialog *dialog;
4265 session = sipe_session_find_chat_by_callid(sip, callid);
4266 dialog = sipe_dialog_find(session, from);
4268 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
4269 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4270 } else {
4271 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4273 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
4276 g_free(self);
4277 g_free(from);
4278 g_free(refer_to);
4279 g_free(referred_by);
4282 static unsigned int
4283 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4285 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4286 struct sip_session *session;
4287 struct sip_dialog *dialog;
4289 if (state == PURPLE_NOT_TYPING)
4290 return 0;
4292 session = sipe_session_find_im(sip, who);
4293 dialog = sipe_dialog_find(session, who);
4295 if (session && dialog && dialog->is_established) {
4296 send_sip_request(gc, "INFO", who, who,
4297 "Content-Type: application/xml\r\n",
4298 SIPE_SEND_TYPING, dialog, NULL);
4300 return SIPE_TYPING_SEND_TIMEOUT;
4303 static gboolean resend_timeout(struct sipe_account_data *sip)
4305 GSList *tmp = sip->transactions;
4306 time_t currtime = time(NULL);
4307 while (tmp) {
4308 struct transaction *trans = tmp->data;
4309 tmp = tmp->next;
4310 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4311 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4312 /* TODO 408 */
4313 } else {
4314 if ((currtime - trans->time > 2) && trans->retries == 0) {
4315 trans->retries++;
4316 sendout_sipmsg(sip, trans->msg);
4320 return TRUE;
4323 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4324 SIPE_UNUSED_PARAMETER void *unused)
4326 /* register again when security token expires */
4327 /* we have to start a new authentication as the security token
4328 * is almost expired by sending a not signed REGISTER message */
4329 purple_debug_info("sipe", "do a full reauthentication\n");
4330 sipe_auth_free(&sip->registrar);
4331 sipe_auth_free(&sip->proxy);
4332 sip->registerstatus = 0;
4333 do_register(sip);
4334 sip->reauthenticate_set = FALSE;
4337 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4339 gchar *from;
4340 gchar *contenttype;
4341 gboolean found = FALSE;
4343 from = parse_from(sipmsg_find_header(msg, "From"));
4345 if (!from) return;
4347 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4349 contenttype = sipmsg_find_header(msg, "Content-Type");
4350 if (!strncmp(contenttype, "text/plain", 10)
4351 || !strncmp(contenttype, "text/html", 9)
4352 || !strncmp(contenttype, "multipart/related", 17)
4353 || !strncmp(contenttype, "multipart/alternative", 21))
4355 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4356 gchar *html = get_html_message(contenttype, msg->body);
4358 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4359 if (!session) {
4360 session = sipe_session_find_im(sip, from);
4363 if (session && session->focus_uri) { /* a conference */
4364 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4365 gchar *sender = parse_from(tmp);
4366 g_free(tmp);
4367 serv_got_chat_in(sip->gc, session->chat_id, sender,
4368 PURPLE_MESSAGE_RECV, html, time(NULL));
4369 g_free(sender);
4370 } else if (session && session->is_multiparty) { /* a multiparty chat */
4371 serv_got_chat_in(sip->gc, session->chat_id, from,
4372 PURPLE_MESSAGE_RECV, html, time(NULL));
4373 } else {
4374 serv_got_im(sip->gc, from, html, 0, time(NULL));
4376 g_free(html);
4377 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4378 found = TRUE;
4380 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
4381 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4382 xmlnode *state;
4383 gchar *statedata;
4385 if (!isc) {
4386 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4387 return;
4390 state = xmlnode_get_child(isc, "state");
4392 if (!state) {
4393 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4394 xmlnode_free(isc);
4395 return;
4398 statedata = xmlnode_get_data(state);
4399 if (statedata) {
4400 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4401 else serv_got_typing_stopped(sip->gc, from);
4403 g_free(statedata);
4405 xmlnode_free(isc);
4406 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4407 found = TRUE;
4409 if (!found) {
4410 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4411 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4412 if (!session) {
4413 session = sipe_session_find_im(sip, from);
4415 if (session) {
4416 gchar *msg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4417 from);
4418 sipe_present_err(sip, session, msg);
4419 g_free(msg);
4422 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
4423 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
4425 g_free(from);
4428 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
4430 gchar *body;
4431 gchar *newTag;
4432 gchar *oldHeader;
4433 gchar *newHeader;
4434 gboolean is_multiparty = FALSE;
4435 gboolean is_triggered = FALSE;
4436 gboolean was_multiparty = TRUE;
4437 gboolean just_joined = FALSE;
4438 gchar *from;
4439 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4440 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4441 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4442 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4443 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4444 GSList *end_points = NULL;
4445 char *tmp = NULL;
4446 struct sip_session *session;
4448 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
4449 g_free(tmp);
4451 /* Invitation to join conference */
4452 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
4453 process_incoming_invite_conf(sip, msg);
4454 return;
4457 /* Only accept text invitations */
4458 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4459 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4460 return;
4463 // TODO There *must* be a better way to clean up the To header to add a tag...
4464 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4465 oldHeader = sipmsg_find_header(msg, "To");
4466 newTag = gentag();
4467 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4468 sipmsg_remove_header_now(msg, "To");
4469 sipmsg_add_header_now(msg, "To", newHeader);
4470 g_free(newHeader);
4472 if (end_points_hdr) {
4473 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4475 if (g_slist_length(end_points) > 2) {
4476 is_multiparty = TRUE;
4479 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4480 is_triggered = TRUE;
4481 is_multiparty = TRUE;
4484 session = sipe_session_find_chat_by_callid(sip, callid);
4485 /* Convert to multiparty */
4486 if (session && is_multiparty && !session->is_multiparty) {
4487 g_free(session->with);
4488 session->with = NULL;
4489 was_multiparty = FALSE;
4490 session->is_multiparty = TRUE;
4491 session->chat_id = rand();
4494 if (!session && is_multiparty) {
4495 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
4497 /* IM session */
4498 from = parse_from(sipmsg_find_header(msg, "From"));
4499 if (!session) {
4500 session = sipe_session_find_or_add_im(sip, from);
4503 g_free(session->callid);
4504 session->callid = g_strdup(callid);
4506 session->is_multiparty = is_multiparty;
4507 if (roster_manager) {
4508 session->roster_manager = g_strdup(roster_manager);
4511 if (is_multiparty && end_points) {
4512 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
4513 GSList *entry = end_points;
4514 while (entry) {
4515 struct sip_dialog *dialog;
4516 struct sipendpoint *end_point = entry->data;
4517 entry = entry->next;
4519 if (!g_strcasecmp(from, end_point->contact) ||
4520 !g_strcasecmp(to, end_point->contact))
4521 continue;
4523 dialog = sipe_dialog_find(session, end_point->contact);
4524 if (dialog) {
4525 g_free(dialog->theirepid);
4526 dialog->theirepid = end_point->epid;
4527 end_point->epid = NULL;
4528 } else {
4529 dialog = sipe_dialog_add(session);
4531 dialog->callid = g_strdup(session->callid);
4532 dialog->with = end_point->contact;
4533 end_point->contact = NULL;
4534 dialog->theirepid = end_point->epid;
4535 end_point->epid = NULL;
4537 just_joined = TRUE;
4539 /* send triggered INVITE */
4540 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
4543 g_free(to);
4546 if (end_points) {
4547 GSList *entry = end_points;
4548 while (entry) {
4549 struct sipendpoint *end_point = entry->data;
4550 entry = entry->next;
4551 g_free(end_point->contact);
4552 g_free(end_point->epid);
4553 g_free(end_point);
4555 g_slist_free(end_points);
4558 if (session) {
4559 struct sip_dialog *dialog = sipe_dialog_find(session, from);
4560 if (dialog) {
4561 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
4562 } else {
4563 dialog = sipe_dialog_add(session);
4565 dialog->callid = g_strdup(session->callid);
4566 dialog->with = g_strdup(from);
4567 sipe_dialog_parse(dialog, msg, FALSE);
4569 if (!dialog->ourtag) {
4570 dialog->ourtag = newTag;
4571 newTag = NULL;
4574 just_joined = TRUE;
4576 } else {
4577 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
4579 g_free(newTag);
4581 if (is_multiparty && !session->conv) {
4582 gchar *chat_title = sipe_chat_get_name(callid);
4583 gchar *self = sip_uri_self(sip);
4584 /* create prpl chat */
4585 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
4586 session->chat_title = g_strdup(chat_title);
4587 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
4588 /* add self */
4589 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4590 self, NULL,
4591 PURPLE_CBFLAGS_NONE, FALSE);
4592 g_free(chat_title);
4593 g_free(self);
4596 if (is_multiparty && !was_multiparty) {
4597 /* add current IM counterparty to chat */
4598 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4599 sipe_dialog_first(session)->with, NULL,
4600 PURPLE_CBFLAGS_NONE, FALSE);
4603 /* add inviting party to chat */
4604 if (just_joined && session->conv) {
4605 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4606 from, NULL,
4607 PURPLE_CBFLAGS_NONE, TRUE);
4610 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4612 /* This used only in 2005 official client, not 2007 or Reuters.
4613 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4614 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4616 if (is_multiparty) {
4617 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4618 gchar *ms_text_format = sipmsg_find_header(msg, "ms-text-format");
4619 if (ms_text_format) {
4620 if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html")) {
4622 gchar *html = get_html_message(ms_text_format, NULL);
4623 if (html) {
4624 if (is_multiparty) {
4625 serv_got_chat_in(sip->gc, session->chat_id, from,
4626 PURPLE_MESSAGE_RECV, html, time(NULL));
4627 } else {
4628 serv_got_im(sip->gc, from, html, 0, time(NULL));
4630 g_free(html);
4631 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
4638 g_free(from);
4640 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
4641 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4642 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4644 body = g_strdup_printf(
4645 "v=0\r\n"
4646 "o=- 0 0 IN IP4 %s\r\n"
4647 "s=session\r\n"
4648 "c=IN IP4 %s\r\n"
4649 "t=0 0\r\n"
4650 "m=%s %d sip sip:%s\r\n"
4651 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4652 purple_network_get_my_ip(-1),
4653 purple_network_get_my_ip(-1),
4654 sip->ocs2007 ? "message" : "x-ms-message",
4655 sip->realport,
4656 sip->username);
4657 send_sip_response(sip->gc, msg, 200, "OK", body);
4658 g_free(body);
4661 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
4663 gchar *body;
4665 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
4666 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4667 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4669 body = g_strdup_printf(
4670 "v=0\r\n"
4671 "o=- 0 0 IN IP4 0.0.0.0\r\n"
4672 "s=session\r\n"
4673 "c=IN IP4 0.0.0.0\r\n"
4674 "t=0 0\r\n"
4675 "m=%s %d sip sip:%s\r\n"
4676 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4677 sip->ocs2007 ? "message" : "x-ms-message",
4678 sip->realport,
4679 sip->username);
4680 send_sip_response(sip->gc, msg, 200, "OK", body);
4681 g_free(body);
4684 static void sipe_connection_cleanup(struct sipe_account_data *);
4685 static void create_connection(struct sipe_account_data *, gchar *, int);
4687 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
4688 SIPE_UNUSED_PARAMETER struct transaction *trans)
4690 gchar *tmp;
4691 const gchar *expires_header;
4692 int expires, i;
4693 GSList *hdr = msg->headers;
4694 struct siphdrelement *elem;
4696 expires_header = sipmsg_find_header(msg, "Expires");
4697 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
4698 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
4700 switch (msg->response) {
4701 case 200:
4702 if (expires == 0) {
4703 sip->registerstatus = 0;
4704 } else {
4705 gchar *contact_hdr = NULL;
4706 gchar *gruu = NULL;
4707 gchar *epid;
4708 gchar *uuid;
4709 gchar *timeout;
4710 gchar *server_hdr = sipmsg_find_header(msg, "Server");
4712 if (!sip->reregister_set) {
4713 gchar *action_name = g_strdup_printf("<%s>", "registration");
4714 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
4715 g_free(action_name);
4716 sip->reregister_set = TRUE;
4719 sip->registerstatus = 3;
4721 if (server_hdr && !sip->server_version) {
4722 sip->server_version = g_strdup(server_hdr);
4723 g_free(default_ua);
4724 default_ua = NULL;
4727 #ifdef USE_KERBEROS
4728 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4729 #endif
4730 tmp = sipmsg_find_auth_header(msg, "NTLM");
4731 #ifdef USE_KERBEROS
4732 } else {
4733 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4735 #endif
4736 if (tmp) {
4737 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
4738 fill_auth(tmp, &sip->registrar);
4741 if (!sip->reauthenticate_set) {
4742 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
4743 guint reauth_timeout;
4744 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
4745 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
4746 reauth_timeout = sip->registrar.expires - 300;
4747 } else {
4748 /* NTLM: we have to reauthenticate as our security token expires
4749 after eight hours (be five minutes early) */
4750 reauth_timeout = (8 * 3600) - 300;
4752 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
4753 g_free(action_name);
4754 sip->reauthenticate_set = TRUE;
4757 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
4759 epid = get_epid(sip);
4760 uuid = generateUUIDfromEPID(epid);
4761 g_free(epid);
4763 // There can be multiple Contact headers (one per location where the user is logged in) so
4764 // make sure to only get the one for this uuid
4765 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
4766 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
4767 if (valid_contact) {
4768 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
4769 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
4770 g_free(valid_contact);
4771 break;
4772 } else {
4773 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
4776 g_free(uuid);
4778 g_free(sip->contact);
4779 if(gruu) {
4780 sip->contact = g_strdup_printf("<%s>", gruu);
4781 g_free(gruu);
4782 } else {
4783 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
4784 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);
4786 sip->ocs2007 = FALSE;
4787 sip->batched_support = FALSE;
4789 while(hdr)
4791 elem = hdr->data;
4792 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
4793 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
4794 /* We interpret this as OCS2007+ indicator */
4795 sip->ocs2007 = TRUE;
4796 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
4798 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
4799 sip->batched_support = TRUE;
4800 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
4803 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
4804 gchar **caps = g_strsplit(elem->value,",",0);
4805 i = 0;
4806 while (caps[i]) {
4807 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
4808 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
4809 i++;
4811 g_strfreev(caps);
4813 hdr = g_slist_next(hdr);
4816 /* rejoin open chats to be able to use them by continue to send messages */
4817 purple_conversation_foreach(sipe_rejoin_chat);
4819 /* subscriptions */
4820 if (!sip->subscribed) { //do it just once, not every re-register
4822 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
4823 (GCompareFunc)g_ascii_strcasecmp)) {
4824 sipe_subscribe_roaming_contacts(sip);
4827 /* For 2007+ it does not make sence to subscribe to:
4828 * vnd-microsoft-roaming-ACL
4829 * vnd-microsoft-provisioning (not v2)
4830 * presence.wpending
4831 * These are for backward compatibility.
4833 if (sip->ocs2007)
4835 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
4836 (GCompareFunc)g_ascii_strcasecmp)) {
4837 sipe_subscribe_roaming_self(sip);
4839 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
4840 (GCompareFunc)g_ascii_strcasecmp)) {
4841 sipe_subscribe_roaming_provisioning_v2(sip);
4844 /* For 2005- servers */
4845 else
4847 //sipe_options_request(sip, sip->sipdomain);
4849 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
4850 (GCompareFunc)g_ascii_strcasecmp)) {
4851 sipe_subscribe_roaming_acl(sip);
4853 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
4854 (GCompareFunc)g_ascii_strcasecmp)) {
4855 sipe_subscribe_roaming_provisioning(sip);
4857 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
4858 (GCompareFunc)g_ascii_strcasecmp)) {
4859 sipe_subscribe_presence_wpending(sip, msg);
4862 /* For 2007+ we publish our initial statuses and calendar data only after
4863 * received our existing publications in sipe_process_roaming_self()
4864 * Only in this case we know versions of current publications made
4865 * on our behalf.
4867 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
4868 /* dalayed run */
4869 sipe_schedule_action("<+update-calendar>",
4870 UPDATE_CALENDAR_DELAY,
4871 (Action)sipe_update_calendar,
4872 NULL,
4873 sip,
4874 NULL);
4877 sip->subscribed = TRUE;
4880 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
4881 "timeout=", ";", NULL);
4882 if (timeout != NULL) {
4883 sscanf(timeout, "%u", &sip->keepalive_timeout);
4884 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
4885 sip->keepalive_timeout);
4886 g_free(timeout);
4889 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
4891 break;
4892 case 301:
4894 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
4896 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
4897 gchar **parts = g_strsplit(redirect + 4, ";", 0);
4898 gchar **tmp;
4899 gchar *hostname;
4900 int port = 0;
4901 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
4902 int i = 1;
4904 tmp = g_strsplit(parts[0], ":", 0);
4905 hostname = g_strdup(tmp[0]);
4906 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
4907 g_strfreev(tmp);
4909 while (parts[i]) {
4910 tmp = g_strsplit(parts[i], "=", 0);
4911 if (tmp[1]) {
4912 if (g_strcasecmp("transport", tmp[0]) == 0) {
4913 if (g_strcasecmp("tcp", tmp[1]) == 0) {
4914 transport = SIPE_TRANSPORT_TCP;
4915 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
4916 transport = SIPE_TRANSPORT_UDP;
4920 g_strfreev(tmp);
4921 i++;
4923 g_strfreev(parts);
4925 /* Close old connection */
4926 sipe_connection_cleanup(sip);
4928 /* Create new connection */
4929 sip->transport = transport;
4930 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
4931 hostname, port, TRANSPORT_DESCRIPTOR);
4932 create_connection(sip, hostname, port);
4934 g_free(redirect);
4936 break;
4937 case 401:
4938 if (sip->registerstatus != 2) {
4939 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
4940 if (sip->registrar.retries > 3) {
4941 sip->gc->wants_to_die = TRUE;
4942 purple_connection_error(sip->gc, _("Wrong password"));
4943 return TRUE;
4945 #ifdef USE_KERBEROS
4946 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4947 #endif
4948 tmp = sipmsg_find_auth_header(msg, "NTLM");
4949 #ifdef USE_KERBEROS
4950 } else {
4951 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4953 #endif
4954 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
4955 fill_auth(tmp, &sip->registrar);
4956 sip->registerstatus = 2;
4957 if (sip->account->disconnecting) {
4958 do_register_exp(sip, 0);
4959 } else {
4960 do_register(sip);
4963 break;
4964 case 403:
4966 gchar *warning = sipmsg_find_header(msg, "Warning");
4967 gchar **reason = NULL;
4968 if (warning != NULL) {
4969 /* Example header:
4970 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4972 reason = g_strsplit(warning, "\"", 0);
4974 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
4975 (reason && reason[1]) ? reason[1] : _("no reason given"));
4976 g_strfreev(reason);
4978 sip->gc->wants_to_die = TRUE;
4979 purple_connection_error(sip->gc, warning);
4980 g_free(warning);
4981 return TRUE;
4983 break;
4984 case 404:
4986 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4987 gchar *reason = NULL;
4988 if (warning != NULL) {
4989 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4991 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
4992 warning ? (reason ? reason : _("no reason given")) :
4993 _("SIP is either not enabled for the destination URI or it does not exist"));
4994 g_free(reason);
4996 sip->gc->wants_to_die = TRUE;
4997 purple_connection_error(sip->gc, warning);
4998 g_free(warning);
4999 return TRUE;
5001 break;
5002 case 503:
5003 case 504: /* Server time-out */
5005 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
5006 gchar *reason = NULL;
5007 if (warning != NULL) {
5008 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
5010 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5011 g_free(reason);
5013 sip->gc->wants_to_die = TRUE;
5014 purple_connection_error(sip->gc, warning);
5015 g_free(warning);
5016 return TRUE;
5018 break;
5020 return TRUE;
5024 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5026 static const char*
5027 sipe_get_status_by_availability(int avail)
5029 const char *status;
5031 if (avail < 3000)
5032 status = SIPE_STATUS_ID_OFFLINE;
5033 else if (avail < 4500)
5034 status = SIPE_STATUS_ID_AVAILABLE;
5035 else if (avail < 6000)
5036 status = SIPE_STATUS_ID_IDLE;
5037 else if (avail < 7500)
5038 status = SIPE_STATUS_ID_BUSY;
5039 else if (avail < 9000)
5040 status = SIPE_STATUS_ID_BUSYIDLE;
5041 else if (avail < 12000)
5042 status = SIPE_STATUS_ID_DND;
5043 else if (avail < 15000)
5044 status = SIPE_STATUS_ID_BRB;
5045 else if (avail < 18000)
5046 status = SIPE_STATUS_ID_AWAY;
5047 else
5048 status = SIPE_STATUS_ID_OFFLINE;
5050 return status;
5053 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5055 const char *uri;
5056 xmlnode *xn_categories;
5057 xmlnode *xn_category;
5058 xmlnode *xn_node;
5060 xn_categories = xmlnode_from_str(data, len);
5061 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
5063 for (xn_category = xmlnode_get_child(xn_categories, "category");
5064 xn_category ;
5065 xn_category = xmlnode_get_next_twin(xn_category) )
5067 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
5069 /* contactCard */
5070 if (!strcmp(attrVar, "contactCard"))
5072 xmlnode *node;
5073 /* identity - Display Name and email */
5074 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
5075 if (node) {
5076 char* display_name = xmlnode_get_data(
5077 xmlnode_get_descendant(node, "name", "displayName", NULL));
5078 char* email = xmlnode_get_data(
5079 xmlnode_get_child(node, "email"));
5081 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5082 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5084 g_free(display_name);
5085 g_free(email);
5087 /* company */
5088 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
5089 if (node) {
5090 char* company = xmlnode_get_data(node);
5091 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5092 g_free(company);
5094 /* department */
5095 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
5096 if (node) {
5097 char* department = xmlnode_get_data(node);
5098 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5099 g_free(department);
5101 /* title */
5102 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
5103 if (node) {
5104 char* title = xmlnode_get_data(node);
5105 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5106 g_free(title);
5108 /* office */
5109 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
5110 if (node) {
5111 char* office = xmlnode_get_data(node);
5112 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5113 g_free(office);
5115 /* site (url) */
5116 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
5117 if (node) {
5118 char* site = xmlnode_get_data(node);
5119 sipe_update_user_info(sip, uri, SITE_PROP, site);
5120 g_free(site);
5122 /* phone */
5123 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
5124 node;
5125 node = xmlnode_get_next_twin(node))
5127 const char *phone_type = xmlnode_get_attrib(node, "type");
5128 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
5129 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
5131 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5133 g_free(phone);
5134 g_free(phone_display_string);
5136 /* address */
5137 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
5138 node;
5139 node = xmlnode_get_next_twin(node))
5141 if (!strcmp(xmlnode_get_attrib(node, "type"), "work")) {
5142 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
5143 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
5144 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
5145 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
5146 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
5148 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5149 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5150 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5151 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5152 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5154 g_free(street);
5155 g_free(city);
5156 g_free(state);
5157 g_free(zipcode);
5158 g_free(country_code);
5160 break;
5164 /* note */
5165 else if (!strcmp(attrVar, "note"))
5167 if (uri) {
5168 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
5170 if (sbuddy) {
5171 char *note;
5173 xn_node = xmlnode_get_child(xn_category, "note");
5174 if (!xn_node) continue;
5175 xn_node = xmlnode_get_child(xn_node, "body");
5176 if (!xn_node) continue;
5177 note = xmlnode_get_data(xn_node);
5178 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
5179 g_free(sbuddy->annotation);
5180 sbuddy->annotation = NULL;
5181 if (note) sbuddy->annotation = g_strdup(note);
5182 g_free(note);
5187 /* state */
5188 else if(!strcmp(attrVar, "state"))
5190 char *data;
5191 int availability;
5192 const char *status;
5193 xmlnode *xn_availability;
5194 xmlnode *xn_activity;
5195 xmlnode *xn_meeting_subject;
5196 xmlnode *xn_meeting_location;
5197 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5199 xn_node = xmlnode_get_child(xn_category, "state");
5200 if (!xn_node) continue;
5201 xn_availability = xmlnode_get_child(xn_node, "availability");
5202 if (!xn_availability) continue;
5203 xn_activity = xmlnode_get_child(xn_node, "activity");
5204 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
5205 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
5207 data = xmlnode_get_data(xn_availability);
5208 availability = atoi(data);
5209 g_free(data);
5211 /* activity, meeting_subject, meeting_location */
5212 if (sbuddy) {
5213 /* activity */
5214 g_free(sbuddy->activity);
5215 sbuddy->activity = NULL;
5216 if (xn_activity) {
5217 const char *token = xmlnode_get_attrib(xn_activity, "token");
5218 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
5220 /* from token */
5221 if (!is_empty(token)) {
5222 if (!strcmp(token, "on-the-phone")) {
5223 sbuddy->activity = g_strdup(_("On the phone"));
5224 } else if (!strcmp(token, "in-a-conference")) {
5225 sbuddy->activity = g_strdup(_("In a conference"));
5226 } else if (!strcmp(token, "in-a-meeting")) {
5227 sbuddy->activity = g_strdup(_("In a meeting"));
5228 } else if (!strcmp(token, "out-of-office")) {
5229 sbuddy->activity = g_strdup(_("Out of office"));
5230 } else if (!strcmp(token, "urgent-interruptions-only")) {
5231 sbuddy->activity = g_strdup(_("Urgent interruptions only"));
5234 /* form custom element */
5235 if (xn_custom) {
5236 char *custom = xmlnode_get_data(xn_custom);
5238 if (!is_empty(custom)) {
5239 sbuddy->activity = custom;
5240 custom = NULL;
5242 g_free(custom);
5245 /* meeting_subject */
5246 g_free(sbuddy->meeting_subject);
5247 sbuddy->meeting_subject = NULL;
5248 if (xn_meeting_subject) {
5249 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
5251 if (!is_empty(meeting_subject)) {
5252 sbuddy->meeting_subject = meeting_subject;
5253 meeting_subject = NULL;
5255 g_free(meeting_subject);
5257 /* meeting_location */
5258 g_free(sbuddy->meeting_location);
5259 sbuddy->meeting_location = NULL;
5260 if (xn_meeting_location) {
5261 char *meeting_location = xmlnode_get_data(xn_meeting_location);
5263 if (!is_empty(meeting_location)) {
5264 sbuddy->meeting_location = meeting_location;
5265 meeting_location = NULL;
5267 g_free(meeting_location);
5271 status = sipe_get_status_by_availability(availability);
5272 if (status) {
5273 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
5274 sipe_got_user_status(sip, uri, status);
5277 /* calendarData */
5278 else if(!strcmp(attrVar, "calendarData"))
5280 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
5281 xmlnode *xn_free_busy = xmlnode_get_descendant(xn_category, "calendarData", "freeBusy", NULL);
5282 xmlnode *xn_working_hours = xmlnode_get_descendant(xn_category, "calendarData", "WorkingHours", NULL);
5284 if (sbuddy && xn_free_busy) {
5285 g_free(sbuddy->cal_start_time);
5286 sbuddy->cal_start_time = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
5288 sbuddy->cal_granularity = !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy, "granularity"), "PT15M") ?
5289 15 : 0;
5291 g_free(sbuddy->cal_free_busy_base64);
5292 sbuddy->cal_free_busy_base64 = xmlnode_get_data(xn_free_busy);
5294 g_free(sbuddy->cal_free_busy);
5295 sbuddy->cal_free_busy = NULL;
5297 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);
5300 if (sbuddy && xn_working_hours) {
5301 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
5306 xmlnode_free(xn_categories);
5309 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
5311 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5312 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
5313 payload->host = g_strdup(host);
5314 payload->buddies = server;
5315 sipe_subscribe_presence_batched_routed(sip, payload);
5316 sipe_subscribe_presence_batched_routed_free(payload);
5319 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
5321 xmlnode *xn_list;
5322 xmlnode *xn_resource;
5323 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
5324 g_free, NULL);
5325 GSList *server;
5326 gchar *host;
5328 xn_list = xmlnode_from_str(data, len);
5330 for (xn_resource = xmlnode_get_child(xn_list, "resource");
5331 xn_resource;
5332 xn_resource = xmlnode_get_next_twin(xn_resource) )
5334 const char *uri, *state;
5335 xmlnode *xn_instance;
5337 xn_instance = xmlnode_get_child(xn_resource, "instance");
5338 if (!xn_instance) continue;
5340 uri = xmlnode_get_attrib(xn_resource, "uri");
5341 state = xmlnode_get_attrib(xn_instance, "state");
5342 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
5344 if (strstr(state, "resubscribe")) {
5345 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
5347 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
5348 gchar *user = g_strdup(uri);
5349 host = g_strdup(poolFqdn);
5350 server = g_hash_table_lookup(servers, host);
5351 server = g_slist_append(server, user);
5352 g_hash_table_insert(servers, host, server);
5353 } else {
5354 sipe_subscribe_presence_single(sip, (void *) uri);
5359 /* Send out any deferred poolFqdn subscriptions */
5360 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
5361 g_hash_table_destroy(servers);
5363 xmlnode_free(xn_list);
5366 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
5368 gchar *uri;
5369 gchar *getbasic;
5370 gchar *activity = NULL;
5371 xmlnode *pidf;
5372 xmlnode *basicstatus = NULL, *tuple, *status;
5373 gboolean isonline = FALSE;
5374 xmlnode *display_name_node;
5376 pidf = xmlnode_from_str(data, len);
5377 if (!pidf) {
5378 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
5379 return;
5382 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
5384 if ((tuple = xmlnode_get_child(pidf, "tuple")))
5386 if ((status = xmlnode_get_child(tuple, "status"))) {
5387 basicstatus = xmlnode_get_child(status, "basic");
5391 if (!basicstatus) {
5392 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
5393 xmlnode_free(pidf);
5394 return;
5397 getbasic = xmlnode_get_data(basicstatus);
5398 if (!getbasic) {
5399 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
5400 xmlnode_free(pidf);
5401 return;
5404 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
5405 if (strstr(getbasic, "open")) {
5406 isonline = TRUE;
5408 g_free(getbasic);
5410 display_name_node = xmlnode_get_child(pidf, "display-name");
5411 if (display_name_node) {
5412 char * display_name = xmlnode_get_data(display_name_node);
5414 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5415 g_free(display_name);
5418 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
5419 if ((status = xmlnode_get_child(tuple, "status"))) {
5420 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
5421 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
5422 activity = xmlnode_get_data(basicstatus);
5423 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
5429 if (isonline) {
5430 const gchar * status_id = NULL;
5431 if (activity) {
5432 if (strstr(activity, "busy")) {
5433 status_id = SIPE_STATUS_ID_BUSY;
5434 } else if (strstr(activity, "away")) {
5435 status_id = SIPE_STATUS_ID_AWAY;
5439 if (!status_id) {
5440 status_id = SIPE_STATUS_ID_AVAILABLE;
5443 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
5444 sipe_got_user_status(sip, uri, status_id);
5445 } else {
5446 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
5449 g_free(activity);
5450 g_free(uri);
5451 xmlnode_free(pidf);
5454 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
5456 const char *activity = NULL;
5457 const char *epid;
5458 const char *status_id = NULL;
5459 const char *name;
5460 char *uri;
5461 int avl;
5462 int act;
5463 const char *device_name = NULL;
5464 const char *cal_start_time = NULL;
5465 const char *cal_granularity = NULL;
5466 char *cal_free_busy_base64 = NULL;
5467 struct sipe_buddy *sbuddy;
5468 xmlnode *node;
5469 xmlnode *xn_presentity;
5470 xmlnode *xn_availability;
5471 xmlnode *xn_activity;
5472 xmlnode *xn_display_name;
5473 xmlnode *xn_email;
5474 xmlnode *xn_phone_number;
5475 xmlnode *xn_userinfo;
5476 xmlnode *xn_oof;
5477 xmlnode *xn_contact;
5478 xmlnode *xn_note;
5479 char *note;
5480 char *free_activity;
5482 /* fix for Reuters environment on Linux */
5483 if (data && strstr(data, "encoding=\"utf-16\"")) {
5484 char *tmp_data;
5485 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
5486 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
5487 g_free(tmp_data);
5488 } else {
5489 xn_presentity = xmlnode_from_str(data, len);
5492 xn_availability = xmlnode_get_child(xn_presentity, "availability");
5493 xn_activity = xmlnode_get_child(xn_presentity, "activity");
5494 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
5495 xn_email = xmlnode_get_child(xn_presentity, "email");
5496 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
5497 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
5498 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
5500 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
5501 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
5502 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
5504 free_activity = NULL;
5506 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
5507 uri = sip_uri_from_name(name);
5508 avl = atoi(xmlnode_get_attrib(xn_availability, "aggregate"));
5509 epid = xmlnode_get_attrib(xn_availability, "epid");
5510 act = atoi(xmlnode_get_attrib(xn_activity, "aggregate"));
5512 if (xn_display_name) {
5513 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
5514 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
5515 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
5516 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
5517 char *tel_uri = sip_to_tel_uri(phone_number);
5519 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5520 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5521 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
5522 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
5524 g_free(tel_uri);
5525 g_free(phone_label);
5526 g_free(phone_number);
5527 g_free(email);
5528 g_free(display_name);
5531 if (xn_contact) {
5532 /* tel */
5533 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
5535 /* Ex.: <tel type="work">tel:+3222220000</tel> */
5536 const char *phone_type = xmlnode_get_attrib(node, "type");
5537 char* phone = xmlnode_get_data(node);
5539 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
5541 g_free(phone);
5545 /* oof */
5546 if (xn_oof) {
5547 activity = _("Out of office");
5550 /* devicePresence */
5551 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
5552 xmlnode *xn_device_name;
5553 xmlnode *xn_calendar_info;
5554 xmlnode *xn_state;
5555 char *state;
5557 if (strcmp(xmlnode_get_attrib(node, "epid"), epid)) continue;
5559 xn_device_name = xmlnode_get_child(node, "deviceName");
5560 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
5562 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
5563 if (xn_calendar_info) {
5564 cal_start_time = xmlnode_get_attrib(xn_calendar_info, "startTime");
5565 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
5566 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
5568 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);
5572 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
5573 state = xn_state ? xmlnode_get_data(xn_state) : NULL;
5575 if (!is_empty(state)) {
5576 if (!strcmp(state, "on-the-phone")) {
5577 activity = _("On the phone");
5578 } else if (!strcmp(state, "presenting")) {
5579 activity = _("In a conference");
5580 } else {
5581 activity = free_activity = state;
5582 state = NULL;
5585 g_free(state);
5589 /* [MS-SIP] 2.2.1 */
5590 if (act < 150)
5591 status_id = SIPE_STATUS_ID_AWAY;
5592 else if (act < 200)
5593 status_id = SIPE_STATUS_ID_LUNCH;
5594 else if (act < 300)
5595 status_id = SIPE_STATUS_ID_IDLE;
5596 else if (act < 400)
5597 status_id = SIPE_STATUS_ID_BRB;
5598 else if (act < 500)
5599 status_id = SIPE_STATUS_ID_AVAILABLE;
5600 else if (act < 600)
5601 status_id = SIPE_STATUS_ID_ONPHONE;
5602 else if (act < 700)
5603 status_id = SIPE_STATUS_ID_BUSY;
5604 else if (act < 800)
5605 status_id = SIPE_STATUS_ID_AWAY;
5606 else
5607 status_id = SIPE_STATUS_ID_AVAILABLE;
5609 if (avl < 100)
5610 status_id = SIPE_STATUS_ID_OFFLINE;
5613 sbuddy = g_hash_table_lookup(sip->buddies, uri);
5614 if (sbuddy)
5616 g_free(sbuddy->activity);
5617 sbuddy->activity = NULL;
5618 if (!is_empty(activity)) { sbuddy->activity = g_strdup(activity); }
5620 g_free(sbuddy->annotation);
5621 sbuddy->annotation = NULL;
5622 if (!is_empty(note)) { sbuddy->annotation = g_strdup(note); }
5624 g_free(sbuddy->device_name);
5625 sbuddy->device_name = NULL;
5626 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
5628 if (!is_empty(cal_free_busy_base64)) {
5629 g_free(sbuddy->cal_start_time);
5630 sbuddy->cal_start_time = g_strdup(cal_start_time);
5632 sbuddy->cal_granularity = !g_ascii_strcasecmp(cal_granularity, "PT15M") ? 15 : 0;
5634 g_free(sbuddy->cal_free_busy_base64);
5635 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
5637 g_free(sbuddy->cal_free_busy);
5638 sbuddy->cal_free_busy = NULL;
5642 if (free_activity) g_free(free_activity);
5644 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
5645 sipe_got_user_status(sip, uri, status_id);
5646 g_free(note);
5647 xmlnode_free(xn_presentity);
5648 g_free(uri);
5651 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
5653 char *ctype = sipmsg_find_header(msg, "Content-Type");
5655 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
5657 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
5658 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
5660 const char *content = msg->body;
5661 unsigned length = msg->bodylen;
5662 PurpleMimeDocument *mime = NULL;
5664 if (strstr(ctype, "multipart"))
5666 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
5667 const char *content_type;
5668 GList* parts;
5669 mime = purple_mime_document_parse(doc);
5670 parts = purple_mime_document_get_parts(mime);
5671 while(parts) {
5672 content = purple_mime_part_get_data(parts->data);
5673 length = purple_mime_part_get_length(parts->data);
5674 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
5675 if(content_type && strstr(content_type,"application/rlmi+xml"))
5677 process_incoming_notify_rlmi_resub(sip, content, length);
5679 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
5681 process_incoming_notify_msrtc(sip, content, length);
5683 else
5685 process_incoming_notify_rlmi(sip, content, length);
5687 parts = parts->next;
5689 g_free(doc);
5691 if (mime)
5693 purple_mime_document_free(mime);
5696 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
5698 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
5700 else if(strstr(ctype, "application/rlmi+xml"))
5702 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
5705 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
5707 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
5709 else
5711 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
5715 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
5717 char *ctype = sipmsg_find_header(msg, "Content-Type");
5718 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
5720 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
5722 if (ctype &&
5723 strstr(ctype, "multipart") &&
5724 (strstr(ctype, "application/rlmi+xml") ||
5725 strstr(ctype, "application/msrtc-event-categories+xml"))) {
5726 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
5727 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
5728 GList *parts = purple_mime_document_get_parts(mime);
5729 GSList *buddies = NULL;
5730 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5732 while (parts) {
5733 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
5734 purple_mime_part_get_length(parts->data));
5736 if (strcmp(xml->name, "list")) {
5737 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
5739 buddies = g_slist_append(buddies, uri);
5741 xmlnode_free(xml);
5743 parts = parts->next;
5745 g_free(doc);
5746 if (mime) purple_mime_document_free(mime);
5748 payload->host = g_strdup(who);
5749 payload->buddies = buddies;
5750 sipe_schedule_action(action_name, timeout,
5751 sipe_subscribe_presence_batched_routed,
5752 sipe_subscribe_presence_batched_routed_free,
5753 sip, payload);
5754 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
5756 } else {
5757 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
5758 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
5760 g_free(action_name);
5764 * Dispatcher for all incoming subscription information
5765 * whether it comes from NOTIFY, BENOTIFY requests or
5766 * piggy-backed to subscription's OK responce.
5768 * @param request whether initiated from BE/NOTIFY request or OK-response message.
5769 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
5771 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
5773 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5774 gchar *event = sipmsg_find_header(msg, "Event");
5775 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
5776 char *tmp;
5777 int timeout = 0;
5779 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
5780 event ? event : "",
5781 tmp = fix_newlines(msg->body));
5782 g_free(tmp);
5783 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
5785 /* implicit subscriptions */
5786 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
5787 sipe_process_imdn(sip, msg);
5790 if (!request)
5792 const gchar *expires_header;
5793 expires_header = sipmsg_find_header(msg, "Expires");
5794 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
5795 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
5796 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout; // 2 min ahead of expiration
5799 /* for one off subscriptions (send with Expire: 0) */
5800 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
5802 sipe_process_provisioning_v2(sip, msg);
5804 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
5806 sipe_process_provisioning(sip, msg);
5809 if (!subscription_state || strstr(subscription_state, "active"))
5811 if (event && !g_ascii_strcasecmp(event, "presence"))
5813 sipe_process_presence(sip, msg);
5815 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
5817 sipe_process_roaming_contacts(sip, msg);
5819 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
5821 sipe_process_roaming_self(sip, msg);
5823 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
5825 sipe_process_roaming_acl(sip, msg);
5827 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
5829 sipe_process_presence_wpending(sip, msg);
5831 else if (event && !g_ascii_strcasecmp(event, "conference"))
5833 sipe_process_conference(sip, msg);
5837 /* The server sends status 'terminated' */
5838 if (subscription_state && strstr(subscription_state, "terminated") ) {
5839 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
5840 gchar *key = sipe_get_subscription_key(event, who);
5842 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
5843 g_free(who);
5845 if (g_hash_table_lookup(sip->subscriptions, key)) {
5846 g_hash_table_remove(sip->subscriptions, key);
5847 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
5850 g_free(key);
5853 if (timeout && event) {// For LSC 2005 and OCS 2007
5854 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
5855 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
5857 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
5858 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
5859 g_free(action_name);
5861 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
5862 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
5864 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
5865 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
5866 g_free(action_name);
5868 else*/
5869 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
5870 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
5872 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
5873 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
5874 g_free(action_name);
5876 else if (!g_ascii_strcasecmp(event, "presence") &&
5877 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
5879 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
5880 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
5881 if (sip->batched_support) {
5882 sipe_process_presence_timeout(sip, msg, who, timeout);
5884 else {
5885 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
5886 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
5888 g_free(action_name);
5889 g_free(who);
5893 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
5895 sipe_process_registration_notify(sip, msg);
5898 /* The client responses on received a NOTIFY message */
5899 if (request && !benotify)
5901 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5905 void
5906 send_presence_soap(struct sipe_account_data *sip,
5907 const char *note,
5908 gboolean do_publish_calendar)
5910 struct sipe_ews* ews = sip->ews;
5911 int availability = 300; /* online */
5912 int activity = 400; /* Available */
5913 gchar *body;
5914 gchar *tmp;
5915 gchar *tmp2 = NULL;
5916 const gchar *note_pub = NULL;
5917 gchar *calendar_data = NULL;
5919 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
5920 activity = 100;
5921 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
5922 activity = 150;
5923 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
5924 activity = 300;
5925 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
5926 activity = 400;
5927 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
5928 activity = 500;
5929 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
5930 activity = 600;
5931 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
5932 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
5933 availability = 0; /* offline */
5934 activity = 100;
5935 } else {
5936 activity = 400; /* available */
5939 if (do_publish_calendar &&
5940 ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
5942 char *fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
5943 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
5944 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
5945 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
5946 fb_start_str,
5947 free_busy_base64);
5948 g_free(fb_start_str);
5949 g_free(free_busy_base64);
5952 if (ews && ews->oof_note) {
5953 note_pub = ews->oof_note;
5954 } else if (note) {
5955 note_pub = note;
5958 //@TODO: send user data - state;
5959 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
5960 sip->username,
5961 availability,
5962 activity,
5963 (tmp = g_ascii_strup(sipe_get_host_name(), -1)),
5964 note_pub ? (tmp2 = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, note_pub)) : "",
5965 ews && ews->oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "",
5966 calendar_data ? calendar_data : "");
5967 g_free(tmp);
5968 g_free(tmp2);
5969 send_soap_request(sip, body);
5970 g_free(body);
5973 static gboolean
5974 process_send_presence_category_publish_response(struct sipe_account_data *sip,
5975 struct sipmsg *msg,
5976 struct transaction *trans)
5978 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
5980 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
5981 xmlnode *xml;
5982 xmlnode *node;
5983 gchar *fault_code;
5984 GHashTable *faults;
5985 int index_our;
5986 gboolean has_device_publication = FALSE;
5988 xml = xmlnode_from_str(msg->body, msg->bodylen);
5990 /* test if version mismatch fault */
5991 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
5992 if (strcmp(fault_code, "Client.BadCall.WrongDelta")) {
5993 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
5994 g_free(fault_code);
5995 xmlnode_free(xml);
5996 return TRUE;
5998 g_free(fault_code);
6000 /* accumulating information about faulty versions */
6001 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
6002 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
6003 node;
6004 node = xmlnode_get_next_twin(node))
6006 const gchar *index = xmlnode_get_attrib(node, "index");
6007 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
6009 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
6010 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
6012 xmlnode_free(xml);
6014 /* here we are parsing own request to figure out what publication
6015 * referensed here only by index went wrong
6017 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
6019 /* publication */
6020 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
6021 index_our = 1; /* starts with 1 - our first publication */
6022 node;
6023 node = xmlnode_get_next_twin(node), index_our++)
6025 gchar *idx = g_strdup_printf("%d", index_our);
6026 const gchar *curVersion = g_hash_table_lookup(faults, idx);
6027 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
6028 g_free(idx);
6030 if (!strcmp("device", categoryName)) {
6031 has_device_publication = TRUE;
6034 if (curVersion) { /* fault exist on this index */
6035 const gchar *container = xmlnode_get_attrib(node, "container");
6036 const gchar *instance = xmlnode_get_attrib(node, "instance");
6037 /* key is <category><instance><container> */
6038 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
6039 struct sipe_publication *publication =
6040 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
6042 purple_debug_info("sipe", "key is %s\n", key);
6044 if (publication) {
6045 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6046 key, curVersion, publication->version);
6047 /* updating publication's version to the correct one */
6048 publication->version = atoi(curVersion);
6050 g_free(key);
6053 xmlnode_free(xml);
6054 g_hash_table_destroy(faults);
6056 /* rebublishing with right versions */
6057 if (has_device_publication) {
6058 send_publish_category_initial(sip);
6059 } else {
6060 send_presence_status(sip);
6063 return TRUE;
6067 * Returns 'device' XML part for publication.
6068 * Must be g_free'd after use.
6070 static gchar *
6071 sipe_publish_get_category_device(struct sipe_account_data *sip)
6073 gchar *uri;
6074 gchar *doc;
6075 gchar *epid = get_epid(sip);
6076 gchar *uuid = generateUUIDfromEPID(epid);
6077 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
6078 /* key is <category><instance><container> */
6079 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
6080 struct sipe_publication *publication =
6081 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
6083 g_free(key);
6084 g_free(epid);
6086 uri = sip_uri_self(sip);
6087 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
6088 device_instance,
6089 publication ? publication->version : 0,
6090 uuid,
6091 uri,
6092 "00:00:00+01:00", /* @TODO make timezone real*/
6093 sipe_get_host_name()
6096 g_free(uri);
6097 g_free(uuid);
6099 return doc;
6103 * A service method - use
6104 * - send_publish_get_category_state_machine and
6105 * - send_publish_get_category_state_user instead.
6106 * Must be g_free'd after use.
6108 static gchar *
6109 sipe_publish_get_category_state(struct sipe_account_data *sip,
6110 gboolean is_user_state)
6112 int availability;
6113 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
6114 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
6115 /* key is <category><instance><container> */
6116 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6117 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6118 struct sipe_publication *publication_2 =
6119 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6120 struct sipe_publication *publication_3 =
6121 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6123 g_free(key_2);
6124 g_free(key_3);
6126 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
6127 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
6128 availability = 15500;
6129 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
6130 availability = 12500;
6131 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
6132 availability = 9500;
6133 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY) ||
6134 !strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
6135 availability = 6500;
6136 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
6137 availability = 3500;
6138 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
6139 availability = 0;
6140 } else {
6141 // Offline or invisible
6142 availability = 18500;
6145 if (publication_2 && (publication_2->availability == availability))
6147 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6148 return NULL; /* nothing to update */
6151 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
6152 instance,
6153 publication_2 ? publication_2->version : 0,
6154 availability,
6155 instance,
6156 publication_3 ? publication_3->version : 0,
6157 availability);
6161 * Only Busy and OOF calendar event are published.
6162 * Different instances are used for that.
6164 * Must be g_free'd after use.
6166 static gchar *
6167 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
6168 struct sipe_cal_event *event,
6169 const char *uri,
6170 int cal_satus)
6172 gchar *start_time_str;
6173 int availability = 0;
6174 gchar *res;
6175 guint instance = (cal_satus == SIPE_CAL_OOF) ?
6176 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
6177 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
6179 /* key is <category><instance><container> */
6180 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6181 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6182 struct sipe_publication *publication_2 =
6183 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6184 struct sipe_publication *publication_3 =
6185 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6187 g_free(key_2);
6188 g_free(key_3);
6190 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
6191 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6192 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
6193 return NULL;
6196 if (event &&
6197 publication_3 &&
6198 (publication_3->availability == availability) &&
6199 !strcmp(publication_3->cal_event_hash, sipe_cal_event_hash(event)))
6201 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6202 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
6203 return NULL; /* nothing to update */
6206 if (event &&
6207 (event->cal_status == SIPE_CAL_BUSY ||
6208 event->cal_status == SIPE_CAL_OOF))
6210 gchar *availability_xml_str = NULL;
6211 gchar *activity_xml_str = NULL;
6213 if (event->cal_status == SIPE_CAL_BUSY) {
6214 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
6217 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
6218 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6219 "in-a-meeting",
6220 "minAvailability=\"6500\"",
6221 "maxAvailability=\"8999\"");
6222 } else if (event->cal_status == SIPE_CAL_OOF) {
6223 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6224 "out-of-office",
6225 "minAvailability=\"12000\"",
6226 "");
6228 start_time_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&event->start_time)));
6230 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
6231 instance,
6232 publication_2 ? publication_2->version : 0,
6233 uri,
6234 start_time_str,
6235 availability_xml_str ? availability_xml_str : "",
6236 activity_xml_str ? activity_xml_str : "",
6237 event->subject ? event->subject : "",
6238 event->location ? event->location : "",
6240 instance,
6241 publication_3 ? publication_3->version : 0,
6242 uri,
6243 start_time_str,
6244 availability_xml_str ? availability_xml_str : "",
6245 activity_xml_str ? activity_xml_str : "",
6246 event->subject ? event->subject : "",
6247 event->location ? event->location : ""
6249 g_free(start_time_str);
6250 g_free(availability_xml_str);
6251 g_free(activity_xml_str);
6254 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
6256 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
6257 instance,
6258 publication_2 ? publication_2->version : 0,
6260 instance,
6261 publication_3 ? publication_3->version : 0
6265 return res;
6269 * Returns 'machineState' XML part for publication.
6270 * Must be g_free'd after use.
6272 static gchar *
6273 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
6275 return sipe_publish_get_category_state(sip, FALSE);
6279 * Returns 'userState' XML part for publication.
6280 * Must be g_free'd after use.
6282 static gchar *
6283 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
6285 return sipe_publish_get_category_state(sip, TRUE);
6289 * Compares two strings even in case both are NULL/empty
6291 static gboolean
6292 sipe_is_equal(const char* n1, const char* n2) {
6293 return ((!n1 || !strlen(n1)) && (!n2 || !strlen(n2))) /* both empty */
6294 || (n1 && n2 && !strcmp(n1, n2)); /* or not empty and equal */
6298 * Returns 'note' XML part for publication.
6299 * Must be g_free'd after use.
6301 * @param note_type either personal or OOF
6303 static gchar *
6304 sipe_publish_get_category_note(struct sipe_account_data *sip,
6305 const char *note,
6306 const char *note_type)
6308 guint instance = !strcmp("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
6309 /* key is <category><instance><container> */
6310 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
6311 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
6312 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
6314 struct sipe_publication *publication_note_200 =
6315 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
6316 struct sipe_publication *publication_note_300 =
6317 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
6318 struct sipe_publication *publication_note_400 =
6319 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
6321 const char *n1 = note;
6322 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
6324 g_free(key_note_200);
6325 g_free(key_note_300);
6326 g_free(key_note_400);
6328 if (sipe_is_equal(n1, n2))
6330 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
6331 return NULL; /* nothing to update */
6334 return g_markup_printf_escaped(SIPE_PUB_XML_NOTE,
6335 instance,
6336 publication_note_200 ? publication_note_200->version : 0,
6337 note_type,
6338 note ? note : "",
6340 instance,
6341 publication_note_300 ? publication_note_300->version : 0,
6342 note_type,
6343 note ? note : "",
6345 instance,
6346 publication_note_400 ? publication_note_400->version : 0,
6347 note_type,
6348 note ? note : "");
6352 * Returns 'calendarData' XML part with WorkingHours for publication.
6353 * Must be g_free'd after use.
6355 static gchar *
6356 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
6358 struct sipe_ews* ews = sip->ews;
6360 /* key is <category><instance><container> */
6361 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
6362 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
6363 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
6364 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
6365 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
6366 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
6368 struct sipe_publication *publication_cal_1 =
6369 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
6370 struct sipe_publication *publication_cal_100 =
6371 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
6372 struct sipe_publication *publication_cal_200 =
6373 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
6374 struct sipe_publication *publication_cal_300 =
6375 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
6376 struct sipe_publication *publication_cal_400 =
6377 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
6378 struct sipe_publication *publication_cal_32000 =
6379 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
6381 const char *n1 = ews->working_hours_xml_str;
6382 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
6384 g_free(key_cal_1);
6385 g_free(key_cal_100);
6386 g_free(key_cal_200);
6387 g_free(key_cal_300);
6388 g_free(key_cal_400);
6389 g_free(key_cal_32000);
6391 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
6392 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
6393 return NULL;
6396 if (sipe_is_equal(n1, n2))
6398 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
6399 return NULL; /* nothing to update */
6402 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
6403 /* 1 */
6404 publication_cal_1 ? publication_cal_1->version : 0,
6405 ews->email,
6406 ews->working_hours_xml_str,
6407 /* 100 - Public */
6408 publication_cal_100 ? publication_cal_100->version : 0,
6409 /* 200 - Company */
6410 publication_cal_200 ? publication_cal_200->version : 0,
6411 ews->email,
6412 ews->working_hours_xml_str,
6413 /* 300 - Team */
6414 publication_cal_300 ? publication_cal_300->version : 0,
6415 ews->email,
6416 ews->working_hours_xml_str,
6417 /* 400 - Personal */
6418 publication_cal_400 ? publication_cal_400->version : 0,
6419 ews->email,
6420 ews->working_hours_xml_str,
6421 /* 32000 - Blocked */
6422 publication_cal_32000 ? publication_cal_32000->version : 0
6427 * Returns 'calendarData' XML part with FreeBusy for publication.
6428 * Must be g_free'd after use.
6430 static gchar *
6431 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
6433 struct sipe_ews* ews = sip->ews;
6434 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
6435 char *fb_start_str;
6436 char *free_busy_base64;
6437 const char *st;
6438 const char *fb;
6439 char *res;
6441 /* key is <category><instance><container> */
6442 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
6443 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
6444 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
6445 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
6446 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
6447 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
6449 struct sipe_publication *publication_cal_1 =
6450 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
6451 struct sipe_publication *publication_cal_100 =
6452 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
6453 struct sipe_publication *publication_cal_200 =
6454 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
6455 struct sipe_publication *publication_cal_300 =
6456 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
6457 struct sipe_publication *publication_cal_400 =
6458 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
6459 struct sipe_publication *publication_cal_32000 =
6460 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
6462 g_free(key_cal_1);
6463 g_free(key_cal_100);
6464 g_free(key_cal_200);
6465 g_free(key_cal_300);
6466 g_free(key_cal_400);
6467 g_free(key_cal_32000);
6469 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
6470 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
6471 return NULL;
6474 fb_start_str = g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN, gmtime(&ews->fb_start)));
6475 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6477 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
6478 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
6480 if (sipe_is_equal(st, fb_start_str) && sipe_is_equal(fb, free_busy_base64))
6482 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
6483 return NULL; /* nothing to update */
6486 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
6487 /* 1 */
6488 cal_data_instance,
6489 publication_cal_1 ? publication_cal_1->version : 0,
6490 /* 100 - Public */
6491 cal_data_instance,
6492 publication_cal_100 ? publication_cal_100->version : 0,
6493 /* 200 - Company */
6494 cal_data_instance,
6495 publication_cal_200 ? publication_cal_200->version : 0,
6496 ews->email,
6497 fb_start_str,
6498 free_busy_base64,
6499 /* 300 - Team */
6500 cal_data_instance,
6501 publication_cal_300 ? publication_cal_300->version : 0,
6502 ews->email,
6503 fb_start_str,
6504 free_busy_base64,
6505 /* 400 - Personal */
6506 cal_data_instance,
6507 publication_cal_400 ? publication_cal_400->version : 0,
6508 ews->email,
6509 fb_start_str,
6510 free_busy_base64,
6511 /* 32000 - Blocked */
6512 cal_data_instance,
6513 publication_cal_32000 ? publication_cal_32000->version : 0
6516 g_free(fb_start_str);
6517 g_free(free_busy_base64);
6518 return res;
6521 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
6523 gchar *uri;
6524 gchar *doc;
6525 gchar *tmp;
6526 gchar *hdr;
6528 uri = sip_uri_self(sip);
6529 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
6530 uri,
6531 publications);
6533 tmp = get_contact(sip);
6534 hdr = g_strdup_printf("Contact: %s\r\n"
6535 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
6537 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
6539 g_free(tmp);
6540 g_free(hdr);
6541 g_free(uri);
6542 g_free(doc);
6545 static void
6546 send_publish_category_initial(struct sipe_account_data *sip)
6548 gchar *pub_device = sipe_publish_get_category_device(sip);
6549 gchar *pub_machine = sipe_publish_get_category_state_machine(sip);
6550 gchar *publications = g_strdup_printf("%s%s",
6551 pub_device,
6552 pub_machine ? pub_machine : "");
6553 g_free(pub_device);
6554 g_free(pub_machine);
6556 send_presence_publish(sip, publications);
6557 g_free(publications);
6560 static void
6561 send_presence_category_publish(struct sipe_account_data *sip,
6562 const char *note)
6565 * Whether user manually changed status or
6566 * it was changed automatically due to user
6567 * became inactive/active again
6569 gboolean is_machine = (sip->was_idle && !sip->is_idle) || (!sip->was_idle && sip->is_idle);
6570 gchar *pub_state = is_machine ? sipe_publish_get_category_state_machine(sip) :
6571 sipe_publish_get_category_state_user(sip);
6572 gchar *pub_note = sipe_publish_get_category_note(sip, note, "personal");
6573 gchar *publications;
6575 if (!pub_state && !pub_note) {
6576 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
6577 return;
6580 publications = g_strdup_printf("%s%s",
6581 pub_state ? pub_state : "",
6582 pub_note ? pub_note : "");
6584 purple_debug_info("sipe", "send_presence_category_publish: sip->status: %s sip->is_idle:%s sip->was_idle:%s\n",
6585 sip->status, sip->is_idle ? "Y" : "N", sip->was_idle ? "Y" : "N");
6587 g_free(pub_state);
6588 g_free(pub_note);
6590 send_presence_publish(sip, publications);
6591 g_free(publications);
6595 * Publishes self status
6596 * based on own calendar information.
6598 * For 2007+
6600 void
6601 publish_calendar_status_self(struct sipe_account_data *sip)
6603 struct sipe_cal_event* event = NULL;
6604 gchar *pub_cal_working_hours = NULL;
6605 gchar *pub_cal_free_busy = NULL;
6606 gchar *pub_calendar = NULL;
6607 gchar *pub_calendar2 = NULL;
6608 gchar *pub_oof_note = NULL;
6609 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
6611 if (sip->ews && sip->ews->cal_events) {
6612 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
6615 if (!event) {
6616 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
6617 } else {
6618 char *desc = sipe_cal_event_describe(event);
6619 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
6620 g_free(desc);
6623 /* Logic
6624 if OOF
6625 OOF publish, Busy clean
6626 ilse if Busy
6627 OOF clean, Busy publish
6628 else
6629 OOF clean, Busy clean
6631 if (event && event->cal_status == SIPE_CAL_OOF) {
6632 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
6633 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
6634 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
6635 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
6636 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
6637 } else {
6638 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
6639 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
6642 if (sip->ews && sip->ews->oof_note) {
6643 pub_oof_note = NULL;//sipe_publish_get_category_note(sip, sip->ews->oof_note, "OOF");
6646 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
6647 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
6649 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
6650 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
6651 } else {
6652 gchar *publications = g_strdup_printf("%s%s%s%s%s",
6653 pub_cal_working_hours ? pub_cal_working_hours : "",
6654 pub_cal_free_busy ? pub_cal_free_busy : "",
6655 pub_calendar ? pub_calendar : "",
6656 pub_calendar2 ? pub_calendar2 : "",
6657 pub_oof_note ? pub_oof_note : "");
6659 send_presence_publish(sip, publications);
6660 g_free(publications);
6663 g_free(pub_cal_working_hours);
6664 g_free(pub_cal_free_busy);
6665 g_free(pub_calendar);
6666 g_free(pub_calendar2);
6667 g_free(pub_oof_note);
6669 /* repeat scheduling */
6670 sipe_sched_calendar_status_self_publish(sip, time(NULL));
6673 static void send_presence_status(struct sipe_account_data *sip)
6675 PurpleStatus * status = purple_account_get_active_status(sip->account);
6676 const gchar *note;
6677 if (!status) return;
6679 note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
6680 purple_debug_info("sipe", "send_presence_status: note: '%s'\n", note ? note : "");
6682 if (sip->ocs2007) {
6683 send_presence_category_publish(sip, note);
6684 } else {
6685 send_presence_soap(sip, note, FALSE);
6689 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
6691 gboolean found = FALSE;
6692 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
6693 if (msg->response == 0) { /* request */
6694 if (!strcmp(msg->method, "MESSAGE")) {
6695 process_incoming_message(sip, msg);
6696 found = TRUE;
6697 } else if (!strcmp(msg->method, "NOTIFY")) {
6698 purple_debug_info("sipe","send->process_incoming_notify\n");
6699 process_incoming_notify(sip, msg, TRUE, FALSE);
6700 found = TRUE;
6701 } else if (!strcmp(msg->method, "BENOTIFY")) {
6702 purple_debug_info("sipe","send->process_incoming_benotify\n");
6703 process_incoming_notify(sip, msg, TRUE, TRUE);
6704 found = TRUE;
6705 } else if (!strcmp(msg->method, "INVITE")) {
6706 process_incoming_invite(sip, msg);
6707 found = TRUE;
6708 } else if (!strcmp(msg->method, "REFER")) {
6709 process_incoming_refer(sip, msg);
6710 found = TRUE;
6711 } else if (!strcmp(msg->method, "OPTIONS")) {
6712 process_incoming_options(sip, msg);
6713 found = TRUE;
6714 } else if (!strcmp(msg->method, "INFO")) {
6715 process_incoming_info(sip, msg);
6716 found = TRUE;
6717 } else if (!strcmp(msg->method, "ACK")) {
6718 // ACK's don't need any response
6719 found = TRUE;
6720 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
6721 // LCS 2005 sends us these - just respond 200 OK
6722 found = TRUE;
6723 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6724 } else if (!strcmp(msg->method, "BYE")) {
6725 process_incoming_bye(sip, msg);
6726 found = TRUE;
6727 } else {
6728 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
6730 } else { /* response */
6731 struct transaction *trans = transactions_find(sip, msg);
6732 if (trans) {
6733 if (msg->response == 407) {
6734 gchar *resend, *auth, *ptmp;
6736 if (sip->proxy.retries > 30) return;
6737 sip->proxy.retries++;
6738 /* do proxy authentication */
6740 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
6742 fill_auth(ptmp, &sip->proxy);
6743 auth = auth_header(sip, &sip->proxy, trans->msg);
6744 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
6745 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
6746 g_free(auth);
6747 resend = sipmsg_to_string(trans->msg);
6748 /* resend request */
6749 sendout_pkt(sip->gc, resend);
6750 g_free(resend);
6751 } else {
6752 if (msg->response < 200) {
6753 /* ignore provisional response */
6754 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
6755 } else {
6756 sip->proxy.retries = 0;
6757 if (!strcmp(trans->msg->method, "REGISTER")) {
6758 if (msg->response == 401)
6760 sip->registrar.retries++;
6762 else
6764 sip->registrar.retries = 0;
6766 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
6767 } else {
6768 if (msg->response == 401) {
6769 gchar *resend, *auth, *ptmp;
6771 if (sip->registrar.retries > 4) return;
6772 sip->registrar.retries++;
6774 #ifdef USE_KERBEROS
6775 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
6776 #endif
6777 ptmp = sipmsg_find_auth_header(msg, "NTLM");
6778 #ifdef USE_KERBEROS
6779 } else {
6780 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
6782 #endif
6784 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp);
6786 fill_auth(ptmp, &sip->registrar);
6787 auth = auth_header(sip, &sip->registrar, trans->msg);
6788 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
6789 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
6791 //sipmsg_remove_header_now(trans->msg, "Authorization");
6792 //sipmsg_add_header(trans->msg, "Authorization", auth);
6793 g_free(auth);
6794 resend = sipmsg_to_string(trans->msg);
6795 /* resend request */
6796 sendout_pkt(sip->gc, resend);
6797 g_free(resend);
6801 if (trans->callback) {
6802 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
6803 /* call the callback to process response*/
6804 (trans->callback)(sip, msg, trans);
6807 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
6808 transactions_remove(sip, trans);
6812 found = TRUE;
6813 } else {
6814 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
6817 if (!found) {
6818 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
6822 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
6824 char *cur;
6825 char *dummy;
6826 char *tmp;
6827 struct sipmsg *msg;
6828 int restlen;
6829 cur = conn->inbuf;
6831 /* according to the RFC remove CRLF at the beginning */
6832 while (*cur == '\r' || *cur == '\n') {
6833 cur++;
6835 if (cur != conn->inbuf) {
6836 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
6837 conn->inbufused = strlen(conn->inbuf);
6840 /* Received a full Header? */
6841 sip->processing_input = TRUE;
6842 while (sip->processing_input &&
6843 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
6844 time_t currtime = time(NULL);
6845 cur += 2;
6846 cur[0] = '\0';
6847 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
6848 g_free(tmp);
6849 msg = sipmsg_parse_header(conn->inbuf);
6850 cur[0] = '\r';
6851 cur += 2;
6852 restlen = conn->inbufused - (cur - conn->inbuf);
6853 if (msg && restlen >= msg->bodylen) {
6854 dummy = g_malloc(msg->bodylen + 1);
6855 memcpy(dummy, cur, msg->bodylen);
6856 dummy[msg->bodylen] = '\0';
6857 msg->body = dummy;
6858 cur += msg->bodylen;
6859 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
6860 conn->inbufused = strlen(conn->inbuf);
6861 } else {
6862 if (msg){
6863 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
6864 sipmsg_free(msg);
6866 return;
6869 /*if (msg->body) {
6870 purple_debug_info("sipe", "body:\n%s", msg->body);
6873 // Verify the signature before processing it
6874 if (sip->registrar.gssapi_context) {
6875 struct sipmsg_breakdown msgbd;
6876 gchar *signature_input_str;
6877 gchar *rspauth;
6878 msgbd.msg = msg;
6879 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
6880 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
6882 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
6884 if (rspauth != NULL) {
6885 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
6886 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
6887 process_input_message(sip, msg);
6888 } else {
6889 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
6890 purple_connection_error(sip->gc, _("Invalid message signature received"));
6891 sip->gc->wants_to_die = TRUE;
6893 } else if (msg->response == 401) {
6894 purple_connection_error(sip->gc, _("Wrong password"));
6895 sip->gc->wants_to_die = TRUE;
6897 g_free(signature_input_str);
6899 g_free(rspauth);
6900 sipmsg_breakdown_free(&msgbd);
6901 } else {
6902 process_input_message(sip, msg);
6905 sipmsg_free(msg);
6909 static void sipe_udp_process(gpointer data, gint source,
6910 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
6912 PurpleConnection *gc = data;
6913 struct sipe_account_data *sip = gc->proto_data;
6914 struct sipmsg *msg;
6915 int len;
6916 time_t currtime;
6918 static char buffer[65536];
6919 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
6920 buffer[len] = '\0';
6921 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
6922 msg = sipmsg_parse_msg(buffer);
6923 if (msg) process_input_message(sip, msg);
6927 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
6929 struct sipe_account_data *sip = gc->proto_data;
6930 PurpleSslConnection *gsc = sip->gsc;
6932 purple_debug_error("sipe", "%s",debug);
6933 purple_connection_error(gc, msg);
6935 /* Invalidate this connection. Next send will open a new one */
6936 if (gsc) {
6937 connection_remove(sip, gsc->fd);
6938 purple_ssl_close(gsc);
6940 sip->gsc = NULL;
6941 sip->fd = -1;
6944 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
6945 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
6947 PurpleConnection *gc = data;
6948 struct sipe_account_data *sip;
6949 struct sip_connection *conn;
6950 int readlen, len;
6951 gboolean firstread = TRUE;
6953 /* NOTE: This check *IS* necessary */
6954 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
6955 purple_ssl_close(gsc);
6956 return;
6959 sip = gc->proto_data;
6960 conn = connection_find(sip, gsc->fd);
6961 if (conn == NULL) {
6962 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
6963 gc->wants_to_die = TRUE;
6964 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
6965 return;
6968 /* Read all available data from the SSL connection */
6969 do {
6970 /* Increase input buffer size as needed */
6971 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
6972 conn->inbuflen += SIMPLE_BUF_INC;
6973 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
6974 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
6977 /* Try to read as much as there is space left in the buffer */
6978 readlen = conn->inbuflen - conn->inbufused - 1;
6979 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
6981 if (len < 0 && errno == EAGAIN) {
6982 /* Try again later */
6983 return;
6984 } else if (len < 0) {
6985 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
6986 return;
6987 } else if (firstread && (len == 0)) {
6988 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
6989 return;
6992 conn->inbufused += len;
6993 firstread = FALSE;
6995 /* Equivalence indicates that there is possibly more data to read */
6996 } while (len == readlen);
6998 conn->inbuf[conn->inbufused] = '\0';
6999 process_input(sip, conn);
7003 static void sipe_input_cb(gpointer data, gint source,
7004 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7006 PurpleConnection *gc = data;
7007 struct sipe_account_data *sip = gc->proto_data;
7008 int len;
7009 struct sip_connection *conn = connection_find(sip, source);
7010 if (!conn) {
7011 purple_debug_error("sipe", "Connection not found!\n");
7012 return;
7015 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
7016 conn->inbuflen += SIMPLE_BUF_INC;
7017 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
7020 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
7022 if (len < 0 && errno == EAGAIN)
7023 return;
7024 else if (len <= 0) {
7025 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7026 connection_remove(sip, source);
7027 if (sip->fd == source) sip->fd = -1;
7028 return;
7031 conn->inbufused += len;
7032 conn->inbuf[conn->inbufused] = '\0';
7034 process_input(sip, conn);
7037 /* Callback for new connections on incoming TCP port */
7038 static void sipe_newconn_cb(gpointer data, gint source,
7039 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7041 PurpleConnection *gc = data;
7042 struct sipe_account_data *sip = gc->proto_data;
7043 struct sip_connection *conn;
7045 int newfd = accept(source, NULL, NULL);
7047 conn = connection_create(sip, newfd);
7049 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7052 static void login_cb(gpointer data, gint source,
7053 SIPE_UNUSED_PARAMETER const gchar *error_message)
7055 PurpleConnection *gc = data;
7056 struct sipe_account_data *sip;
7057 struct sip_connection *conn;
7059 if (!PURPLE_CONNECTION_IS_VALID(gc))
7061 if (source >= 0)
7062 close(source);
7063 return;
7066 if (source < 0) {
7067 purple_connection_error(gc, _("Could not connect"));
7068 return;
7071 sip = gc->proto_data;
7072 sip->fd = source;
7073 sip->last_keepalive = time(NULL);
7075 conn = connection_create(sip, source);
7077 do_register(sip);
7079 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
7082 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
7083 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
7085 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
7086 if (sip == NULL) return;
7088 do_register(sip);
7091 static guint sipe_ht_hash_nick(const char *nick)
7093 char *lc = g_utf8_strdown(nick, -1);
7094 guint bucket = g_str_hash(lc);
7095 g_free(lc);
7097 return bucket;
7100 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
7102 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
7105 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
7107 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7109 sip->listen_data = NULL;
7111 if (listenfd == -1) {
7112 purple_connection_error(sip->gc, _("Could not create listen socket"));
7113 return;
7116 sip->fd = listenfd;
7118 sip->listenport = purple_network_get_port_from_fd(sip->fd);
7119 sip->listenfd = sip->fd;
7121 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
7123 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
7124 do_register(sip);
7127 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
7128 SIPE_UNUSED_PARAMETER const char *error_message)
7130 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7132 sip->query_data = NULL;
7134 if (!hosts || !hosts->data) {
7135 purple_connection_error(sip->gc, _("Could not resolve hostname"));
7136 return;
7139 hosts = g_slist_remove(hosts, hosts->data);
7140 g_free(sip->serveraddr);
7141 sip->serveraddr = hosts->data;
7142 hosts = g_slist_remove(hosts, hosts->data);
7143 while (hosts) {
7144 hosts = g_slist_remove(hosts, hosts->data);
7145 g_free(hosts->data);
7146 hosts = g_slist_remove(hosts, hosts->data);
7149 /* create socket for incoming connections */
7150 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
7151 sipe_udp_host_resolved_listen_cb, sip);
7152 if (sip->listen_data == NULL) {
7153 purple_connection_error(sip->gc, _("Could not create listen socket"));
7154 return;
7158 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
7159 PurpleSslErrorType error,
7160 gpointer data)
7162 PurpleConnection *gc = data;
7163 struct sipe_account_data *sip;
7165 /* If the connection is already disconnected, we don't need to do anything else */
7166 if (!PURPLE_CONNECTION_IS_VALID(gc))
7167 return;
7169 sip = gc->proto_data;
7170 sip->fd = -1;
7171 sip->gsc = NULL;
7173 switch(error) {
7174 case PURPLE_SSL_CONNECT_FAILED:
7175 purple_connection_error(gc, _("Connection failed"));
7176 break;
7177 case PURPLE_SSL_HANDSHAKE_FAILED:
7178 purple_connection_error(gc, _("SSL handshake failed"));
7179 break;
7180 case PURPLE_SSL_CERTIFICATE_INVALID:
7181 purple_connection_error(gc, _("SSL certificate invalid"));
7182 break;
7186 static void
7187 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
7189 struct sipe_account_data *sip = (struct sipe_account_data*) data;
7190 PurpleProxyConnectData *connect_data;
7192 sip->listen_data = NULL;
7194 sip->listenfd = listenfd;
7195 if (sip->listenfd == -1) {
7196 purple_connection_error(sip->gc, _("Could not create listen socket"));
7197 return;
7200 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
7201 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7202 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7203 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
7204 sipe_newconn_cb, sip->gc);
7205 purple_debug_info("sipe", "connecting to %s port %d\n",
7206 sip->realhostname, sip->realport);
7207 /* open tcp connection to the server */
7208 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
7209 sip->realport, login_cb, sip->gc);
7211 if (connect_data == NULL) {
7212 purple_connection_error(sip->gc, _("Could not create socket"));
7216 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
7218 PurpleAccount *account = sip->account;
7219 PurpleConnection *gc = sip->gc;
7221 if (port == 0) {
7222 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
7225 sip->realhostname = hostname;
7226 sip->realport = port;
7228 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
7229 hostname, port);
7231 /* TODO: is there a good default grow size? */
7232 if (sip->transport != SIPE_TRANSPORT_UDP)
7233 sip->txbuf = purple_circ_buffer_new(0);
7235 if (sip->transport == SIPE_TRANSPORT_TLS) {
7236 /* SSL case */
7237 if (!purple_ssl_is_supported()) {
7238 gc->wants_to_die = TRUE;
7239 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
7240 return;
7243 purple_debug_info("sipe", "using SSL\n");
7245 sip->gsc = purple_ssl_connect(account, hostname, port,
7246 login_cb_ssl, sipe_ssl_connect_failure, gc);
7247 if (sip->gsc == NULL) {
7248 purple_connection_error(gc, _("Could not create SSL context"));
7249 return;
7251 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
7252 /* UDP case */
7253 purple_debug_info("sipe", "using UDP\n");
7255 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
7256 if (sip->query_data == NULL) {
7257 purple_connection_error(gc, _("Could not resolve hostname"));
7259 } else {
7260 /* TCP case */
7261 purple_debug_info("sipe", "using TCP\n");
7262 /* create socket for incoming connections */
7263 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
7264 sipe_tcp_connect_listen_cb, sip);
7265 if (sip->listen_data == NULL) {
7266 purple_connection_error(gc, _("Could not create listen socket"));
7267 return;
7272 /* Service list for autodection */
7273 static const struct sipe_service_data service_autodetect[] = {
7274 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
7275 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
7276 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
7277 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
7278 { NULL, NULL, 0 }
7281 /* Service list for SSL/TLS */
7282 static const struct sipe_service_data service_tls[] = {
7283 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
7284 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
7285 { NULL, NULL, 0 }
7288 /* Service list for TCP */
7289 static const struct sipe_service_data service_tcp[] = {
7290 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
7291 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
7292 { NULL, NULL, 0 }
7295 /* Service list for UDP */
7296 static const struct sipe_service_data service_udp[] = {
7297 { "sip", "udp", SIPE_TRANSPORT_UDP },
7298 { NULL, NULL, 0 }
7301 static void srvresolved(PurpleSrvResponse *, int, gpointer);
7302 static void resolve_next_service(struct sipe_account_data *sip,
7303 const struct sipe_service_data *start)
7305 if (start) {
7306 sip->service_data = start;
7307 } else {
7308 sip->service_data++;
7309 if (sip->service_data->service == NULL) {
7310 gchar *hostname;
7311 /* Try connecting to the SIP hostname directly */
7312 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
7313 if (sip->auto_transport) {
7314 // If SSL is supported, default to using it; OCS servers aren't configured
7315 // by default to accept TCP
7316 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
7317 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
7318 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
7321 hostname = g_strdup(sip->sipdomain);
7322 create_connection(sip, hostname, 0);
7323 return;
7327 /* Try to resolve next service */
7328 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
7329 sip->service_data->transport,
7330 sip->sipdomain,
7331 srvresolved, sip);
7334 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
7336 struct sipe_account_data *sip = data;
7338 sip->srv_query_data = NULL;
7340 /* find the host to connect to */
7341 if (results) {
7342 gchar *hostname = g_strdup(resp->hostname);
7343 int port = resp->port;
7344 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
7345 hostname, port);
7346 g_free(resp);
7348 sip->transport = sip->service_data->type;
7350 create_connection(sip, hostname, port);
7351 } else {
7352 resolve_next_service(sip, NULL);
7356 static void sipe_login(PurpleAccount *account)
7358 PurpleConnection *gc;
7359 struct sipe_account_data *sip;
7360 gchar **signinname_login, **userserver;
7361 const char *transport;
7362 const char *email;
7364 const char *username = purple_account_get_username(account);
7365 gc = purple_account_get_connection(account);
7367 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
7369 if (strpbrk(username, "\t\v\r\n") != NULL) {
7370 gc->wants_to_die = TRUE;
7371 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
7372 return;
7375 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
7376 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
7377 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
7378 sip->gc = gc;
7379 sip->account = account;
7380 sip->reregister_set = FALSE;
7381 sip->reauthenticate_set = FALSE;
7382 sip->subscribed = FALSE;
7383 sip->subscribed_buddies = FALSE;
7384 sip->initial_state_published = FALSE;
7386 /* username format: <username>,[<optional login>] */
7387 signinname_login = g_strsplit(username, ",", 2);
7388 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
7390 /* ensure that username format is name@domain */
7391 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
7392 g_strfreev(signinname_login);
7393 gc->wants_to_die = TRUE;
7394 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
7395 return;
7397 sip->username = g_strdup(signinname_login[0]);
7399 /* ensure that email format is name@domain if provided */
7400 email = purple_account_get_string(sip->account, "email", NULL);
7401 if (!is_empty(email) &&
7402 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
7404 gc->wants_to_die = TRUE;
7405 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
7406 return;
7408 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
7410 /* login name specified? */
7411 if (signinname_login[1] && strlen(signinname_login[1])) {
7412 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
7413 gboolean has_domain = domain_user[1] != NULL;
7414 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
7415 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
7416 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
7417 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
7418 sip->authdomain ? sip->authdomain : "", sip->authuser);
7419 g_strfreev(domain_user);
7422 userserver = g_strsplit(signinname_login[0], "@", 2);
7423 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
7424 purple_connection_set_display_name(gc, userserver[0]);
7425 sip->sipdomain = g_strdup(userserver[1]);
7426 g_strfreev(userserver);
7427 g_strfreev(signinname_login);
7429 if (strchr(sip->username, ' ') != NULL) {
7430 gc->wants_to_die = TRUE;
7431 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
7432 return;
7435 sip->password = g_strdup(purple_connection_get_password(gc));
7437 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
7438 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
7439 g_free, (GDestroyNotify)g_hash_table_destroy);
7440 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
7441 g_free, (GDestroyNotify)sipe_subscription_free);
7443 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
7445 sip->status = g_strdup(purple_status_get_id(purple_account_get_active_status(account)));
7447 sip->auto_transport = FALSE;
7448 transport = purple_account_get_string(account, "transport", "auto");
7449 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
7450 if (userserver[0]) {
7451 /* Use user specified server[:port] */
7452 int port = 0;
7454 if (userserver[1])
7455 port = atoi(userserver[1]);
7457 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
7458 userserver[0], port);
7460 if (strcmp(transport, "auto") == 0) {
7461 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
7462 } else if (strcmp(transport, "tls") == 0) {
7463 sip->transport = SIPE_TRANSPORT_TLS;
7464 } else if (strcmp(transport, "tcp") == 0) {
7465 sip->transport = SIPE_TRANSPORT_TCP;
7466 } else {
7467 sip->transport = SIPE_TRANSPORT_UDP;
7470 create_connection(sip, g_strdup(userserver[0]), port);
7471 } else {
7472 /* Server auto-discovery */
7473 if (strcmp(transport, "auto") == 0) {
7474 sip->auto_transport = TRUE;
7475 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
7476 } else if (strcmp(transport, "tls") == 0) {
7477 resolve_next_service(sip, service_tls);
7478 } else if (strcmp(transport, "tcp") == 0) {
7479 resolve_next_service(sip, service_tcp);
7480 } else {
7481 resolve_next_service(sip, service_udp);
7484 g_strfreev(userserver);
7487 static void sipe_connection_cleanup(struct sipe_account_data *sip)
7489 connection_free_all(sip);
7491 g_free(sip->epid);
7492 sip->epid = NULL;
7494 if (sip->query_data != NULL)
7495 purple_dnsquery_destroy(sip->query_data);
7496 sip->query_data = NULL;
7498 if (sip->srv_query_data != NULL)
7499 purple_srv_cancel(sip->srv_query_data);
7500 sip->srv_query_data = NULL;
7502 if (sip->listen_data != NULL)
7503 purple_network_listen_cancel(sip->listen_data);
7504 sip->listen_data = NULL;
7506 if (sip->gsc != NULL)
7507 purple_ssl_close(sip->gsc);
7508 sip->gsc = NULL;
7510 sipe_auth_free(&sip->registrar);
7511 sipe_auth_free(&sip->proxy);
7513 if (sip->txbuf)
7514 purple_circ_buffer_destroy(sip->txbuf);
7515 sip->txbuf = NULL;
7517 g_free(sip->realhostname);
7518 sip->realhostname = NULL;
7520 g_free(sip->server_version);
7521 sip->server_version = NULL;
7523 if (sip->listenpa)
7524 purple_input_remove(sip->listenpa);
7525 sip->listenpa = 0;
7526 if (sip->tx_handler)
7527 purple_input_remove(sip->tx_handler);
7528 sip->tx_handler = 0;
7529 if (sip->resendtimeout)
7530 purple_timeout_remove(sip->resendtimeout);
7531 sip->resendtimeout = 0;
7532 if (sip->timeouts) {
7533 GSList *entry = sip->timeouts;
7534 while (entry) {
7535 struct scheduled_action *sched_action = entry->data;
7536 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
7537 purple_timeout_remove(sched_action->timeout_handler);
7538 if (sched_action->destroy) {
7539 (*sched_action->destroy)(sched_action->payload);
7541 g_free(sched_action->name);
7542 g_free(sched_action);
7543 entry = entry->next;
7546 g_slist_free(sip->timeouts);
7548 if (sip->allow_events) {
7549 GSList *entry = sip->allow_events;
7550 while (entry) {
7551 g_free(entry->data);
7552 entry = entry->next;
7555 g_slist_free(sip->allow_events);
7557 if (sip->containers) {
7558 GSList *entry = sip->containers;
7559 while (entry) {
7560 free_container((struct sipe_container *)entry->data);
7561 entry = entry->next;
7564 g_slist_free(sip->containers);
7566 if (sip->contact)
7567 g_free(sip->contact);
7568 sip->contact = NULL;
7569 if (sip->regcallid)
7570 g_free(sip->regcallid);
7571 sip->regcallid = NULL;
7573 if (sip->serveraddr)
7574 g_free(sip->serveraddr);
7575 sip->serveraddr = NULL;
7577 if (sip->focus_factory_uri)
7578 g_free(sip->focus_factory_uri);
7579 sip->focus_factory_uri = NULL;
7581 sip->fd = -1;
7582 sip->processing_input = FALSE;
7584 if (sip->ews) {
7585 sipe_ews_free(sip->ews);
7587 sip->ews = NULL;
7589 if (sip->email)
7590 g_free(sip->email);
7591 sip->email = NULL;
7595 * A callback for g_hash_table_foreach_remove
7597 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
7598 SIPE_UNUSED_PARAMETER gpointer user_data)
7600 sipe_free_buddy((struct sipe_buddy *) buddy);
7602 /* We must return TRUE as the key/value have already been deleted */
7603 return(TRUE);
7606 static void sipe_close(PurpleConnection *gc)
7608 struct sipe_account_data *sip = gc->proto_data;
7610 if (sip) {
7611 /* leave all conversations */
7612 sipe_session_close_all(sip);
7613 sipe_session_remove_all(sip);
7615 if (sip->csta) {
7616 sip_csta_close(sip);
7619 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
7620 /* unsubscribe all */
7621 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
7623 /* unregister */
7624 do_register_exp(sip, 0);
7627 sipe_connection_cleanup(sip);
7628 g_free(sip->sipdomain);
7629 g_free(sip->username);
7630 g_free(sip->password);
7631 g_free(sip->authdomain);
7632 g_free(sip->authuser);
7633 g_free(sip->status);
7635 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
7636 g_hash_table_destroy(sip->buddies);
7637 g_hash_table_destroy(sip->our_publications);
7638 g_hash_table_destroy(sip->subscriptions);
7640 if (sip->groups) {
7641 GSList *entry = sip->groups;
7642 while (entry) {
7643 struct sipe_group *group = entry->data;
7644 g_free(group->name);
7645 g_free(group);
7646 entry = entry->next;
7649 g_slist_free(sip->groups);
7651 if (sip->our_publication_keys) {
7652 GSList *entry = sip->our_publication_keys;
7653 while (entry) {
7654 g_free(entry->data);
7655 entry = entry->next;
7658 g_slist_free(sip->our_publication_keys);
7660 while (sip->transactions)
7661 transactions_remove(sip, sip->transactions->data);
7663 g_free(gc->proto_data);
7664 gc->proto_data = NULL;
7667 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
7668 SIPE_UNUSED_PARAMETER void *user_data)
7670 PurpleAccount *acct = purple_connection_get_account(gc);
7671 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
7672 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
7673 if (conv == NULL)
7674 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
7675 purple_conversation_present(conv);
7676 g_free(id);
7679 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
7680 SIPE_UNUSED_PARAMETER void *user_data)
7683 purple_blist_request_add_buddy(purple_connection_get_account(gc),
7684 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
7687 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
7688 SIPE_UNUSED_PARAMETER struct transaction *trans)
7690 PurpleNotifySearchResults *results;
7691 PurpleNotifySearchColumn *column;
7692 xmlnode *searchResults;
7693 xmlnode *mrow;
7694 int match_count = 0;
7695 gboolean more = FALSE;
7696 gchar *secondary;
7698 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
7700 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
7701 if (!searchResults) {
7702 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
7703 return FALSE;
7706 results = purple_notify_searchresults_new();
7708 if (results == NULL) {
7709 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
7710 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
7712 xmlnode_free(searchResults);
7713 return FALSE;
7716 column = purple_notify_searchresults_column_new(_("User name"));
7717 purple_notify_searchresults_column_add(results, column);
7719 column = purple_notify_searchresults_column_new(_("Name"));
7720 purple_notify_searchresults_column_add(results, column);
7722 column = purple_notify_searchresults_column_new(_("Company"));
7723 purple_notify_searchresults_column_add(results, column);
7725 column = purple_notify_searchresults_column_new(_("Country"));
7726 purple_notify_searchresults_column_add(results, column);
7728 column = purple_notify_searchresults_column_new(_("Email"));
7729 purple_notify_searchresults_column_add(results, column);
7731 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
7732 GList *row = NULL;
7734 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
7735 row = g_list_append(row, g_strdup(uri_parts[1]));
7736 g_strfreev(uri_parts);
7738 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
7739 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
7740 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
7741 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
7743 purple_notify_searchresults_row_add(results, row);
7744 match_count++;
7747 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
7748 char *data = xmlnode_get_data_unescaped(mrow);
7749 more = (g_strcasecmp(data, "true") == 0);
7750 g_free(data);
7753 secondary = g_strdup_printf(
7754 dngettext(GETTEXT_PACKAGE,
7755 "Found %d contact%s:",
7756 "Found %d contacts%s:", match_count),
7757 match_count, more ? _(" (more matched your query)") : "");
7759 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
7760 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
7761 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
7763 g_free(secondary);
7764 xmlnode_free(searchResults);
7765 return TRUE;
7768 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
7770 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
7771 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
7772 unsigned i = 0;
7774 do {
7775 PurpleRequestField *field = entries->data;
7776 const char *id = purple_request_field_get_id(field);
7777 const char *value = purple_request_field_string_get_value(field);
7779 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
7781 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
7782 } while ((entries = g_list_next(entries)) != NULL);
7783 attrs[i] = NULL;
7785 if (i > 0) {
7786 struct sipe_account_data *sip = gc->proto_data;
7787 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
7788 gchar *query = g_strjoinv(NULL, attrs);
7789 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
7790 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
7791 send_soap_request_with_cb(sip, domain_uri, body,
7792 (TransCallback) process_search_contact_response, NULL);
7793 g_free(domain_uri);
7794 g_free(body);
7795 g_free(query);
7798 g_strfreev(attrs);
7801 static void sipe_show_find_contact(PurplePluginAction *action)
7803 PurpleConnection *gc = (PurpleConnection *) action->context;
7804 PurpleRequestFields *fields;
7805 PurpleRequestFieldGroup *group;
7806 PurpleRequestField *field;
7808 fields = purple_request_fields_new();
7809 group = purple_request_field_group_new(NULL);
7810 purple_request_fields_add_group(fields, group);
7812 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
7813 purple_request_field_group_add_field(group, field);
7814 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
7815 purple_request_field_group_add_field(group, field);
7816 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
7817 purple_request_field_group_add_field(group, field);
7818 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
7819 purple_request_field_group_add_field(group, field);
7821 purple_request_fields(gc,
7822 _("Search"),
7823 _("Search for a contact"),
7824 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
7825 fields,
7826 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
7827 _("_Cancel"), NULL,
7828 purple_connection_get_account(gc), NULL, NULL, gc);
7831 static void sipe_show_about_plugin(PurplePluginAction *action)
7833 PurpleConnection *gc = (PurpleConnection *) action->context;
7834 const char *txt =
7835 "<b><font size=\"+1\">Sipe " SIPE_VERSION "</font></b><br/>"
7836 "<br/>"
7837 "A third-party plugin implementing extended version of SIP/SIMPLE used by various products:<br/>"
7838 "<li> - MS Office Communications Server 2007 (R2)</li><br/>"
7839 "<li> - MS Live Communications Server 2005/2003</li><br/>"
7840 "<li> - Reuters Messaging</li><br/>"
7841 "<br/>"
7842 "Home: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
7843 "Support: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">Help Forum</a><br/>"
7844 "License: GPLv2<br/>"
7845 "<br/>"
7846 "We support users in the following organizations to mention a few:<br/>"
7847 " - CERN<br/>"
7848 " - Reuters Messaging network<br/>"
7849 " - Deutsche Bank<br/>"
7850 " - Merrill Lynch<br/>"
7851 " - Wachovia<br/>"
7852 " - Siemens<br/>"
7853 " - Alcatel-Lucent<br/>"
7854 " - BT<br/>"
7855 " - Nokia<br/>"
7856 " - HP<br/>"
7857 "<br/>"
7858 "<b>Authors:</b><br/>"
7859 " - Anibal Avelar<br/>"
7860 " - Gabriel Burt<br/>"
7861 " - Stefan Becker<br/>"
7862 " - pier11<br/>";
7864 purple_notify_formatted(gc, NULL, " ", NULL, txt, NULL, NULL);
7867 static void sipe_republish_calendar(PurplePluginAction *action)
7869 PurpleConnection *gc = (PurpleConnection *) action->context;
7870 struct sipe_account_data *sip = gc->proto_data;
7872 sipe_update_calendar(sip);
7875 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
7876 gpointer context)
7878 PurpleConnection *gc = (PurpleConnection *)context;
7879 struct sipe_account_data *sip = gc->proto_data;
7880 GList *menu = NULL;
7881 PurplePluginAction *act;
7882 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
7884 act = purple_plugin_action_new(_("About SIPE plugin"), sipe_show_about_plugin);
7885 menu = g_list_prepend(menu, act);
7887 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
7888 menu = g_list_prepend(menu, act);
7890 if (!strcmp(calendar, "EXCH")) {
7891 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
7892 menu = g_list_prepend(menu, act);
7895 menu = g_list_reverse(menu);
7897 return menu;
7900 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
7904 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
7906 return TRUE;
7910 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
7912 return TRUE;
7916 static char *sipe_status_text(PurpleBuddy *buddy)
7918 struct sipe_account_data *sip;
7919 struct sipe_buddy *sbuddy;
7920 char *text = NULL;
7922 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
7923 if (sip) //happens on pidgin exit
7925 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
7926 if (sbuddy) {
7927 if (!is_empty(sbuddy->activity) && !is_empty(sbuddy->annotation))
7929 text = g_strdup_printf("%s. %s", sbuddy->activity, sbuddy->annotation);
7931 else if (!is_empty(sbuddy->activity))
7933 text = g_strdup(sbuddy->activity);
7935 else
7937 text = g_strdup(sbuddy->annotation);
7942 return text;
7945 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
7947 const PurplePresence *presence = purple_buddy_get_presence(buddy);
7948 const PurpleStatus *status = purple_presence_get_active_status(presence);
7949 struct sipe_account_data *sip;
7950 struct sipe_buddy *sbuddy;
7951 char *annotation = NULL;
7952 char *activity = NULL;
7953 char *calendar = NULL;
7954 char *meeting_subject = NULL;
7955 char *meeting_location = NULL;
7957 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
7958 if (sip) //happens on pidgin exit
7960 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
7961 if (sbuddy)
7963 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
7964 activity = sbuddy->activity;
7965 calendar = sipe_cal_get_description(sbuddy);
7966 meeting_subject = sbuddy->meeting_subject;
7967 meeting_location = sbuddy->meeting_location;
7971 //Layout
7972 if (purple_presence_is_online(presence))
7974 char *tmp = NULL;
7975 const char *status_str = activity && status && strcmp(purple_status_get_id(status), SIPE_STATUS_ID_ONPHONE) ?
7976 (tmp = g_strdup_printf("%s (%s)", purple_status_get_name(status), activity)) :
7977 purple_status_get_name(status);
7979 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
7980 g_free(tmp);
7982 if (purple_presence_is_online(presence) &&
7983 !is_empty(calendar))
7985 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
7987 g_free(calendar);
7988 if (!is_empty(meeting_location))
7990 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
7992 if (!is_empty(meeting_subject))
7994 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
7997 if (annotation)
7999 /* Tooltip does not know how to handle markup like <br> */
8000 gchar *s = annotation;
8001 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, annotation);
8002 while ((s = strchr(s, '<')) != NULL) {
8003 if (!g_ascii_strncasecmp(s, "<br>", 4)) {
8004 *s = '\n';
8005 strcpy(s + 1, s + 4);
8007 s++;
8009 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, annotation);
8011 purple_notify_user_info_add_pair(user_info, _("Note"), annotation);
8012 g_free(annotation);
8017 #if PURPLE_VERSION_CHECK(2,5,0)
8018 static GHashTable *
8019 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
8021 GHashTable *table;
8022 table = g_hash_table_new(g_str_hash, g_str_equal);
8023 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
8024 return table;
8026 #endif
8028 static PurpleBuddy *
8029 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
8031 PurpleBuddy *clone;
8032 const gchar *server_alias, *email;
8033 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
8035 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
8037 purple_blist_add_buddy(clone, NULL, group, NULL);
8039 server_alias = purple_buddy_get_server_alias(buddy);
8040 if (server_alias) {
8041 purple_blist_server_alias_buddy(clone, server_alias);
8044 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8045 if (email) {
8046 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
8049 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
8050 //for UI to update;
8051 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
8052 return clone;
8055 static void
8056 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
8058 PurpleBuddy *buddy, *b;
8059 PurpleConnection *gc;
8060 PurpleGroup * group = purple_find_group(group_name);
8062 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
8064 buddy = (PurpleBuddy *)node;
8066 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
8067 gc = purple_account_get_connection(buddy->account);
8069 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
8070 if (!b){
8071 b = purple_blist_add_buddy_clone(group, buddy);
8074 sipe_group_buddy(gc, buddy->name, NULL, group_name);
8077 static void
8078 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
8080 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8082 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
8084 /* 2007+ conference */
8085 if (sip->ocs2007)
8087 sipe_conf_add(sip, buddy->name);
8089 else /* 2005- multiparty chat */
8091 gchar *self = sip_uri_self(sip);
8092 struct sip_session *session;
8094 session = sipe_session_add_chat(sip);
8095 session->chat_title = sipe_chat_get_name(session->callid);
8096 session->roster_manager = g_strdup(self);
8098 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
8099 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
8100 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
8101 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
8103 g_free(self);
8107 static gboolean
8108 sipe_is_election_finished(struct sip_session *session)
8110 gboolean res = TRUE;
8112 SIPE_DIALOG_FOREACH {
8113 if (dialog->election_vote == 0) {
8114 res = FALSE;
8115 break;
8117 } SIPE_DIALOG_FOREACH_END;
8119 if (res) {
8120 session->is_voting_in_progress = FALSE;
8122 return res;
8125 static void
8126 sipe_election_start(struct sipe_account_data *sip,
8127 struct sip_session *session)
8129 int election_timeout;
8131 if (session->is_voting_in_progress) {
8132 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
8133 return;
8134 } else {
8135 session->is_voting_in_progress = TRUE;
8137 session->bid = rand();
8139 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
8141 SIPE_DIALOG_FOREACH {
8142 /* reset election_vote for each chat participant */
8143 dialog->election_vote = 0;
8145 /* send RequestRM to each chat participant*/
8146 sipe_send_election_request_rm(sip, dialog, session->bid);
8147 } SIPE_DIALOG_FOREACH_END;
8149 election_timeout = 15; /* sec */
8150 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
8154 * @param who a URI to whom to invite to chat
8156 void
8157 sipe_invite_to_chat(struct sipe_account_data *sip,
8158 struct sip_session *session,
8159 const gchar *who)
8161 /* a conference */
8162 if (session->focus_uri)
8164 sipe_invite_conf(sip, session, who);
8166 else /* a multi-party chat */
8168 gchar *self = sip_uri_self(sip);
8169 if (session->roster_manager) {
8170 if (!strcmp(session->roster_manager, self)) {
8171 sipe_invite(sip, session, who, NULL, NULL, FALSE);
8172 } else {
8173 sipe_refer(sip, session, who);
8175 } else {
8176 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
8178 session->pending_invite_queue = slist_insert_unique_sorted(
8179 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
8181 sipe_election_start(sip, session);
8183 g_free(self);
8187 void
8188 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
8189 struct sip_session *session)
8191 gchar *invitee;
8192 GSList *entry = session->pending_invite_queue;
8194 while (entry) {
8195 invitee = entry->data;
8196 sipe_invite_to_chat(sip, session, invitee);
8197 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
8198 g_free(invitee);
8202 static void
8203 sipe_election_result(struct sipe_account_data *sip,
8204 void *sess)
8206 struct sip_session *session = (struct sip_session *)sess;
8207 gchar *rival;
8208 gboolean has_won = TRUE;
8210 if (session->roster_manager) {
8211 purple_debug_info("sipe",
8212 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
8213 return;
8216 session->is_voting_in_progress = FALSE;
8218 SIPE_DIALOG_FOREACH {
8219 if (dialog->election_vote < 0) {
8220 has_won = FALSE;
8221 rival = dialog->with;
8222 break;
8224 } SIPE_DIALOG_FOREACH_END;
8226 if (has_won) {
8227 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
8229 session->roster_manager = sip_uri_self(sip);
8231 SIPE_DIALOG_FOREACH {
8232 /* send SetRM to each chat participant*/
8233 sipe_send_election_set_rm(sip, dialog);
8234 } SIPE_DIALOG_FOREACH_END;
8235 } else {
8236 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
8238 session->bid = 0;
8240 sipe_process_pending_invite_queue(sip, session);
8244 * For 2007+ conference only.
8246 static void
8247 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
8249 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8250 struct sip_session *session;
8252 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
8253 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
8255 session = sipe_session_find_chat_by_title(sip, chat_title);
8257 sipe_conf_modify_user_role(sip, session, buddy->name);
8261 * For 2007+ conference only.
8263 static void
8264 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
8266 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8267 struct sip_session *session;
8269 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
8270 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
8272 session = sipe_session_find_chat_by_title(sip, chat_title);
8274 sipe_conf_delete_user(sip, session, buddy->name);
8277 static void
8278 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
8280 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8281 struct sip_session *session;
8283 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
8284 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
8286 session = sipe_session_find_chat_by_title(sip, chat_title);
8288 sipe_invite_to_chat(sip, session, buddy->name);
8291 static void
8292 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
8294 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8296 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
8297 if (phone) {
8298 char *tel_uri = sip_to_tel_uri(phone);
8300 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
8301 sip_csta_make_call(sip, tel_uri);
8303 g_free(tel_uri);
8307 static void
8308 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
8310 const gchar *email;
8311 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
8313 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8314 if (email)
8316 char *mailto = g_strdup_printf("mailto:%s", email);
8317 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
8318 #ifndef _WIN32
8320 pid_t pid;
8321 char *const parmList[] = {"xdg-email", mailto, NULL};
8322 if ((pid = fork()) == -1)
8324 purple_debug_info("sipe", "fork() error\n");
8326 else if (pid == 0)
8328 execvp(parmList[0], parmList);
8329 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
8332 #else
8334 BOOL ret;
8335 _flushall();
8336 errno = 0;
8337 //@TODO resolve env variable %WINDIR% first
8338 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
8339 if (errno)
8341 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
8344 #endif
8346 g_free(mailto);
8348 else
8350 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
8355 * A menu which appear when right-clicking on buddy in contact list.
8357 static GList *
8358 sipe_buddy_menu(PurpleBuddy *buddy)
8360 PurpleBlistNode *g_node;
8361 PurpleGroup *group, *gr_parent;
8362 PurpleMenuAction *act;
8363 GList *menu = NULL;
8364 GList *menu_groups = NULL;
8365 struct sipe_account_data *sip = buddy->account->gc->proto_data;
8366 const char *email;
8367 const char *phone;
8368 const char *phone_disp_str;
8369 gchar *self = sip_uri_self(sip);
8371 SIPE_SESSION_FOREACH {
8372 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
8374 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
8376 PurpleConvChatBuddyFlags flags;
8377 PurpleConvChatBuddyFlags flags_us;
8379 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
8380 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
8381 if (session->focus_uri
8382 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
8383 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
8385 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
8386 act = purple_menu_action_new(label,
8387 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
8388 session->chat_title, NULL);
8389 g_free(label);
8390 menu = g_list_prepend(menu, act);
8393 if (session->focus_uri
8394 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
8396 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
8397 act = purple_menu_action_new(label,
8398 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
8399 session->chat_title, NULL);
8400 g_free(label);
8401 menu = g_list_prepend(menu, act);
8404 else
8406 if (!session->focus_uri
8407 || (session->focus_uri && !session->locked))
8409 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
8410 act = purple_menu_action_new(label,
8411 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
8412 session->chat_title, NULL);
8413 g_free(label);
8414 menu = g_list_prepend(menu, act);
8418 } SIPE_SESSION_FOREACH_END;
8420 act = purple_menu_action_new(_("New chat"),
8421 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
8422 NULL, NULL);
8423 menu = g_list_prepend(menu, act);
8425 if (sip->csta && !sip->csta->line_status) {
8426 gchar *tmp = NULL;
8427 /* work phone */
8428 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
8429 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
8430 if (phone) {
8431 gchar *label = g_strdup_printf(_("Work %s"),
8432 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8433 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8434 g_free(tmp);
8435 tmp = NULL;
8436 g_free(label);
8437 menu = g_list_prepend(menu, act);
8440 /* mobile phone */
8441 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
8442 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
8443 if (phone) {
8444 gchar *label = g_strdup_printf(_("Mobile %s"),
8445 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8446 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8447 g_free(tmp);
8448 tmp = NULL;
8449 g_free(label);
8450 menu = g_list_prepend(menu, act);
8453 /* home phone */
8454 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
8455 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
8456 if (phone) {
8457 gchar *label = g_strdup_printf(_("Home %s"),
8458 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8459 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8460 g_free(tmp);
8461 tmp = NULL;
8462 g_free(label);
8463 menu = g_list_prepend(menu, act);
8466 /* other phone */
8467 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
8468 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
8469 if (phone) {
8470 gchar *label = g_strdup_printf(_("Other %s"),
8471 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8472 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8473 g_free(tmp);
8474 tmp = NULL;
8475 g_free(label);
8476 menu = g_list_prepend(menu, act);
8479 /* custom1 phone */
8480 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
8481 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
8482 if (phone) {
8483 gchar *label = g_strdup_printf(_("Custom1 %s"),
8484 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8485 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8486 g_free(tmp);
8487 tmp = NULL;
8488 g_free(label);
8489 menu = g_list_prepend(menu, act);
8493 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8494 if (email) {
8495 act = purple_menu_action_new(_("Send email..."),
8496 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
8497 NULL, NULL);
8498 menu = g_list_prepend(menu, act);
8501 gr_parent = purple_buddy_get_group(buddy);
8502 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
8503 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
8504 continue;
8506 group = (PurpleGroup *)g_node;
8507 if (group == gr_parent)
8508 continue;
8510 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
8511 continue;
8513 act = purple_menu_action_new(purple_group_get_name(group),
8514 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
8515 group->name, NULL);
8516 menu_groups = g_list_prepend(menu_groups, act);
8518 menu_groups = g_list_reverse(menu_groups);
8520 act = purple_menu_action_new(_("Copy to"),
8521 NULL,
8522 NULL, menu_groups);
8523 menu = g_list_prepend(menu, act);
8524 menu = g_list_reverse(menu);
8526 g_free(self);
8527 return menu;
8530 static void
8531 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
8533 struct sipe_account_data *sip = chat->account->gc->proto_data;
8534 struct sip_session *session;
8536 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
8537 sipe_conf_modify_conference_lock(sip, session, locked);
8540 static void
8541 sipe_chat_menu_unlock_cb(PurpleChat *chat)
8543 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
8544 sipe_conf_modify_lock(chat, FALSE);
8547 static void
8548 sipe_chat_menu_lock_cb(PurpleChat *chat)
8550 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
8551 sipe_conf_modify_lock(chat, TRUE);
8554 static GList *
8555 sipe_chat_menu(PurpleChat *chat)
8557 PurpleMenuAction *act;
8558 PurpleConvChatBuddyFlags flags_us;
8559 GList *menu = NULL;
8560 struct sipe_account_data *sip = chat->account->gc->proto_data;
8561 struct sip_session *session;
8562 gchar *self;
8564 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
8565 if (!session) return NULL;
8567 self = sip_uri_self(sip);
8568 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
8570 if (session->focus_uri
8571 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
8573 if (session->locked) {
8574 act = purple_menu_action_new(_("Unlock"),
8575 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
8576 NULL, NULL);
8577 menu = g_list_prepend(menu, act);
8578 } else {
8579 act = purple_menu_action_new(_("Lock"),
8580 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
8581 NULL, NULL);
8582 menu = g_list_prepend(menu, act);
8586 menu = g_list_reverse(menu);
8588 g_free(self);
8589 return menu;
8592 static GList *
8593 sipe_blist_node_menu(PurpleBlistNode *node)
8595 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
8596 return sipe_buddy_menu((PurpleBuddy *) node);
8597 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
8598 return sipe_chat_menu((PurpleChat *)node);
8599 } else {
8600 return NULL;
8604 static gboolean
8605 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
8607 gboolean ret = TRUE;
8608 char *uri = trans->payload->data;
8610 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
8611 PurpleBuddy *pbuddy;
8612 struct sipe_buddy *sbuddy;
8613 const char *alias;
8614 char *device_name = NULL;
8615 char *server_alias = NULL;
8616 char *phone_number = NULL;
8617 char *email = NULL;
8618 const char *site;
8620 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
8622 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
8623 alias = purple_buddy_get_local_alias(pbuddy);
8625 if (sip)
8627 //will query buddy UA's capabilities and send answer to log
8628 sipe_options_request(sip, uri);
8630 sbuddy = g_hash_table_lookup(sip->buddies, uri);
8631 if (sbuddy)
8633 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
8637 if (msg->response != 200) {
8638 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
8639 } else {
8640 xmlnode *searchResults;
8641 xmlnode *mrow;
8643 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
8644 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8645 if (!searchResults) {
8646 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
8647 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
8648 const char *value;
8649 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
8650 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
8651 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
8653 /* For 2007 system we will take this from ContactCard -
8654 * it has cleaner tel: URIs at least
8656 if (!sip->ocs2007) {
8657 char *tel_uri = sip_to_tel_uri(phone_number);
8658 /* trims its parameters, so call first */
8659 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
8660 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
8661 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
8662 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
8663 g_free(tel_uri);
8666 if (server_alias && strlen(server_alias) > 0) {
8667 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
8669 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
8670 purple_notify_user_info_add_pair(info, _("Job title"), value);
8672 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
8673 purple_notify_user_info_add_pair(info, _("Office"), value);
8675 if (phone_number && strlen(phone_number) > 0) {
8676 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
8678 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
8679 purple_notify_user_info_add_pair(info, _("Company"), value);
8681 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
8682 purple_notify_user_info_add_pair(info, _("City"), value);
8684 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
8685 purple_notify_user_info_add_pair(info, _("State"), value);
8687 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
8688 purple_notify_user_info_add_pair(info, _("Country"), value);
8690 if (email && strlen(email) > 0) {
8691 purple_notify_user_info_add_pair(info, _("Email address"), email);
8695 xmlnode_free(searchResults);
8698 purple_notify_user_info_add_section_break(info);
8700 if (!server_alias || !strcmp("", server_alias)) {
8701 g_free(server_alias);
8702 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
8703 if (server_alias) {
8704 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
8708 /* present alias if it differs from server alias */
8709 if (alias && (!server_alias || strcmp(alias, server_alias)))
8711 purple_notify_user_info_add_pair(info, _("Alias"), alias);
8714 if (!email || !strcmp("", email)) {
8715 g_free(email);
8716 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
8717 if (email) {
8718 purple_notify_user_info_add_pair(info, _("Email address"), email);
8722 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
8723 if (site) {
8724 purple_notify_user_info_add_pair(info, _("Site"), site);
8727 if (device_name) {
8728 purple_notify_user_info_add_pair(info, _("Device"), device_name);
8731 /* show a buddy's user info in a nice dialog box */
8732 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
8733 uri, /* buddy's URI */
8734 info, /* body */
8735 NULL, /* callback called when dialog closed */
8736 NULL); /* userdata for callback */
8738 g_free(phone_number);
8739 g_free(server_alias);
8740 g_free(email);
8741 g_free(device_name);
8743 return ret;
8747 * AD search first, LDAP based
8749 static void sipe_get_info(PurpleConnection *gc, const char *username)
8751 struct sipe_account_data *sip = gc->proto_data;
8752 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8753 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
8754 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
8755 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
8757 payload->destroy = g_free;
8758 payload->data = g_strdup(username);
8760 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
8761 send_soap_request_with_cb(sip, domain_uri, body,
8762 (TransCallback) process_get_info_response, payload);
8763 g_free(domain_uri);
8764 g_free(body);
8765 g_free(row);
8768 static PurplePlugin *my_protocol = NULL;
8770 static PurplePluginProtocolInfo prpl_info =
8772 OPT_PROTO_CHAT_TOPIC,
8773 NULL, /* user_splits */
8774 NULL, /* protocol_options */
8775 NO_BUDDY_ICONS, /* icon_spec */
8776 sipe_list_icon, /* list_icon */
8777 NULL, /* list_emblems */
8778 sipe_status_text, /* status_text */
8779 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
8780 sipe_status_types, /* away_states */
8781 sipe_blist_node_menu, /* blist_node_menu */
8782 NULL, /* chat_info */
8783 NULL, /* chat_info_defaults */
8784 sipe_login, /* login */
8785 sipe_close, /* close */
8786 sipe_im_send, /* send_im */
8787 NULL, /* set_info */ // TODO maybe
8788 sipe_send_typing, /* send_typing */
8789 sipe_get_info, /* get_info */
8790 sipe_set_status, /* set_status */
8791 sipe_set_idle, /* set_idle */
8792 NULL, /* change_passwd */
8793 sipe_add_buddy, /* add_buddy */
8794 NULL, /* add_buddies */
8795 sipe_remove_buddy, /* remove_buddy */
8796 NULL, /* remove_buddies */
8797 sipe_add_permit, /* add_permit */
8798 sipe_add_deny, /* add_deny */
8799 sipe_add_deny, /* rem_permit */
8800 sipe_add_permit, /* rem_deny */
8801 dummy_permit_deny, /* set_permit_deny */
8802 NULL, /* join_chat */
8803 NULL, /* reject_chat */
8804 NULL, /* get_chat_name */
8805 sipe_chat_invite, /* chat_invite */
8806 sipe_chat_leave, /* chat_leave */
8807 NULL, /* chat_whisper */
8808 sipe_chat_send, /* chat_send */
8809 sipe_keep_alive, /* keepalive */
8810 NULL, /* register_user */
8811 NULL, /* get_cb_info */ // deprecated
8812 NULL, /* get_cb_away */ // deprecated
8813 sipe_alias_buddy, /* alias_buddy */
8814 sipe_group_buddy, /* group_buddy */
8815 sipe_rename_group, /* rename_group */
8816 NULL, /* buddy_free */
8817 sipe_convo_closed, /* convo_closed */
8818 purple_normalize_nocase, /* normalize */
8819 NULL, /* set_buddy_icon */
8820 sipe_remove_group, /* remove_group */
8821 NULL, /* get_cb_real_name */ // TODO?
8822 NULL, /* set_chat_topic */
8823 NULL, /* find_blist_chat */
8824 NULL, /* roomlist_get_list */
8825 NULL, /* roomlist_cancel */
8826 NULL, /* roomlist_expand_category */
8827 NULL, /* can_receive_file */
8828 NULL, /* send_file */
8829 NULL, /* new_xfer */
8830 NULL, /* offline_message */
8831 NULL, /* whiteboard_prpl_ops */
8832 sipe_send_raw, /* send_raw */
8833 NULL, /* roomlist_room_serialize */
8834 NULL, /* unregister_user */
8835 NULL, /* send_attention */
8836 NULL, /* get_attention_types */
8837 #if !PURPLE_VERSION_CHECK(2,5,0)
8838 /* Backward compatibility when compiling against 2.4.x API */
8839 (void (*)(void)) /* _purple_reserved4 */
8840 #endif
8841 sizeof(PurplePluginProtocolInfo), /* struct_size */
8842 #if PURPLE_VERSION_CHECK(2,5,0)
8843 sipe_get_account_text_table, /* get_account_text_table */
8844 #if PURPLE_VERSION_CHECK(2,6,0)
8845 NULL, /* initiate_media */
8846 NULL, /* get_media_caps */
8847 #endif
8848 #endif
8852 static PurplePluginInfo info = {
8853 PURPLE_PLUGIN_MAGIC,
8854 PURPLE_MAJOR_VERSION,
8855 PURPLE_MINOR_VERSION,
8856 PURPLE_PLUGIN_PROTOCOL, /**< type */
8857 NULL, /**< ui_requirement */
8858 0, /**< flags */
8859 NULL, /**< dependencies */
8860 PURPLE_PRIORITY_DEFAULT, /**< priority */
8861 "prpl-sipe", /**< id */
8862 "Office Communicator", /**< name */
8863 SIPE_VERSION, /**< version */
8864 "Microsoft Office Communicator Protocol Plugin", /**< summary */
8865 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
8866 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
8867 "Anibal Avelar <avelar@gmail.com>, " /**< author */
8868 "Gabriel Burt <gburt@novell.com>, " /**< author */
8869 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
8870 "pier11 <pier11@operamail.com>", /**< author */
8871 "http://sipe.sourceforge.net/", /**< homepage */
8872 sipe_plugin_load, /**< load */
8873 sipe_plugin_unload, /**< unload */
8874 sipe_plugin_destroy, /**< destroy */
8875 NULL, /**< ui_info */
8876 &prpl_info, /**< extra_info */
8877 NULL,
8878 sipe_actions,
8879 NULL,
8880 NULL,
8881 NULL,
8882 NULL
8885 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8887 GList *entry;
8889 entry = prpl_info.protocol_options;
8890 while (entry) {
8891 purple_account_option_destroy(entry->data);
8892 entry = g_list_delete_link(entry, entry);
8894 prpl_info.protocol_options = NULL;
8896 entry = prpl_info.user_splits;
8897 while (entry) {
8898 purple_account_user_split_destroy(entry->data);
8899 entry = g_list_delete_link(entry, entry);
8901 prpl_info.user_splits = NULL;
8904 static void init_plugin(PurplePlugin *plugin)
8906 PurpleAccountUserSplit *split;
8907 PurpleAccountOption *option;
8909 srand(time(NULL));
8911 #ifdef ENABLE_NLS
8912 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
8913 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
8914 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
8915 textdomain(GETTEXT_PACKAGE);
8916 #endif
8918 purple_plugin_register(plugin);
8920 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
8921 purple_account_user_split_set_reverse(split, FALSE);
8922 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
8924 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
8925 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8927 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
8928 purple_account_option_add_list_item(option, _("Auto"), "auto");
8929 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
8930 purple_account_option_add_list_item(option, _("TCP"), "tcp");
8931 purple_account_option_add_list_item(option, _("UDP"), "udp");
8932 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8934 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
8935 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
8937 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
8938 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8940 #ifdef USE_KERBEROS
8941 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
8942 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8944 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
8945 * No login/password is taken into account if this option present,
8946 * instead used default credentials stored in OS.
8948 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
8949 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8950 #endif
8952 option = purple_account_option_list_new(_("Calendar source"), "calendar", NULL);
8953 purple_account_option_add_list_item(option, _("Exchange 2007/2010"), "EXCH");
8954 purple_account_option_add_list_item(option, _("None"), "NONE");
8955 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8957 /** Example: https://server.company.com/EWS/Exchange.asmx */
8958 option = purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
8959 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8961 option = purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
8962 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8964 /** Example: DOMAIN\user or user@company.com */
8965 option = purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
8966 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8968 option = purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
8969 purple_account_option_set_masked(option, TRUE);
8970 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8972 my_protocol = plugin;
8975 PURPLE_INIT_PLUGIN(sipe, init_plugin, info);
8978 Local Variables:
8979 mode: c
8980 c-file-style: "bsd"
8981 indent-tabs-mode: t
8982 tab-width: 8
8983 End: